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 > |
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.
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.
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ą).
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ą.