Blog literacki, portal erotyczny - seks i humor nie z tej ziemi


Do spisu tresci tematu 7

7.1.6 Algorytmy open, close, read, write, ioctl, lseek,
select






Spis tresci


Wprowadzenie

Fakty ogolne

lseek

open/close (release)

read/write

Procedury strategii

ioctl

check_media_change/revalidate


select

Bibliografia

Pytania i odpowiedzi





Wprowadzenie

W Linuxie, tak jak w innych Unixach, dostep do urzadzen zewnetrznych
odbywa sie poprzez system plikow. Wobec tego operacje wykonywane na urzadzeniach
zewnetrznych sa takie same, jak na plikach - sa to: open, close, read,
write, lseek lecz takze ioctl, select, check_media_change
i revalidate.


Fakty ogolne

Jesli plik, na ktorym ktoras z tych operacji jest wykonywana jest plikiem
specjalnym (tzn. plikiem urzadzenia), to wowczas przypisana mu w jego i-wezle
tablica rozdzielcza file_operations wskazuje na tablice danego
urzadzenia i jest wykonywana funkcja specyficzna dla tego urzadzenia.

Tablica rozdzielcza zdefiniowana jest za pomoca nastepujacej struktury
(zawartej w pliku include/linux/fs.h):


struct file_operations {
int (*lseek) (struct inode *, struct file *, off_t, int);
int (*read) (struct inode *, struct file *, char *, int);
int (*write) (struct inode *, struct file *, const char *, int);
int (*readdir) (struct inode *, struct file *, void *, filldir_t);
int (*select) (struct inode *, struct file *, int, select_table *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct inode *, struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
void (*release) (struct inode *, struct file *);
int (*fsync) (struct inode *, struct file *);
int (*fasync) (struct inode *, struct file *, int);
int (*check_media_change) (kdev_t dev);
int (*revalidate) (kdev_t dev);
};



Jak widac, wszystkie funkcje dostaja wskaznik na i-wezel oraz na plik,
lub zakodowany numer glowny i drugorzedny urzadzenia. W pierwszym przypadku
poznaja drugorzedny numer urzadzenia (potrzebny do jego zidentyfikowania)
ze struktury reprezentujacej i-wezel (pole rdev).

Nalezy jednak zaznaczyc, ze znaczna wiekszosc rzeczy, ktore system musi
zrobic, wykonuje sama funkcja systemowa, wobec czego charakterystyczna
dla urzadzenia funkcja moze byc bardzo krotka, lub wrecz nie robic nic
(tak jest czesto np. w przypadku lseek).

Niektore pola tablicy rozdzielczej nie maja nic wspolnego z urzadzeniami
( readdir). fsync sluzy do synchronizacji (zapis buforow
na dysk) i jest opisany w Temacie 6.

Wobec tego, proces wykonywania funkcji systemowych dla plikow specjalnych
mozna podzielic na dwie czesci:


Wykonanie funkcji systemowej, praktycznie identyczne jak dla pliku
zwyklego;

Wykonanie funkcji specyficznej dla urzadzenia, wskazywanej przez odpowiedni
element tablicy rozdzielczej.


Funkcje specyficzne dla urzadzen sa zdefiniowane w programach obslugi
urzadzen znajdujacych sie w katalogu drivers/char lub drivers/block
(dla urzadzen znakowych i blokowych, odpowiednio). Sa one inne dla kazdego
rodzaju urzadzenia (tzn. dla kazdego urzadzenia o innym numerze glownym).

Dlatego tez duzo ciekawszymi funkcjami sa specyficzne dla plikow specjalnych
funkcje select, ioctl, check_media_change i revalidate.



lseek

- zmiana offsetu (standardowy algorytm - patrz Temat
6)

DEFINICJA: asmlinkage long sys_lseek(unsigned int fd, off_t offset, unsigned int origin)
WYNIK: nowa pozycja lub
EBADF (fd jest blednym deskryptorem),
EINVAL (origin jest bledna wartoscia).


Funkcja lseek sluzy do zmiany miejsca, w ktorym zostanie wykonana nastepna
operacja odczytu lub zapisu. Realizuje ja funkcja systemowa sys_lseek.
offset oznacza miejsce (prawdopodobnie liczbe bajtow od poczatku),
od ktorego ma sie rozpoczac najblizsza operacja odczytu/zapisu, a origin
ma nastepujace znaczenie:

0 = liczymy offset od samego poczatku;

1 = liczymy offset od aktualnej pozycji;

2 = liczymy offset od konca.

Jesli w tablicy rozdzielczej urzadzenia wskaznik lseek jest
ustawiony na NULL (tak jest w przypadku wiekszosci urzadzen),
wykonywana jest standardowa operacja, polegajaca na odpowiednim zmodyfikowaniu
wskaznika file->f_pos.

Funkcja lseek zwraca pozycje w pliku.

Jesli zdefiniowana jest lseek w programie obslugi urzadzenia,
jest ona wykonywana (tasma np. moze zostac przewinieta w odpowiednie miejsce).




open/close (release)

- otwarcie/zamkniecie pliku specjalnego (urzadzenia) - standardowy algorytm
- patrz Temat 6.

DEFINICJA: asmlinkage int sys_open(const char * filename,int flags,int mode)
WYNIK: nowy deskryptor lub
ENFILE (przekroczona max liczba otwartych deskryptorow procesu),
EMFILE (przekroczona max liczba otwartych deskryptorow pliku),
ENOTDIR, ENOENT, ENAMETOOLONG, EFAULT, EACCES, ETXTBSY, EISDIR.

DEFINICJA: asmlinkage int sys_close(unsigned int fd)
WYNIK: 0 (sukces) lub
EBADF (fd nie jest poprawnym, otwartym deskryptorem).



Znaczenie flags i mode opisane jest dobrze w manie.

Funkcja open sluzy do otwierania pliku specjalnego (rozpoczecie
pracy z urzadzeniem), a funkcja close - do zamkniecia pliku specjalnego.
Funkcje systemowe sys_open i sys_close dzialaja identycznie
jak w przypadku plikow zwyklych; ich algorytmy podane sa w Temacie
6.

Podczas otwierania pliku system przydziela miejsce w pamieci na i-wezel,
zwieksza jego licznik odwolan, przydziela pozycje w tablicy plikow i deskryptor.
Warto pamietac, ze w i-wezle pliku specjalnego, w polu rdev zakodowany
jest numer glowny i drugorzedny urzadzenia, ktorego tenze plik dotyczy.

Funkcja sys_open wywoluje funkcje *_open specyficzna
dla urzadzenia (jesli jest ona zdefiniowana w tablicy rozdzielczej). Funkcja
otwarcia urzadzenia musi utworzyc wszystkie potrzebne sobie struktury danych,
ew. uruchomic urzadzenie.

Funkcja sys_close wywoluje funkcje *_release tylko
wtedy, gdy juz zaden proces nie uzywa i-wezla.

Funkcje sys_open i sys_close sa zdefiniowane w pliku
fs/open.c

Funkcja *_open zdefiniowana w programie obslugi bardzo czesto
liczy liczbe odwolan do urzadzenia (nie mozna do tego celu wykorzystac
liczby odwolan do i-wezla, poniewaz jedno urzadzenie moze miec kilka i-wezlow;
mozna utworzyc kilka plikow specjalnych reprezentujacych ten sam numer
glowny i drugorzedny). Ten licznik jest wykorzystywany pozniej, przy *_release.
Jesli zaden proces nie uzywa urzadzenia, mozna je (w razie potrzeby) np.
wylaczyc. W przypadku urzadzen blokowych, funkcja *_release musi
jeszcze pamietac o ew. zapisaniu wszystkich buforow znajdujacych sie wciaz
w pamieci (robi to np. wykonujac block_fsync).


read/write

- czytanie/zapis z/na urzadzenie - standardowy algorytm - patrz Temat
6

DEFINICJA: asmlinkage int sys_read(unsigned int fd,char * buf,int count)
WYNIK: liczba przeczytanych bajtow lub
EFAULT (buf poza przestrzenia adresowa procesu)
EINVAL (nie mozna czytac z urzadzenia)
EBADF (bledny deskryptor), EISDIR (katalog)
EAGAIN (operacja nieblokujaca nie dala rezultatow)
EINTR (przerwanie przez sygnal)

DEFINICJA: asmlinkage int sys_write(unsigned int fd,char * buf,unsigned int count)
WYNIK: liczba zapisanych bajtow lub
EFAULT, EINVAL, EBADF, EAGAIN, EINT (jak wyzej), EPIPE.



Argumenty:

buf - wskaznik na poczatek bufora;

count - liczba bajtow do odczytania/zapisania;

Wynik: ilosc odczytanych/zapisanych bajtow.



Te funkcje sluza do czytania i pisania z/na urzadzenie. Sa one obslugiwane
przez funkcje systemowe sys_read i sys_write zdefiniowane
w pliku fs/read_write.c.

Funkcje systemowe uruchamiaja odpowiednie funkcje zdefiniowane w programie
obslugi urzadzenia. W przypadku urzadzen znakowych sa to po prostu funkcje
*_read i *_write, ktore odpowiednio czytaja/zapisuja
na urzadzenie. W przypadku urzadzen blokowych sprawa jest bardziej skomplikowana,
poniewaz odczyt i zapis jest buforowany i odbywa sie poprzez tzw. procedury
strategii. Najczesciej wowczas wskazniki w tablicy rozdzielczej wskazuja
na funkcje (odpowiednio) block_read
i block_write
zdefiniowane w pliku fs/block_dev.c, ktore wywoluja nastepnie
procedury strategii (patrz Operacje dyskowe
i Procedury strategii). (Dokladnie tak jest w przypadku
dysku twardego; w przypadku stacji dyskow elastycznych istnieja funkcje
floppy_read i floppy_write, ktorych jedyna funkcja oprocz
uruchamiania block_read/block_write jest jedynie sprawdzanie,
czy nie zostala zmieniona dyskietka).


Procedury strategii

Procedury strategii sluza do czytania/zapisu z urzadzen blokowych za
pomoca buforow.

Jesli program zada odczytania/zapisania danych, system okresla blok,
w ktorym te dane sie znajduja. Jesli ten blok jest w buforze (w pamieci),
pobiera go stamtad. Jesli nie, bufor tworzy zadanie (request), ktore
ma wykonac operacje dyskowa.

Wobec tego, programy obslugi urzadzen blokowych nie posiadaja funkcji
*_read i *_write, tylko procedury obslugi zadan do_*_request.

Dziala to nastepujaco (omawiamy czytanie; zapis jest analogiczny):


okreslamy, ktore bloki dyskowe mamy przeczytac (wlacznie z ew. czytaniem
z wyprzedzeniem - read ahead);

pobieramy algorytmem getblk naglowki dla kazdego bloku - moze
okazac sie, ze blok jest juz w pamieci.

wywolujemy ll_rw_block;



ll_rw_block tworzy zadania [ten wyraz warto napisac tak: z~a~dania
:)].

Dodajemy zadanie do kolejki zadan (all_requests)
za pomoca funkcji make_request;


Uwaga: zadania zapisu moga zajac co najwyzej 2/3 tej kolejki. Zadania
sa wsortowywane za pomoca algorytmu windowego (ang. elevator algorithm),
ktory minimalizuje czas ruchu glowicy. Jest to realizowane za pomoca nastepujacego
porzadku:

1. W starszych wersjach Linuxa (nie w 2.0): zadania odczytu przed zadaniami
zapisu;

2. Zadania pochodzace od urzadzen o mniejszych numerach drugorzednych
najpierw;

3. Zadania dotyczace blokow o mniejszych numerach najpierw;

Algorytm windowy realizuje makro IN_ORDER() zdefiniowane w
include/linux/blk.h:

#define IN_ORDER(s1,s2) ((s1)->rq_dev<(s2)->rq_dev||(((s1)->rq_dev==(s2)->rq_dev && (s1)->sector<(s2)->sector)))


lista all_requests miesci NR_REQUEST zadan (standardowo
64). W tym miejscu proces moze zostac uspiony (w stanie TASK_UNINTERRUPTIBLE),
czekajac na przydzielenie slotu na zadanie.


jesli nasze zadanie jest pierwsze na liscie, wywolujemy zdefiniowana
dla kazdego urzadzenia blokowego funkcje request_fn (jest to wlasnie
procedura strategii), ktora inicjuje realizacje zadania (komunikuje sie
z urzadzeniem zewnetrznym i ustawia funkcje, ktora uruchomi sie po nadejsciu
przerwania).

po nadejsciu przerwania (zakonczeniu operacji) uaktualniamy bufor,
budzimy ewentualne procesy, ktore byc moze czekaja na zwolnienie slotu
w all_requests i realizujemy nastepne zadanie z kolejki.


Dokladniejszy opis operacji dyskowych znajduje sie w Operacjach
dyskowych.


ioctl

DEFINICJA: asmlinkage int sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)
WYNIK: 0 (sukces) lub
EBADF (bledny deskryptor),
ENOTTY/EINVAL (bledny kod zadania dla danego urzadzenia).



Argumenty:

cmd - kod polecenia

arg - argument (zazwyczaj wskaznik do struktury)

ioctl jest realizowana przez funkcje systemowa sys_ioctl,
zawarta w pliku fs/ioctl.c. Jest to ogolny interface dla wszystkich
polecen specyficznych dla urzadzen, ktorych nie da sie zrealizowac za pomoca
pozostalych funkcji. Sa to przewaznie funkcje sterownicze, definiujace
parametry pracy danego urzadzenia.

Dla kazdego urzadzenia komendy ioctl sa inne. Sama funkcja sys_ioctl
obsluguje ich tylko kilka (np. FIOCLEX - ustawia flage close_on_exit itd.).

W przypadku stacji dyskow elastycznych np. ioctle sluza do: wyjmowania
nosnika, formatowania dyskow, pobierania parametrow nosnikow i samego napedu
itd. W przypadku dyskow twardych mozna za ich pomoca pobrac geometrie dysku,
odczytac tablice partycji itd. Duze znaczenie ma ioctl przy ustawianiu
parametrow terminali (patrz Terminale).


check_media_change/revalidate

Te funkcje sa zdefiniowane tylko dla urzadzen z wymienialnym nosnikiem
(np. stacja dyskow elastycznych). check_media_change sprawdza,
czy nosnik zostal zmieniony i, ewentualnie, przez revalidate uaktualnia
wszystkie informacje, jakie system posiada o urzadzeniu (np. tablice i-wezlow).
W szczegolnosci, w pliku devices.c jest zdefiniowana funkcja

int check_disk_change (kdev_t dev)

Jest ona wywolywana podczas mount i open, a jej dzialanie bardzo dobrze
pokazuje korzystanie z dwoch opisywanych funkcji:


wywoluje fops->check_media_change(dev);

uaktualnia superblok, i-wezly, bufory;

jesli trzeba (tzn. jesli jest zdefiniowana), wywoluje fops->revalidate(dev).






select

DEFINICJA: asmlinkage int sys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp)
WYNIK: liczba deskryptorow, ktorych stan sie zmienil lub
EBADF (w jednym ze zbiorow znalazl sie bledny deskryptor),
EINTR, EINVAL (n<0), ENOMEM (brak pamieci).



Argumenty:

n - najwiekszy numer deskryptora uzytego w zbiorach inp,
outp i exp plus 1.

inp - zbior deskryptorow urzadzen, na ktorych maja pojawic
sie dane;

outp - zbior deskryptorow urzadzen, na ktorych ma sie pojawic
mozliwosc zapisu;

exp - zbior deskryptorow urzadzen, na ktorych maja sie pojawic
wyjatki;

tvp - maksymalny czas, ktory mamy czekac (gdy = 0, czekamy
do skutku).

select sluzy do odpytywania urzadzen. Stosuje sie je w sytuacjach,
kiedy nie chcemy tracic czasu procesora na aktywne sprawdzanie, czy na
ktoryms z urzadzen mozna juz cos zapisac lub z niego odczytac. Funkcja
sluzy do ominiecia aktywnego czekania w petlach z operacja read/write
z flaga O_NDELAY.

Select konczy sie, gdy:


na deskryptorze ze zbioru inp pojawily sie jakies znaki do
odczytu;

na deskryptorze ze zbioru outp mozna natychmiast cos zapisac;


na deskryptorze ze zbioru exp pojawil sie wyjatek;

skonczyl sie czas okreslony przez tvp.


Po zakonczeniu dzialania funkcji zbiory deskryptorow sa modyfikowane,
aby zaznaczyc, ktore z nich zmienily stan (tzn. na ktorych odpowiednie
operacje moga sie powiesc).

Zbiory deskryptorow (struktura fd_set) obsluguje sie za pomoca
makr zdefiniowanych w include/asm-i386/posix_types.h:

FD_CLR(int fd, fd_set *set) - usuwa fd ze zbioru set;

FD_ISSET(int fd, fd_set *set) - zwraca 1 jesli fd nalezy do
set;

FD_SET(int fd, fd_set *set) - dodaje fd do zbioru set.

FD_ZERO(fd_set *set) - czysci zbior set.

Funkcja select dziala nastepujaco:


zerujemy zbiory wyjsciowe res_in, res_out i res_ex;


zmieniamy stan procesu na TASK_INTERRUPTIBLE;

for i=0..n-1 (petla po wszystkich deskryptorach) jesli deskryptor
i nalezy do ktoregos ze zbiorow i operacja select specyficzna
dla urzadzenia zwraca 1 to dodajemy ten deskryptor do odpowiedniego zbioru
wyjsciowego;

jesli (nic nie znalezlismy i mamy czas), to: schedule(); powrot
do for;

zmieniamy stan procesu na TASK_RUNNING. Idea tej funkcji polega na
tym, ze po pierwszym sprawdzeniu wszystkich deskryptorow proces jest usypiany,
po czym moze zostac obudzony dopiero po otrzymaniu przerwania od ktoregos
z odpytywanych urzadzen. Wowczas petla sprawdzajaca jest wykonywana ponownie.
Patrz rowniez Pytania.


Bibliografia


Pliki zrodlowe Linuxa:


include/linux/fs.h
(struktura tablicy rozdzielczej )

ipc/msg.c (block_read
i block_write)

ipc/read_write.c
(sys_lseek, sys_read i sys_write)

ipc/open.c (sys_open,
sys_close)

ipc/ioctl.c (sys_ioctl)


ipc/select.c (sys_select)



Man pages: lseek(2), open(2), close(2), read(2), write(2), ioctl(2),
select(2).

plik /usr/src/linux/Documentation/ioctl-numbers.txt

Maurice J. Bach "Budowa systemu operacyjnego UNIX", rozdz.
10: System wejscia-wyjscia

Michael K. Johnson Writing
Linux Device Drivers

Linux Kernel Hacker's Guide: Device Drivers





Pytania i odpowiedzi


1. Ile deskryptorow mozna umiescic w zbiorze deskryptorow obslugiwanym
za pomoca makr FD_SET, FD_CLR, FD_ISSET i uzywanych w funkcji
select?

Maksymalna wielkosc zbioru wynosi 256 elementow. Okresla to zawarta
w pliku include/linux/posix_types.h linijka: #define __FD_SETSIZE
256

Ta liczba musi byc co najmniej rowna stalej FD_OPEN.





Autor: Jacek Brzezinski
  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • qualintaka.pev.pl
  •