Do spisu treści tematu 6

6.5.4 Czytanie z pliku - funkcja read()

Spis treści


Opis

Funkcja systemowa read() służy do czytania danych z otwartego pliku. Ma ona postać:
int read ( unsigned int dp, char *bufor, int licznik ), gdzie dp jest deskryptorem otwartego pliku, bufor jest adresem struktury danych w procesie użytkownika, do której wczytujemy dane, natomiast licznik określa liczbę bajtów, jaką chce wczytać użytkownik. Jeżeli funkcja wykona się pomyślnie, to jako wynik zwróci rzeczywistą liczbę wczytanych bajtów lub 0 w przypadku napotkania końca pliku. W przeciwnym przypadku zwróci wartość -1, a zmienna errno będzie zawierała rodzaj błędu: EDEADLOCK(plik lub rekord ma założoną blokadę), EFAULT(w przestrzeni wirtualnej nie ma zaalokowanego bufora użytkownika ), EINVAL(nie można wywołać funkcji read dla danego systemu plików), EBADF(niepoprawny deskryptor pliku).
Funkcja rozpoczyna czytanie od bieżącej pozycji w pliku, a po jego zakończeniu zwiększa bieżącą pozycję w pliku o liczbę przeczytanych bajtów. W przypadku stwierdzenia odczytu sekwencyjnego wykorzystuje ona czytanie z wyprzedzeniem, co znacznie zwiększa wydajność systemu.
Funkcja read(), podobnie jak funkcja write(), jest niezależna od zainstalowanego rodzaju systemu plików. Zrealizowana jest ona na dwóch poziomach: najpierw wykonuje się część wspólną dla wszystkich systemów plików (sprawdzenie praw dostępu oraz poprawności argumentów funkcji), potem następuje wywołanie funkcji właściwej dla danego systemu plików. Ta przynależna konkretnemu systemowi funkcja realizuje odczyt danych z pliku.


Algorytm

algorytm read
wejście: deskryptor pliku
         adres bufora w procesie użytkownika
         liczba bajtów do wczytania
wyjście: liczba bajtów skopiowanych do bufora użytkownika 
{
  pobierz i-węzeł odpowiadający deskryptorowi pliku użytkownika;
   
  sprawdź prawo do czytania dla pliku;
   
  sprawdź możliwość wywołania funkcji read dla konkretnego systemu plików;
   
  sprawdź, czy nie ma blokady na rekord do czytania 
  (funkcja locks_verivy_area);
   
  sprawdź, czy struktura danych użytkownika jest zaalokowana w jego
  przestrzeni adresowej (funkcja verify_area);
  
  wywołaj funkcję read właściwą dla danego systemu plików (dla systemu
  plików EXT2 jest to funkcja generic_file_read) i zwróć jej wynik;
}
Informacja o funkcjach, które realizują operacje na plikach dla danego systemu plików znajdują się w strukturze file_operations. Do struktury tej prowadzi wskaźnik z każdego pliku w tablicy plików. Na przykład dla systemu EXT2 funkcją służącą do czytania danych z pliku jest generic_file_read(), a dla FAT fat_file_read().


Funkcja generic_file_read()

Dla systemu EXT2 funkcją służącą do czytania z pliku jest funkcja generic_file_read(). Posiada ona następujący nagłówek:
int generic_file_read ( struct inode *inode, struct file *filp, char *bufor, int licznik ), gdzie inode jest i-węzłem czytanego pliku, filp jest pozycją w tablicy plików odpowiadającą danemu plikowi, bufor jest adresem struktury danych w procesie użytkownika, do której wczytujemy dane, natomiast licznik określa liczbę bajtów, jaką chce wczytać użytkownik. Jeżeli funkcja wykona się pomyślnie, to jako wynik zwróci rzeczywistą liczbę wczytanych bajtów lub 0 w przypadku napotkania końca pliku. W przeciwnym przypadku zwróci kod błędu: -ENOMEM(błąd przy tworzeniu ramki pamięci), -EIO(błąd wejścia-wyjścia).
Funkcja rozpoczyna czytanie od bieżącej pozycji w pliku, a po jego zakończeniu zwiększa bieżącą pozycję w pliku o liczbę przeczytanych bajtów. W przypadku stwierdzenia odczytu sekwencyjnego wywołuje funkcję generic_file_readahead(), która realizuje czytanie z wyprzedzeniem. Funkcja generic_file_readahead() korzysta m. in. z wartości dwóch pól znajdujących się w strukturze file czytanego pliku, a mianowicie pola f_ramax, które zawiera aktualny maksymalny rozmiar danych, jaki można przeczytać z wyprzedzeniem oraz pola f_rawin przechowującego rozmiar danych wczytanych w trakcie ostatnio wykonywanego czytania z wyprzedzeniem. Wartość pola f_ramax zmienia się w trakcie działania funkcji, ale nie może być większa niż zdefiniowana stała MAX_READAHEAD = PAGE_SIZE*18 i mniejsza niż MIN_READAHEAD = PAGE_SIZE*3, gdzie PAGE_SIZE jest rozmiarem ramki w pamięci operacyjnej.
algorytm generic_file_read
wejście: i-węzeł czytanego pliku
         pozycja w tablicy plików odpowiadająca danemu plikowi
         adres bufora w procesie użytkownika
         liczba bajtów do wczytania
wyjście: liczba bajtów skopiowanych do bufora użytkownika 
{
   sprawdź, czy kontynuujemy czytanie sekwencyjne pliku i
   stosownie ustaw zmienne pomocnicze (f_ramax i f_rawin);   

   while (true)
   {
    znajdź ramkę pamięci zawierającą dane z pliku w pamięci podręcznej;

    if (znaleziona ramka)
       przejdź do etykiety znaleziono_ramkę;
    else
       przejdź do etykiety nie_znaleziono_ramki;

znaleziono_ramkę:
    if (aktualna ramka jest pełna)
      czytaj z wyprzedzeniem (funkcja generic_file_readahead);
    
    poczekaj na ramkę (aż skończą się operacje wejścia-wyjścia)

    if (ramka nie jest gotowa)
       przejdź do etykiety ramka_błąd;
    else
       przejdź do etykiety sukces;

sukces:
   
    skopiuj dane z ramki do bufora użytkownika;
    
    zwolnij ramkę (funkcja release_page);
    
    zwiększ pozycję w pliku, pozycję w buforze, ilość przeczytanych bajtów
    oraz zmniejsz ilość bajtów do przeczytania o ilość bajtów przeczytanych 
    z ramki;

    if (nie osiągnięto wskazania licznika)
       {
         if (potrzebne jest przeszeregowanie procesów)
             wywołanie funkcji szeregującej procesy (funkcja schedule());

         idź na początek pętli while;
       }
    else
       wyskocz z pętli whilenie_znaleziono_ramki:

    if (nie mamy dodatkowej ramki przeczytanej z wyprzedzeniem)
       {
         stwórz nową ramkę (_get_free_page);

         idź na początek pętli while;
       }
    else    
       dodaj ramkę do kolejki haszującej ramek (add_to_page_cache);

       zapisz ramkę danymi z dysku (funkcja readpage);
    
       przejdź do etykiety znaleziono_ramkę;

ramka_błąd:
    zgłoś żądanie zapisu ramki (funkcja readpage);
     
    if (żądanie zostało zrealizowane pomyślnie)
       {
         poczekaj na ramkę (być może jest w trakcie zapełniania);

         przejdź do etykiety sukces;
       }

    zwolnij ramkę (funkcja release_page);

    wyskocz z pętli while;      
 }

 zaktualizuj bieżącą pozycję w pliku;

 zwolnij dodatkową ramkę (free_page);
 
 wywołaj funkcję UPDATE_ATIME(inode) w celu zaktualizowania czasu
 ostatniego dostępu do i-węzła;
 
 return (ilość wczytanych bajtów);
}

Uwagi

  1. Funkcja systemowa read() nie może opóźniać wczytywania danych, tak jak to robi funkcja write() w przypadku zapisu. Jeśli dane nie znajdują się w wewnętrznym buforze jądra, to proces musi zaczekać na pobranie ich z dysku.
  2. W przypadku plików specjalnych oraz łączy komunikacyjnych powrót z funkcji read() jest natychmiastowy, jeśli plik był otworzony z flagą O_NDELAY i nie ma danych do przesłania.
  3. Jeśli system nie jest zbyt przeciążony i dane mogą przez pewien czas pozostawać w buforze, to w przypadku czytania sekwencyjnego, zastosowane wczytywanie z wyprzedzeniem jest skuteczne i w znaczny sposób poprawia wydajność systemu.
  4. Implementacja funkcji read() w Linux 2.0.32 jest taka sama jak w wersji 2.0.30. Natomiast trochę zmodyfikowana została funkcja generic_file_read(). A mianowicie dodano wywołanie funkcji szeregującej procesy schedule(), zapewne w celu zoptymalizowania pracy systemu. Dodano także wywołanie funkcji UPDATE_ATIME(inode) do aktualizacji atrybutów i-węzła.

Bibliografia

  1. Bach M. J. "Budowa systemu operacyjnego Unix"
  2. Pliki źródłowe Linuxa: mm/filemap.c, fs/read_write.c

Autor: Piotr Niedolistek