Cyprian Gracz <c.gracz@students.mimuw.edu.pl>


Obsluga plikow specjalnych i tablic rozdzielczych w systemie Linux



1. Wstep

      W systemie operacyjnym Linux dostep do urzadzen zewnetrznych uzyskuje sie poprzez system plikow specjalnych. Pozwala to na dostep do urzadzen fizycznych za pomoca tych samych funkcji, co do zwyklych plikow. Jesli zostaje wywolana funkcja systemu plikow, to jadro sprawdza, czy zostala ona wywolana w stosunku do zwyklego pliku, czy do pliku specjalnego. Jesli jest to zwykly plik, wykonywane sa zwykle czynnosci systemu plikow. Jesli natomiast jest to plik specjalny, obsluga jego jest realizowana przez podprogram obslugi urzadzenia, z ktorym zwiazany jest plik specjalny.

      Urzadzenia ze wzgledu na dostep do nich podzielone sa na urzadzenia znakowe (character devices) oraz urzadzenia blokowe (block devices). Do urzadzen znakowych mamy dostep sekwencyjny, bajt po bajcie (np. karty dzwiekowe). Natomiast do urzadzen blokowych mamy dostep swobodny, w blokach dowolnej wielkosci (np. dyski twarde).



2. Obsluga plikow specjalnych

      Jak juz wczesniej wspomnialem urzadzenia w Linuxie obslugiwane sa przez system plikow specjalnych. Sa one przewaznie zamontowane w katalogu /dev, lecz kazde urzadzenie mozna montowac w dowolnym miejscu drzewa katalogow. Poniewaz urzadzenia widziane sa jako pliki, mozna nadawac uzytkownikom do nich prawa dostepu tak samo, jak ma to miejsce w przypadku zwyklych plikow.

      Urzadzenia (pliki specjalne) tworzymy w systemie za pomoca funkcji mknod (zob. man mknod). Jej argumentami sa: nazwa nowego urzadzenia, jego typ (blokowe czy znakowe), numer glowny (MAJOR) oraz numer podrzedny (MINOR). Jako typ urzadzenia podajemy 'c' dla urzadzen znakowych lub 'b' dla urzadzen blokowych. Numer glowny okresla przynaleznosc urzadzenia do pewnej grupy. Urzadzenia z tej samej grupy obslugiwane sa przez ten sam podprogram obslugi. Numer podrzedny przekazywany jest jako argument podprogramowi obslugi. Identyfikuje on urzadzenie w obrebie grupy (musi byc unikatowy). Zatem stworzenie urzadzenia znakowego o nazwie 'tmpdev', o numerze glownym 123 i numerze podrzednym 12, wyglada nastepujaco:

  mknod tmpdev c 123 12

      Numer glowny (MAJOR) i podrzedny sa liczbami z zakresu od 0 do 255, i tworza razem 16-bitowa liczba jednoznacznie identyfikujaca urzadzenie w systemie (8 starszych bitow to MAJOR, 8 mlodszych to MINOR). Zadne urzadzenie nie moze miec numeru glownego rownego 0, ani 255 (zarezerwowane na pozniejsze rozszerzenia systemu).

MAJOR identyfikuje grupe urzadzen. Sa one obslugiwane przez ten sam podprogram obslugi. Wszystkie podprogramy obslugi mozna znalezc w jednej z dwu tablic systemowych: blkdevs[] lub chrdevs[]. blkdevs odpowiada za obsluge urzadzen blokowych, a chrdevs za obsluge urzadzen znakowych. Maja one identyczna strukture:

struct device_struct chrdevs[MAX_CHRDEV]

  lub

struct device_struct blkdevs[MAX_BLKDEV]

Struktura device_struct jest nastepujacej postaci:

struct device_struct {

    const char * name;   //nazwa urzadzenia

    struct file_operations * fops;   //wskaznik do struktury operacji, jakie mozna wykonac na pliku

};

Skoro mamy juz urzadzenie i jego podprogram obslugi, trzeba go zarejestrowac. Robi sie to za pomoca funkcjiregister_chrdev/register_blkdev. Opisze pokrotce dzialanie funkcji register_chrdev.

int register_chrdev(unsigned int major, const char * name, struct file_operations *fops){

/* funkcja przyjmuje jako argumenty: swoj numer glowny, nazwe urzadzenia, ktore podprogram obsluguje, wskaznik do struktury operacji dozwolonych na pliku */

Jesli major==0, to major=ostatni wolny numer i

  • jesli major==0 (nie ma wolnych miejsc w tablicy), to zwracamy -EBUSY
  • wpp. chrdevs[major].name=name i chrdevs[major].fops=fops i zwracamy major

    Jesli major>=MAX_CHRDEV, to zwracamy -EINVAL

    Jesli ten numer major jest juz zajety i fops!=chrdevs[major].fops (jest to urzadzenie z innymi operacjami na nim dozwolonymi), to zwracamy -EBUSY

    Jesli major jest dozwolonej wielkosci i chrdevs[major]==NULL, to wstawiamy w to miejsce name i fops

    Dla urzadzen blokowych dziala to identycznie, z ta roznica, ze zamiast chrdevs[] jest uzywana blkdevs[].

    Wyrejestrowanie urzadzenia jest proste.

    int unregister_chrdev(unsigned int major, const char * name)

    Po sprawdzeniu, czy major jest z odpowiedniego zakresu i czy nazwa urzadzenia zajestrowanego jest taka sama jak name, pola struktury w chrdevs[major] sa ustawiane na NULL.



    Poniewaz urzadzenia sa reprezentowane w systemie jako pliki, jest do nich zapewniony dostep tak samo, jak do systemu plikow (np. open, close, read, write, itd...). Jednak ze wzgledu na to, ze kazde urzadzenie powinno inaczej "rozumiec" poszczegolne funkcje, funkcje systemowe sa podmieniane przez funkcje z podprogramow obslugi urzadzen. Omowie dzialanie niektorych funkcji systemowych na plikach specjalnych.

    open()

  • Najpierw postepuje jak dla zwyklych plikow: inicjalizuje obiekt pliku,
  • otwiera chrdev_open()/blkdev_open() (z zaleznosci od informacji zawartej w i-wezle)
  • pobiera numer MAJOR z i-wezla
  • na operacje zdefiniowane dla pliku przypisuje operacje sterownika urzadzenia
  • jesli zostala w sterowniku zdefiniowana funkcja open(), to ja wywoluje

    close()
  • Postepuje jak dla zwyklych plikow, z tym wyjatkiem, ze jesli licznik uzycia pliku jest rowny 0, to wywoluje (jesli oczywiscie jest zdefiniowana) specyficzna dla urzadzenia funkcje release()

    read()/write()
  • Jesli tylko istnieja, to wywoluje funkcje read()/write() specyficzne dla urzadzen

    llseek()
  • Jesli istnieje odpowiednia funkcja llseek()specyficzna dla urzadzenia to ja wywoluje, wpp. wywoluje systemowa default_llseek()