Do spisu tresci tematu 3

3.1.2 Kolejki FIFO




Spis tresci


Wprowadzenie

Kolejki FIFO (lacza nazwane) sluza do komunikacji miedzy procesami opierajacej sie na zasadzie kolejki prostej. Kolejki FIFO, w odroznienu od laczy nienazwanych, umozliwiaja wymiane informacji pomiedzy procesami niespokrewnionymi ze soba. Sa one w budowie i obsludze podobne do systemu plikow. Dostep do nich odbywa sie poprzez funkcje systemowe obslugi plikow.

Kolejki FIFO nie dostarczaja zadnych mechanizmow, ktore umozliwialyby wysylanie komunikatow przeznaczonych dla konkretnych procesow tak jak jest to w przypadku kolejek komunikatow.


Struktury danych


Wprowadzenie

Implementacja kolejek FIFO w duzym stopniu korzysta ze stuktur danych dostarczonych przez system plikow.

Kazda kolejki FIFO tak jak plik posiada i-wezel. I-wezel ma w swoich strukturach zapamietane, ze zwiazana jest z nim kolejka FIFO. Posiada takze wskaznik do struktury pipe_inode_info postaci:

Dane do lacza zapisywane sa cyklicznie. Informacja o tym, gdzie rozpocznie sie kolejna operacja czytania lub pisania jest pamietan w i_wezle poprzez stukture pipe_inode_info na zmiennych start i len. Jest to zasadnicza roznica pomiedzy i_wezlem zwiazanym z kolejka FIFO i i_wezlem przeznaczonym dla zwyklego pliku., gdzie informacja ta jest zapisana w tablicy plikow. Gdyby tak bylo w przypadku kolejek FIFO komunikaty bylyby odczytywane i zapisywane w dowolnej kolejnosci, poniewaz kazdy proces mialby inne informacje o poczatku i koncu danych.

W pliku include/linux/limits.h zdefiniowana jest wielkosc bufora przechowujacego dane. Jest to jednoczesnie maksymalny rozmiar komunikatu, ktorego umieszczenie w kolejce bedzie wykonane poprzez tzw. niepodzielna operacje:

#define PIPE_BUF 4096    

W i-wezle zwiazanym z kolejka FIFO nie ma wskaznikow na bloki posrednie i bezposrednie.
Przesylane dane przetrzymywane sa w pamieci operacyjnej wskazywanej przez pole base ze struktury pipe_inode_info. Rozni sie to opisu kolejek FIFO podanego przez Maurice J. Bacha w ksiazce "Budowa systemu operacyjnego UNIX ", ktory mowi, ze w kolejkach FIFO do przechowywania danych wykorzystuje sie bloki bezposrednie.


Funkcje i ich implementacja


Wprowadzenie

Istnieje piec funkcji systemowych do obslugi kolejek FIFO:
-mknod - tworzy nowe lacza
-open - otwiera lacze
-write - zapisuje dane do lacza
-read - czyta dane z lacza
-close - zamyka deskryptor zwiazany z laczem
-unlink - zamyka lacze

Przy opisie bledow jakie moga zwracac funkcje bede podawal tylko te bledy, ktore sa bezposrednio zwiazane z laczami.

Funkcja mknod()

Funkcja ta sluzy do utworzenia nowej kolejki FIFO.
DEFINICJA: int mknod(nazwa_lacza, prawa_dostepu | S_IFIFO, 0)
    WYNIK: 0 w przypadku powodzenia operacji, 
           -1, gdy blad: errno = EEXIST  (kolejka o podanym kluczu istnieje,
                                          wiec niemozliwe jest jej utworzenie)
                                 

Pierwszym argumentem funkcji jest nazwa powstajacego lacza. Drugi argument okresla prawa dostepu do lacza. Trzeci argument oznacza pierwszy i drugorzedny numer urzadzenia dla plikow specjalnych. W przypadku kolejek FIFO nie ma on znaczenia.

Implementacja funkcji:

{
   sprawdz prawa dostepu,sprawdz czy taki plik istnieje; jezeli tak to zwroc
   blad EEXIST, 
   przydziel nowy i-wezel, wywolaj funkcje fifo_init, ktora inicjuje strukture
   pipe_inode_info. 
   Ta czesc implementacji nalezy do systemu plikow. Dalsza czesc algorytmu
   wykonana jest przez funkcje fifo_init.
   Funkcja fifo_init:
   okresl operacje dopuszczalne na i-wezle (na razie jedyna dopuszczalna
   operacja jest open)
   inode->i_op = &fifo_inode_operations;
   zaznacz, ze i-wezel jest laczem FIFO 
   inode->i_pipe = 1;
   zaznacz, ze nikt nie czyta ani nie pisze z lacza
   PIPE_LOCK(*inode)= 0;
   Na razie nie przydzielaj pamieci na lacze
   PIPE_BASE(*inode)= NULL;
   ustal poczatek i rozmiar bufora na 0
   PIPE_START(*inode)= PIPE_LEN(*inode)= 0;
   zaznacz, ze nikt nie jest w trakcie otwierania lub zamykania lacza
   PIPE_RD_OPENERS(*inode)= PIPE_WR_OPENERS(*inode)= 0;
   PIPE_WAIT(*inode)= NULL;
   zaznacz, ze nie ma czytalnikow ani pisarzy
   PIPE_READERS(*inode)= PIPE_WRITERS(*inode)= 0;
}   

W tym momencie nie jest jeszcze przydzielana pamiec na bufor wskazywany przez pole base. Bedzie ona przydzielona przy piewszym wywolaniu funkcji open. Funkcja fifo_init inicjuje takze pole i_op nowopowstalego i-wezla. Okresla ono jakie operacje mozna wykonywac na i-wezle. W tym momencie dopuszczalna jest tylko operacja open.

Funkcja open()

-otwiera lacze. 
DEFINICJA: int open(char* nazwa_lacza, int flagi)
    WYNIK: 0 w przypadku sukcesu
           -1, gdy blad: errno = ENXIO - otwieranie lacza do pisania, gdy nie ma procesow czytajacych
                                 ENOMEM - brakuje pamieci w jadrze
                                 EINTR - w czasie wykonania funkcji proces otrzymal sygnal

Pierwszym argumentem funkcji jest nazwa lacza. Drugi argument okresla sposob dostepu do lacza.
Argument flagi moze przyjmowac nastepujace wartosci:
-O_WRONLY - otwieranie lacza tylko do pisania
-O_RDONLY - otwieranie lacza tylko do czytania
-O_RDWR - otwieranie lacza do pisania i czytania
-O_NDELAY - okresla czy operacje, ktorych wykonanie nie moze byc natychmiast wykonane maja byc blokowane. Flaga ustawiona - brak blokowania.

Implementacja:
{
   sprawdz prawa dostepu, czy istnieje plik nazwa_lacza, przydziel nowy deskryptor - 
   ta czesc nalezy do systemu plikow. Dalsza czesc kodu to funkcja fifo_open. 
   switch( deskryptor otwierany do: ) {
   case czytania:
      okresl typ deskryptora (na razie sestaw operacji okreslony na i-wezle jest troche inny
      od ostatecznego zestawu operacji przydzelanego tutaj. Jednak roznice miedzy nimi sa na 
      tyle subtelne, ze nie bede ich wyjasnial)
      filp->f_op = &connecting_fifo_fops;
      Jezeli nie ma zadnych czytelnikow, to obudz spiace procesy-moze sa w srod nich 
      czytelnicy, ktorzy zasneli w trakcie otwierania lacza 
      if (!PIPE_READERS(*inode)++)
         wake_up_interruptible(&PIPE_WAIT(*inode));
      if (open wykonywane z blokowaniem lub jest przynajmniej jeden pisarz) {
         zwieksz licznik czytelnikow otwierajacych lacze 
         PIPE_RD_OPENERS(*inode)++;
         while (nie ma czytelnikow) {
            if (przyszedl sygnal) return EINTR;
            interruptible_sleep_on(&PIPE_WAIT(*inode));spij w kolejce
      } 
      jezeli po obudzeniu sa jacys czytelnicy, ktorzy zasneli w trakcie wykonywnia open
      to ich obudz
      if (!--PIPE_RD_OPENERS(*inode))
         wake_up_interruptible(&PIPE_WAIT(*inode));
      }
      dopoki sa pisarze, ktorzy usneli w trakcie wykonywania open to poczekaj, az wszyscy 
      oni sie obudza i dojda do tego miejsca 
      while (PIPE_WR_OPENERS(*inode))
         interruptible_sleep_on(&PIPE_WAIT(*inode));
      Jezeli sa czytelnicy do nadaj teskryptorowi pliku pelne prawa czytania. Ta czesc kodu 
      zawsze wykonana dla open z opcja blokowania.
      if (PIPE_WRITERS(*inode))
      filp->f_op = &read_fifo_fops;
      break;
   case pisania:
      if (jezeli open jest wykonywane z opcja bez czekania i nie ma czytelnkow) 
      return (ENXIO); 
      okresl funkcje dopuszczalne na deskryptorze
      filp->f_op = &write_fifo_fops;
      jezeli nie bylo do tej pory pisarzy obudz wszystkie spiace procesy. Spac moga zarowno 
      czytelnicy bedacy w trakcie otwierania lacza jak i czytelnicy probujacy czytac z lacza do
      ktorego nikt nie pisze
      if (!PIPE_WRITERS(*inode)++)
         wake_up_interruptible(&PIPE_WAIT(*inode));
      jezeli nie ma czytelnikow
      if (!PIPE_READERS(*inode)) {
         PIPE_WR_OPENERS(*inode)++; zwieksz ilosc pisarzy bedacych w trakcie otwierania lacza
         while (!PIPE_READERS(*inode)) {
            if (przyszedl sygnal) return (ERESTARTSYS);
               interruptible_sleep_on(&PIPE_WAIT(*inode));
         }
         jezeli po obudzeniu sa jacys pisarze, ktorzy zaseli w trakcie wykonania open to 
         ich obudz
         if (!--PIPE_WR_OPENERS(*inode))
            wake_up_interruptible(&PIPE_WAIT(*inode));
      }
      jezeli sa czytelnicy, ktorzy zasneli w trakcie wykonania open to poczekaj, az 
      wszyscy oni sie obudza i dojda do tego miejsca w swoim kodzie
      while (PIPE_RD_OPENERS(*inode))
         interruptible_sleep_on(&PIPE_WAIT(*inode));
      break;
   case pisania i czytania:
      okreskl funkcje dopuszczalne na deskryptorze 
      filp->f_op = &rdwr_fifo_fops;
      jezeli wczesniej nie bylo czytelnikow to obudz wszystkich spiacych pisarzy
      if (!PIPE_READERS(*inode)++)
         wake_up_interruptible(&PIPE_WAIT(*inode));
      obudz wszystkich pisarzy, ktorzy zasneli wykonujac funkcje open
      while (PIPE_WR_OPENERS(*inode))
         interruptible_sleep_on(&PIPE_WAIT(*inode));
      jezeli wczesniej nie bylo pisarzy to obudz wszystkich spiacych czytelnikow
      if (!PIPE_WRITERS(*inode)++)
         wake_up_interruptible(&PIPE_WAIT(*inode));
      obudz wszystkich czytelnikow, ktorzy zasneli wykonujac open
      while (PIPE_RD_OPENERS(*inode))
         interruptible_sleep_on(&PIPE_WAIT(*inode));
      break;
   default:
      return EINVAL;jezeli deskryptor nie byl otwierany ani do pisania, ani to czytania, 
                     ani do czytania i pisania to zwroc EINVAL 
   }
   jezeli jest juz przeznaczona pamiec na bufor dla danych to wyjdz z funkcji, jezeli nie 
   topen jest wykonywane poraz pierwszy lub od czasu powstania lacza bylo ono uzywane a nastepnie 
   ilosc procesow uzywajacych lacza spadla do zera (w takim przypadku zwalniana jest pamiec 
   przeznaczona na  bufora lacza). W obu przypadkach trzeba przydzielic pamiec.
   if (PIPE_BASE(*inode))
      return 0;
   page = __get_free_page(GFP_KERNEL);
   byc moze juz ktos przydzielil pamiec; jezeli tak to zwalniamy to co zaalokowalismy
   if (PIPE_BASE(*inode)) {
      free_page(page);
      return 0;
   }
   if (!page)   return ENOMEM;brakuje nam pamieci
   trzeba jeszcze uaktualnic informacje o buforze. Byc moze lacze istnieje juz od dawna, jakies procesy je 
   urzywaly i potem je zamknely. Jezeli ilosc procesow uzywajacych lacza spadnie do zera to kasowana 
   jest zawartosc bufora, ale nie sa uaktualniane informacje o poczatku i dlugosci bufora. Trzeba to 
   zrobic teraz.
   PIPE_LOCK(*inode) = 0;
   PIPE_START(*inode) = PIPE_LEN(*inode) = 0;
   PIPE_BASE(*inode) = (char *) page;
   return 0;
}

Warto zwrocic uwage na niesymetryczne zachowanie sie funkcji open w przypadku, gdy jest ona wywolywana z flago O_NDELAY.
Jezeli jest wywolywana do czytania i nie ma procesow piszacych do lacza, to powrot z niej nastepuje bez kodu bledu.
Jezeli jest wywolywana do pisania i nie ma procesow czytajacych, to powrot nastepuje z bledem ENXIO.


Funkcja write()

- sluzy do zapisywnia danych do lacza.

DEFINICJA: int write(int fd, char* buf, int count)
    WYNIK: liczba bajtow skopiowanych do lacza w przypadku sukcesu
           -1, gdy blad: errno = EFAULT (zly adres buf)
                   EINTR  (otrzymano sygnal podczas czekania)
                   EAGAIN (lacze otwarte z opcja bez czekania i nie ma miejsca 
                   do zapisania danych.
                   EPIPE (nie ma procesu czytajacego z lacza. Proces piszacy otrzymuje
                    sygnal SIGPIPE. Jezeli go przechwytuje to errno = EPIPE.

Pierwszym argumentem jest numer deskryptora, ktory zwrocila funkcja open. Drugi argument to bufor z danymi do przeslania, a trzeci argument to rozmiar przesylanych danych.

Dzialanie funkcji write zalezy od rozmiaru danych jakie chcemy wpisac do lacza. Jezeli sa one mniejsze od rozmiaru lacza to wpisanie danych bedzie wykonane przez niepodzielna operacje, tzn beda zapisane w calosci. Jezeli natomiast rozmiar danych jest wiekszy od rozmiaru lacza to dane sa dzielone na mniejsze porcje, ktore sa umieszczane w laczu niezaleznie od siebie. Moze to spowodowac przemieszanie danych wpisywanych przez kilka procesow.

Dzialanie funkcji write zalezne takze od ustawienia flagi (z O_NDELAY czy bez) w funkcji open zwracajacej deskryptor fd.

z O_NDELAY bez O_NDELAY
nie ma czytelnikow sygnal SIG_PIPE sygnal SIG_PIPE
wpisujemy mniej danych niz jest miejsca w laczu liczba zapisanych bajtow = count liczba zapisanych bajtow = count
wpisujemy wiecej danych niz jest miejsca w laczu jezeli rozmiar danych jest mniejszy od calkowitego rozmiaru lacza to zwracany jest blad EAGAIN wpp zwracana jest ilosc zapisanych bajtow (jezeli jest niezerowa) albo EAGAIN czeka na wolne miejsce

Implementacja funkcji:

{
   sprawdz prawa dostepu, znajdz i-wezel zwiazany z deskryptorem - tym zajmuje sie system plikow
   if (nie ma czytelnikow){
      wyslij sygnal SIGPIPE;
      return EPIPE;
   }
   jezeli ilosc danych do wpisania jest mniejsza od rozmiaru lacza, to wpisanie danych ma 
   byc operacja niepodzielna 
   if (ilosc danych do wpisania <= PIPE_BUF)
      free = count;
   else
      free = 1;  rozmiar danych jest tak duzy, ze nie mozna ich wszystkich jednoczesnie 
                   umiescic w buforze. Bedziemy umieszczac male kawalki, nawet rozmiaru 
                   jednego bajta
   while (ilosc danych do wpisania>0) {
      while (ilosc wolnego miejsca w laczu < free) lub ktos pisze lub czyta z lacza) {
         if (nie ma czytelnikow) {
            wyslij sygnal SIGPIPE; 
            if (zadne dane nie sa zapisane) retrun EEPIPE;
            if (jakies dane sa zapisane) return ilosc zapisanych danych. Ta sytuacja mozliwa
                                                        tylko w przypadku, gdy ilosc zapisywanych danych>rozmiar bufora
         }
         if (proces otrzymal sygnal)
         if (zadne dane nie sa zapisane) return EINTR;
         if (jakies dane sa zapisane) return ilosc zapisanych danych. Ta sytuacja mozliwa
                                                             tylko w przypadku, gdy ilosc zapisywanych danych>rozmiar bufora
         if (pisanie ma byc wykonane bez blokowania) {
            if (zadne dane nie sa zapisane) retrun EAGAIN;
            if (jakies dane sa zapisane) return ilosc zapisanych danych. Ta sytuacja mozliwa
                                                  tylko w przypadku, gdy ilosc zapisywanych danych>rozmiar bufora
            zasnij oczekujac na wolne miejse w buforze
         }
      PIPE_LOCK(*inode)++;zablokuj dostep do lacza
      Zapisz do lacza tyle danych ile sie zmiesci. 
      Jezeli rozmiar danych wejsciowych <= rozmiarowi lacza to w tym momencie beda zapisane 
      wszyskie dane. Wpp zostanie zapisana tylko taka czesc danych jaka zmiesci sie w laczu i 
      bedziemy dalej krecic sie w tej petli.
      PIPE_LOCK(*inode)--;oblokuj dostep do i-wezla
      Obudz spiace procesy. W szczegolnosci moga tam byc czytelnicy, ktorzy cos odczytaja i 
      zwolnia miejsce 
   }
return ilosc zapisanych bajtow;
}

Funkcja read()

- sluzy do pobierania danych z lacza

DEFINICJA: int read(int fd, char* buf, int count)
    WYNIK: 0 w przypadku sukcesu
           -1, gdy blad: errno = EFAULT (zly adres buf)
                                 EAGAIN - ustawiona flaga O_NDELAY i nie ma co czytac
                                 EINTR - funkcja przerwana przez sygnal
Znaczenia argumentow sa taki jak w funkcji write, z ta roznica, ze buf oznacza bufor, do ktorego
beda zapisywane dane otrzymane z lacza.

Dzialanie funkcji read zalezy od ustawienia flagi (z O_NDELAY czy bez) w funkcji open zwracajacej deskryptor fd.

z O_NDELAY bez O_NDELAY
danych do odczytania jest mniej niz danych w laczu odczytana liczba bajtow = count odczytana liczba bajtow = count
puste lacze sa pisarze - EAGAIN, nie ma ich - 0 sa pisarze - czeka na dane, nie ma ich - 0
danych do odczytania jest wiecej niz danych w laczu odczytana liczba bajtow = ilosci danych w laczu odczytana liczba bajtow = ilosc danych w laczu
Implementacja funkcji
{
    sprawdz prawa dostepu, znajdz i-wezel zwiazany z deskryptorem - tym zajmuje sie system plikow
    if (czytanie bez mozliwosci blokowania) {
       if (ktos czyta lub pisze do lacza)
          return EAGAIN;
       if (lacze jest puste)
          if (sa pisarze)
             return EAGAIN;
          else nie ma pisarzy
             return 0;
    } else czytanie moze byc blokowane
       while (puste lacze lub ktos pisze do lub czyta z lacza) {
          if (puste lacze) {
             if (nie ma pisarzy)
               return 0;
          }
          if (przyszedl sygnal)
             return EINTR;
          interruptible_sleep_on(&PIPE_WAIT(*inode));zasnij do czasu, gdy lacze bedzie niepuste i 
                                                           nikt nie bedzie z niego czytal ani pisal
       }
    PIPE_LOCK(*inode)++;zablokuj dostep do lacza
    przeczytaj dane z lacza. Jezeli w laczu jest mniej danych niz potrzebujesz, przeczytaj tylko tyle i
    sie jest
    PIPE_LOCK(*inode)--;odblokuj dostep do lacza
    wake_up_interruptible(&PIPE_WAIT(*inode));obudz spiace procesy - w szczegolnosci pisarzy
    jezeli zostaly przeczytane jakies dane to je zwroc
    wpp sprawdz czy sa pisarze. Jezeli sa to zwroc EAGAIN. Jezeli nie ma pisarzy to zwroc 0

}

Nalezy zauwazyc, ze funkcja read nie moze przeczytac wiecej danych niz rozmiar lacza. Dlatego tez, jezeli chcemy odczytac wiecej bajtow niz lacze moze pomiescic, funkcje read trzeba wywolac kilkakrotnie. To jednak moze powodowac, ze nie otrzymamy spojnych danych.

Z przedstawionej implentacji funkcji write i read widac, ze nie zapewniaja one zywotnosci.
Zalozmy, ze mamy wielu wciaz dochodzacych pisarzy klasy A, ktorzy chca zapisac dane wielkosci 1/4 rozmiaru bufora, jednego pisarza klasy B, ktory chce zapisac dane wielkosci 1/2 rozmiaru bufora, jednego czytelnika, ktory co pewien czas (tzn. na tyle rzadko, zeby funkcja write zdazyla sie w pelni wykonac) pobiera dane wielkosci 1/4 rozmiaru lacza. Bufor na poczatku jest pelny. Funkcje read i write powoduja zaglodzenie pisarza klasy B, poniewaz:
po odebraniu przez czytelnika danych zwalnia sie 1/4 bufora, budzeni sa wszyscy pisarze, ale zapisuje tylko ten, ktoremu uda sie zmiescic swoje dane, a wiec pisarz klasy A. Znow przychodzi czytelnik i tak w nieskonczonosc.

close()

-zamyka deskryptor lacza

DEFINICJA: int close(int fd)
    WYNIK: 0 gdy sukces
          -1 gdy blad:

Jedynym parametrem jest deskryptor pliku

Podczas wykonania funkcji close() wykonywane sa te same funkcje co przy zamykaniu zwyklego pliku, z ta roznica, ze zostaje zmniejszona ilosc czytelnkow i pisarzy zgodnie z typem deskryptora i budzone sa wszystkie spiace procesy czekajace na kolejce zwiazanej z laczem. Jak widac z implementacji funkcji read, jezeli liczba pisarzy spadnie do zera i sa czytelnicy czekajacy na dane, to wszyscy oni powroca z wywolania funkcji read bez przeczytania danych.
Jezeli natomiast liczba czytelnikow spadnie do zera i sa pisarze czekajacy na wolne miejsce w buforze to bedzie im wyslany sygnal SIGPIPE.

Jezeli liczba czytelnikow i pisarzy spadnie do zera tzn liczba dowiazan w i-wezle spadnie do zera, to zostaje wywolana taka procedura, jak w przypadku zwyklego pliku, ktora dodatkowo zwalnia pamiec wskazywana przez pole base struktury pipe_inode_info. Powoduje to utrate wszystkich danych znajdujacych sie w buforze.

unlink()

-usuwa lacze nazwane

DEFINICJA: int unlink(char* nazwa_lacza)
     WYNIK: 0 gdy sukces
            -1 gdy blad

Usuwa lacze nazwane. Jezeli w czasie wywolania sa procesy, ktore z lacza korzystaja to zostaje usunieta tylko nazwa lacza z dysku, tak ze juz nowe procesy nie mogo z niego korzystac. Lacze zostanie usuniete, gdy wszystkie procesy korzystajace z niego zamkna deskryptory zwiazane z tym laczem.


Bibliografia

  1. Pliki zrodlowe Linuxa:


Autor: Leszek Gryz