PODSYSTEM WEJŚCIA WYJŚCIA

Spis treści


1 Wstęp

Spis treści

2 Pojęcia podstawowe


2.1 Numery główne i drugorzędne.

Spis treści

2.2 Pliki specjalne. Spis treści

2.3 Tworzenie i usuwanie urządzeń. Spis treści

2.4 Porty wejścia/wyjścia. Spis treści

2.5 Tablice rozdzielcze urządzeń znakowych i blokowych. Spis treści

2.6 Inicjowanie urządzeń w jądrze. Spis treści

2.7 Podprogramy obsługi urządzeń. Spis treści

2.8 Obsługa funkcji systemowych. Spis treści

3 Urządzenia blokowe

3.1 Wprowadzenie

3.1.1 Charakterystyczne cechy urządzeń blokowych

3.1.2 Urządzenia blokowe w jądrze

Spis treści

3.2 Funkcje block_read, block_write

 

Spis treści


3.3 Buforowanie

 

 
 











spis treści


3.4 Szeregowanie zadań

 

3.4.1 Struktury danych

W pliku include/linux/blkdev.h zdefiniowano strukturę request. Opisuje ona każde żądanie odczytu i zapisu z urządzenia blokowego

struct request {
volatile int rq_status;
#define RQ_INACTIVE  (-1)
#define RQ_ACTIVE  1
#define RQ_SCSI_BUSY  0xffff
#define RQ_SCSI_DONE  0xfffe
#define RQ_SCSI_DISCONNECTING 0xffe0

 kdev_t rq_dev;                  /*do tego urządzenia skierowane jest żądanie*/
 int cmd;                       /* komenda, może przyjmować wartości READ or WRITE */
 int errors;                     /* zlicza błędy powstałe przy komunikacji z urządzeniem */
 unsigned long sector;           /* pierwszy sektor, którego dotyczy żądanie */
 unsigned long nr_sectors;      /*liczba sektorów (rozmiar) bieżącego żądanie. Gdy piszemy sterownik, powinien on sie odwoływać do zmiennej current_nr_sectors       ignorować nr_sectors (została ona podana tylko dla zachowania pełności opisu).*/
 unsigned long nr_segments;
 unsigned long current_nr_sectors;
 char * buffer;                 /* bufor do zapisu lub odczytu */
 struct semaphore * sem;        /* semafor używany przy korzystaniu z urządzeń SCSI oraz pliku wymiany*/
 struct buffer_head * bh;       /* struktura opisująca pierwszy bufor na liście danego żądania */
 struct buffer_head * bhtail;
 struct request * next;
};
 
 

3.4.2 Funkcje

Spis treści

3.5 Sterowniki dysku twardego ide-disk v. 1.08

3.5.1 Garść informacji na temat sterownika ide.c-disk v.1.08


 

3.5.1 Najważniejsze struktury danych

Oto opis najważniejszych struktur utrzymywanych przez ide.c oraz niektórych z ich pól.

    ide_drive - dane o pojedynczym napędzie

    typedef struct ide_drive_s {
        struct request          *queue; - kolejka żądań do urządzenia
        special_t       special;        - flagi poleceń specjalnych (np. rekalibracji)
        byte            media;          - rodzaj urządzenia (disk, floppy, ...)
        byte            usage;          - liczba operacji open() na napędzie
        byte            head;           - liczba głowic
        byte            sect;           - liczba sektorów
        unsigned short  cyl;           - liczba cylindrów
        void              *hwif;        - wskaźnik do struktury hwif
        struct hd_driveid *id;         - dokładne dane na temat napędu
        struct hd_struct  *part;        - tablica partycji
        char            name[4];        - nazwa napędu (np. "hda")
        void            *driver;        - sterownik urządzenia (ide_driver)
        [...]
        } ide_drive_t;
hwif - jedna struktura odpowiada jednemu interfejsowi IDE
        typedef struct hwif_s {
        void            *hwgroup;       - wskaźnik do struktury hwgroup
        ide_drive_t drives[MAX_DRIVES]; - napędy podłączone do portu (MAX_DRIVES = 2)
        int             irq;           - numer przerwania obsługującego interfejs
        byte            major;          - numer główny
        char            name[6];        - nazwa interfejsu (np. "ide0")
        [...]
        } ide_hwif_t;
hwgroup - struktura wspólna dla portów obsługiwanych przez to samo przerwanie
    typedef struct hwgroup_s {
      ide_handler_t   *handler;       - procedura obsługi przerwania (może być NULL)
      ide_drive_t *drive;             - aktualnie obsługiwany napęd
      ide_hwif_t *hwif;               - wskaźnik do struktury interfejsu
      struct request  *rq;            - aktualnie realizowane żądanie
        [...]
        } ide_hwgroup_t;
ide_driver - opis "podsterownika" obsługującego konkretny typ urządzenia
    typedef struct ide_driver_s {
      const char  *name;                - nazwa sterownika (np. ide-disk)
      const char  *version;             - wersja sterownika
      byte        media;                - typ urządzenia (disk, floppy, ...)
      ide_do_request_proc  *do_request; - funkcja rozpoczynająca obsługę żądania
      ide_end_request_proc *end_request; - funkcja kończąca obsługę żądania
        [...]
     } ide_driver_t;
ide_module - informacje o module
    typedef struct ide_module_s {
      int  type;                        - typ modułu
      ide_module_init_proc   *init;      - funkcja inicjalizująca moduł
      void     *info;                   - dodatkowe informacje o module
      struct ide_module_s *next;        - wskaźnik do następnego modułu
     } ide_module_t;
    ide_hwif_t hwifs[] - struktura będąca głównym repozytorium danych o wszystkim co dzieje się w ide.c

    ide_module_t *ide_modules- lista modułów
 
 
 

3.5.3 Działanie ide-disk.c

Sterownik ide-disk.c jest częścią ("podsterownikiem") sterownika ide.c, który zajmuje się obsługą wszelkich urządzeń podłączonych do interfejsu IDE, np.: CD-ROM'u, dysku twardego.
Funkcja zajmująca się strategią szeregowania po wstawieniu żądania do pustej kolejki wywołuje przypisaną urządzeniu w strukturze blk_dev_struct funkcję strategii. W naszym przypadku jest to funkcja do_ideX_request(), gdzie X jest numerem portu IDE, zdefiniowana w ide.c. Ide.c zajmuje się obsługą żądania tak długo jak jest to możliwe, natomiast gdy jest to konieczne sterowanie przejmuje ide-disk.c. Współpracę miedzy tymi modułami przedstawia poniższy rysunek.
 
 


 

Spis treści


3.6 Źródła informacji

1. Kod źródłowy Linuxa, pliki:
       include/linux/blk.h
     include/linux/blkdev.h
     drivers/block/ll_rw_blk.c
     fs/devices.c
     fs/block_dev.c

2. Michael K. Johnson: Kernel Hackers' Guide -urzadzenia blokowe

3. M.Beck, H.Bohme, M.Dziadzka, U.Kunitz, R.Magnus, D.Verworner "Linux kernel - jądro systemu"

4. M.J.Bach "Budowa systemu operacyjnego Unix"
 

Spis treści


4 Urządzenia znakowe

4.1 Terminal

Rodzaje urządzeń terminalowych: Urządzenie terminalowe odpowiada za komunikację: Schemat działania terminala:

Uwaga:  w trybie surowym (raw mode) komunikacja odbywa się z pominięciem dyscypliny lini.

Struktura danych urządzenia terminalowego (deklaracja w include/linux/tty.h):

struct tty_struct {
        ...
        
        struct tty_driver driver;
        /* 
         * Interfejs pomiędzy sterownikiem niskopozimowym terminala,
         * a procedurami tty.
         */
        
        struct tty_ldisc ldisc;
        /*
         * Interfejs pomiędzy implementacją dyscypliny lini,
         * a procedurami tty.
         */
        
        struct termios *termios;
        /*
         * Parametry pracy urządzenia terminalowego.
         */
        
        int pgrp;
        /*
         * Identyfikator grupy terminalowej.
         * Procesy z grupy terminalowej: o numerze grupy równej pgrp.
         */
        
        int session;
        /*
         * Numer sesji terminala.
         */

        ...
        
        /*
         * Tutaj także znajdują się dane dla dyscypliny lini N_TTY
         */

};
Terminal jest odpowiedzialny za wysyłanie odpowiednich sygnałów do procesów należący do grupy terminalowej np.: Źródła: Spis treści

4.2 Pseudoterminal

Pseudoterminal to para terminali: master-slave
master /dev/ptyxx
slave /dev/ttyxx
Typowe programy dla terminala master:

Dokumentajca: Documentation/devices.txt

Spis treści


4.3 Konsola

Jest to urządzenie znakowe odpowiadające za lokalną komunikację z użytkownikiem.
Po starcie systemu naszym terminalem staje się właśnie konsola.
W Linuksie mamy 63 konsole, (tty1..63).
Dzielą one ten sam ekran - są więc wirtualne (ang. virtual consoles).
Tylko jedna z nich może być w jednej chwili widoczna.
Ta aktywna konsola odpowiada urządzeniu tty0.
/dev/console. - odrębne urządzenie - aktualna konsola
Używane przez urządzenia z buforem ramki (ang frame buffer)

Obsługa konsoli:
sterownik urządzeń terminalowych

Jedną z ważniejszych właściwości programów obsługi konsoli jest emulacja terminali VT100.

Działanie
Odczyt
Efekty


Magic SysRQ Key.
Zestaw dosyć zlożonych komend uruchamianych przez kombinacje klawiszy (SysRQ to najczęściej Print Screen na klawiaturze).
Magic SysRQ dostępne jest (są??) dopiero w jadrach od 2.y.x gdzie y>=1, a x jest dowolne.
Przykład komend (pełny wykaz można znaleźć w pliku Documentation/sysrq.txt - źrodła kernela):

Obsługa Magicznych Klawiszy jest zaimplementowana w linux/drivers/char/sysrq.c.

przeglądanie wcześniejszych ekranów

(standardowe klawisze: Shift+ PgUp, PgDn)
Sterownik konsoli wykorzystuje możliwości tzw. hard scrollingu, dotępnego na kartach graficznych.
Ekrany widoczne poprzednio na konsoli, są pamiętane tylko w pamieci karty graficznej, a przegladanie ich (Shift +....) to po prostu zmiana adresu początku wyświetlanego obrazu. Z tego powodu, jeżeli zapragniemy zmienić konsolę na inną, program obsługujący konsolę wyczyści zawartość karty graficznej (żeby zachować sens działania Shift+....) i po powrocie na pierwotną konsolę nie bedziemy mogli obejrzeć tego, co poprzednio było wyświetlone.
 
Zmiana i tworzenie konsol
Zmieniac aktywną można na kilka sposobów:


Podane kombinacje obowiązują przy domyślnym mapowaniu klawiszy.
Zmiana mapowania - loadkeys
Konsolę tworzy się za pomocą chvt, a usuwa za pomocą deallocvt.
Na utworzonej konsoli można uruchomić program używając polecenia openvt.
 

Źródła informacji:

Spis treści

4.4 Dyscyplina linii


Dyscyplina linii polega na tłumaczeniu znaków przesyłanych przez terminal do procesu. Jest to przydatne, aby mieć pewien standardowy sposób reagowania procesu na wejście ( np. Ctrl-Z zatrymanie procesu ).
Interfejs dyscypliny linii zdefiniowany jest w pliku include/linux/tty_ldisc.h
Wersja 2.2.x jądra wyróżnia 2 dyscypliny linii, są to:


Dyscyplina N_TTY obsługuje następujące konwersje:

  1. koniec linii \n
  2. powrót karetki \r
  3. tabulator \t

  4. i wiele innych. Zainteresowanych odsyłam do źródeł: drivers/char/n_tty.c

Interfejs

W pliku include/linux/tty_ldisc.h jest zdefiniowany interfejs pomiędzy tty_discipline a procedurami obsługi tty
 
 

 Funkcje wywoływane ``z góry'', czyli przez użytkownika

Funkcje wywoływane ``z dołu'' - przez sterownik niskopoziomowy


Źródła informacji:

Spis treści

4.5 Urządzenie mem

Wstęp

Pamięć jest dostępna jako urządzenie znakowe o numerze głównym 1.
Wyróżniamy kilka podurządzeń mem:
 
Podnumer(minor)  lokalizacja w systemie plików cel 
1  /dev/mem  pamięć fizyczna 
2  /dev/kmem  pamięć wirtualna widziana przez jądro 
3 /dev/null  czarna dziura , wszystko tam wrzucone przepada 
4 /dev/port  dostęp do portów 
5 /dev/zero  generator zer
6 /dev/core 
7 /dev/full  urzadzenie zwracajace ENOSPC przy próbie zapisu 
8  /dev/random  generator liczb pseudolosowych 
9 /dev/urandom  j.w. tyle że nie blokujące

 

mem i kmem

Na /dev/mem i /dev/kmem można wykonywać operacje:

Funkcje

Funkcje obsługi mem są zaimplementowane w pliku drivers/char/mem.c .
Do najważniejszych można zaliczyć :

Zmienna globalna

Jedną z zmiennych globalnych często wykorzystywaną w powyższych funkcjach to
high_memory (wskazuje poczatek pamieci)
jest zadeklarowana w include/linux/mm.h
void * high_memory = NULL;

Różnice między mem a kmem

Mem i kmem różnią o tę częćś pamięci, którą zajmuje jądro
Zatem mem inaczej przelicza się offset w obu tych przypadkach
służą do tego makra:__pa,__va
 

Spis treści



5 Jak napisać program obsługi urządzenia

5.1 Wiadomości wstępne

 

5.1.1 Inicjowanie programu obsługi urządzenia

Funkcja inicjująca program obsługi urządzenia odpowiada za: Postać funkcji inicjującej:
    int init_mydrv (void)
Wywołanie funkcji należy dopisać do któregoś z plików: Inicjowanie programu obsługi, który będzie w postaci modułu odbywa sie w standardowej funkcji inicjującej moduł:
    int init_module (void)
Spis treści

5.1.2 Odczyt parametrów


Funkcja odczytująca parametry jest wywoływana przed funkcją inicjującą.

Postać funkcji:

    int mydrv_setup (char *s, int *p)
Tablica p (o rozmiarze p[0])
zawiera parametry przekazane do programu, które udało się jądru zamienić na liczby (próba zamiany dotyczy pierwszych dziesięciu parametrów).
Ciąg s
składa się z pozostałych argumentów.
Funkcję należy dopisać do pliku <init/main.c> w cooked_params:
    static struct kernel_param cooked_params[] __initdata = {
        ......
        {"mydrv=", mydrv_setup },
        ......
        {0, 0}
    };
Istnieje jeszcze tablica raw_params[] (dla jej programów obsługi jądro nie próbuje zamieniać pierwszych parametrów na liczby).

W module zadeklarowanie zmiennej jako parametru odbywa się za pomocą makra:

    MODULE_PARM(variable, type-description);
variable
nazwa zmiennej-parametru.
type-description
jej opis (dokumentacja w pliku <linux/module.h>).
Spis treści

5.1.3 Dostęp do przestrzeni użytkownika

Funkcje realizujące dostęp do przestrzeni użytkownika:
int access_ok (int type, unsigned long addr, unsigned long size)
int get_user (lvalue, address)
int __get_user(lvalue, address)
Przypisanie na lvalue wartości spod adresu address.
int put_user (expression, address)
__put_user (expression, address)
Zapisanie wartości expression pod adres address.
unsigned long copy_from_user (unsigned long to, unsigned long from, 
                              unsigned long len)
unsigned long copy_to_user   (unsigned long to, unsigned long from, 
                              unsigned long len)
Funkcje kopiujące dane pomiędzy przestrzeniami użytkownika i jądra. Spis treści

5.1.4 Alokacja pamięci

Dynamiczna alokacja i dealokacja pamięci w jądrze.
    void *kmalloc (size_t size, int flags)
    void kfree (const void *objp)
Niektóre możliwe wartości pola flags:
GFP_KERNEL
W przypadku braku wolnej pamięci jądro "czeka" na zwolnienie się jakiejś strony wstrzymując aktualnie wykonujący się proces.
GFP_ATOMIC
Jeśli zabraknie pamięci od razu zwracany jest błąd.
GFP_DMA
Flaga używana razem z poprzednimi. Zaznacza, że dany obszar będzie służyć do transmisji DMA.
Dla alokacji obszarów pamięci wirtualnej służą funkcje:
    void *vmalloc (unsigned long size)
    void vfree (void *addr)
Spis treści

5.1.5 Wsparcie dla mechanizmów sprzętowych

Kilka najbardziej popularnych mechanizmów sprzętowych ma wsparcie ze strony jądra. Oznacza to, ze programista, zamiast komunikować się ze sprzętem przy pomocy portów lub BIOS32, może używać zestawu funkcji i struktur danych, które tworzą wygodny interface i pomagają tworzyć przenośne programy obsługi urządzeń.

Do najlepiej wspieranych mechanizmów należy magistala PCI. Opis interface'u można znaleźć w pliku Documentation/pci.txt w źródłach jądra.

Spis treści 


5.2 Jak napisać program obsługi urządzenia znakowego

5.2.1 Rejestrowanie programu obsługi urządzenia

Do rejestracji programu obsługi urządzenia znakowego służy funkcja:
    int register_chrdev (unsigned int major, const char *name,
                         struct file_operations *fops)
major
numer nadrzędny pod jakim chcemy zarejestrować program.
name
nazwa urządzenia.
fops
wskaźnik do struktury operacji plikowych.
W programie w postaci modułów można także korzystać z funkcji zwalniającej:
    int unregister_chrdev (unsigned int major, const char *name)
Spis treści

 

5.2.2 Operacje plikowe

Struktura file_operations

Przykładowa struktura file_operations dla urządzenia znakowego:
        struct file_operations testdrv_fops {
                testdrv_llseek,         /* llseek */
                testdrv_read,           /* read */
                testdrv_write,          /* write */
                NULL,                   /* readdir */
                NULL,                   /* poll */
                testdrv_ioctl,          /* ioctl */
                NULL,                   /* mmap */
                testdrv_open,           /* open */
                NULL,                   /* flush */
                testdrv_release,        /* release */
                NULL,                   /* fsync */
                NULL,                   /* fasync */
                NULL,                   /* check_media_change */
                NULL,                   /* revalidate */
                NULL                    /* lock */
        };

Struktura file

Struktura struct file zdefiniowana w pliku <include/linux/fs.h>

Otwieranie i zamykanie urządzenia

        int (*testdrv_open) (struct inode * inode, struct file *filp);
        int (*testdrv_release) (struct inode *inode, struct file *filp);
Funkcja open służy programowi obsługi do wstępnego przygotowania dla kolejnych operacji. Zazwyczaj powinna wykonywać następujące działania: Funkcja release powinna realizować:

Czytanie i pisanie do/z urządzenia

    ssize_t (*read) (struct file *filp, char *buf, 
                     size_t size, loff_t *loff);
    ssize_t (*write) (struct file *filp, const char *buf, 
                      size_t size, loff_t *loff);
Funkcje implementują czytanie danych z urządzenia i pisanie do urządzenia. Powinny zwracać ilość przeczytanych/zapisanych danych.

Do przesyłania danych między przestrzenią użytkownika a przestrzenią jądra używamy makr copy_from_user, copy_to_user, get_user i put_user.
 

Funkcja kontroli urządzenia

    int (*ioctl) (struct inode *inode, struct file *filp, 
                  unsigned int cmd, unsigned long arg);
Zaimplementowanie tej metody pozwoli użytkownikowi urządzenia na dodatkowe, specyficzne tylko dla tego urządzenia operacje. Należy wybrać unikatowe numery komend, aby uniemożliwic wydanie poprawnej komendy błędnemu urządzeniu. Służą do tego makra:
        _IO(type, nr)
        _IOR(type, nr, size)    // czyta do przestrzeni jądra
        _IOW(type, nr, size)    // zapisuje do przestrzeni jądra
        _IOWR(type, nr, size)   // czyta i zapisuje
Kolejne parametry oznaczają: Oto przykład wycięty z pliku include/linux/random.h
/* Get the entropy count. */
#define RNDGETENTCNT    _IOR( 'R', 0x00, int )

/* Add to (or subtract from) the entropy count.  (Superuser only.) */
#define RNDADDTOENTCNT  _IOW( 'R', 0x01, int )

/* Get the contents of the entropy pool.  (Superuser only.) */
#define RNDGETPOOL      _IOR( 'R', 0x02, int [2] )
W podanym przykładzie numerem magicznym jest 'R'. Należy zwrócic uwagę, że jako size podajemy typ danych, a nie sizeof(typ).

Spis treści


5.3 Jak napisać program obsługi urządzenia blokowego

5.3.1 Rejestracja i operacje plikowe

Do rejestracji programu obsługi urządzenia blokowego służy funkcja:
    int register_blkdev (unsigned int major, const char *name,
                         struct file_operations *fops)
major
numer nadrzędny pod jakim chcemy zarejestrować program.
name
nazwa urządzenia.
fops
wskaźnik do struktury operacji plikowych.
Programu w postaci modułów mogę także korzystać z funkcji zwalniającej:
    int unregister_blkdev (unsigned int major, const char *name)
Przykładowe wypełnienie struktury operacji plikowych (fops):
    struct file_operations mydrv_fops {
        NULL,                     /* llseek */
        block_read,               /* read */
        block_write,              /* write */
        NULL,                     /* readdir */
        NULL,                     /* poll */
        mydrv_ioctl,              /* ioctl */
        NULL,                     /* mmap */
        mydrv_open,               /* open */
        NULL,                     /* flush */
        mydrv_release,            /* release */
        block_fsync,              /* fsync */
        NULL,                     /* fasync */
        mydrv_check_media_change, /* check_media_change */
        mydrv_revalidate,         /* revalidate */
        NULL                      /* lock */
    };
Nie trzeba implementować własnych funkcji czytających i piszących. Używamy standardowe funkcje block_* korzystające z mechanizmu podręcznej pamięci buforowej. Za rzeczywiste przesłania danych (kiedy bufory nie są w stanie obsłuzyć żądania) odpowiada procedura strategii.

Spis treści


5.3.2 Dodatkowe struktury danych

Każde urządzenie blokowe opisane jest przez strukturęblk_dev_struct:
    struct blk_dev_struct {
        request_fn_proc         *request_fn;
        queue_proc              *queue;
        void                    *data;
        struct request          *current_request;
        struct request   plug;
        struct tq_struct plug_tq;
    };
gdzie,
    typedef void (request_fn_proc) (void);
Jądro utrzymuje tablicę tych struktur (indeksowaną numerami nadrzędnymi):
    extern struct blk_dev_struct blk_dev[MAX_BLKDEV];
Musimy wypełnić pole request_fn. Jest to wskaźnik na procedurę strategii (gdzie są zaimplementowane lub zlecane niskopoziomowe operacje specyficzne dla danego urządzenia).

Inne dodatkowe struktury danych to:

int *blk_size [MAX_BLKDEV]
Opisuje rozmiar każdego urządzenia w kilobajtach. Pierwszy indeks to numer nadrzędny urządzenia, a drugi to numer drugorzędny.
int *blksize_size [MAX_BLKDEV]
Rozmiar jednego bloku urządzenia w bajtach.
int *hardsect_size [MAX_BLKDEV]
Rozmiar sektora urządzenia.
int read_ahead [MAX_BLKDEV]
Liczba sektorów, które mają zostać przeczytane z wyprzedzeniem, gdy plik będzie czytany sekwencyjnie.
Spis treści

5.3.3 Interfejs programisty, plik <linux/blk.h>

W pliku <linux/blk.h> znajduje się interfejs wspólnego kodu dla programów obsługi urządzeń blokowych.

Aby z niego korzystać trzeba wcześniej zdefiniować pewne symbole wymagane przez makrodefinicje z tego pliku:

MAJOR_NR
Numer nadrzędny urządzenia (nie musi być zdefinowany jako stała).
DEVICE_NAME
Nazwa urządzenia.
DEVICE_NR(device)
Służy do określenia numeru fizycznego urządzenia na podstawie drugorzędnego numeru urządzenia (często oba numery są sobie równe).
DEVICE_ON i DEVICE_OFF
Wykorzystywane w urządzeniach wyłączanych i włączanych (np stacja dyskietek). Definiujemy je puste.
Przykładowy kod dla programu obsługi urządzenia mydrv:
    #define MAJOR_NR          mydrv_major
    #define DEVICE_NAME       "mydrv"
    #define DEVICE_NR(device) MINOR(device)     
        /* numer fizycznego urządzenia aktualnie obsługiwanego 
           przez program jest równy numerowi drugorzędnemu */
    #define DEVICE_ON(device)        // nie obsługiwane
    #define DEVICE_OFF(device)       // nie obsługiwane
    int mydrv_major;
    ......
    #include <linux/blk.h>
Spis treści

5.3.4 Obsługa żądań, procedura strategii

Postać funkcji:
    void mydrv_request (void)
Jest to funkcja, która odpowiada za zlecanie (ew. wykonanie) niskopoziomowych instrukcji wejścia/wyjścia (na nią ustawiamy wskaźnik request_fn ze struktury blk_dev_struct).

Dla zwiększenia wydajności systemu jądro kolejkuje żądania przesłania danych do urządzenia (szeregowanie) i później przekazuje do programu obsługi całą listę żądań.

Dostęp do kolejki żądań odbywa się przy pomocy:

Pola struktury request przydatne dla funkcji żądania to:

kdev_t rq_de
Urządzenie, do którego następuje dostęp przez to żądanie.
int cmd
Rodzaj zlecenia. Może przybierać wartości READ lub WRITE.
unsigned long sector
Pierwszy sektor, którego dotyczy żądanie.
unsigned long current_nr_sectors
Liczba sektorów bieżącego żądania.
char *buffer
Obszar w pamięci podręcznej, do którego powinny być zapisane (READ) lub z którego powinny być odczytane dane (WRITE).
Schemat funkcji obsługi żądania może wyglądać tak:
    void mydrv_request (void)
    {
        while (1) {
        /* 
           w pętli nieskończonej bo INIT_REQUEST sam 
           zakończy wykonywanie naszej funkcji jeśli
           nie będzie już żądań 
        */
            INIT_REQUEST;
            ......
            /* sprawdzenie poprawności */
            ......
            switch (CURRET->cmd) {
                case READ :
                    /* czytanie */
                    break;
                case WRITE :
                    /* pisanie */
                    break;
                default:
                    /* oops */
                    end_request(0);        /* błąd */
                    continue;
            }
            end_request(1);         /* ok, udało się */
        }
    }
W powyższym przykładzie blokujemy jednak procesor na czas wykonywania operacji czytania/pisania.

Schemat działania programu obsługi urządzenia korzystającego z przerwań:

Spis treści 

5.4 Moduły

Opcjonalne części jądra, np. programy obsługi urządzeń, mogą być kompilowane w postaci modułów. Moduł to kod obiektowy, który można włączać do działającego jądra i z niego usuwać.

Korzyści, jakie daje możliwość stosowania modułów:

Pakiet modutils:

lsmod - pokazuje załadowane moduły, ich liczniki użyć i odwołania między nimi. Jest to prawie dokładnie to samo, co w pliku /proc/modules
insmod - instalowanie modułów w jądrze
rmmod - usuwanie modułów z jądra
modprobe - narzędzie silniejsze niż insmod - znając zależności między modułami (modules.dep), potrafi załadować od razu stos modułów, ma wygodny plik konfiguracyjny (/etc/conf.modules).
depmod - oblicza zależności między modułami dla modprobe

5.4.1 Funkcje init_module() i cleanup_module()

Te dwie funkcje moduł musi posiadać.

5.4.2 Symbole eksportowane przez jądro

Podczas ładowania jądro próbuje dowiązać niezdefiniowane symbole w module do własnych (eksportowanych) funkcji i zmiennych.

Moduł może eksportować własne zmienne i funkcje.

Listę symboli eksportowanych przez jądro można obejrzeć w /proc/ksyms.

Przykład:

...
c382004c ne_probe       [ne]
c381d04c ei_open        [8390]
c381d0ac ei_close       [8390]
c381d578 ei_interrupt   [8390]
c381dfa4 ethdev_init    [8390]
c381e014 NS8390_init    [8390]
c0239b40 drive_info
...
c01222e0 kmalloc
c0122474 kfree
c0122644 kfree_s
c0121390 vmalloc
c0121328 vfree
...
Makra:

5.4.3 Liczniki użyć modułu i referencje

Moduł można usunąć z jądra tylko wtedy, kiedy nie jest używany.

Najczęstsze sposoby użycia modułu:

Jądro śledzi odwołania do symboli między modułami i nie pozwala usunąć modułu, do którego odwołuje się inny moduł.

W pozostałych przypadkach używa się liczników użycia modułu. Po załadowaniu modułu licznik ma wartość 0. Do zwiększania i zmniejszania jego wartości służą makra:

MOD_INC_USE_COUNT - zwiększa wartość licznika
MOD_DEC_USE_COUNT - zmniejsza wartość licznika

Moduł można usunąć tylko wtedy, kiedy nie ma żadnych referencji do niego i jego licznik użyć ma wartość 0. 

5.4.4 Parametry modułu

Przekazanie parametrów do ładowanego modułu:
insmod ne.o io=0x300 irq=10
Do zaznaczenia zmiennej jako parametr służy makro:
MODULE_PARM(zmienna, typ);
Typ jest ciągiem formatu "[min[-max]]{b,h,i,l,s}", gdzie min i max są opcjonalnymi ograniczeniami długości tablicy (domyślnie 1, czyli pojedyncza zmienna), a litera oznacza:

b - bajt
h - słowo (2 bajty)
i - int
l - long
s - string

5.4.5 Przykładowy moduł

Oto kod przykładowego modułu. Eksportuje jedną funkcję, ma parametr i wypisuje kilka komunikatów podczas ładowania i usuwania.
/* 
 * Kompilacja:
 * gcc -O2 -D__KERNEL__ -DMODULE -c modul.c
 *
 * Opcja -O2 powoduje m. in. rozwijanie funkcji inline w miejscu wywołania.
 * Niektóre funkcje nie są udostępniane przez jądro i muszą zostać skompilowane
 * właśnie w ten sposób.
 */

#include <linux/kernel.h> /* printk */

#define EXPORT_SYMTAB
#include <linux/module.h>

int moja_funkcja(void);
EXPORT_SYMBOL(moja_funkcja);

int parametr = 0;
MODULE_PARM(parametr, "i");

int init_module(void)
{       
        printk("<1>modul: parametr == %d\n", parametr);
        
        printk("<1>modul: moduł załadowany\n");
        return 0;
}

void cleanup_module(void)
{
        printk("<1>modul: moduł jest usuwany\n");
}

int moja_funkcja(void)
{
        return 0;
}
Spis treści