0 onuuuvvi5T\u 3Y5I CIVHJ UNIA____________
całą komunikację programu z urządzeniami zewnętrznymi obsługuje wspólny, jednorodny aparat.
W najbardziej ogólnym przypadku, zanim zaczniemy czytać z pliku lub pisać do pliku, musimy poinformować system o naszym zamiarze. Proces ten nazywa się otwieraniem pliku. Jeśli mamy zamiar pisać do pliku, to może się okazać, że plik ten trzeba najpierw utworzyć lub skasować jego dotychczasową zawartość. System sprawdzi, czy mamy do tego prawo. (Czy plik istnieje? Czy mamy pozwolenie na korzystanie z niego?) Jeżeli wszystko jest w porządku, to system przekazuje do programu pewną niewielką, nieujemną liczbę całkowitą zwaną deskryptorem pliku. We wszystkich operacjach wejścia-wyjścia zamiast nazwy identyfikującej plik używa się właśnie tego deskryptora. (Deskryptor pliku jest obiektem analogicznym do wskaźnika pliku używanego przez bibliotekę standardową lub do opisu pliku (ang. the file handle) w systemie MS-DOS.) Wszystkie informacje o otwartym pliku są przetwarzane przez system; programy użytkowe odwołują się do plików wyłącznie za pomocą deskryptorów.
Ze względu na to, że znaczna część przesłań danych odbywa się między programem a klawiaturą i ekranem, stworzono specjalne mechanizmy ułatwiające tę komunikację. Interpretator poleceń (shell) uruchamiając program otwiera trzy pliki z deskryptorami 0, 1 i 2, nazwane odpowiednio standardowym wejściem, standardowym wyjściem
1 standardowym wyjściem błędów. Czytając z pliku o deskryptorze 0 lub pisząc do pliku o deskryptorze 1 lub 2, program może wprowadzać dane i wypisywać wyniki bez otwierania plików.
Użytkownik programu może przełączyć standardowe wejście lub wyjście na inne pliki, stosując w poleceniu notację;
próg cinfile >outfile
W takim przypadku interpretator poleceń zmieni domyślne dowiązania deskryptorów 0 i 1 oraz połączy je z nazwanymi plikami. Zwykle deskryptor 2 pozostaje dowiązany do ekranu, aby można było tam wysyłać komunikaty o błędach. Podobnie dzieje się. gdy standardowe wejście lub wyjście jest związane z potokiem. We wszystkich tych przypadkach dowiązania do plików są zmieniane przez interpretator poleceń, a nie przez program. Dopóki program używa pliku 0 dla wejścia oraz plików 1 i 2 dla wyjścia, dopóty nie wie, skąd dane są pobierane i dokąd wysyłane.
Operacje wejścia i wyjścia używają odwołań systemowych read i write; są one dostępne w programach C dzięki dwóm funkcjom read i write. Pierwszym argumentem obu funkcji jest deskryptor pliku. Drugim argumentem jest tablica znakowa w progra-
mie użytkownika, która przechowuje dane przychodzące do progra syłane. Trzeci argument określa liczbę bajtów do przesłania.
int n_read = read(int fd, char *buf, int n); int n_written = write(int fd, char *buf, int n);
Każde wywołanie zwraca liczbę rzeczywiście przesłanych bajtów. Przy czytaniu wartość ta może być mniejsza niż liczba żądanych bajtów. Zero oznacza koniec pliku, a -1 informuje o wystąpieniu jakiegoś błędu. Przy pisaniu zwracaną wartością jest liczba wypisanych bajtów; jeśli nie jest ona równa liczbie bajtów przeznaczonych do wypisania, to znaczy że wystąpił błąd.
W jednym wywołaniu można zażądać przeczytania lub wypisania dowolnej liczby bajtów. Najczęściej pojawiającymi się wartościami są: 1, co oznacza przesłanie jednego znaku („niebuforowane”), oraz liczby w rodzaju 1024 lub 4096, które odpowiadają rozmiarom fizycznych bloków danych dla urządzeń zewnętrznych. Im liczba jednocześnie przesyłanych znaków jest większa, tym operacje przesyłania są bardziej efektywne, gdyż wymagają mniejszej liczby odwołań do systemu.
Podsumowując to wszystko, co zostało powiedziane, możemy napisać prosty program kopiujący dane z wejścia na wyjście - odpowiednik programu napisanego w rozdz. 1. Ten program będzie kopiował cokolwiek na cokolwiek, można bowiem zarówno wejście, jak i wyjście, przełączyć na dowolny plik lub urządzenie.
#include "syscalls.h”
main() /* kopiuj wejście na wyjście */
char buf[BUFSIZJ;
int n;
while ((n = read(0, buf, BUFSIZ)) > 0) write(1, buf, n); return 0;
Prototypy funkcji niezbędnych do obsługi odwołań systemowych umieściliśmy w pliku o nazwie syscalls.h. Możemy więc go włączać do programów w tym rozdziale. Nie jest on jednak nagłówkiem standardowym.
Parametr BUFSIZ także został zdefiniowany w syscalls.h; jego wartość reprezentuje rozmiar bufora najbardziej odpowiedni w lokalnym systemie. Jeżeli rozmiar pliku nie jest wielokrotnością BUFSIZ, to pewne wywołanie funkcji read zwróci mniejszą niż
227