background image

 

 

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.

background image

 

 

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 !

background image

 

 

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.

background image

 

 

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.

background image

 

 

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ą.

background image

 

 

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.

background image

 

 

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  signal.

background image

 

 

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).

background image

 

 

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

        ...

    }

background image

 

 

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.

background image

 

 

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).


Document Outline