Pobierz prezentację
Pobieranie prezentacji. Proszę czekać
OpublikowałLuiza Kędziora Został zmieniony 11 lat temu
1
10. PROSTE MECHANIZMY KOORDYNACJI DOSTĘPNE W JĘZYKU C W systemie Unix użytkownikowi (nie będącemu administratorem) nie wolno wykonywać bezpośrednio żadnych operacji na zasobach. Operacje te wykonywane są przez jądro systemu na zlecenie użytkownika wydane poprzez wywołanie funkcji systemowej. Użytkownik dostrzega system poprzez zbiór funkcji systemowych i struktur logicznych, na których one operują, jako tak zwaną maszynę wirtualną (niezależną od szczegółów realizacji sprzętowej). Funkcje systemowe są dostępne dla użytkownika za pośrednictwem języka, w którym użytkownik porozumiewa się z systemem (na przykład języka komend shella lub języka programowania C). W każdym języku funkcje są obudowane w pewien interfejs (nazwa, postać parametrów itd.). Uwaga. Wiele komend shella i funkcji dostępnych w języku C wywołuje nie pojedyncze funkcje systemowe, ale stanowi podprogramy wywołujące wiele różnych funkcji systemowych.
2
W języku C każda funkcja jest scharakteryzowana przez: - nazwę; - liczbę, kolejność i typy argumentów; - typ wyniku; - specyfikację efektu wykonania funkcji; - wykaz możliwych sytuacji błędnych i odpowiadających im wartości zmiennej globalnej errno. Dla niektórych funkcji argumenty mogą tworzyć listę o niezdeterminowanej długości (zakończoną znakiem pustym). Jedyną funkcją systemową, dla której nie są przewidziane żadne sytuacje błędne, jest funkcja exit (powodująca zakończenie procesu). Uwaga. W profesjonalnych programach wszystkie wywołania jakichkolwiek funkcji, które mogą zwrócić kod błędu, powinny być testowane pod kątem takiej możliwości !
3
Funkcje operujące na identyfikatorach. Każdy proces poza procesem o numerze 0 powstaje wskutek utworzenia przez inny proces. Numery procesów są liczbami naturalnymi przydzielanymi rosnąco modulo rozmiar tablicy procesów (zwykle 32 K) z pominięciem numerów aktualnie używanych. Każdy proces pamięta swój PID i PPID, ale nie zapamiętuje w sposób automatyczny identyfikatorów tworzonych potomków (programista może spowodować przechowywanie ich w zmiennych). Jeśli proces kończy działanie wcześniej, niż jego (niektóre) procesy potomne, wszystkie procesy potomne otrzymują PPID=1 (jest to PID procesu Init) i kontynuują działanie. int getpid(void); - zwraca PID procesu int getppid(void); - zwraca PPID procesu int getpgrp(void); - zwraca PGRP procesu int setpgrp(void); - odłącza proces od dotychczasowej grupy i ustanawia go przywódcą nowej grupy (PGRP = PID) Uwaga. Istnieją też odpowiednie funkcje dla identyfikatorów użytkowników i ich grup.
4
Funkcje związane z tworzeniem i kończeniem procesów. Tworzenie nowego procesu: int fork(void); zwraca -1 w przypadku niepowodzenia (na przykład brak zasobów) zwraca 0 utworzonemu procesowi potomnemu zwraca PID utworzonego potomka procesowi rodzicielskiemu Wykonanie funkcji fork przez jądro systemu wiąże się z szeregiem skomplikowanych czynności (przydział zasobów, wpisanie do tablicy procesów, kopiowanie środowiska itp.) i jest czasochłonne. Segment instrukcji nie jest kopiowany, segment danych jest zwykle kopiowany dopiero w przypadku próby dokonania zapisu przez nowy proces.
5
Zamiana kontekstu procesu: Funkcja systemowa exec ma sześć interfejsów w języku C (różniących się sposobem przekazywania parametrów i zmiennych środowiska). Jej zadaniem jest zamiana kontekstu procesu (przy zachowaniu tożsamości procesu), to jest spowodowanie, żeby proces zaczął wykonywać inny program. int execl (char ścieżka, char arg0, char arg1,..., char argn, NULL); ścieżka - pełna nazwa ścieżkowa pliku z nowym programem; arg0 - powtórzona sama nazwa pliku z nowym programem; arg1... argn - lista parametrów dla nowego programu zakończona znakiem pustym (NULL). int execv (char ścieżka, char argv [ ] ); int execle (...); int execve (...); int execlp (...); int execvp (...); Funkcje fork i exec zazwyczaj współpracują ze sobą.
6
Kończenie wykonywania procesu: void exit (int kod); Kończy działanie procesu, wysyła sygnał do procesu rodzicielskiego oraz jednobajtowy kod wyjścia. Oczekiwanie na zakończenie działania potomka: int wait (int wsk); Zawiesza proces w oczekiwaniu na zakończenie któregokolwiek procesu potomnego. Zwraca PID zakończonego potomka lub -1 w przypadku błędu. wsk zwraca dwa bajty: - jeśli prawy bajt ma wartość 0, to lewy bajt zwraca kod wyjścia potomka; - jeśli prawy bajt ma wartość niezerową, to określa, jaki sygnał spowodował zakończenie potomka, oraz czy nastąpił zrzut pamięci do pliku core. Uwaga. Obecnie istnieje też funkcja pozwalająca czekać na zakończenie określonego potomka.
7
Funkcje związane z operowaniem na sygnałach. Wysłanie sygnału: int kill (int pid, int sig); Umożliwia wysłanie określonego sygnału do określonego procesu / grupy procesów. Przechwycenie sygnału: void ( signal (int sig, void ( func) (int))) (int); Umożliwia przechwycenie określonego sygnału (jeśli to możliwe) i wykonanie wskazanej funkcji obsługi. Polecenia shella kill i trap są obudowami funkcji systemowych kill i signal.
8
Funkcje związane z operowaniem na łączach nienazwanych. Pierwotnie łącza nienazwane mogły być używane jedynie jako jednokierunkowe: P Q zapis odczyt kolejka prosta Funkcja tworząca łącze: int pipe (int fd [2] ); fd [0] - deskryptor pliku służący do odczytu z łącza fd [1] - deskryptor pliku służący do zapisu do łącza Do zapisów / odczytów stosujemy funkcje systemowe write i read (są wykonywane niepodzielnie). Łącze ma pojemność zależną od ustawień systemowych (co najmniej pół KB, zazwyczaj 4 KB).
9
Zazwyczaj bezpośrednio po wywołaniu funkcji pipe wywoływana jest funkcja fork (proces potomny dziedziczy deskryptory plików), a następnie, w zależności od zamierzonego kierunku przesyłania, zamykane są niepotrzebne deskryptory (po jednym w każdym procesie).... pipe (fd); if (fork ( ) = = 0) fd [1] fd [1] { close (fd [0] );... } fd [0] łącze fd [0] else { proces proces close (fd [1] ); potomny rodzicielski... }
10
Główną wadą łącz nienazwanych jest to, że mogą łączyć tylko procesy spokrewnione (zazwyczaj pary rodzic - potomek, ale mogą też być dziadek - wnuk, dwóch potomków itp.). W nowszych wersjach Unixa łącza są implementowane jako dwukierunkowe (full duplex). W starszych mogły być tylko jednokierunkowe (half-duplex) - chcąc uzyskać łączność dwukierunkową należało skorzystać z dwóch par deskryptorów i dwukrotnie wywołać funkcję pipe. Uwaga. 1) W przypadku próby odczytu z pustego łącza lub próby zapisu do pełnego łącza procesy są czasowo zawieszane. 2) W przypadku łącz dwukierunkowych może być potrzebna synchronizacja operacji zapisu i odczytu po obu stronach łącza (na przykład za pomocą semaforów). 3) Na zakończenie działania programu należy pozamykać wszystkie otwarte deskryptory.
11
Funkcje związane z operowaniem na łączach nazwanych (FIFO). Łącza nazwane są uwidoczniane w systemie plików jako specjalny rodzaj plików o zerowym rozmiarze. Mogą być tworzone i usuwane zarówno w programach, jak i przy użyciu komend shella. Z łączami nazwanymi mogą współpracować dowolne procesy (niekoniecznie spokrewnione), które posiadają odpowiednie prawa dostępu. Funkcja tworząca kolejkę FIFO: int mknod (const char ścieżka, int tryb); ścieżka - pełna nazwa ścieżkowa kolejki FIFO tryb - słowo trybu, którego bity informują między innymi o prawach dostępu do kolejki Przed użyciem łącze nazwane musi być otwarte (open), a przed zakończeniem wykonywania programu zamknięte (close) przez każdy proces współpracujący z łączem. Jest wymuszona synchronizacja otwarcia łącza do zapisu i otwarcia łącza do odczytu przez dwa procesy chcące korzystać z łącza. Samo korzystanie z łącza wygląda podobnie, jak w przypadku łącz nienazwanych (funkcje write i read).
Podobne prezentacje
© 2024 SlidePlayer.pl Inc.
All rights reserved.