Podsystem Wejścia/Wyjścia w systemie Linux 2.4.7 | ||
Urządzenia znakowe i blokowe. Funkcja block_read(), block_write(). | ||
3. Rejestracja urządzenia znakowego | ||
< Poprzednia strona | Spis treści | Następna strona > |
Jądro przechowuje wszystkie informacje na temat zarejestrowanych urządzeń w tablicy chrdevs. Rozmiar
tej tablicy odpowiada możliwej ilości różnych numerów pierwszorzędnych dla urządzeń znakowych, która
przechowywana jest w zmiennej MAX_CHRDEV. Numery pierwszorzędne są liczbami
naturalnymi z zakresu 1-(MAX_CHRDEV-1). Tablica zawierająca informacje o urządzeniach znakowych definiowana
jest w pliku fs/devices.c:
struct device_struct {
const char * name;
struct file_operations * fops;
};
...
static struct device_struct chrdevs[MAX_CHRDEV];
Pole name w strukturze device_struct zawiera nazwę urządzenia, natomiast pole fops zawiera wskaźnik do
struktury file_operations.
Struktura file_operations definiowana jest w pliku include/linux/fs.h i wygląda
następująco:
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 *);
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);
};
Informacje zawarte w tej strukturze używane są przez wirtualny system plików Linuxa. Operacje read, write,
open, release, llseek, flush, ioctl pełnią oczywiste funkcje w odwołaniach do pliku specjalnego (jak
wspomniałem w podrozdziale 2.2 pole w strukturze file ustawiane jest tak, by
wskazywać właśnie na tą strukturę). Ciekawą funkcją jest mmap - pozwala ona na zmapowanie pliku do obszaru w
pamięci. Dzięki czemu operacje odczytu, zapisu do tego obszaru pamięci będą tożsame z odczytem/zapisem do
pliku specjalnego. Funkcja poll definiowana jest w przypadku urządzeń, dla których potrzebne jest odpytywanie o
to, czy można wykonać operację odczytu/zapisu (przykładem takiego urządzenia może być konsola użytkownika -
/dev/ttyX, często trzeba czekać, aż użytkownik wpisze coś z klawiatury). Reszta funkcji dużo rzadziej pojawia
się w zarejestrowanych urządzeniach, więc pominę ich opis.
Funkcja ta używana jest do zarejestrowania funkcji obsługi urządzenia znakowego w systemie. Zdefiniowana jest
ona w pliku fs/devices.c. Jako pierwszy parametr przyjmuje numer pierwszorzędny, pod
jakim ma zostać zarejestrowane urządzenie, jako drugi parametr przyjmuje nazwę urządzenia, jako trzeci zaś
przyjmuje strukturę file_operations. Funkcja ta wpisuje wartości dwóch ostatnich parametrów do struktury
device_struct pod odpowiednim indeksem. Oczywiście w strukturze podawanej jako ostatni parametr funkcji
register_chrdev() nie potrzebne jest tworzenie funkcji dla wszystkich pól w strukturze,
niektóre z tych funkcji w przypadku pewnych urządzeń nie miałoby sensu (na przykład poll w przypadku urządzenia
/dev/null). W rozdziale pierwszym mówiłem, że każde urządzenie da zdefiniować się przez trójkę: typ, numer
pierwszorzędny, numer drugorzędny. Gdzie ginie informacja o numerze drugorzędnym (podczas rejestracji go nie
podajemy)? Otóż jeśli dla jednego numeru pierwszorzędnego istnieje kilka urządzeń różniących się tylko numerami
drugorzędnymi, to rozróżnienie urządzeń pozostawiamy funkcjom ze struktury file_operations
Jak pamiętamy, w polu i_rdev i-węzła znajduje się zakodowana para numerów pierwszorzędny i drugorzędny
("wyciągnąć" je możemy przy pomocy makr MINOR i MAJOR). Dobrym przykładem obsługi różnych urządzeń dla
różnych numerów drugorzędnych jest fragment funkcji memory_open (elementu struktury file_operations używanej
podczas rejestracji urządzenia znakowego o numerze pierwszorzędnym równym 1) znajdującej się w pliku
drivers/char/mem.c
static int memory_open(struct inode * inode, struct file * filp)
{
switch (MINOR(inode->i_rdev)) {
case 1:
filp->f_op = &mem_fops;
break;
case 2:
filp->f_op = &kmem_fops;
break;
case 3:
filp->f_op = &null_fops;
break;
...
Jak widać funkcja ta w zależności od drugorzędnego numeru zapisuje w polu f_op różne struktury. Funkcja
posiada też funkcję dualną do niej: jest nią unregister_chrdev służy ona
usunięciu informacji o urządzeniu z tablicy chrdevs. Umożliwia to usuwanie sterowników dla nieużywanych
urządzeń znakowych.
Funkcja get_chrfops() (znajduje się ona w pliku fs/devices.c)
pojawiła się w kodzie funkcji chrdev_open(),
o której już wspominałem w rodziale 2.2. Funkcja ta jako parametry przyjmuje
numery pierwszo- i drugorzędny, a zwraca strukturę file_operations znajdującą się w tablicy chrdevs. W przypadku
gdy urządzenie znakowe nie jest jeszcze zarejestrowane w systemie, funkcja próbuję załadować moduł o nazwie
"char-major-NR". Oprócz zwrócenia struktury funkcja sprawdza czy pole owner znajdujące się w niej zawiera
moduł i jeśli tak, to zwiększa licznik odwołań do modułu.
W rodziale 2.1 przedstawiłem fragment kodu funkcji
init_special_inode(). Pojawiła się tam funkcja cdget()
(zdefiniowana się w pliku fs/char_dev.c). Funkcja ta zwraca strukturę
char_device (zdefiniowaną w pliku include/linux/fs.h
związaną z numerem urządzenia znajdującym się w i-węźle. Struktura ta wygląda następująco:
struct char_device {
struct list_head hash;
atomic_t count;
dev_t dev;
atomic_t openers;
struct semaphore sem;
};
Jak widać struktura ta zawiera ilość odwołań do urządzenia, ilość otwarć pliku specjalnego związanego z tym
urządzeniem, oraz semafor. Struktury dla urządzeń znakowych trzymane są w tablicy mieszającej. Wstawiane
są do tej tablicy właśnie przez funkcje cdget() (tworzy ona taką strukturę, gdy
okazuje się, że nie ma jej jeszcze w tablicy, zwiększa ilość count. Umożliwia ona
sprawdzenie ilości odwołań do urządzenia, oraz zapewnienie wyłączności na dane urządzenie. Funkcją dualną do
niej jest metoda cdput(), która zmniejsza count o 1, i
jeśli count = 0 to usuwa strukturę z pamięci. Struktura ta pojawiłá się w Linuxie
dosyć niedawno (w jądrze w wersji 2.2.19 jeszcze jej nie było) i nie jest na razie wykorzystywana. Wspominam o
niej, ponieważ w późniejszych wersjach jądra planowane jest umieszczanie w tej strukturze danych dla
urządzenia, które powinny być współdzielone przez wszystkie odwołania do tego urządzenia. Rozważana jest też
możliwość usunięcia pola i_rdev z i-węzła, ponieważ jako identyfikator urządzenia byłby używany i_cdev lub
i_bdev.