Do spisu tresci tematu 6

6.4.2 Funkcja systemowa fcntl()



Spis tresci


Opis funkcji fcntl()

Funkcja fcntl() umozliwia programiscie wykonywanie bardzo wielu manipulacji na deskryptorze otwartego pliku. Czesc z operacji dostepna jest rowniez w postaci osobnej funkcji systemowej, ale nie wszyskie daja takie mozliwosci jak odwolanie do fcntl(). Za pomoca tej funkcji mamy rowniez dostep do flag pliku, mechanizmu blokad typu POSIX oraz do kontroli nad gniazdami.
Deklaracja funkcji w pliku fs/fcntl.c ma postac:

int fcntl(
      unsigned int fd     /* deskryptor pliku */,
      unsigned int cmd    /* komenda */
      unsigned long arg   /* argument zalezny od wykonywanej komendy */ );


Komendy funkcji fcntl() zdefiniowane sa w pliku include/asm/fcntl.h nastepujaco:

#define F_DUPFD         0       /* duplikacja deskryptora pliku */
#define F_GETFD         1       /* pobierz flage close_on_exec */
#define F_SETFD         2       /* ustaw flage close_on_exec */
#define F_GETFL         3       /* pobranie flag pliku (get f_flags) */
#define F_SETFL         4       /* ustawienie flag pliku (set f_flags) */
#define F_GETLK         5       /* pobierz blokady zalozone na plik */
#define F_SETLK         6       /* ustaw blokade na plik */
#define F_SETLKW        7       /* ustaw blokade na plik jesli mozliwe */

#define F_SETOWN        8       /*  ustaw wlasciciela gniazda (ang. socket) */
#define F_GETOWN        9       /*  pobierz wlasciciela gniazda (ang. socket) */


Dodatkowo zdefiniowana jest jedna wartosc argumentu w tym samym pliku:

#define FD_CLOEXEC      1  /* wlasciwie wszystko nieparzyste jest dobre */


Akcja przeprowadzana w przypadku roznych komend jest nastepujaca:

F_DUPFD
Duplikuje desryptor pliku, jednak nowy numer deskryptora nie moze byc mniejszy niz wartosc argumentu. Zwraca numer deskryptora lub blad. Aby dowiedziec sie wiecej, zajrzyj do opisu funkcji dup.
F_GETFD
Zwraca czy flaga close_on_exec jest ustawiona dla tego pliku. Flaga ta znajduje sie w tablicy deskryptorow.
F_SETFD
Ustawia lub kasuje flage close_on_exec w zaleznosci od argumentu. Jesli arg rowne FD_CLOEXEC, flaga zostaje ustawiona. Gdy arg rowne zero, flaga jest kasowana. Funkcja nie zwraca nic istotnego.
F_GETFL
Zwraca flagi pliku z obszaru plikow procesu. Flagi sa ustawiane zgodnie z parametrem funkcji open().
F_SETFL
Ustawia flagi pliku na wartosc argumentu arg, po wczesniejszym sprawdzeniu sensownosci flag, mozliwosci takiej zmiany oraz obcieciu wartosci do dozwolonych bitow.
F_SETLK, F_GETLK, F_SETLKW
Operuje na blokadach typu POSIX. Omowienie dalej.
F_GETOWN
Zwraca pole f_owner zawierajace identyfikator wlasciciela gniazda. Wartosc ujemna oznacza wlasciciela grupowego. Brak kontroli, czy operacja jest przeprowadzana na gniezdzie.
F_SETOWN
Ustawia wlasciciela gniazda (pole f_owner), jesli gniazdo do nikogo juz nie nalezy.


Ustawianie i kasowanie blokad typu POSIX


Jesli wywolujemy funkcje fcntl() z parametrem F_GETLK, F_SETLK lub F_SETLKW, argument arg jest traktowany jako wskaznik na strukture typu struct flock, ktorej definicja znajduje sie w pliku include/asm/fcntl.h, a wyglada nastepujaco:

struct flock {
        short l_type;   /* typ blokady */
        short l_whence; /* tryb obliczania przesuniecia w rekordzie (jak w lseek)*/
	off_t l_start;  /* poczatek obszaru */
        off_t l_len;    /* przesuniecie w bajtach */
        pid_t l_pid;    /* wlasciciel blokady zwracany przez F_GETLK */
};


Stale dla typu blokady zdefiniowane sa w tym samym pliku :

#define F_RDLCK         0  /* blokada odczytu */
#define F_WRLCK         1  /* blokada zapisu */
#define F_UNLCK         2  /* usuwanie blokady */


Pole l_whence determinuje sposob wyznaczania poczatku rekordu do zablokowania. Jesli pole zawiera wartosc 0, pozycja obliczona bedzie od poczatku pliku; jesli 1, od aktualnej pozycji pliku oraz od konca pliku, jesli podamy wartosc 2.
Pobranie blokady (F_GETLK) polega na przeszukaniu listy blokad podwieszonej pod i-wezlem danego pliku (pole i_flock) i sprawdzeniu, czy kolejne blokady nie koliduja z blokada, jaka zostala okreslona przez uzytkownika w strukturze flock. Jesli napotkana zostanie blokada kolidujaca, jej parametry zostaja przypisane odpowiednim polom struktury, lacznie z wlascicielem. Jesli zadna z blokad nie koliduje, na pole l_type zostaje przypisana wartosc F_UNLCK.
Zdejmowanie blokady jest rownie proste jak sprawdzanie konfliktow. Polega na znalezieniu na liscie blokad tej, ktora chcemy zlikwidowac. Znacznie bardziej skomplikowanie przedstawia sie zakladanie blokady. Oczywiscie w pierwszej kolejnosci sprawdzane jest, czy planowana blokada nie koliduje z zadna, zalozona wczesniej. W przypadku kolizji postepowanie zalezy od sposobu wywolania. Jesli byla wywolana w trybie nieblokujacym, zostaje zwrocony blad i dodatkowo zwracana jest informacja o blokadzie kolidujacej (podobnie jak przy F_GETLK).
A oto wlasciwy opis alorytmu zakladania i zdejmowania blokad typu POSIX:

algorytm fcntl_setlk z pliku fs/locks.c
wejscie: fd    deskryptor pliku
         cmd   komenda (F_SETLK,F_SETLKW,F_UNLCK)
         l     wskaznik na stukture typu flock
wyjscie: 0 jesli wykonano, kod bledu jesli nie mozna wykonac

  if (zly deskryptor)
     return (-EBADF);
  if (plik nie ma i-wezla)
     return (-EINVAL);
  skopiuj dane ze struktury l do struktury file_lock;
  if ((plik tylko do odczytu)&&(blokada typu F_RDLCK))
     return (-EBADF);
  if ((plik tylko do zapisu)&&(blokada typu F_WRLCK))
     return (-EBADF);

etykieta repeat:

  if (na plik zalozona blokada typu FLOCK)
     return (-EBUSY);
  if (zalozenie blokady)
     {
     wez pierwsza zalozona blokade;
     while (sa jeszcze blokady zalozone)
        if (zalozenie blokady niemozliwe (konflikt))
           {
           if (wywolanie nie blokujace)
              return (-EAGAIN);
           if (uspienie wywola blokade)
              return (-EDEADLOCK);
           uspij proces na konfliktowej blokadzie;
           goto repeat;
           }
     }
  znajdz blokade nalezaca do wolajacego ;
  while (sa jeszcze blokady nalezace do wolajacego)
     {
     if (poczatek zakladanej blokady wiekszy od konca znalezionej)
        {
        wstaw nowa blokade w tym miejscu;
        }
     if (znaleziona blokada ma niepuste przeciecie z zakladana)
        {
        if (zakladana blokada tego samego typu)
           {
           rozszerz blokade aby obejmowala obydwie;
           sprawdzaj konflikt blokad z poszerzona;
           }
        else
           {
           if (zakladana zawiera istniejaca)
              {
              if (juz raz dokonalismy rozszerzenia)
                 usun aktualna blokade z listy;
              obudz wszystkich czekajacych na starej blokadzie;
              zastap istniejaca blokade nowozakladana;
              }
           }
        }
     }
  wez pierwsza zniesiona blokade;
  if ("wystaje" z obu stron zalozonej)
     {
     stworz dwie blokady;
     przypisz na nie fragmenty wystajace;
     wstaw je do kolejki blokad;
     obudz procesy czekajace na nich;
     }
  if ("wystaje" z prawej)
     {
     skroc stara blokade;
     wstaw do kolejki;
     obudz procesy czejakace na niej;
     }
  wez ostatnia zniesiona blokade;
  if (rozna od pierwszej usunietej)&&("wystaje" z lewej strony)
     {
     skroc blokade do czesci "wystajacej";
     wstaw do kolejki blokad;
     }
  return (0);


Komentarza wymaga sposob sprawdzania zaistnienia zastoju. Sprawdzanie takiego stanu wywolywane jest przed uspieniem procesu, aby nie doprowadzic do uspienia kilku procesow bez szans na ich ponowne obudzenie.
Algorytm postepowania jest bardzo prosty :

algorytm posix_locks_deadlock z pliku fs/locks.c
wejscie: my_task        wskaznik do procesu, ktory chcemy uspic
         blocked_task   wskaznik do procesu blokujacego
wyjscie: 1 gdy zaistnial zastoj; 0 jesli nie

etykieta next_task:

  if (my_task==blocked_task)
     return (1);
  for (wszystkie blokady)
      {
      if (blocked_task uspiony na kolejce tej blokady)
         {
         if (wlasciciel blokady==my_task)
            return (0);
         blocked_task=wlasciciel blokady;
         goto next_task;
         }
      }
  return (0);


Bibliografia

  1. Pliki zrodlowe Linuxa:
  2. Dokumentacja do linuxa:
  3. M. Gabassi, B. Dupouy, Przetwarzanie rozproszone w systemia Unix, LUPUS 1996 (opis gniazdek)


Pytania i odpowiedzi

1. W niektorych wersjach UNIXa istnieje funkcja lockf() sluzaca do obslugi blokad typu POSIX. Czy w Linuxie istnieje analogon tej funkcji?
W poczatkowych wersjach istniala funkcja lockf(), ktora byla tylko opakowaniem (ang. wrapper) funkcji fcntl(). Od wersji 1.2.4 zrezygnowano z tej funkcji, chyba ze wzgledu na wielkosc jadra, i teraz zakladaniem blokad typu POSIX bedzie sie zajmowala tylko funkcja fcntl().


Autor: Andrzej Boczek