   #Start Contents English version

                       Pisanie modulow jadra Linuksa

   Do jadra systemu Linux na stale wkompilowane sa tylko najwazniejsze
   sterowniki podstawowych urzadzen (na przyklad dyski twarde), gdyz
   umieszczanie tam wszystkich to strata pamieci a przede wszystkim czasu
   na uruchomienie i wylaczenie sie sterownikow do urzadzen
   nieistniejacych w danym komputerze. Dlatego sterowniki do urzadzen
   opcjonalnych umieszczono w modulach jadra, ladowanych przez system na
   zadanie.

   Spis tresci:
    1. Podstawy
    2. Najprostszy modul jadra 2.4
    3. Rejestracja urzadzenia znakowego
    4. Rejestracja portow wejscia-wyjscia oraz obszaru pamieci
    5. Rejestracja zasobu IRQ
    6. Przyklad modulu jadra 2.4
    7. Najprostszy modul jadra 2.6
    8. Rezerwacja zasobow w jadrze 2.6
    9. Przyklad modulu jadra 2.6
   10. Inne jadra i inne sztuczki
     _________________________________________________________________

Podstawy

   Modul jadra to najzwyklejszy skompilowany plik w standardowym formacie
   ELF. Musi eksportowac na zewnatrz dwie funkcje: init_module, sluzaca
   do inicjalizacji modulu (i uruchamiana w czasie jego ladowania) oraz
   cleanup_module, sluzaca do wykonania czynnosci koniecznych do
   prawidlowego zakonczenia pracy (uruchamiana w czasie usuwania modulu z
   jadra).

   Funkcja init_module musi byc tak napisana, ze w przypadku sukcesu
   zwraca zero, a w przypadku porazki - najlepiej jedna ze znanych
   ujemnych wartosci bledu, ktora dobrze bedzie opisywac problem.

   Sporo informacji dotyczacych jadra 2.4 przenosi sie na jadro 2.6, wiec
   w sekcji poswieconej jadru 2.6 powiem tylko, co sie zmienilo w
   stosunku do 2.4.
     _________________________________________________________________

Najprostszy modul jadra 2.4

   (przeskocz najprostszy modul)

   Zgodnie z tym, co powiedzialem wyzej, najprostszy modul wyglada tak:
   (przeskocz kod najprostszego modulu)
        format ELF

        section ".text" executable      ; poczatek sekcji kodu

        ; eksportowanie dwoch wymaganych funkcji
        public  init_module
        public  cleanup_module

        ; deklaracja zewnetrznej funkcji, sluzacej do wyswietlania
        extrn   printk

        init_module:
                push    dword napis1    ; napis do wyswietlenia
                call    printk
                pop     eax             ; zdejmujemy argumenty ze stosu

                xor     eax, eax        ; zero oznacza brak bledu
                ret

        cleanup_module:
                push    dword napis2
                call    printk
                pop     eax

                ret

        section ".data" writeable
        napis1          db      "<1> Jestem w init_module."   , 10, 0
        napis2          db      "<1> Jestem w cleanup_module.", 10, 0

        section ".modinfo"
        __module_kernel_version db      "kernel_version=2.4.26", 0
        __module_license        db      "license=GPL", 0
        __module_author         db      "author=Bogdan D.", 0
        __module_description    db "description=Pierwszy modul jadra.", 0

   Zauwazcie kilka spraw:
    1. Wyswietlanie napisow odbywa sie wewnetrzna funkcja jadra - printk.
       Dziala ona podobnie do funkcji printf z jezyka C, ktora na etapie
       ladowania jadra jest oczywiscie niedostepna.
       W skrocie: adres napisu podajemy na stosie, poprzedzajac
       dodatkowymi danymi w odwrotnej kolejnosci, jesli funkcja w ogole
       ma wyswietlic jakies zmienne w napisie, na przyklad %d (liczba
       calkowita). Bedzie to dokladniej pokazane na przykladowym module.
       Napis powinien sie zaczynac wyrazeniem <N>, gdzie N to pewna
       liczba. Ma to pozwolic jadru rozroznic powage wiadomosci. Nam
       wystarczy za N wstawiac 1.
       Jesli wyswietlanych napisow nie widac na ekranie, to na pewno
       pojawia sie po komendzie dmesg (zwykle na koncu) oraz w pliku
       /var/log/messages.
    2. Skladnia jest dla kompilatora FASM.
       Moduly kompilowane NASMem z niewiadomych przyczyn nie chcialy mi
       wchodzic do jadra.
    3. Kazda funkcja jadra uruchamiana jest w konwencji C, czyli my
       sprzatamy argumenty ze stosu.
    4. Nowa sekcja - modinfo.
       Zawiera informacje, dla ktorej wersji jadra modul jest
       przeznaczony, kto jest jego autorem, na jakiej jest licencji,
       argumenty. Nazwy zmiennych musza pozostac bez zmian, tresc po
       znakach rownosci powinniscie pozmieniac wedlug potrzeb.

   Modul ten, po kompilacji (fasm modul_hello.asm) instaluje sie jako
   root komenda
        insmod ./modul_hello.o

   a usuwa z jadra - komenda
        rmmod modul_hello

   (zauwazcie brak rozszerzenia .o).

   Liste modulow obecnych w jadrze mozna otrzymac komenda lsmod.

   Pokaze teraz, jak zarejestrowac urzadzenie znakowe, zajac dla niego
   zasoby IRQ oraz zakres portow i pamieci.
     _________________________________________________________________

Rejestracja urzadzenia znakowego

   (przeskocz rejestracje urzadzenia znakowego)

   Do rejestracji urzadzenia znakowego (czyli takiego, z ktorego mozna
   odczytywac po bajcie, w przeciwienstwie do na przyklad dysku twardego)
   sluzy eksportowana przez jadro funkcja register_chrdev. Przyjmuje ona
   3 argumenty. Od lewej (ostatni wkladany na stos) sa to:
    1. Numer glowny urzadzenia, ktory sobie wybralismy.
       Mozna podac zero, wtedy jadro przydzieli nam jakis wolny. Numer
       glowny to pierwszy z dwoch numerow (drugi to poboczny), widoczny w
       szczegolowym widoku plikow z katalogu /dev, na przyklad
        crw-rw-rw-  1 root root 1, 5 sie 16 15:28 /dev/zero
       Urzadzenie /dev/zero ma numer glowny 1 i poboczny 5, litera C na
       poczatku oznacza wlasnie urzadzenie znakowe. Inne oznaczenia to D
       (katalog), S (gniazdo), B (urzadzenie blokowe), P (FIFO), L
       (dowiazanie symboliczne).
    2. Adres nazwy urzadzenia w postaci ciagu znakow zakonczonego bajtem
       zerowym.
    3. Adres struktury file_operations, do ktorej wpiszemy adresy
       odpowiednich funkcji do operacji na pliku.
       Najwazniejsze sa: otwieranie, zamykanie, zapis i czytanie z
       urzadzenia. Sama struktura wyglada tak dla jadra 2.4:
       (przeskocz strukture file_operations)
        struct file_operations {
                struct module *owner;
                loff_t (*llseek) (struct file *, loff_t, int);
                ssize_t (*read) (struct file*, char*, size_t, loff_t *);
                ssize_t (*write) (struct file *, const char *, size_t,
                        loff_t *);
                int (*readdir) (struct file *, void *, filldir_t);
                unsigned int (*poll) (struct file *,
                        struct poll_table_struct *);
                int (*ioctl) (struct inode*, struct file*, unsigned int,
                        unsigned long);
                int (*mmap) (struct file *, struct vm_area_struct *);
                int (*open) (struct inode *, struct file *);
                int (*flush) (struct file *);
                int (*release) (struct inode *, struct file *);
                int (*fsync) (struct file*,struct dentry*, int datasync);
                int (*fasync) (int, struct file *, int);
                int (*lock) (struct file *, int, struct file_lock *);
                ssize_t (*readv) (struct file *, const struct iovec *,
                        unsigned long, loff_t *);
                ssize_t (*writev) (struct file *, const struct iovec *,
                        unsigned long, loff_t *);
        };
       Kazde pole tej struktury to DWORD. Do podstawowej funkcjonalnosci
       wystarczy wypelnic pola: trzecie, czwarte, dziewiate i jedenaste
       (zamykanie pliku). Jesli jakiejs funkcji nie planujemy pisac,
       nalezy na odpowiadajace jej miejsce w tej strukturze wpisac zero.

   Jesli podalismy tej funkcji nasz wlasny numer glowny urzadzenia, to
   jesli rejestracja sie udala, register_chrdev zwroci zero w EAX. Jesli
   poprosilismy o przydzielenie nam numeru glownego, to jesli rejestracja
   sie powiedzie, register_chrdev zwroci liczbe wieksza od zera, ktora to
   liczba bedzie przeznaczonym dla naszego urzadzenia numerem glownym.

   UWAGA: Funkcja register_chrdev nie tworzy pliku urzadzenia w katalogu
   /dev. O to musimy zadbac sami, po zaladowaniu modulu.

   Wyrejestrowanie urzadzenia znakowego nastepuje poprzez wywolanie
   funkcji unregister_chrdev. Pierwszy argument od lewej (ostatni na
   stos) to przydzielony urzadzeniu numer glowny, a drugi - adres nazwy
   urzadzenia.
     _________________________________________________________________

Rejestracja portow wejscia-wyjscia oraz obszaru pamieci

   (przeskocz rejestracje portow i pamieci)

   Zarezerwowanie tych zasobow jest dosc latwe. Nalezy tylko uruchomic
   funkcje __request_region. Przyjmuje ona 4 argumenty. Od lewej (ostatni
   wkladany na stos) sa to:
    1. Typ zasobu. Jesli chcemy zarezerwowac porty, podajemy tu adres
       zmiennej ioport_resource, jesli pamiec - iomem_resource. Obie
       zmienne sa eksportowane przez jadro, wiec mozna je zadeklarowac
       jako zewnetrzne dla modulu.
    2. Poczatkowy numer portu lub poczatkowy adres pamieci.
    3. Dlugosc zakresu portow lub pamieci
    4. Adres nazwy urzadzenia.

   W przypadku niepowodzenia, funkcja zwraca zero (w EAX).

   Oba te rodzaje zasobow zwalnia sie funkcja __release_region. Jako
   swoje argumenty przyjmuje ona 3 pierwsze z powyzszych (typ oraz
   poczatek i dlugosc zakresu).
     _________________________________________________________________

Rejestracja zasobu IRQ

   (przeskocz rejestracje IRQ)

   Zasoby zadania przerwania (IRQ) rejestruje sie funkcja request_irq.
   Przyjmuje ona az 5 argumentow typu DWORD. Od lewej (ostatni wkladany
   na stos) sa to:
    1. Numer przerwania IRQ, ktore chcemy zajac.
    2. Adres naszej funkcji, ktora bedzie obslugiwac przerwania. Prototyp
       takiej funkcji wyglada tak:
        void handler (int irq, void *dev_id, struct pt_regs *regs);
       Jak widac, bedzie mozna ze stosu otrzymac informacje, ktore
       przerwanie zostalo wywolane oraz przez jakie urzadzenie. Ostatni
       argument podobno jest juz rzadko uzywany.
    3. Wartosc SA_INTERRUPT = 0x20000000
    4. Adres nazwy urzadzenia.
    5. Adres struktury file_operations, uzupelnionej adresami funkcji

   Jesli zajecie przerwania sie nie powiedzie, funkcja zwroci wartosc
   ujemna.

   Zwolnienie przerwania odbywa sie poprzez wywolanie funkcji free_irq.
   Jej pierwszy argument od lewej (ostatni na stos) to nasz numer IRQ, a
   drugi - adres naszej struktury file_operations.
     _________________________________________________________________

Przyklad modulu jadra 2.4

   (przeskocz do jadra 2.6)

   Pokazany nizej program zarejestruje programowe urzadzenie znakowe
   (czyli takie, dla ktorego nie ma odpowiednika w sprzecie, jak na
   przyklad /dev/null) z IRQ 4, zakresem portow 600h-6FFh, zakresem
   pamieci 80000000h - 8000FFFFh oraz z podstawowymi operacjami:
   otwieranie, zamykanie, czytanie, zapis, zmiana pozycji. Dla
   uproszczenia kodu nie sprawdzam, czy dane zakresy sa wolne. Jesli
   okaza sie zajete, jadro zwroci blad i modul sie nie zaladuje.
   (przeskocz kod modulu)
        ; Przykladowy modul jadra 2.4
        ;
        ; Autor: Bogdan D., bogdandr (na) op . pl
        ;
        ; kompilacja:
        ;   fasm modul_dev_fasm.asm

        format ELF
        section ".text" executable

        ; eksportowanie wymaganych funkcji
        public  init_module
        public  cleanup_module

        ; importowanie uzywanych funkcji i symboli
        extrn   printk
        extrn   register_chrdev
        extrn   unregister_chrdev
        extrn   request_irq
        extrn   free_irq

        extrn   __check_region
        extrn   __request_region
        extrn   __release_region
        extrn   ioport_resource
        extrn   iomem_resource

        ; zakresy zasobow, o ktore poprosimy jadro
        PORTY_START     = 0x600
        PORTY_ILE       = 0x100

        RAM_START       = 0x80000000
        RAM_ILE         = 0x00010000

        ; stale potrzebne do rezerwacji przerwania IRQ.
        SA_INTERRUPT    = 0x20000000
        NUMER_IRQ       = 4

        ; funkcja inicjalizacji modulu
        init_module:
                pushfd

                ; rejestrowanie urzadzenia znakowego:
                push    dword file_oper
                push    dword nazwa
                push    dword 0                 ; numer przydziel dynamicznie
                call    register_chrdev
                add     esp, 3*4                ; usuwamy argumenty ze stosu

                cmp     eax, 0                  ; sprawdzamy, czy blad
                jg      .dev_ok

                ; tu wiemy, ze jest blad. wyswietlmy to.
                push    eax                     ; argument do informacji o bled
zie
                push    dword dev_blad          ; adres informacji o bledzie
                call    printk                  ; wyswietl informacje o bledzie
                add     esp, 1*4                ; specjalnie usuwam tylko 1*4

                pop     eax                     ; wychodzimy z oryginalnym bled
em
                jmp     .koniec

        .dev_ok:

                mov     [major], eax

                ; rezerwacja portow wejscia-wyjscia
                push    dword nazwa
                push    dword PORTY_ILE
                push    dword PORTY_START
                push    dword ioport_resource
                call    __request_region
                add     esp, 4*4

                test    eax, eax                ; sprawdzamy, czy blad
                jnz     .iop_ok

                push    eax                     ; argument do informacji o bled
zie
                push    dword porty_blad                ; adres informacji o b
ledzie
                call    printk                  ; wyswietl informacje o bledzie
                add     esp, 1*4                ; potem pop eax

                ; wyrejestrowanie urzadzenia
                push    dword nazwa
                push    dword [major]
                call    unregister_chrdev
                add     esp, 2*4

                pop     eax                     ; wychodzimy z oryginalnym bled
em
                jmp     .koniec

        .iop_ok:

                ; rezerwacja pamieci
                push    dword nazwa
                push    dword RAM_ILE
                push    dword RAM_START
                push    dword iomem_resource
                call    __request_region
                add     esp, 4*4

                test    eax, eax                ; sprawdzamy, czy blad
                jnz     .iomem_ok

                push    eax
                push    dword ram_blad
                call    printk                  ; wyswietl informacje o bledzie
                add     esp, 1*4                ; potem pop eax

                ; wyrejestrowanie urzadzenia
                push    dword nazwa
                push    dword [major]
                call    unregister_chrdev
                add     esp, 2*4

                ; zwolnienie zajetych przez nas portow
                push    dword PORTY_ILE
                push    dword PORTY_START
                push    dword ioport_resource
                call    __release_region
                add     esp, 3*4

                pop     eax                     ; wychodzimy z oryginalnym bled
em
                jmp     .koniec

        .iomem_ok:
                ; przydzielanie przerwania IRQ:
                push    dword file_oper
                push    dword nazwa
                push    dword SA_INTERRUPT
                push    dword obsluga_irq
                push    dword NUMER_IRQ
                call    request_irq
                add     esp, 5*4

                cmp     eax, 0
                jge     .irq_ok

                push    eax
                push    dword irq_blad
                call    printk                  ; wyswietl informacje o bledzie
                add     esp, 1*4                ; potem pop eax

                ; wyrejestrowanie urzadzenia
                push    dword nazwa
                push    dword [major]
                call    unregister_chrdev
                add     esp, 2*4

                ; zwolnienie zajetych przez nas portow
                push    dword PORTY_ILE
                push    dword PORTY_START
                push    dword ioport_resource
                call    __release_region
                add     esp, 3*4

                ; zwolnienie zajetej przez nas pamieci
                push    dword RAM_ILE
                push    dword RAM_START
                push    dword iomem_resource
                call    __release_region
                add     esp, 3*4

                pop     eax                     ; wychodzimy z oryginalnym bled
em
                jmp     .koniec

        .irq_ok:

                ; wyswietlenie informacji o poprawnym uruchomieniu modulu
                push    dword NUMER_IRQ
                push    dword [major]
                push    dword uruch
                call    printk
                add     esp, 3*4

                xor     eax, eax                ; zero - brak bledu

        .koniec:

                popfd
                ret

        ; funkcja uruchamiana przy usuwaniu modulu
        cleanup_module:
                pushfd
                push    eax

                ; zwolnienie numeru IRQ:
                push    dword file_oper
                push    dword NUMER_IRQ
                call    free_irq
                add     esp, 2*4

                ; wyrejestrowanie urzadzenia:
                push    dword nazwa
                push    dword [major]
                call    unregister_chrdev
                add     esp, 2*4

                ; zwolnienie zajetych przez nas portow
                push    dword PORTY_ILE
                push    dword PORTY_START
                push    dword ioport_resource
                call    __release_region
                add     esp, 3*4

                ; zwolnienie zajetej przez nas pamieci
                push    dword RAM_ILE
                push    dword RAM_START
                push    dword iomem_resource
                call    __release_region
                add     esp, 3*4

                ; wyswietlenie informacji o usunieciu modulu
                push    dword usun
                call    printk
                add     esp, 1*4

                pop     eax
                popfd
                ret

        ; nasza funkcja obslugi przerwania. Ta tutaj nie robi nic, ale
        ;       pokazuje rozmieszczenie argumentow na stosie
        obsluga_irq:
                push    ebp
                mov     ebp, esp

        ; [ebp] = stary EBP
        ; [ebp+4] = adres powrotny
        ; [ebp+8] = arg1
        ; ...

                        irq     equ     ebp+8
                        dev_id  equ     ebp+12
                        regs    equ     ebp+16

                leave
                ret


        ; Zdefiniowane operacje na urzadzeniu

        ; Czytanie z urzadzenia - zwracamy zadanej dlugosci ciag bajtow 1Eh.
        ; To urzadzenie staje sie nieskonczonym zrodlem, podobnie jak /dev/zero
        czytanie:
                push    ebp
                mov     ebp, esp

                ; rozmieszczenie argumentow na stosie:
                s_file  equ     ebp+8   ; wskaznik na strukture file
                bufor   equ     ebp+12  ; adres bufora na dane
                l_jedn  equ     ebp+16  ; zadana liczba bajtow
                loff    equ     ebp+20  ; zadany offset czytania

                pushfd
                push    edi
                push    ecx

                mov     ecx, [l_jedn]
                mov     al, 0x1e
                cld
                mov     edi, [bufor]
                rep     stosb           ; zapychamy bufor bajtami 1Eh

                pop     ecx
                pop     edi
                popfd

                mov     eax, [l_jedn]   ; zwracamy tyle, ile chciano

                leave
                ret

        zapis:
                push    ebp
                mov     ebp, esp

                ; nic fizycznie nie zapisujemy, zwracamy tylko liczbe bajtow,
                ;       ktora mielismy zapisac
                mov     eax, [l_jedn]

                leave
                ret

        przejscie:
        zamykanie:
        otwieranie:
                xor     eax, eax
                ret



        section ".data" writeable

        major   dd      0       ; numer glowny urzadzenia przydzielany przez j
adro

        ; adresy funkcji operacji na tym urzadzeniu
        file_oper:      dd 0, przejscie, czytanie, zapis, 0, 0, 0, 0, otwierani
e, 0
                        dd zamykanie, 0, 0, 0, 0, 0

        dev_blad        db      "<1>Blad rejestracji urzadzenia: %d.", 10, 0
        irq_blad        db      "<1>Blad przydzielania IRQ: %d.", 10, 0
        porty_blad      db      "<1>Blad przydzielania portow:  EAX=%d", 10, 0
        ram_blad        db      "<1>Blad przydzielania pamieci: EAX=%d", 10, 0


        uruch           db      "<1>Modul zaladowany. Maj=%d, IRQ=%d", 10, 0
        usun            db      "<1>Modul usuniety.", 10, 0

        nazwa           db      "test00", 0
        sciezka         db      "/dev/test00", 0

        section ".modinfo"
        __module_kernel_version db      "kernel_version=2.4.26", 0
        __module_license        db      "license=GPL", 0
        __module_author         db      "author=Bogdan D.", 0
        __module_description    db      "description=Pierwszy modul jadra", 0
        __module_device         db      "device=test00", 0

   Powyzszy modul po kompilacji najprosciej zainstalowac w jadrze
   stosujac taki oto skrypt:
   (przeskocz skrypt instalacji)
        #!/bin/bash

        PLIK="modul_dev_fasm.o"         # Tu wstawiacie swoja nazwe
        NAZWA="test00"

        # umieszczenie modulu w jadrze.
        /sbin/insmod $PLIK $* || { echo "Problem insmod!" ; exit -1; }

        # wyszukanie naszej nazwy modulu wsrod wszystkich
        /sbin/lsmod | grep `echo $PLIK | sed 's/[^a-z]/ /g' | awk '{print $1}'
`
        # wyswietlenie informacji o zajmowanych zasobach
        grep $NAZWA /proc/devices
        grep $NAZWA /proc/ioports
        grep $NAZWA /proc/iomem
        grep $NAZWA /proc/interrupts

        # znalezienie i wyswietlenie numeru glownego urzadzenia
        NR=`grep $NAZWA /proc/devices | awk '{print $1}'`
        echo "Major = $NR"

        # ewentualne usuniecie starego pliku urzadzenia
        rm -f /dev/$NAZWA

        # fizyczne utworzenie pliku urzadzenia w katalogu /dev
        # wykonanie funkcji sys_mknod z modulu NIE dziala
        mknod /dev/$NAZWA c $NR 0
        ls -l /dev/$NAZWA

        # krotki test: czytanie 512 bajtow i sprawdzenie ich zawartosci
        dd count=1 if=/dev/$NAZWA of=/x && hexdump /x && rm -f /x

   Wystarczy ten skrypt zachowac na przyklad pod nazwa instal.sh, nadac
   prawo wykonywania komenda chmod u+x instal.sh i uruchamiac poprzez
   ./instal.sh, oczywiscie jako root. Jesli zaladowanie modulu sie uda,
   skrypt wyswietli przydzielone modulowi zasoby - porty, IRQ, pamiec -
   poprzez zajrzenie do odpowiednich plikow katalogu /proc. Skrypt
   utworzy tez plik urzadzenia w katalogu /dev z odpowiednim numerem
   glownym oraz wykona prosty test.

   Odinstalowac modul mozna latwo takim oto skryptem:
        #!/bin/bash

        PLIK="modul_dev_fasm"   # Tu wstawiacie swoja nazwe, bez rozszerzenia .
o
        NAZWA="test00"

        /sbin/rmmod $PLIK && rm -f /dev/$NAZWA
     _________________________________________________________________

Najprostszy modul jadra 2.6

   (przeskocz najprostszy modul jadra 2.6)

   Najprostszy modul jadra 2.6 wyglada tak:
   (przeskocz kod najprostszego modulu jadra 2.6)
        format ELF
        section ".init.text" executable align 1
        section ".text" executable align 4

        public init_module
        public cleanup_module

        extrn printk

        init_module:
                push    dword str1
                call    printk
                pop     eax
                xor     eax, eax
                ret

        cleanup_module:
                push    dword str2
                call    printk
                pop     eax
                ret

        section ".modinfo" align 32
        __kernel_version        db      "kernel_version=2.6.16", 0
        __mod_vermagic db "vermagic=2.6.16 686 REGPARM 4KSTACKS gcc-4.0", 0
        __module_license        db      "license=GPL", 0
        __module_author         db      "author=Bogdan D.", 0
        __module_description    db      "description=Pierwszy modul jadra 2.6",
 0

        section "__versions" align 32
                dd      0xfa02c634
        n1:     db      "struct_module"
                times   64-4-($-n1) db 0

                dd      0x1b7d4074
        n2:     db      "printk"
                times   64-4-($-n2) db 0

        section ".data" writeable align 4

        str1            db      "<1> Jestem w init_module(). ", 10, 0
        str2            db      "<1> Jestem w cleanup_module(). ", 10, 0

        section ".gnu.linkonce.this_module" writeable align 128

        align 128
        __this_module:          ; laczna dlugosc: 512 bajtow
                                dd 0, 0, 0

                        .nazwa: db "modul", 0
                                times 64-4-($-.nazwa) db 0

                                times 100 db 0
                                dd init_module
                                times 220 db 0
                                dd cleanup_module
                                times 112 db 0

   Od razu widac sporo roznic, prawda? Omowmy je po jednej sekcji na raz:
    1. .init.text
       W zasadzie powinny byc co najmniej dwie: .init.text, zawierajaca
       procedure inicjalizacji oraz .exit.text, zawierajaca procedure
       wyjscia.
       Dodatkowo, moze byc oczywiscie sekcja danych .data i kodu .text.
       Jesli podczas proby zainstalowania modulu dostajecie komunikat
       "Accessing a corrupted shared library" ("Dostep do uszkodzonej
       biblioteki wspoldzielonej"), to pogrzebcie w sekcjach - dorobcie
       .text, usuncie .init.text, zamiencie kolejnosc itp.
    2. .gnu.linkonce.this_module
       Ta jest najwazniejsza. Bez niej proba instalacji modulu w jadrze
       zakonczy sie komunikatem "No module found in object" ("w pliku
       obiektowym nie znaleziono modulu"). Zawartosc tej sekcji to
       struktura typu "module" o nazwie "__this_module". Najlepiej
       zrobicie, przepisujac te powyzej do swoich modulow, zmieniajac
       tylko nazwe modulu oraz funkcje wejscia i wyjscia.
       Mozecie tez skorzystac z nastepujacego makra:
        macro   gen_this_module         name*, entry, exit
        {
                section '.gnu.linkonce.this_module' writeable align 128

                align 128
                __this_module:
                                dd 0, 0, 0
                .mod_nazwa:     db name, 0
                                times 64-4-($-.mod_nazwa) db 0
                                times 100 db 0
                                if entry eq
                                        dd init_module
                                else
                                        dd entry
                                end if
                                times 220 db 0
                                if exit eq
                                        dd cleanup_module
                                else
                                        dd exit
                                end if
                                times 112 db 0

        }
       Korzysta sie z niego dosc latwo: wystarczy podac nazwe modulu,
       ktora ma byc wyswietlana po komendzie lsmod oraz nazwy procedur
       wejscia i wyjscia z modulu, na przyklad
        gen_this_module "nasz_modul", init_module, cleanup_module
       To wywolanie makra nalezy umiescic tam, gdzie normalnie ta sekcja
       by sie znalazla, czyli na przyklad po ostatniej deklaracji
       czegokolwiek w sekcji danych. W kazdym razie NIE tak, zeby bylo to
       w srodku jakiejkolwiek sekcji.
    3. modinfo
       Sekcja ta wzbogacila sie w stosunku do tej z jadra 2.4 o tylko
       jeden, ale za to bardzo wazny wpis - "vermagic". U wiekszosci z
       Was ten napis bedzie sie roznil od mojego tylko wersja jadra. W
       oryginale wyglada on tak:
       (przeskocz definicje vermagic)
        #define VERMAGIC_STRING                                 \
          UTS_RELEASE " "                                       \
          MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT           \
          MODULE_ARCH_VERMAGIC                                  \
          "gcc-" __stringify(__GNUC__) "." __stringify(__GNUC_MINOR__)
        #define MODULE_ARCH_VERMAGIC MODULE_PROC_FAMILY \
                 MODULE_REGPARM MODULE_STACKSIZE
       a mozna go znalezc w podkatalogach asm* katalogu INCLUDE w
       zrodlach jadra oraz w pliku VERMAGIC.H.
    4. __versions
       Ta sekcja zawiera informacje o wersjach procedur, z ktorych nasz
       modul korzysta. Struktura jest dosc prosta: najpierw jako DWORD
       wpisujemy numerek odpowiadajacy danej funkcji jadra, a znaleziony
       w pliku MODULE.SYMVERS w katalogu glownym zrodel jadra. Zaraz za
       numerkiem wpisujemy nazwe naszej funkcji, dopelniona zerami do 64
       bajtow.
       Ta sekcja nie jest wymagana do prawidlowej pracy modulu, ale
       powinna sie w kazdym znalezc, zeby nie pojawialy sie komunikaty o
       "zanieczyszczeniu" jadra ("kernel tainted").
       Cala te sekcje mozecie wygenerowac, korzystajac z mojego skryptu
       symvers-fasm.txt. Wystarczy uruchomic perl symvers-fasm.pl
       wasz_modul.asm.
     _________________________________________________________________

Rezerwacja zasobow w jadrze 2.6

   (przeskocz rezerwacje zasobow w jadrze 2.6)

   Rezerwacja zasobow w jadrze 2.6 z zewnatrz (czyli z perspektywy jezyka
   C) nie rozni sie od tej z jadra 2.4. Ale tak naprawde zaszly dwie
   istotne zmiany:
    1. Struktura file_operations
       W jadrze 2.6 wyglada tak:
       (przeskocz strukture file_operations w jadrze 2.6)
        struct file_operations {
                struct module *owner;
                loff_t (*llseek) (struct file *, loff_t, int);
                ssize_t (*read) (struct file*,char __user*,size_t,
                        loff_t*);
                ssize_t (*aio_read) (struct kiocb *, char __user *,
                        size_t, loff_t);
                ssize_t (*write) (struct file *, const char __user *,
                        size_t, loff_t *);
                ssize_t (*aio_write) (struct kiocb *, const char __user*,
                        size_t, loff_t);
                int (*readdir) (struct file *, void *, filldir_t);
                unsigned int (*poll) (struct file *,
                        struct poll_table_struct *);
                int (*ioctl) (struct inode *, struct file *,
                        unsigned int, unsigned long);
                long (*unlocked_ioctl) (struct file *, unsigned int,
                        unsigned long);
                long (*compat_ioctl) (struct file *, unsigned int,
                        unsigned long);
                int (*mmap) (struct file *, struct vm_area_struct *);
                int (*open) (struct inode *, struct file *);
                int (*flush) (struct file *);
                int (*release) (struct inode *, struct file *);
                int (*fsync) (struct file *, struct dentry *,
                        int datasync);
                int (*aio_fsync) (struct kiocb *, int datasync);
                int (*fasync) (int, struct file *, int);
                int (*lock) (struct file *, int, struct file_lock *);
                ssize_t (*readv) (struct file *, const struct iovec *,
                        unsigned long, loff_t *);
                ssize_t (*writev) (struct file *, const struct iovec *,
                        unsigned long, loff_t *);
                ssize_t (*sendfile) (struct file *, loff_t *, size_t,
                        read_actor_t, void *);
                ssize_t (*sendpage) (struct file *, struct page *, int,
                        size_t, loff_t *, int);
                unsigned long (*get_unmapped_area)(struct file *,
                        unsigned long, unsigned long, unsigned long,
                        unsigned long);
                int (*check_flags)(int);
                int (*dir_notify)(struct file *filp, unsigned long arg);
                int (*flock) (struct file *, int, struct file_lock *);
        };
    2. Sposob przekazywania parametrow
       Moje jadro dystrybucyjne zostalo skompilowane tak, zeby trzy
       pierwsze parametry do kazdej procedury z wyjatkiem printk
       przekazywalo w rejestrach: EAX, EDX, ECX, a reszte na stosie. Aby
       sprawdzic, czy u Was tez tak jest, wykonajcie komendy:
        grep -R regpar /lib/modules/`uname -r`/build/|grep Makefile
        grep -R REGPAR /lib/modules/`uname -r`/build/|grep config
       Jesli ich wyniki zawieraja takie cos:
        CONFIG_REGPARM=y
        #define CONFIG_REGPARM 1
       to prawdopodobnie tez tak macie. Mozecie wtedy bez przeszkod
       uzywac makra URUCHOM, ktore umieszcze w module ponizej. Jesli nie,
       mozecie je zmodyfikowac. Potrzeba modyfikacji moze wynikac z
       zawieszania sie calego systemu podczas proby zainstalowania
       modulu.
     _________________________________________________________________

Przyklad modulu jadra 2.6

   (przeskocz przyklad modulu jadra 2.6)

   Podobnie, jak w jadrze 2.4, pokazany nizej program zarejestruje
   programowe urzadzenie znakowe (czyli takie, dla ktorego nie ma
   odpowiednika w sprzecie, jak na przyklad /dev/null) z IRQ 4, zakresem
   portow 600h-6FFh, zakresem pamieci 80000000h - 8000FFFFh oraz z
   podstawowymi operacjami: otwieranie, zamykanie, czytanie, zapis,
   zmiana pozycji. Dla uproszczenia kodu nie sprawdzam, czy dane zakresy
   sa wolne. Jesli okaza sie zajete, jadro zwroci blad i modul sie nie
   zaladuje.
        format ELF
        section ".text" executable align 4

        public  init_module
        public  cleanup_module

        extrn   printk
        extrn   register_chrdev
        extrn   unregister_chrdev
        extrn   request_irq
        extrn   free_irq

        extrn   __request_region
        extrn   __release_region
        extrn   ioport_resource
        extrn   iomem_resource

        PORTY_START     = 0x600
        PORTY_ILE       = 0x100

        RAM_START       = 0x80000000
        RAM_ILE         = 0x00010000

        SA_INTERRUPT    = 0x20000000
        NUMER_IRQ       = 4

        macro   uruchom         funkcja, par1, par2, par3, par4, par5
        {
                if ~ par5 eq
                        push    dword par5
                end if
                if ~ par4 eq
                        push    dword par4
                end if
                if ~ par3 eq
                        mov     ecx, par3
                end if
                if ~ par2 eq
                        mov     edx, par2
                end if
                if ~ par1 eq
                        mov     eax, par1
                end if
                call    funkcja
                if ~ par5 eq
                        add     esp, 4
                end if
                if ~ par4 eq
                        add     esp, 4
                end if
        }

        init_module:
                pushfd

                ; rejestrowanie urzadzenia znakowego:
                uruchom register_chrdev, 0, nazwa, file_oper

                cmp     eax, 0
                jg      .dev_ok

                ; wyswietlenie informacji o bledzie
                push    eax
                push    dword dev_blad
                call    printk
                add     esp, 1*4                ; specjalnie tylko 1*4

                pop     eax                     ; wychodzimy z oryginalnym bled
em
                jmp     .koniec

        .dev_ok:

                mov     [major], eax

                ; rejestrowanie zakresu portow
        uruchom __request_region, ioport_resource, PORTY_START, PORTY_ILE, nazw
a

                test    eax, eax
                jnz     .iop_ok

                push    eax
                push    dword porty_blad
                call    printk
                add     esp, 1*4                ; potem pop eax

                ; wyrejestrowanie urzadzenia
                uruchom unregister_chrdev, [major], nazwa

                pop     eax                     ; wychodzimy z oryginalnym bled
em
                jmp     .koniec

        .iop_ok:

                ; rejestrowanie zakresu pamieci
                uruchom __request_region, iomem_resource, RAM_START, RAM_ILE, n
azwa

                test    eax, eax
                jnz     .iomem_ok

                push    eax
                push    dword ram_blad
                call    printk
                add     esp, 1*4                ; potem pop eax

                ; wyrejestrowanie urzadzenia
                uruchom unregister_chrdev, [major], nazwa

                ; wyrejestrowanie zakresu portow
                uruchom __release_region, ioport_resource, PORTY_START, PORTY_I
LE

                pop     eax                     ; wychodzimy z oryginalnym bled
em
                jmp     .koniec

        .iomem_ok:

                ; przydzielanie przerwania IRQ:
        uruchom request_irq, NUMER_IRQ, obsluga_irq, SA_INTERRUPT, nazwa, file_
oper

                cmp     eax, 0
                jge     .irq_ok

                push    eax
                push    dword irq_blad
                call    printk
                add     esp, 1*4                ; potem pop eax

                ; wyrejestrowanie urzadzenia
                uruchom unregister_chrdev, [major], nazwa

                ; wyrejestrowanie zakresu portow
                uruchom __release_region, ioport_resource, PORTY_START, PORTY_I
LE

                ; wyrejestrowanie zakresu pamieci
                uruchom __release_region, iomem_resource, RAM_START, RAM_ILE

                pop     eax                     ; wychodzimy z oryginalnym bled
em
                jmp     .koniec

        .irq_ok:

                ; wyswietlenie informacji o zaladowaniu modulu
                push    dword NUMER_IRQ
                push    dword [major]
                push    dword uruch
                call    printk
                add     esp, 3*4

                xor     eax, eax

        .koniec:

                popfd
                ret

        ; funkcja uruchamiana przy usuwaniu modulu
        cleanup_module:
                pushfd
                push    eax

                ; zwolnienie numeru IRQ:
                uruchom free_irq, NUMER_IRQ, file_oper

                ; wyrejestrowanie urzadzenia:
                uruchom unregister_chrdev, [major], nazwa

                ; wyrejestrowanie zakresu portow
                uruchom __release_region, ioport_resource, PORTY_START, PORTY_I
LE

                ; wyrejestrowanie zakresu pamieci
                uruchom __release_region, iomem_resource, RAM_START, RAM_ILE

                push    dword usun
                call    printk
                add     esp, 1*4

                pop     eax
                popfd
                ret

        ; deklaracja wyglada tak:
        ; void handler (int irq, void *dev_id, struct pt_regs *regs);
        ; ostatni argument zwykle nieuzywany

        section ".text" executable align 4

        obsluga_irq:
                push    ebp
                mov     ebp, esp

                ; tu Wasz kod

                leave
                ret

        ; Zdefiniowane operacje:

        czytanie:
        ;       ssize_t (*read) (struct file *, char *, size_t, loff_t *);
                push    ebp
                mov     ebp, esp

                loff    equ     ebp+8

                pushfd
                push    edi
                push    ecx

                mov     al, 0x1e
                cld
                mov     edi, edx
                rep     stosb

                pop     ecx
                pop     edi
                popfd

                ; mowimy, ze przeczytano tyle bajtow, ile zadano
                mov     eax, ecx

                leave
                ret

        zapis:
        ;       ssize_t (*write) (struct file *, const char *, size_t, loff_t *
);
                push    ebp
                mov     ebp, esp

                ; nic fizycznie nie zapisujemy, zwracamy tylko liczbe bajtow,
                ;       ktora mielismy zapisac (trzeci parametr)
                mov     eax, ecx

                leave
                ret

        przejscie:
        zamykanie:
        otwieranie:
                xor     eax, eax
                ret



        section ".data" writeable align 4

        major   dd      0       ; numer glowny urzadzenia przydzielany przez j
adro

        ; adresy funkcji operacji na tym urzadzeniu
        file_oper:      dd 0, przejscie, czytanie, 0, zapis, 0, 0, 0, 0, 0, 0,
0
                        dd otwieranie, 0, zamykanie, 0, 0, 0, 0, 0, 0, 0, 0, 0
                        dd 0, 0, 0
                        dd 0, 0, 0

        dev_blad        db      "<1>Blad rejestracji urzadzenia: %d.", 10, 0
        irq_blad        db      "<1>Blad przydzielania IRQ: %d.", 10, 0
        porty_blad      db      "<1>Blad przydzielania portow:  EAX=%d", 10, 0
        ram_blad        db      "<1>Blad przydzielania pamieci: EAX=%d", 10, 0


        uruch           db      "<1>Modul zaladowany. Maj=%d, IRQ=%d", 10, 0
        usun            db      "<1>Modul usuniety.", 10, 0

        nazwa           db      "test00", 0, 0
        sciezka         db      "/dev/test00", 0

        section ".modinfo" align 32
        __kernel_version        db      "kernel_version=2.6.16", 0
        __mod_vermagic db "vermagic=2.6.16 686 REGPARM 4KSTACKS gcc-4.0",0
        __module_license        db      "license=GPL", 0
        __module_author         db      "author=Bogdan D.", 0
        __module_description    db      "description=Pierwszy modul jadra 2.6",
 0
        __module_device         db      "device=test00", 0
        __module_depends        db      "depends=", 0

        ; nieistotne, wziete ze skompilowanego modulu C:
        __mod_srcversion        db      "srcversion=F5CE0CFFE0191EDB2F816D4", 0

        section "__versions" align 32

        ____versions:
                dd      0xfa02c634              ; Z MODULE.SYMVERS
        n1:     db      "struct_module", 0
                times   64-4-($-n1) db 0

                dd      0x1b7d4074
        n2:     db      "printk", 0
                times   64-4-($-n2) db 0

                dd      0xb5145e00
        n3:     db      "register_chrdev", 0
                times   64-4-($-n3) db 0

                dd      0xc192d491
        n4:     db      "unregister_chrdev", 0
                times   64-4-($-n4) db 0

                dd      0x26e96637
        n5:     db      "request_irq", 0
                times   64-4-($-n5) db 0

                dd      0xf20dabd8
        n6:     db      "free_irq", 0
                times   64-4-($-n6) db 0

                dd      0x1a1a4f09
        n7:     db      "__request_region", 0
                times   64-4-($-n7) db 0

                dd      0xd49501d4
        n8:     db      "__release_region", 0
                times   64-4-($-n8) db 0

                dd      0x865ebccd
        n9:     db      "ioport_resource", 0
                times   64-4-($-n9) db 0

                dd      0x9efed5af
        n10:    db      "iomem_resource", 0
                times   64-4-($-n10) db 0


        section ".gnu.linkonce.this_module" writeable align 128

        align 128
        __this_module:          ; laczna dlugosc: 512 bajtow
                                dd 0, 0, 0
                .mod_nazwa:     db "modul_dev_fasm", 0
                                times 64-4-($-.mod_nazwa) db 0
                                times 100 db 0
                                dd init_module
                                times 220 db 0
                                dd cleanup_module
                                times 112 db 0

   Do instalacji i usuwania modulu z jadra mozna uzyc tych samych
   skryptow, ktore byly dla jadra 2.4, zmieniajac ewentualnie nazwe pliku
   modulu.
     _________________________________________________________________

Inne jadra i inne sztuczki

   W pozniejszych wersjach jadra ogolny sposob pisania modulow nie ulegl
   zmianie. Ale jadro systemu Linux, jak kazdy wiekszy program, jest
   rozbudowywane i ulega zmianom. Takim zmianom ulegaja miedzy innymi:
     * lokalizacja pliku z sumami kontrolnymi do umieszczenia w sekcji
       __versions,
     * same wartosci sum kontrolnych do umieszczenia w sekcji __versions,
     * zawartosc struktury "module" umieszczonej w __this_module (a, co
       gorsza, czesc jej elementow istnieje tylko warunkowo, w zaleznosci
       od konfiguracji konkretnego jadra, co wplywa na adresy innych
       elementow),
     * zawartosc struktury file_operations,
     * zawartosc sekcji .modinfo, w szczegolnosci pojawienie sie nowych
       parametrow, na przyklad:
          + "retpoline=Y", oznaczajacy kompilacje bezpieczna, bez skokow
            niebezposrednich (np. jmp [eax]),
          + "intree=Y", oznaczajacy modul z drzewa jadra, co zapobiega
            "zanieczyszczeniu",
     * nazwy funkcji jadra, ktore chcemy wykorzystac,
     * fizyczna zawartosc binarnego pliku modulu typu ELF, na przyklad:
          + od ktorejs wersji okolo wersji 4.9 tak zwane relokacje, czyli
            adresy niektorych wykorzystywanych funkcji czy zmiennych,
            musza byc zerowane w finalnym pliku ELF, inaczej jadro go nie
            zaladuje,
          + od ktorejs wersji nie mozna juz zlinkowac jadra nowa wersja
            linkera LD, zwana "gold", nie mozna nawet skonfigurowac
            jadra, gdy ten linker jest uzywany,
          + zalecane jest samo istnienie sekcji, ktorej nazwa mowi
            linkerowi o tym, ze kodu na stosie nie mozna uruchomic:
               o dla FASMa: section '.note.GNU-stack'
               o dla NASMa: section .note.GNU-stack noalloc noexec
                 nowrite progbits

   Sposob kompilacji modulow jadra mozna "podpatrzec", wykonujac make (z
   odpowiednimi parametrami) z flaga V=1, na przyklad make O=build/ V=1
   modules (do zbudowania wszystkich skonfigurowanych modulow).

   Lokalizacja pliku z sumami kontrolnymi moze sie zmieniac w zaleznosci
   od wersji jadra i od konkretnej dystrybucji Linuksa - moze to byc plik
   Module.symvers gdzies w katalogu /lib/modules/wersja_jadra/, moze to
   byc plik symvers-wersja_jadra (byc moze spakowany) w katalogu /boot/.

   Zawartosc struktury "module" powinna znajdowac sie w pliku module.h w
   katalogu rozpakowanych zrodel jadra w odpowiedniej wersji:
   linux-X.Y.Z/include/linux/. Plik /usr/include/linux/module.h moze byc
   do tego celu nieodpowiedni.

   Inne interesujace pliki to, w zaleznosci od wersji jadra, na przyklad:
     * linux-X.Y.Z/include/asm-i386/MODULE.H,
     * linux-X.Y.Z/include/linux/VERSION.H,
     * linux-X.Y.Z/scripts/mod/MODPOST,
     * linux-X.Y.Z/include/linux/init.h,
     * linux-X.Y.Z/include/linux/vermagic.h,
     * linux-X.Y.Z/include/linux/module.h,
     * linux-X.Y.Z/include/linux/moduleparam.h,
     * linux-X.Y.Z/Makefile,
     * linux-X.Y.Z/scripts/Makefile.modpost,
     * linux-X.Y.Z/scripts/Kbuild.include,
     * linux-X.Y.Z/scripts/Makefile.build,
     * linux-X.Y.Z/arch/x86/Makefile,

   a sama obsluga modulow zajmuja sie na przyklad
   linux-X.Y.Z/kernel/module.c i linux-X.Y.Z/arch/x86/module.c.

   Strukture file_operations znajdziemy w linux-X.Y.Z/include/linux/fs.h,
   a w jadrze 5.5.12 wyglada juz tak:
   (przeskocz nowa strukture file_operations)
        struct file_operations {
                struct module *owner;
                loff_t (*llseek) (struct file *, loff_t, int);
                ssize_t (*read) (struct file *, char __user *, size_t, loff_t *
);
                ssize_t (*write) (struct file *, const char __user *, size_t, l
off_t *);
                ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
                ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
                int (*iopoll)(struct kiocb *kiocb, bool spin);
                int (*iterate) (struct file *, struct dir_context *);
                int (*iterate_shared) (struct file *, struct dir_context *);
                __poll_t (*poll) (struct file *, struct poll_table_struct *);
                long (*unlocked_ioctl) (struct file *, unsigned int, unsigned l
ong);
                long (*compat_ioctl) (struct file *, unsigned int, unsigned lon
g);
                int (*mmap) (struct file *, struct vm_area_struct *);
                unsigned long mmap_supported_flags;
                int (*open) (struct inode *, struct file *);
                int (*flush) (struct file *, fl_owner_t id);
                int (*release) (struct inode *, struct file *);
                int (*fsync) (struct file *, loff_t, loff_t, int datasync);
                int (*fasync) (int, struct file *, int);
                int (*lock) (struct file *, int, struct file_lock *);
                ssize_t (*sendpage) (struct file *, struct page *, int, size_t,
 loff_t *, int);
                unsigned long (*get_unmapped_area)(struct file *, unsigned long
, unsigned long, unsigned long, unsigned long);
                int (*check_flags)(int);
                int (*flock) (struct file *, int, struct file_lock *);
                ssize_t (*splice_write)(struct pipe_inode_info *, struct file *
, loff_t *, size_t, unsigned int);
                ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_ino
de_info *, size_t, unsigned int);
                int (*setlease)(struct file *, long, struct file_lock **, void
**);
                long (*fallocate)(struct file *file, int mode, loff_t offset,
                                loff_t len);
                void (*show_fdinfo)(struct seq_file *m, struct file *f);
        #ifndef CONFIG_MMU
                unsigned (*mmap_capabilities)(struct file *);
        #endif
                ssize_t (*copy_file_range)(struct file *, loff_t, struct file *
,
                                loff_t, size_t, unsigned int);
                loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
                                        struct file *file_out, loff_t pos_out,
                                        loff_t len, unsigned int remap_flags);
                int (*fadvise)(struct file *, loff_t, loff_t, int);
        }

   Zamiast funkcji register_chrdev trzeba prawdopodobnie uzywac
   register_chrdev_region, zamiast request_irq - prawdopodobnie
   pci_request_irq (taki symbol na liscie symboli jest zaznaczony jako
   eksportowany przez vmlinux, czyli przez samo jadro).

   Majac kod jadra skonfigurowany zgodnie z jadrem docelowym, poprzez
   make XXXconfig, mozecie skorzystac z ponizszego generatora sekcji
   .modinfo (jesli kompilujemy na swoj system, wystarczy skopiowac plik
   konfiguracyjny, na przyklad /boot/config-wersja_jadra jako plik
   .config do katalogu zrodel jadra, po czym wykonac make oldconfig).
   (przeskocz generator)
        #include <stdio.h>
        #include <stddef.h>
        #include <linux/module.h>

        #define NAZWA_MODULU "modul1"

        static void wyswietl_wspolne (const char nazwa[], const char wskaznik[]
)
        {
                struct module m;

                puts ("align 128");
                puts ("__this_module:");
                printf ("\t\t\ttimes %d db 0\n", offsetof (struct module, name)
);
                printf ("\t.mod_nazwa:\tdb '%s', 0\n", nazwa);
                printf ("\t\t\ttimes %d - ($ - .mod_nazwa) db 0\n", sizeof (m.n
ame));
                printf ("\t\t\ttimes %d db 0\n", offsetof (struct module, init)
 - offsetof (struct module, name) - sizeof (m.name));
                printf ("\t.mod_init:\t%s init_module\n", wskaznik);
                printf ("\t\t\ttimes %d db 0\n", offsetof (struct module, exit)
 - offsetof (struct module, init) - sizeof (m.init));
                printf ("\t.mod_exit:\t%s cleanup_module\n", wskaznik);
                printf ("\t\t\ttimes %d db 0\n", sizeof (struct module) - offse
tof (struct module, exit) - sizeof (m.exit));
                puts ("--------------------------------");
        }

        static void wyswietl_nasm (const char nazwa[], const char wskaznik[])
        {
                puts ("--------------------------------\nsection .gnu.linkonce.
this_module");
                wyswietl_wspolne (nazwa, wskaznik);
        }

        static void wyswietl_fasm (const char nazwa[], const char wskaznik[])
        {
                puts ("--------------------------------\nsection '.gnu.linkonce
.this_module' writeable align 128");
                wyswietl_wspolne (nazwa, wskaznik);
        }

        int main (void)
        {
                puts ("NASM, 32-bit:");
                wyswietl_nasm (NAZWA_MODULU, "dd");
                puts ("NASM, 64-bit:");
                wyswietl_nasm (NAZWA_MODULU, "dq");

                puts ("FASM, 32-bit:");
                wyswietl_fasm (NAZWA_MODULU, "dd");
                puts ("FASM, 64-bit:");
                wyswietl_fasm (NAZWA_MODULU, "dq");

                return 0;
        }

   Kompiluje sie go takim skryptem:
   (przeskocz skrypt kompilujacy)
        #!/bin/bash

        lpath=/sciezka/do/linux-X.Y.Z
        gcc     \
                -I /usr/include \
                -I $lpath/arch/x86/include \
                -I $lpath/arch/x86/include/generated \
                -I $lpath/arch/x86/include/uapi \
                -I $lpath/arch/x86/include/generated/uapi \
                -I $lpath/include \
                -I $lpath/include/uapi \
                -I $lpath/include/generated \
                -I $lpath/include/generated/uapi \
                -I $lpath/build/include \
                -I $lpath/build/arch/x86/include \
                -I $lpath/build/arch/x86/include/generated \
                -I $lpath/build/arch/x86/include/uapi \
                -I $lpath/build/arch/x86/include/generated/uapi \
                -include $lpath/include/linux/kconfig.h \
                -D__KERNEL__ \
                -DMODULE        \
                -o gen-modul-info       \
                gen-modul-info.c

   wstawiajac swoja sciezke do rozpakowanych zrodel jadra.

   Po uruchomieniu programu wyswietli on zawartosc, ktora nalezy
   ewentualnie uzupelnic (np. innymi polami struktury, bo ustawiane sa
   tylko: nazwa, adres funkcji uruchamiajacej i konczacej), po czym
   wstawic do sekcji .modinfo:
   (przeskocz przykladowy wynik)
        section .gnu.linkonce.this_module
        align 128
        __this_module:
                                times 24 db 0
                .mod_nazwa:     db 'modul1', 0
                                times 64 - ($ - .mod_nazwa) db 0
                                times 296 db 0
                .mod_init:      dq init_module
                                times 432 db 0
                .mod_exit:      dq cleanup_module
                                times 72 db 0

   Jadro jest pisane w jezyku C, wiec programisci piszacy moduly w tym
   jezyku maja te wygode, ze nie musza do swoich modulow kopiowac i
   dostosowywac struktur, bo juz maja je w plikach naglowkowych. Tak
   samo, do inicjalizacji struktur wystarczy zainicjalizowac odpowiednie
   pola, a kompilator juz umiesci odpowiednie wartosci w odpowiednich
   miejscach - nie trzeba liczyc, po ilu bajtach nalezy umiescic kolejne
   pole. Dla programistow innych jezykow staje sie to coraz trudniejsze.

   W zwiazku z tym, mozna tez rozwazyc napisanie "frontowej" czesci
   swojego modulu (tej z deklaracja funkcji uruchamiajacej, konczacej, z
   sekcja .modinfo i ze wszystkimi strukturami) w jezyku C, a samych
   funkcjonalnosci - w asemblerze, po czym polaczyc te czesci w calosc
   wedlug zasad uruchamiania funkcji miedzy C i asemblerem, opisanych w
   dziesiatej czesci mojego kursu.

   Problem moze stwarzac tez architektura: 32- lub 64-bitowa, gdyz inne
   sa nazwy rejestrow oraz sposob przekazywania parametrow do
   wywolywanych funkcji.

   W przypadku FASM-a, gdzie typ pliku wynikowego jest umieszczony w
   kodzie zrodlowym, trzeba pisac osobne wersje na systemy 32- i
   64-bitowe.

   W przypadku NASM-a moze byc troche latwiej, gdyz w kodzie mozna
   sprawdzic format docelowy (podawany na linii polecen) i odpowiednio
   zmodyfikowac nazwy rejestrow lub rozkazow. Mozna wykorzystac na
   przyklad takie makra:
   (przeskocz makra architektury)
        %ifidn __OUTPUT_FORMAT__, elf64
                bits 64
                %define ARCH            'x64'
                %define RET_REG         rax
                %define wskaznik        dq
                %define rozmiar_wsk     8
                %define pushflags       pushfq
                %define popflags        popfq
        %else
                bits 32
                %define ARCH            'x86'
                %define RET_REG         eax
                %define wskaznik        dd
                %define rozmiar_wsk     4
                %define pushflags       pushfd
                %define popflags        popfd
        %endif
        %macro  uruchom         1-7 ; funkcja, par1, par2, par3, par4, par5, pa
r6

                %if ARCH = 'x64'
                        %ifnempty %7
                                mov     r9, %7
                        %endif
                        %ifnempty %6
                                mov     r8, %6
                        %endif
                        %ifnempty %5
                                mov     r10, %5
                        %endif
                        %ifnempty %4
                                mov     rdx, %4
                        %endif
                        %ifnempty %3
                                mov     rsi, %3
                        %endif
                        %ifnempty %2
                                mov     rdi, %2
                        %endif
                        call    %1
                %else
                        %ifnempty %7
                                push    dword %7
                        %endif
                        %ifnempty %6
                                push    dword %6
                        %endif
                        %ifnempty %5
                                push    dword %5
                        %endif
                        %ifnempty %4
                                mov     ecx, %4
                        %endif
                        %ifnempty %3
                                mov     edx, %3
                        %endif
                        %ifnempty %2
                                mov     eax, %2
                        %endif
                        call    %1
                        %ifnempty %7
                                add     esp, 4
                        %endif
                        %ifnempty %6
                                add     esp, 4
                        %endif
                        %ifnempty %5
                                add     esp, 4
                        %endif
                %endif

        %endmacro

   I potem wykorzystac je w kodzie:
        init_module:
                uruchom printk, tekst_uruchomiono

                xor     RET_REG, RET_REG
                ret

   To powinno troche ulatwic prace i zmniejszyc duplikacje kodu miedzy
   wieloma plikami.

   Jesli chcecie sie na powaznie zajac pisaniem modulow, mozecie zaczac
   od przeczytania dokumentacji o tym, jak wszystko zrobic poprawnie oraz
   jakie funkcjonalnosci i mechanizmy jadro oferuje:
     * dokumentacja na kernel.org
     * Kernel newbies
     * pisanie sterownikow dla Linuksa

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
