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 >

3. Rejestracja urządzenia znakowego


3.1 Sposób przechowywania informacji o urządzeniach znakowych

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.



3.2 Funkcja register_chrdev()

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.



3.3 Inne funkcje korzystające z tablicy chrdevs

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.

3.4 Struktura char_device

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.


Autor: Filip Łukasik