Podsystem Wejścia/Wyjścia w systemie Linux 2.4.7
Urządzenia znakowe i blokowe. Funkcja block_read(), block_write().
4. Rejestracja urządzenia blokowego
< Poprzednia strona Spis treści Następna strona >

4. Rejestracja urządzenia blokowego


4.1 Sposób przechowywania informacji o urządzeniach blokowych

Analogicznie do urządzeń znakowych, wszystkie zarejestrowane w jądrze urządzenia blokowe, przechowywane są w tablicy blkdevs indeksowanej numerami pierwszorzędnymi urządzeń. Tablica ta definiowana jest w pliku fs/block_dev.c i wygląda następująco:

static struct {
  const char *name;
  struct block_device_operations *bdops;
} blkdevs[MAX_BLKDEV];


Jak widać maksymalna liczba różnych numerów pierwszorzędnych to MAX_BLKDEV. Struktura opisująca urządzenie blokowe posiada pole zawierające nazwę urządzenia i wskaźnik do struktury block_device_operations, podanej jako parametr podczas rejestracji, o której opowiem za chwilę. Struktura block_device_operations definiowana jest w pliku include/linux/fs.h i w pełnej krasie wygląda następująco:

struct block_device_operations {
  int (*open) (struct inode *, struct file *);
  int (*release) (struct inode *, struct file *);
  int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);
  int (*check_media_change) (kdev_t);
  int (*revalidate) (kdev_t);
};


Jak widać pojawiają się tam funkcje open, release, ioctl analogicznie jak w strukturze file_operations. Funkcja check_media_change() ma umożliwić sprawdzenie, czy doszło do zmiany nośnika (co ma sens w przypadku urządzeń takich jak CD-ROM, stacja dyskietek). Funkcja revalidate nakazuje odświeżyć bufory dla urządzenia (o buforach powiem wkrótce), taka potrzeba zachodzi na przykład po zmianie nośnika właśnie.

4.2 Proces rejestracji urządzenia blokowego

Już wcześniej zwróciłem uwagę na brak operacji read i write w strukturze block_device_operations. Teraz postaram opisać się dokładniej jak wynika proces inicjalizacji sterownika dla urządzenia blokowego. Urządzenie najpierw rejestrowane jest w jądrze przy pomocy funkcji register_blkdev(). Jest to funkcja analogiczna do register_chrdev() - umieszcza ona informacje przekazane jako drugi i trzeci parametr w tablicy blkdevs (ważną różnicą jest, że dostajemy tu strukturę block_device_operations, a nie file_operations. Następnie program inicjujący sterownik, ustawia funkcję realizującą zlecenia z kolejki zleceń do urządzenia, będzie ona uruchamiana raz na pewien czas. Dobry przykład tej filozofii działania obrazuje fragment kodu zawartego w pliku (drivers/ide/hd.c):

...
if (devfs_register_blkdev(MAJOR_NR,"hd",&hd_fops)) {
  printk("hd: unable to get major %d for hard disk\n",MAJOR_NR);
  return -1;
}
blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), DEVICE_REQUEST);
...


Warto zamieścić parę linii komentarzu do tego kodu. W pierwszej linii kodu pojawia się funkcja devfs_register_blkdev(), o której dotychczas nie wspominałem. Jest to świeży pomysł twórców Linuxa. Jest on jednak ciągle w fazie eksperymentalnej, więc opiszę go bardzo skrótowo. Celem jest stworzenie systemu plików (podobnie jak katalog /proc), w którym po zarejestrowaniu urządzenia, pojawiałoby się ono automatycznie, jako plik specjalny. Główną korzyścią takiego rozwiązania jest pominięcie procesu tworzenia urządzenia w katalogu /dev. Aktualnie funkcja ta właściwie tylko wywołuje funkcję register_blkdev() i na tym kończy swoje działanie. Oczywiście istnieje analogiczna funkcja dla urządzeń znakowych i funkcje dualne do nich.

Dwie kolejne linijki kodu to obsługa sytuacji błędnej. W kolejnej linijce widzimy wywołanie funkcji blk_init_queue. Funkcja ta zdefiniowana jest w pliku drivers/block/ll_rw_block.c, jako pierwszy parametr przyjmuje kolejkę zleceń (jak widać korzystamy z domyślnej kolejki zleceń dla urządzenia). Drugim parametrem jest funkcja obsługująca zlecenia z kolejki zleceń. Mechanizm ten jest opisany w dokumencie mówiącym o szeregowaniu zleceń do urządzeń blokowych, więc nie będziemy go tutaj dokładniej omawiać. Warto jedynie wiedzieć, że w funkcjach block_read i block_write (opisanych w rozdziałach 7 i 8), wykorzystywana jest funkcja, ll_rw_block(), która operuje na kolejce zleceń i wymusza obsługę zlecenia. Ten mechanizm umożliwia stosowanie bardziej zaawansowanych technik obsługi zleceń odczytu/zapisu do wolnych urządzeń blokowych, takich jak wyszukiwanie windowe itp.



4.3 Inne funkcje korzystające z tablicy blkdevs

Podobnie jak w przypadku urządzeń znakowych, zdefiniowana jest (w pliku fs/block_dev.c) funkcja get_blkfops(). Funkcja ta, podobnie jak jej odpowiednik, sprawdza, czy wskaźnik do struktury block_device_operations pod odpowiednim indeksem (numer pierwszorzędny podany jako parametr) w tablicy blkdevs jest różny od NULL. Jeśli tak, zwraca wskaźnik do tej struktury, wpp. próbuje załadować moduł o nazwie block-major-NR i ponawia próbę (w przypadku znalezienia wartości NULL zwraca ją).



4.4 Struktura block_device

W tym przypadku również mamy do czynienia z analogią do urządzeń znakokowych. Opisywana struktura od struktury char_device, różni się obecnością pola zawierającego wskaźnik do struktury block_device_operations. Struktura ta jest również dosyć nowa w jądrze i ciągle ulega zmianom (na przykład w jądrze 2.4.13 dodano do niej pole zawierające i-węzeł). Struktura ta ma zapewnić w przyszłości podobną funkcjonalność jak jej odpowiednik dla urządzeń znakowych (patrz rozdział 3.4), lecz na razie jest używana bardzo rzadko (choć już w jądrze 2.4.13 korzysta już z niej na przykład sterownik ramdysku). Struktury block_device dla zarejestrowanych urządzeń, są trzymane w tablicy mieszającej, do której dostęp realizowany jest przez funkcje bdget() i bdput(). Pierwsza funkcja sprawdza czy dana struktura jest w pamięci, jeśli nie to alokuje nową strukturę, wpp. zwiększa wartość w polu bd_count struktury o jeden i zwraca strukturę. Funkcja bdput(), zmniejsza wartość bd_count o 1 i jeśli wartość jest równa zero, to zwalnia pamięć związaną ze strukturą.


Autor: Filip Łukasik