Wywołania systemowe Uniksa: Różnice pomiędzy wersjami

[wersja przejrzana][wersja przejrzana]
Usunięta treść Dodana treść
Nie podano opisu zmian
m clean up przy użyciu AWB
Linia 2:
W [[Unix|systemach uniksowych]] program jest całkowicie odizolowany od sprzętu, dlatego zawsze musi się odwoływać do odpowiednich funkcji [[Jądro systemu operacyjnego|jądra]].
 
Z punktu widzenia programu odwołania te są ukryte w [[Biblioteka standardowa języka C|bibliotece libc]] - program nie wie, czy dana funkcja dostarczana jest bezpośrednio przez jądro, czy też implementuje ją libc korzystając z innych mechanizmów jądra (np. w [[GNU/Linux|GNU/Linuksie]] <tt>[[Fork (Unix)|fork]]</tt> zaimplementowany jest za pomocą <tt>clone</tt>).
 
Na x86 i innych systemach o podobnej architekturze <tt>libc</tt> (lub też czasem program bezpośrednio) komunikuje się z jądrem za pośrednictwem [[przerwanie|przerwań]] systemowych. W Linuksie funkcje systemowe są dostępne przez przerwanie <tt>0x80</tt>, argumenty są przekazywane w rejestrach w następującej kolejności: <tt>eax</tt>, <tt>ebx</tt>, <tt>ecx</tt>, <tt>edx</tt>, <tt>edi</tt>, <tt>esi</tt>, <tt>ebp</tt>. Numer funkcji systemowej jest przekazywany w <tt>eax</tt>, natomiast pozostałe argumenty zależą od rodzaju funkcji (nie wszystkie muszą być wykorzystane). Status operacji zwracany jest w rejestrze <tt>eax</tt>. Gdy operacja wykona się bezbłędnie, jego wartość jest równa 0, w przeciwnym razie jest to (ujemna) stała z pliku <tt>asm/errno.h</tt>[http://lxr.linux.no/linux/include/asm-generic/errno.h]. Pozostałe rejestry nie są zmieniane.
 
W przypadku innych procesorów wywołania systemowe są wykonywane przez specjalizowane instrukcje procesora - np. Pentium 4 posiada instrukcję <tt>sysenter</tt> ({{ang.|system enter}}).
 
== Śledzenie wywołań ==
Linia 43:
Uwaga: szczegóły dotyczą jądra Linux 2.4, ale różnice nie są aż tak duże.
 
== Otwieranie i zamykanie plików - <tt>open</tt>, <tt>create</tt> i <tt>close</tt> ==
Pliki otwiera się za pomocą trzyargumentowego <tt>open</tt>, którego definicja znajduje się w <tt>fcntl.h</tt>[http://lxr.linux.no/linux/include/asm-generic/fcntl.h]:
<syntaxhighlight lang="c">
Linia 51:
Pierwszy argument <tt>pathname</tt> oznacza ścieżkę do pliku.
 
Drugi <tt>flags</tt> - opcje otwarcia, z czego ważniejsze to:
* <tt>O_RDONLY</tt> - otwórz tylko do odczytu
* <tt>O_WRONLY</tt> - otwórz tylko do zapisu
* <tt>O_RDWR</tt> - otwórz do zapisu i odczytu
* <tt>O_CREAT</tt> - utwórz plik jeśli nie istnieje
* <tt>O_EXCL</tt> - używane razem z <tt>O_CREAT</tt> - zwróć błąd jeśli plik już istnieje
* <tt>O_TRUNC</tt> - jeśli w pliku są już jakieś dane, skasuj je
* <tt>O_APPEND</tt> - plik jest otwierany w trybie dopisywania do końca
* <tt>O_NONBLOCK</tt> - otwórz plik w trybie nieblokującym
 
'''Opcjonalny''' trzeci - uprawnienia dla nowo utworzonych plików.
<tt>open</tt> należy do nielicznych wywołań systemowych dopuszczających pomijanie argumentu:
<syntaxhighlight lang="c">
Linia 81:
zamyka otwarty deskryptor pliku. W dawnych czasach <tt>close</tt> nie zwracało kodu błędu,
więc nikt go nie sprawdzał. Współcześnie zwraca kod błędu, co z punktu widzenia architektury systemu
jest kompletnym nieporozumieniem - nikt tak naprawdę nie zdefiniował co konkretnie ma znaczyć
błąd przy zamykaniu pliku i co program ma z tym zrobić.
 
Kernel może zwracać błędy <tt>EBADF</tt> (deskryptor jest zły), <tt>[[EINTR]]</tt> i dość ogólny <tt>EIO</tt> (błąd wejścia/wyjścia).
 
== Tworzenie plików specjalnych - <tt>mkdir</tt>, <tt>mkfifo</tt>, <tt>mknod</tt> ==
<tt>open</tt> i <tt>creat</tt> potrafią tworzyć tylko zwykłe pliki.
Do tworzenia innych plików stworzono osobne wywołania systemowe.
Linia 102:
gdzie <tt>pathname</tt> i <tt>mode</tt> mają to samo znaczenie a <tt>dev</tt> to informacje o typie urządzenia.
 
== Zakończenie pracy - <tt>_exit</tt> ==
<syntaxhighlight lang="c">
void _exit(int status);
Linia 109:
<tt>status</tt> zostanie zwrócony jako kod wyjścia.
 
== Zarządzanie pamięcią - <tt>brk</tt> ==
<syntaxhighlight lang="c">
int brk(void *end_data_segment);
Linia 137:
w rzeczywistości odczytana.
 
Liczba ta może być mniejsza od żądanej z wielu przyczyn - np. jeśli akurat w danej chwili ilość danych dostępnych
na połączeniu sieciowym jest mniejsza od żądanej, lub też jeśli zanim odczytano wszystkie dane nastąpiło przerwanie.
 
I o ile wartości od <tt>1</tt> do <tt>count</tt> są poprawne, wartość <tt>0</tt> może oznaczać tylko jedno - koniec pliku. Jeśli przerwanie nastąpiło przed odczytaniem danych, kernel zwraca [[errno|kod błędu (errno)]] <tt>[[EINTR]]</tt>,
jeśli zaś nie było aktualnie żadnych danych, a połączenie było otwarte w trybie nieblokującym - <tt>EAGAIN</tt>.
 
Inne możliwe błędy to:
* <tt>EBADF</tt> - błędny deskryptor
* <tt>EINVAL</tt> - deskryptor nie do odczytu (np. otwarty jako tylko do zapisu)
* <tt>EIO</tt> - błąd wejścia wyjścia
* <tt>EISDIR</tt> - deskryptor wskazuje na katalog. Na niektórych systemach katalogi można czytać za pomocą <tt>read</tt>, jednak służyło to wyłącznie implementacji odpowiednich procedur [[Biblioteka standardowa języka C|libc]]. Na innych jest to niedozwolone, a libc radzi sobie w inny sposób.
* <tt>EFAULT</tt> - błędny adres bufora, poza przestrzenią adresową procesu
 
=== <tt>write</tt> ===
Linia 156:
</syntaxhighlight>
 
Argumenty mają takie samo znaczenie jak w <tt>read</tt> - <tt>write</tt> pisze do deskryptora <tt>fd</tt> co najwyżej <tt>count</tt> bajtów z bufora <tt>buf</tt> i zwraca liczbę zapisanych bajtów. W przypadku <tt>write</tt> liczba <tt>0</tt> jest jednak równie poprawna jak pozostałe i można próbować dalej.
 
=== <tt>readv</tt> i <tt>writev</tt> ===
Linia 164:
# przepiąć dane tak, żeby były ciągłe w pamięci, po czym wywołać <tt>write</tt> tylko jeden raz
 
Nic nie stoi jednak na przeszkodzie, żeby kernel sam zajął się tą operacją - służą temu zdefiniowane w <tt>sys/uio.h</tt> wywołania:
<syntaxhighlight lang="c">
int readv(int filedes, const struct iovec *vector, size_t count);
Linia 209:
</syntaxhighlight>
 
<tt>out_fd</tt> to deskryptor wyjściowy, <tt>in_fd</tt> - wejściowy, <tt>offset</tt> to wskaźnik na
zmienną przechowującą offset w pliku wejściowym, od którego ma zacząć dane wywołanie, a <tt>count</tt> - ilość danych do przetransferowania. Wywołanie zwraca ilość rzeczywiście zapisanych danych i poprawia <tt>offset</tt>
na nową wartość.
 
Linia 218:
nie musi wielokrotnie duplikować deskryptora, a po ich rozłączeniu wielokrotnie go zamykać.
 
Offset pliku wyjściowego jest poprawiany normalnie - wysyłanie jednocześnie kilku plików na ten
sam deskryptor nie miałoby większego sensu (sekwencyjnemu wysyłaniu oczywiście to nie przeszkadza).
 
Linia 258:
</syntaxhighlight>
 
Ponieważ dane nie wędrują przez pamięć procesu, można podawać "absurdalne"„absurdalne” wartości typu (jak wyżej) 64 megabajty
i kernel dobrze sobie z nimi radzi. Powyższy program kopiuje plik 32219641-bajtowy (<tt>linux-2.4.19.tar.gz</tt>) prawie dwukrotnie szybciej niż <tt>cat</tt> (który robi to 4-kilobajtowymi odwołaniami <tt>read</tt> i <tt>write</tt>),
czy standardowy <tt>cp</tt> (niektóre nowsze wersje używają <tt>mmap</tt> lub <tt>sendfile</tt>).
Linia 280:
 
Tryb to maska złożona z:
* <tt>R_OK</tt> - plik można czytać
* <tt>W_OK</tt> - do pliku można pisać
* <tt>X_OK</tt> - plik jest wykonywalny
* <tt>F_OK</tt> - plik istnieje
 
Semantyka wywołania <tt>access</tt> nie jest jednak prosta.
Linia 293:
* itd.
 
<tt>access</tt> zwraca tylko prawa przysługujące uprawnieniom '''real''', nie zaś '''effective''' - tak więc
ma pewne zastosowanie w programach używających praw '''setuid''' czy też '''setgid'''.
Naiwne stosowanie - sprawdzenie za pomocą wywołania <tt>access</tt>, po czym otwarcie pliku za pomocą <tt>open</tt> - stwarza jednak lukę czasową, w trakcie której plik może zostać podmieniony.
 
Przykład działania: