Systemy Operacyjne - Konspekt Agh, Semestr II, SOP


Systemy operacyjne

  1. -

  2. Systemy plików

  3. Rozwidlanie procesów - fork

  4. Synchronizacja - semafory

  5. Pamięć dzielona - shared memory

  6. Mechanizm pipe

  7. Kolejki komunikatów - message queue

  8. Projekty

2. Systemy plików

Systemy plików DOS i UNIX znacznie się różnią. Różnice występują na wielu poziomach. Inny jest format dysku i struktura służąca zapisowi i mapowaniu sektorów fizycznych na kolejne bajty i fragmenty plików. Inne są również zasady i możliwości umiejscawiania plików w katalogach i inne jest znaczenie nazwy plików.

Fizyczny podział dysku zapewnia nam producent, określa on ilość głowic, ilość ścieżek (cylindrów) oraz ilość sektorów na ścieżce. Kodowanie położenia danych na fizycznych sektorach dyskowych jest zależne od systemu operacyjnego. Każdy dysk niezależnie od systemu operacyjnego (może to być zależne od rodzaju sterownika np. IDE, SCSI) posiada Master Boot Record.

Master boot record (MBR)

Jest to pierwszy fizyczny sektor na dysku (head 0, cylinder 0, sector 1). Zawiera on program, który zostaje wczytany i uruchomiony po restarcie komputera, służący do określenia, która partycja jest aktywna i wczytania tzw. boot sektora tej partycji a następnie uruchomienia programu zawartego w nim. W końcowej części MBR znajduje się również tzw. tablica partycji dysku, czyli opis podziału dysku na logiczne wolumeny. Tablica partycji może zawierać cztery elementy - tak więc istnieją cztery dopuszczalne partycje dysków (tzw. primary partitions).

przesunięcie

rozmiar

zawartość

+0

0x01be

kod do załadowania i uruchomienia prog­ramu z boot-sektora aktywnej partycji

+0x01be

16

1 wiersz tablicy partycji

+0x01ce

16

2 wiersz tablicy partycji

+0x01de

16

3 wiersz tablicy partycji

+0x01ee

16

4 wiersz tablicy partycji

+0x01fe

2

0x55

0xAA

Wiersz (element) tablicy partycji dysku:

przesunięcie

rozmiar

zawartość

+0

1

boot-flag /czy partycja jest aktywna/ 0-nie 127-tak

+1

1

HD - numer głowicy (head) rozpoczynającej

+2

2

SEC/CYL - numer sektora i ścieżki rozpoczynającej 6/10 bitów

+4

1

kod systemu operacyjnego - np. DOS-12, DOS-16, DOS-EXT, LINUX, itp.

+5

1

HD - numer głowicy ostatniego sektora

+6

2

SEC/CYL - numer sektora i ścieżki ostatniego sektora

+8

4

początkowy sektor względny dostępny do operacji dyskowych

+12

4

rozmiar partycji w sektorach

+16

następna partycja lub bajty 0x55 i 0xAA

Indeks sektora względnego określa następujący wzór:

W każdej partycji typu DOS-EXT można z kolei umieścić pewną ilość tzw. dysków logicznych (logical drives), które będą z poziomu systemu operacyjnego widoczne jako osobne urządzenia. Odpowiednikami ich tablic partycji są tzw. drive tables. Różnice są takie, że z takich „dysków” nie można boot'ować komputera, nie można w nich umieszczać filesystemów innych niż DOS (i Linux). Każdy z dysków logicznych trzeba formatować niezależnie, po to aby utworzyć w nich trzy elementy: boot sektor, FAT, directory table. Boot sektory dysków logicznych z reguły różnią się w zależności od systemu operacyjnego.

Filesystem

Dysk podzielony na partycje jest widoczny jako kilka dysków logicznych. Na dyskach logicznych musi zostać utworzona odpowiednia struktura logiczna umożliwiająca ich użycie. Proces tworzenia tej struktury to formatowanie dysku lub zakładanie filesystemu. W przypadku DOS formatowanie dzieli dysk na tzw. klastry, tworzy boot sektor, tablice FAT oraz root directory. W systemie UNIX zakładanie filesystemu powoduje podział dysku na tzw. bloki, tworzony jest boot blok, super blok oraz lista i-node'ów.

Boot sektor dysku logicznego w systemie DOS

Dysk logiczny w DOS może być utworzony bezpośrednio w fizycznej partycji FAT-12, FAT-16 (jeden logiczny dysk to jedna primary partition) albo może być jednym z kilku dysków logicznych w partycji rozszerzonej extended DOS partition. System Linux również pozwala na tworzenie partycji extended z logicznymi dyskami, umieszczonymi wewnątrz niej.

przesunięcie

rozmiar

zawartość

+0

3

kod skoku do boot kodu

+3

8

identyfikator np.nazwa i wersja systemu operacyjnego

+11

2

BYTES per SECTOR

BIOS

+13

1

SECTORS per CLUSTER

Parameter

+14

2

sektory zarezerwowane

Block

+16

1

liczba tablic FAT

+17

2

maksymalna liczba directory entries

+19

2

całkowita liczba sektorów

+21

1

typ FAT (typ partycji) ten sam bajt co w tablicy FAT[0]

+22

2

liczba sektorów w jednym FAT

+24

2

SECTORS per TRACK

+26

1

HEADS (powierzchnie - dwustronna fizyczna głowica to dwie powierzchnie)

+27

2

sektory ukryte (zarezerwowane)

+29

w systemie DOS tu może być początek boot kodu

File allocation table (FAT) w systemie DOS

FAT jest w systemie DOS tablicą , używaną w celu odwzorowania fizycznego położenia danych na dysku na pliki widoczne z poziomu systemu operacyjnego. W każdym directory entry znajdują się dwa bajty przeznaczone na indeks (numer) klastra od którego zaczynają się kolejne bajty pliku. Jest to pierwszy klaster tzw. łańcucha alokacji (cluster allocation chain). Pod tym indeksem w tablicy FAT znajduje się indeks kolejnego klastra lub

oznaczenie, że jest to już ostatni klaster pliku.

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
Przykład:

Directory entry:

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
FAT :

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

Directory entry w systemie DOS: 32 bajty

przesunięcie

rozmiar

zawartość

+0

8

`F'

`I'

`L'

`E'

`N'

`A'

`M'

`E'

+8

3

`E'

`X'

`T'

+11

1

atrybuty

+12

10

zarezerwowane

+22

2

czas modyfikacji pliku

+24

2

data modyfikacji pliku

+26

2

numer klastra początkowego

+28

4

rozmiar pliku

Atrybuty plików w systemie DOS

Atrybut pliku to 1 bajt związany z każdą nazwą/plikiem, zapisaną w tzw. „directory entry”, opisującym jeden element katalogu - np. plik. Różne bity tego bajtu informują o tym czy nazwa oznacza plik, katalog, etykietę dysku oraz czy jest on tylko do odczytu, czy jest systemowy, czy jest ukryty i czy był ostatnio zarchiwizowany (to ostatnie jest używane w zasadzie tylko przez systemowe programy backup i xcopy, których „nikt inny nie używa”).

Zasady tworzenia nazw w systemie DOS

Duże i małe litery są nierozróżnialne (w Windows 95 są rozróżnialne - do pewnego stopnia). Dopuszczalne znaki, które można użyć w nazwie katalogu lub pliku to: litery od `A' do `Z' oraz od `a' do `z', cyfry `0' do `9', a także inne znaki: `!', `@', `#', `$', `%', `&', `(`, `)', `-`, `_', `{` `}' `'', ``', `~'. Znak `~' posiada pewne znaczenie specjalne w systemie W95, w przypadku wykorzystywania tych samych dysków i plików w systemie DOS. Nie można używać znaków takich jak: 'spacja', `/', `,', `;', `^', `+', `[`, `]', `', `='. Natomiast znaki: `*', `?', `:', `.', `\', `|', `<', `>' mają znaczenie specjalne.

Postać ścieżki dostępu do pliku: drive: dirname filename .ext

drive: jedna litera z dwukropkiem; Litery A i B zarezerwowane są dla stacji dysków lub dysków logicznych, pozostałe litery dyski fizyczne lub logiczne. Ostatnia dopuszczalna litera określona jest klauzulą LASTDRIVE w pliku konfiguracji systemu. Z każdą zarezerwowaną literą wiąże się pewna ilość zarezerwowanej pamięci RAM systemu (około 80 bajtów). W systemach W95/NT nie ma to praktycznie żadnego znaczenia. LASTDRIVE=Z „kosztuje” około 2kB.

dirname jest to lista katalogów, oddzielonych znakiem `backslash \', która prowadzi od katalogu głównego (root) do katalogu docelowego, w którym znajduje się plik. Długość połączonej nazwy katalogu i nazwy pliku nie może przekroczyć 63 znaków. Nie są dopuszczalne wildcards w naz­wach katalogów. Dwie specjalne nazwy są zarezerwowane. Kropka `.' oznacza katalog aktualny, ten „w którym się akurat jest”, a dwie kropki `..' oznacza katalog nadrzędny w stosunku do aktualnego. Katalog główny nie posiada swojego katalogu nadrzędnego.

filename jest to właściwa nazwa pliku; długość od 1 do 8 znaków; Duże i małe litery są nierozróżnialne. Niektóre nazwy plików są zarezerwowane, z tego względu, że odnoszą się do urządzeń. Są to PRN, LPTn, AUX, COMn, NUL, CON. Każde rozszerzenie dodane do tych nazw plików jest ignorowane.

ext rozszerzenie nazwy pliku, mogące określać jego typ. Od 0 do 3 znaków, poprzedzone znakiem kropki '.'. Rozszerzenia COM, EXE, OVL, BAT, OBJ, LIB, SYS są „tradycyjnie” zarezerwowane. Rozszerzenia niepoprawne np. więcej niż trzy-znakowe mogą być w niektórych przypadkach automatycznie poprawiane przez system, np. obcinane po trzech znakach.

Struktura dysku logicznego w systemie UNIX

blok 0

blok 1

blok 2

blok 3

blok 4

...

blok i

...

blok i+1

...

BOOT

SUPER

8 ⋅ i-node

8 ⋅ i-node

8 ⋅ i-node

...

dane

...

wolne

...

Boot blok w systemie UNIX pełni taką samą rolę jak boot sektor w systemie DOS. Znajduje się w nim kod programu ładującego dalsze elementy systemu operacyjnego.

Struktura super bloku

liczba bloków w filesystemie

nazwa urządzenia

nazwa filesystemu

liczba i-node'ów

wskaźnik na pierwszy element tablicy wolnych bloków

wskaźnik na pierwszy element tablicy i-node'ów

data modyfikacji

data ostatniego dostępu

rozmiar bloku

Tablice wolnych bloków i i-node'ów również znajdują się superbloku i cały czas rezydują w pamięci operacyjnej. Dzięki temu system jest w stanie bardzo szybko znaleźć wolny blok lub i-node przy tworzeniu lub modyfikacji plików. W tablicach tych nie muszą znaleźć się wszystkie bloki i i-node'y. W przypadku braku wolnego i-node'u lub bloku w tablicy, wykonywany jest specjalny algorytm poszukujący odpowiedniego elementu w strukturze filesystemu.

Katalogi i węzły plików - i-node

W systemie UNIX każdy element katalogu jest reprezentowany przez strukturę informacyjną nazywaną i-node. Położenie i-node'u katalogu głównego (root directory) danego filesystemu jest ściśle określone. Jest to i-node o numerze 2. W UNIX każdy katalog jest „prawie” zwyczajnym plikiem. Kolejne bajty tego pliku zawierają informację o zawartości katalogu.

przesunięcie

rozmiar

zawartość

+0

2

numer i-node pliku `.'

Directory

+2

14

nazwa pliku `.'

Entry

+16

2

numer i-node pliku `..'

+18

14

nazwa pliku `..'

+32

2

numer i-node pliku

+34

14

nazwa pliku

+48

2

numer i-node pliku

+50

14

nazwa pliku

...

...

...

Dowolny plik (również katalog) posiada więc oprócz nazwy numer swojego i-node'u. Dalsze informacje o pliku można odczytać odtwarzając zawartość komórki tablicy i-node'ów określoną zadanym numerem i-node'u.

Struktura i-node'u

Węzeł pliku zawiera m.in. następujące elementy:

typ pliku / prawa dostępu

licznik dowiązań

userID / groupID

data modyfikacji i-node'u

data modyfikacji pliku

data dostępu do pliku

0x08 graphic
wskaźnik #1 na blok danych pliku

blok 512 B danych

0x08 graphic
wskaźnik #2 na blok danych pliku

blok 512 B danych

...

...

0x08 graphic
wskaźnik #10 na blok danych pliku

blok 512 B danych

0x08 graphic
wskaźnik pośredni 1-go stopnia na blok wskaźników

blok 128 wskaźników

0x08 graphic
wskaźnik pośredni 2-go stopnia na blok wskaźników

128 ⋅ 128 wskaźników

0x08 graphic
wskaźnik pośredni 3-go stopnia na blok wskaźników

128 ⋅ 128 ⋅ 128 wskaźników

Przy blokach 512 bajtowych maksymalny rozmiar pliku w UNIX wynosi więc 1 GB, a przy blokach o wielkości 1 kB wynosi 16 GB.

Atrybuty pliku w systemie UNIX

W systemie UNIX nie istnieje odpowiednik bajtu atrybutów plików z systemu DOS. W węźle pliku jest zapisany typ pliku (np. plik zwykły, device driver, katalog, soft link, itp.) oraz parametry dostępu użytkownika, grupy i pozostałych osób. Nie istnieje atrybut informujący o tym czy plik jest ukryty, czy nie. Ukrycie pliku jest możliwe przez nadanie mu nazwy rozpoczynającej się od znaku kropki `.'. To czy plik jest wykonywalny nie zależy od rozszerzenia, ani od zawartości pliku, a jedynie od parametru dostępu - ten kto próbuje plik „wykonać” musi mieć takie uprawnienia. W systemie DOS istniała możliwość tworzenia wielu wolumenów dyskowych niezależnych. W UNIX istnieje jeden wspólny filesystem. Jeden fizyczny filesystem jest filesystemem głównym, pozostałe dyski logiczne (np. partycje) lub dyski sieciowe dołącza się do podkatalogów filesystemu głównego.

Nazwy plików i metaznaki

Nazwy plików w systemie UNIX są ograniczone do 14 znaków. Duże i małe litery są rozróżnialne. Znak kropki może wystąpić w nazwie i to wielokrotnie. Nie istnieje coś takiego jak rozszerzenie pliku, chociaż istnieją konwencje dodawania pewnych przyrostków do plików określonego typu. Np. `.c' jest plikiem tekstowym, zawierającym kod programu w języku C, `.C' jest plikiem zawierającym kod programu w C++, itp.

W UNIX istnieją większe możliwości maskowania znaków w nazwach plików. Znak `*' określa dowolny ciąg znaków (również pusty). Znak `?' określa dokładnie jeden dowolny znak. Znaki `[` i `]' umożliwiają zdefiniowanie zbioru dopuszczalnych znaków. Zapis `[`...-...']' umożliwia zdefiniowanie zakresu dopuszczalnych znaków. Metaznaki mogą występować wielokrotnie, zarówno w nazwie pliku, jak i w nazwach katalogów na ścieżce dostępu do pliku. Kolejne katalogi na ścieżce dostępu do pliku oddzielane są znakiem `/', a nie tak jak w systemie DOS znakiem `\'. W każdym pliku katalogu istnieją dwa specjalne wpisy o nazwach `.' i '..', które oznaczają odpowiednio katalog aktualny i katalog nadrzędny. Jedynie w pliku katalogu głównego oba te wpisy prowadzą do tego samego miejsca - do katalogu głównego.

Ćwiczenia:

polecenie ls, opcje -l -a -i -F

pliki z `.' na początku

polecenie rm, opcja -r

maskowanie nazw plików

polecenie ln src dest

3. Rozwidlanie procesów - fork

W systemie UNIX wszystkie procesy są tworzone przez inne procesy. Dla danego procesu zawsze można określić proces, który go utworzył. Wszystkie procesy tworzone są przy pomocy tego samego mechanizmu - rozwidlania procesów (fork).

W systemie UNIX wszystkie procesy posiadają swoje identyfikatory tzw. PID (process identifier).

Funkcja getpid (IBM AIX 3.2):

prototyp: pid_t getpid ( );

header files: <unistd.h>

opis: Funkcja getpid zwraca wartość identyfikatora danego procesu. Jest to identyfikator unikalny w systemie. Wartość typu pid_t można wyświetlić w postaci liczby całkowitej (np. po zrzutowaniu na int).

Funkcja getppid (IBM AIX 3.2):

prototyp: pid_t getppid ( );

header files: <unistd.h>

opis: Funkcja getppid zwraca wartość identyfikatora procesu, który utworzył wcześniej (parent process) dany proces. Jeżeli proces ten (parent) z jakiegoś powodu już w systemie nie istnieje, wówczas dany proces (child) staje się tak zwanym procesem osieroconym. Jego identyfikatorowi PPID będzie przypisana nowa wartość identyfikatora procesu specjalnego - procesu init i będzie to wartość równa 1. W systemie UNIX, każdy proces musi mieć unikalny identyfikator PID i odpowiedni identyfikator PPID. Stąd bierze się ten mechanizm przejmowania „odpowiedzialności” za procesy osierocone, przez proces systemowy init. Sam proces init ma identyfikator PID równy 1, a jego identyfikator PPID wynosi 0. Jest to pierwszy proces powstający po uruchomieniu systemu i jedyny, który nie posiada swojego procesu parent.

Funkcja sleep (IBM AIX 3.2):

prototyp: unsigned int sleep ( unsigned int sekundy );

header files: <unistd.h>

opis: Funkcja sleep zawiesza wykonywanie procesu na czas określony w sekundach. Oczekiwanie może zostać przerwane w przypadku, gdy proces otrzymuje sygnał od systemu, lub gdy jest przerywany i kończy działanie. Zwracana jest wartość 0, gdy funkcja kończy działanie po pełnym okresie zawieszenia procesu. Gdy funkcja kończy działanie wcześniej, np. z powodu odebrania sygnału przez proces, wówczas zwracana jest liczba sekund, które jeszcze zostały do „odczekania”, natomiast zmienna globalna errno przyjmuje wartość kodu błędu - czyli kodu przyczyny wcześniejszego przerwania funkcji sleep.

Polecenie systemowe ps (IBM AIX 3.2):

składnia: ps [-A][-a][-d][-e][-f][-k][-l][-F Format][-G Glist][-g Glist][-p Plist][-t Tlist][-U Ulist][-u Ulist]

ps [a][c][e][ew][eww][g][n][U][w][x][l|s|u|v][t Tty][ProcessNumber]

opis: Polecenie systemu ps umożliwia wyświetlenie informacji o stanie prosesów w danym systemie. Szczegółowy opis wszystkich opcji - help do systemu operacyjego: man ps.

Przykład 1

#include <stdio.h>

#include <unistd.h>

int main() {

int myID, myParentID;

myID = (int) getpid(); /* rzutowanie jest potrzebne, bo funkcje zwracają typ pid_t a nie dokładnie int */

myParentID = (int) getppid();

printf( ”Mój identyfikator PID = %d, a identyfikator mojego parent'a PPID = %d\n”, myID, myParentID );

return 0;

}

Przykład 2

#include <stdio.h>

#include <unistd.h>

int main() {

int myID, myParentID;

myID = (int) getpid();

myParentID = (int) getppid();

printf( ”Mój identyfikator PID = %d, a identyfikator mojego parent'a PPID = %d\n”, myID, myParentID );

sleep( 10 );

return 0;

}

program należy po skompilowaniu uruchomić w tle ( np. a.out & ) i zaobserwować wyniki polecenia ps.

Funkcja fork (IBM AIX 3.2):

prototyp: pid_t fork ( );

header files: <unistd.h>

opis: Funkcja fork powoduje utworzenie nowego procesu. Proces ten, nazywany procesem potomnym (child process) jest niemal dokładną kopią procesu tworzącego, nazywanego procesem rodzica (parent process). Cały segment danych procesu jest kopiowany i proces potomny otrzymuje dokładną kopię danych procesu tworzącego (wraz ze wskaźnikami, przydzieloną dynamicznie pamięcią itp.) Dokładniej proces potomny dziedziczy z procesu tworzącego następujące elementy:

Proces potomny różni się od rodzica tym, że posiada:

Funkcja zwraca wartość zero w procesie pochodnym, natomiast w procesie tworzącym zwracana jest wartość identyfikatora utworzonego procesu (wartość większa od zero). W przypadku, gdy nie udało się utworzyć procesu potomnego zwracana jest wartość -1, a kod błędu jest wpisywany do globalnej zmiennej errno. Wykonanie odpowiedniego kodu (różnych kodów) w procesie parent i w procesie potomnym można uzyskać poprzez zastosowanie konstrukcji warunkowej if, która zbada co zwróciła funkcja fork.

Przykład 3

#include <stdio.h>

#include <unistd.h>

extern int errno;

int main() {

int forkValue;

printf( ”Przed fork: PID = %d, PPID = %d\n”, (int) getpid(), (int) getppid() );

forkValue = (int) fork( ); /* rzutowanie jest dlatego, że fork zwraca typ pid_t a nie dokładnie int */

if( forkValue == -1 ) {

printf( „Nie można utworzyć procesu potomnego funkcją fork! Wartość errno = %d\n”, errno );

perror(„Error”);

exit();

}

if( forkValue == 0 ) /* to znaczy jesteśmy w procesie child */ {

printf( „Wartość z fork = %d, PID = %d, PPID = %d\n”, forkValue, (int) getpid(), (int) getppid() );

sleep( 10 );

}

else /* to znaczy jesteśmy w procesie parent */ {

printf( „Wartość z fork = %d, PID = %d, PPID = %d\n”, forkValue, (int) getpid(), (int) getppid() );

sleep( 10 );

}

return 0;

}

program należy po skompilowaniu uruchomić w tle ( np. a.out & ) i zaobserwować wyniki polecenia ps.

Funkcje exec (IBM AIX 3.2):

prototypy: int execl ( const char * path, const char * arg0, const char * arg1, ... , 0 );

int execv ( const char * path, char * const argTAB [] );

........

header files: <unistd.h>

opis: Funkcja exec we wszystkich swoich postaciach powoduje uruchomienie nowego programu w środowisku wywołującego procesu. Funkcja ta nie tworzy nowego procesu! Bieżący program jest likwidowany (nie proces), a w jego miejsce „nakładany” jest nowy tzw. „new-process image”. Plik z ładowanym programem może mieć format „exec'a”, czyli tzw. extended COFF format, lub może to być plik tekstowy z procedurą shellową. Otwarte deskryptory plików pozostają otwarte w nowym programie. Wskaźnik pliku tych deskryptorów nie ulega zmianie. Procedury obsługi sygnałów zostają przywrócone (tzn. ustawiane są domyślne procedury obsługi). Pierwszy parametr określa ścieżkę dostępu do pliku, z którego ładowany jest nowy program, pozostałe parametry określają jakie argumenty zostaną przekazane do nowego programu. Należy pamiętać, że wartość zerowego argumentu powinna zawierać nazwę pliku z którego ładowany jest program (niezależnie od tego, że była ona przekazana w pierwszym parametrze funkcji).

Nowy „process-image” dziedziczy ze starego procesu następujące elementy:

W przypadku, gdy funkcja uruchomi nowy program, żadna wartość nie jest zwracana (co jest oczywiste, bo w tym momencie stary program już nie istnieje - zaczyna się nowy). Jeżeli jakaś wartość jest zwracana, to tylko -1 w przypadku wystąpienia błędu. Zmienna globalna errno określa jego kod (np. błędna ścieżka dostępu do pliku, lub brak uprawnień).

4. Synchronizacja procesów - semafory

W systemie UNIX wszystkie procesy mogą być wykonywane równolegle. W przypadku maszyn z jednym procesorem, „równoległość” wykonywania się programów zapewniają odpowiednie mechanizmy np. scheduling i wywłaszczanie. System decyduje, kiedy i przez jaki czas dany program będzie się wykonywał. Ze względu na to, że czasy wykonywania i czasy przestoju są bardzo niewielkie, dla użytkownika wygląda to tak, jak by programy wykonywały się rzeczywiście równolegle. W przypadku, gdy mamy do czynienia z maszynami o większej liczbie procesorów, programy mogą wykonywać się rzeczywiście równolegle. Z drugiej strony większość zasobów systemu jest wspólna. Są to np. pamięć, dyski i pliki, terminale. Podczas równoczesnych prób dostępu do tych zasobów przez różne procesy musi istnieć jakiś mechanizm umożliwiający regulację tego dostępu i synchronizację procesów.

Inny przypadek, kiedy synchronizacja procesów jest niezbędna to komunikacja między procesami, odbywająca się poprzez pewne wspólne medium np. przez system plików lub mechanizm wspólnej pamięci. Proces, który oczekuje komunikatów może je odczytywać tylko w tym momencie, gdy inny proces je utworzył i wysłał, w przeciwnym wypadku mogłyby zostać odczytane komunikaty „puste”, lub mogłyby zostać odczytane kilkakrotnie te same komunikaty. Z kolei proces, który wysyła komunikaty może je wysłać (wprowadzić do medium) tylko wtedy, gdy proces czytający zdążył odczytać poprzedni komunikat, w przeciwnym wypadku treść poprzedniego komunikatu mogłaby ulec zamazaniu.

Niektóre zasoby mają własne mechanizmy zabezpieczeń w środowisku konkurencyjnego dostępu. Inne muszą być synchronizowane przez programistę. Najpopularniejszym mechanizmem synchronizacji procesów w systemie UNIX są semafory.

Funkcja semget (IBM AIX 3.2):

prototyp: int semget ( key_t key, int semNB, int semFLG );

header files: <sys/sem.h>, <sys/types.h>, <sys/mode.h>

opis: Funkcja semget tworzy w systemie zbiór semaforowy (nie jest to zbiór w rozumieniu pliku) - set of semaphores (jest to struktura danych zawierająca między innymi indeksowalną tablicę semaforów) i zwraca jego identyfikator. Identyfikator takiego zbioru jest tworzony na podstawie podanego klucza. Jeżeli zostanie podany taki klucz, dla którego istnieje już zbiór semaforowy utworzony przez jakiś inny proces, to wówczas funkcja będzie próbowała „dołączyć się do tego istniejącego zbioru” - zwróci identyfikator tego zbioru, ale bez tworzenia go na nowo. Zbiór semaforów będzie zawierał tyle pojedynczych semaforów ile wynosi wartość parametru semNB. Będą one indeksowane od wartości 0 do semNB - 1. Funkcja zawsze będzie próbowała utworzyć nowy zbiór semaforowy wtedy, gdy jako wartość parametru key zostanie przekazane IPC_PRIVATE. Jest możliwe określenie ustalonego klucza, co spowoduje utworzenie zbioru semaforów z ustalonym identyfikatorem. Można taki klucz ustalić przez zrzutowanie pewnej liczby całkowitej na wartość typu key_t lub użyć np. funkcji ftok. W ten sposób różne programy/procesy mogą się dołączać do tego samego zbioru semaforów (nie muszą to być procesy związane ze sobą zależnością parent-child). Ostatni parametr określa między innymi sposób tworzenia zbioru i uprawnienia procesów w zakresie dostępu i modyfikacji wartości poszczególnych semaforów w zbiorze. Jego wartość powstaje przez wykonanie logicznej operacji „or” na poniższych wartościach:

W przypadku błędu funkcja zwraca wartość -1 i wpisuje kod błędu do globalnej zmiennej errno. Dokładny opis funkcji - polecenie man semget.

Funkcja semctl (IBM AIX 3.2):

prototyp: int semctl ( int semID, int semIndex, int command, int value );

int semctl ( int semID, int semIndex, int command, struct semid_ds * buffer );

int semctl ( int semID, int semIndex, int command, unsigned short array [] );

header files: <sys/sem.h>, <sys/types.h>

opis: Funkcja semctl umożliwia realizację operacji kontrolnych na zbiorach semaforowych. Dzięki niej można między innymi usunąć z systemu zbiór semaforowy, ustawiać wartości poszczególnych semaforów w zbiorze, lub je odczytywać. Można również zmieniać uprawnienia procesów w zakresie dostępu do zbioru semaforów. Parametr semID określa identyfikator zbioru semaforów (zwrócony przez funkcję semget); semIndex określa indeks semaforu, którego dotyczy dana operacja. Parametr command określa rodzaj operacji kontrolnej, którą chcemy wykonać na danym semaforze. Ostatni parametr zależy od rodzaju komendy i zawiera niezbędne dane do jej wykonania. Najważniejsze operacje kontrolne:

Dokładniejsze informacje - polecenie man semctl.

Funkcja semop (IBM AIX 3.2):

prototyp: int semop ( int semID, struct sembuf * operations, int operationsNB );

header files: <sys/sem.h>, <sys/types.h>

opis: Funkcja semop wykonuje ciąg operacji na zbiorze semaforowym. Opisy operacji są przekazane przy pomocy wskaźnika operations (w szczególności może to być jedna operacja). Liczba tych operacji przekazana jest przy pomocy paramteru operationsNB. Identyfikator zbioru semaforowego, którego dotyczą operacje jest przekazany przy pomocy parametru semID. Opis pojedynczej operacji zawiera się w strukturze typu struct sembuf. Struktura ta między innymi posiada następujące pola:

Operacja określona przez pole sem_op jest wykonywana na semaforze określonym przez indeks sem_num. Pole sem_op może określić jedną z trzech rodzajów operacji semaforowych, w zależności od znaku:

  1. sem_op ma wartość ujemną, wówczas:

  1. sem_op ma wartość dodatnią, wówczas:

  1. sem_op ma wartość zero, wówczas:

W przypadku poprawnie wykonanej operacji funkcja zwraca wartość zero. W przypadku błędu funkcja zwraca wartość -1, a zmienna globalna errno zawiera kod błędu.

Polecenie systemowe ipcs (IBM AIX 3.2):

składnia: ipcs [ -m][ -q][ -s][ -a | -b -c -o -p -t][ -Ccorefile][ -N Kernel]

opis: Polecenie systemu ipcs umożliwia wyświetlenie informacji o stanie zasobów systemowych - semaforów, pamięci dzielonej i kolejek komunikatów. Wyświetlany jest identyfikator danego zasobu, klucz, z którym był utworzony, jedenastoznakowy stan zasobu, dostępność zasobu dla różnych procesów i inne informacje. Szczegółowy opis wszystkich opcji - help do systemu operacyjego: man ipcs.

Polecenie systemowe ipcrm (IBM AIX 3.2):

składnia: ipcrm [-m ShmID][-M ShmKEY][-q MsgqID][-Q MsgqKEY][-s SemID][-S SemKEY]

opis: Polecenie systemu ipcrm umożliwia usunięcie z systemu zasobów określonych przez identyfikator lub przez klucz. Jest to szczególnie przydatne, gdy jakieś programy przez swoje nieprawidłowe działanie „zaśmiecają” system nieużywanymi zasobami (przy wychodzeniu z programu zasoby te nie są automatycznie zwalniane). Szczegółowy opis wszystkich opcji - help do systemu operacyjego: man ipcrm.

Przykład 1 - Schemat synchronizacji dwóch procesów wypisujących naprzemiennie kolejne liczby naturalne.

UWAGI:

Program #1 do realizacji:

  1. Funkcje semUP( int sn ), semDOWN( int sn ), child(), parent()

  2. Tworzenie zbioru semaforowego

  3. Rozwidlenie procesu

  4. W procesie child'a wywołanie funkcji child()

  5. W procesie parent'a wywołanie funkcji parent(), a następnie usunięcie zbioru semaforów

Funkcje semUP i semDOWN odpowiednio mają podnosić i opuszczać zadany semafor.

Funkcje parent'a i child'a powinny wypisać w pętli kolejne n liczb naturalnych (z informacją o tym, który to proces).

Program #2 do realizacji:

  1. Uzupełnienie programu #1 o schemat synchronizacji z przykładu 1

5. Pamięć dzielona - shared memory

W systemie UNIX istnieje mechanizm umożliwiający bardzo efektywną komunikację między procesami. Jest to tak zwana pamięć dzielona. Obszar pamięci dzielonej może być utworzony przez pewien proces, a następnie inne procesy mogą się do niego dołączać i z niego korzystać. To co zostanie do pamięci dzielonej wpisane przez jeden proces jest natychmiast widoczne w innych procesach. Przy niezsynchronizowanych próbach dostępu do danych o tych samych adresach mogą pojawiać się konflikty (np. przy próbie wprowadzenia różnych danych do tego samego bajtu przez dwa różne procesy). Budowanie systemu komunikacji między procesami, w oparciu o mechanizm segmentów pamięci dzielonej, wymaga zastosowania odpowiednich mechanizmów synchronizacji - np. semaforów.

Funkcja shmget (IBM AIX 3.2):

prototyp: int shmget ( key_t key, size_t size, int shmFLG );

header files: <sys/shm.h>, <sys/types.h>, <sys/mode.h>

opis: Funkcja shmget tworzy w systemie strukturę reprezentującą pamięć dzieloną. Jest to struktura danych, z której można uzyskać wskaźnik do tablicy bajtów, w której można przechowywać dane wspólne dla wielu procesów). Funkcja ta zwraca identyfikator pamięci dzielonej. Identyfikator takiej struktury jest tworzony, podobnie jak w przypadku zbioru semaforowego, na podstawie podanego klucza. Jeżeli zostanie podany taki klucz, dla którego istnieje już segment pamięci dzielonej utworzony przez jakiś inny proces, to wówczas funkcja będzie próbowała „dołączyć się” do tego istniejącego segmentu - zwróci identyfikator tego segmentu, ale bez tworzenia go na nowo. Segment pamięci dzielonej będzie zawierał tyle bajtów ile zostało określone parametrem size. Funkcja zawsze będzie próbowała utworzyć nowy segment wtedy, gdy jako wartość parametru key zostanie przekazane IPC_PRIVATE. Jest możliwe określenie ustalonego klucza, co spowoduje utworzenie segmentu z ustalonym identyfikatorem. Można taki klucz ustalić przez zrzutowanie pewnej liczby całkowitej na wartość typu key_t lub użyć np. funkcji ftok. W ten sposób różne programy/procesy mogą się dołączać do tego samego segmentu pamięci dzielonej (nie muszą to być procesy związane ze sobą zależnością parent-child). Ostatni parametr określa między innymi sposób tworzenia segmentu i uprawnienia procesów w zakresie dostępu i modyfikacji wartości bajtów współdzielonego obszaru. Jego wartość powstaje przez wykonanie logicznej operacji „or” na poniższych wartościach:

W przypadku błędu funkcja zwraca wartość -1 i wpisuje kod błędu do globalnej zmiennej errno. Dokładny opis funkcji - polecenie man shmget. Zarówno zbiory semaforowe jak i segmenty pamięci dzielonej nie są automatycznie usuwane po zakończeniu działania procesu, który je utworzył. Usunięcie segmentu jest realizowane przez funkcję shmctl lub polecenie systemowe ipcrm.

Funkcja shmctl (IBM AIX 3.2):

prototyp: int shmctl ( int semID, int command, struct shmid_ds * buffer );

header files: <sys/shm.h>, <sys/types.h>

opis: Funkcja shmctl umożliwia realizację operacji kontrolnych na segmentach pamięci dzielonej. Dzięki niej można między innymi usunąć z systemu segment pamięci dzielonej. Można również zmieniać uprawnienia procesów w zakresie dostępu do pamięci dzielonej. Parametr shmID określa identyfikator segmentu pamięci dzielonej (zwrócony przez funkcję shmget). Parametr command określa rodzaj operacji kontrolnej, którą chcemy wykonać na pamięci dzielonej. Ostatni parametr zależy od rodzaju komendy i zawiera niezbędne dane do jej wykonania. Najważniejsze operacje kontrolne:

W przypadku błędu funkcja zwraca wartość -1 i wpisuje kod błędu do globalnej zmiennej errno. Dokładniejsze informacje - polecenie man shmctl.

Funkcja shmat (IBM AIX 3.2):

prototyp: void * shmat ( int shmID, char * shmAddress, int shmFLG );

header files: <sys/shm.h>, <sys/types.h>

opis: Funkcja shmat umożliwia dołączenie procesu do utworzonego wcześniej przez ten lub inny proces segmentu pamięci dzielonej. Dzięki tej funkcji proces otrzymuje wskaźnik na tablicę bajtów znajdujących się we wspólnej pamięci. Pierwszy parametr określa identyfikator segmentu pamięci dzielonej, z którego pobieramy wskaźnik (do którego następuje przyłączenie). Drugi określa adres z przestrzeni adresowej procesu, do którego programista chce przyłączyć pamięć dzieloną.

  1. Jeżeli wartość adresu podanego przez użytkownika wynosi zero (NULL), wówczas system operacyjny samodzielnie dobiera odpowiednią wartość i zwraca ją jako wynik działania funkcji.

  1. Jeżeli wartość adresu podanego przez użytkownika jest niezerowa, wówczas:

Domyślne ograniczenia związane z segmentami pamięci dzielonej: Maksymalna wielkość segmentu pamięci dzielonej wynosi 256 MB, minimalna wielkość wynosi 1 bajt, maksymalna liczba utworzonych segmentów wynosi 4096.
Trzeci argument funkcji określa parametry dołączania do segmentu pamięci dzielonej (dokładny opis - polecenie man shmat).
Zwracana wartość: W przypadku pomyślnego dołączenia do segmentu pamięci dzielonej, funkcja zwraca adres początku współdzielonej tablicy bajtów. W przypadku wystąpienia błędów zwracana jest wartość -1. Typ zwracanej wartości to void *. Aby prawidłowo wykonać kontrolę błędów należy więc zwróconą przez funkcję wartość zrzutować na typ int i porównać ze stałą -1. Jeżeli wartość ta jest różna od -1, to znaczy, że segment został prawidłowo dołączony i można z niego korzystać. Aby można było z niego korzystać, należy kolejno zrzutować zwróconą wartość na wskaźnik na odpowiedni typ np. na char *.

Przykład 1 - fragment kodu programu tworzącego pamięć dzieloną i dołączającego się do niego

int shmID;

char * sharedptr;

shmID = shmget( IPC_PRIVATE, 128, IPC_CREAT | S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP );

if( shmID == -1 ) { printf(„ERROR in shmget\n”); exit(); }

sharedptr = (char *) shmat( shmID, 0, 0 );

if( (int)sharedptr == -1 ) { printf(„ERROR in shmat\n”); exit(); }

/* tu już można korzystać z pamięci dzielonej

* np. można coś do niej wpisać:

*/

strcpy( sharedptr, „Ten tekst trafi do pamięci dzielonej” );

Funkcja shmdt (IBM AIX 3.2):

prototyp: int shmdt ( char * shmAddress );

header files: <sys/shm.h>, <sys/types.h>

opis: Funkcja shmdt umożliwia odłączenie procesu od utworzonego wcześniej przez ten lub inny proces segmentu pamięci dzielonej, do którego dany proces jest dołączony. Argument funkcji określa adres poprzez który proces jest dołączony do segmentu pamięci dzielonej. W przypadku pomyślnego wykonania operacji funkcja zwraca wartość zero. W przypadku błędu funkcja zwraca wartość niezerową, a kod błędu wpisywany jest do zmiennej globalnej errno.

Przykład 2 - fragment kodu programu odłączającego się od pamięci dzielonej i usuwającego strukturę segmentu pamięci dzielonej z systemu operacyjnego.

shmdt ( sharedptr );

/* tu już NIE można korzystać ze wskaźnika sharedptr */

shmctl ( shmID, IPC_RMID, 0 );

/* tu już pamięć dzielona nie istnieje */

Przykład 3 - Schemat synchronizacji przy przesyłaniu przez pamięć dzieloną komunikatu i odpowiedzi w relacji klient-serwer przy użyciu jednego bufora dla polecenia i dla odpowiedzi.

W tym schemacie gdy działa tylko jeden klient, semafor Z nie jest potrzebny.

UWAGI:

Program #1 do realizacji:

  1. Funkcje semUP( int sn ), semDOWN( int sn ), child(), parent()

  2. Tworzenie zbioru semaforowego i segmentu pamięci dzielonej

  3. Rozwidlenie procesu

  4. W procesie child'a dołączenie się do segmentu, wywołanie funkcji child() i odłączenie się od segmentu

  5. W procesie parent'a dołączenie się do segmentu, wywołanie funkcji parent(), a następnie odłączenie się od segmentu, usunięcie zbioru semaforów i segmentu pamięci dzielonej

Funkcje semUP i semDOWN odpowiednio mają podnosić i opuszczać zadany semafor (wzięte z programu dotyczącego semaforów).

Funkcja parent'a powinna wpisać dowolny tekst do pamięci dzielonej, natomiast funkcja child'a powinna odczytać ten tekst i wypisać na ekranie

Program #2 do realizacji:

  1. Uzupełnienie programu #1 o uśpienie procesu odczytującego z pamięci dzielonej na pewien czas przed odczytem.

Program #3 do realizacji:

  1. Uzupełnienie programu #1 o synchronizację i schemat komunikacji z przykładu 3; realizacja operacji mnożenia przez 2 w serwerze.

6. Mechanizm pipe, strumienie w UNIX

W systemie UNIX istnieje mechanizm umożliwiający wymianę danych między dwoma procesami związanymi zależnością parent-child lub child-child. Jest to mechanizm strumieni. Pomiędzy dwoma procesami tworzy się tzw. „pipe” czyli strumień, do którego procesy mogą wpisywać informację lub j --> [Author:P] ą z niego odczytywać. Utworzony strumień - „pipe” daje dwa deskryptory plików (file handle - lub file descriptor). Poprzez jeden z deskryptorów możemy wpisywać dane do strumienia, a poprzez drugi, możemy dane odczytywać. W ten sposób jest możliwa realizacja jednokierunkowej komunikacji (bez dodatkowej synchronizacji). Z dodatkową synchronizacją komunikacja może być dwukierunkowa. Dwukierunkowe przesyłanie danych można również zrealizować przy pomocy dwóch niezależnych - jednokierunkowych strumieni. Np. pierwszy od klienta do serwera i drugi od serwera do klienta.

Przykład 1 Schemat strumienia jednokierunkowego

Przykład 2 Schemat strumienia dwukierunkowego (komunikacja wymaga pewnej synchronizacji)

Funkcja pipe (IBM AIX 3.2):

prototyp: int pipe ( int FD [ 2 ] );

header files: <unistd.h>

opis: Funkcja pipe tworzy komunikacyjny kanał międzyprocesowy, który nazywany jest pipe. Ustawia wartość dwóch deskryptorów plików. Są to FD[ 0 ] oraz FD[ 1 ]. Deskryptor FD[ 0 ] jest otwarty dla odczytu, natomiast deskryptor FD[ 1 ] jest otwarty dla zapisu.
Operacja odczytu przeprowadzona na deskryptorze FD[ 0 ] umożliwia odtworzenie tego co zostało wprowadzone przez operację zapisu przeprowadzoną przy pomocy deskryptora FD[ 1 ]. Strumień UNIX'owy jest strukturą typu FIFO (first-in, first-out).
Funkcja zwraca wartość 0 w przypadku poprawnego wykonania. W przypadku błędów zwraca wartość -1, wówczas zmienna globalna errno przechowuje kod błędu.

Makrodefinicja PIPE_BUF (IBM AIX 3.2):

header files: <unistd.h>

opis: Makrodefinicja PIPE_BUF określa dopuszczalną liczbę bajtów, która jest zapisywana do strumienia w sposób niepodzielny. Jeżeli do strumienia zostanie wprowadzona liczba bajtów mniejsza bądź równa PIPE_BUF, to jest zagwarantowane, że bajty te nie będą przemieszane z ewentualnymi innymi bajtami wprowadzonymi przez ewentualne inne procesy. W przypadku wprowadzania większej liczby bajtów niż PIPE_BUF mogą one zostać pofragmentowane i przemieszane z innymi danymi, wprowadzanymi przez inne procesy.

Zastosowanie funkcji pipe do komunikacji międzyprocesowej z reguły polega na utworzeniu jednego lub dwóch (dla komunikacji dwukierunkowej) strumieni, a następnie rozwidlenia procesów potomnych. Każdy z procesów potomnych otrzymuje swoje własne kopie deskryptorów plików (ale same wskaźniki plików są współdzielone), tak więc również kopie deskryptorów służących do zapisu i odczytu ze strumienia. Przyjęto, że procesy powinny pozamykać nie używane deskryptory. Również przed zakończeniem działania procesy powinny pozamykać wszystkie otwarte deskryptory.

Schemat tworzenia jednokierunkowego strumienia od procesu child'a do procesu parent'a. Po utworzeniu strumienia proces child'a może do niego wpisywać dane, a proces parent'a odczytywać.

Funkcja read (IBM AIX 3.2):

prototyp: int read ( int fh, void * bufor, size_t nbytes );

header files: <unistd.h>

opis: Funkcja read umożliwia odczytywanie danych z pliku bądź strumienia, poprzez podany deskryptor. Pierwszy parametr funkcji to właśnie deskryptor otwartego strumienia lub pliku, drugi to obszar, do którego mają trafić odczytane bajty, a trzeci liczba bajtów do odczytania. W przypadku, gdy liczba bajtów do odczytania jest większa niż liczba bajtów znajdujących się aktualnie w strumieniu, wówczas funkcja może zablokować dalsze działanie programu, do czasu, aż inny proces wprowadzi do strumienia wystarczającą liczbę bajtów. Ewentualna blokada działania programu jest uzależniona od ustawionych opcji dla danego deskryptora pliku.
Zwracana wartość to liczba bajtów rzeczywiście wczytanych ze strumienia, lub wartość -1 w przypadku wystąpienia błędu. W przypadku błędu zmienna globalna errno przyjmuje wartość kodu błędu.

Funkcja write (IBM AIX 3.2):

prototyp: size_t write ( int fh, const void * bufor, size_t nbytes );

header files: <unistd.h>

opis: Funkcja write umożliwia wpisanie danych do pliku bądź strumienia, poprzez podany deskryptor. Pierwszy parametr funkcji to deskryptor otwartego strumienia lub pliku, drugi to obszar, z którego dane mają zostać pobrane, a trzeci to liczba bajtów, które mają trafić do strumienia. Jeżeli liczba bajtów przekracza wartość zdefiniowaną przy pomocy makrodefinicji PIPE_BUF, wówczas wprowadzane dane mogą zostać pofragmentowane i pomieszane z innymi. Jeżeli strumień jest w danej chwili „pełny” wówczas funkcja write może zostać zablokowana, do czasu, aż inny proces odczyta odpowiednią ilość bajtów, co spowoduje zwolnienie miejsca.
Zwracana wartość to liczba rzeczywiście wprowadzonych do strumienia danych. W przypadku błędu lub niepowodzenia zwracana jest wartość -1, a zmienna globalna errno przyjmuje kod błędu operacji.

Funkcja close (IBM AIX 3.2):

prototyp: int close ( int fh );

header files: <unistd.h>

opis: Funkcja close powoduje zamknięcie pliku lub strumienia określonego przez podany deskryptor. Jeżeli deskryptory wszystkich procesów korzystających ze strumienia zostaną zamknięte, a w strumieniu pozostaną nieodczytane dane, to zostaną one stracone.
W przypadku poprawnego wykonania funkcja zwraca wartość 0, w przypadku błędu wartość -1 oraz zmienna globalna errno przyjmuje wartość kodu błędu.

Przykład 3 Schemat komunikacji dwukierunkowej przez dwa strumienie (komunikacja nie wymaga zewnętrznej synchronizacji)

UWAGI:

Program #1 do realizacji:

  1. Funkcje child(), parent()

  2. Tworzenie dwóch strumieni jednokierunkowych zgodnie z przykładem 3; np. odAdoB i odBdoA

  3. Rozwidlenie procesu

  4. W procesie child'a zamknięcie nieużywanych deskryptorów, wywołanie funkcji child() i zamknięcie otwartych deskryptorów

  5. W procesie parent'a zamknięcie nieużywanych deskryptorów, wywołanie funkcji parent() i zamknięcie otwartych deskryptorów

Program #2 do realizacji:

  1. Uzupełnienie programu #1 o komunikację przez pipe w funkcjach child() i parent() - realizacja zamiany dużych liter na małe

Program #3 do realizacji:

  1. Uzupełnienie programu #2 o pętlę w serwerze i kliencie oraz sterowanie wyjściem z programu (np. po odebraniu specjalnego tekstu).

7. Kolejki komunikatów - message queue

Trzeci mechanizm komunikacyjny ze standardu IPC (InterProcess Communication) w systemie UNIX to kolejki komunikatów. Kolejka komunikatów może być utworzona przez pewien proces, a następnie inne procesy mogą wstawiać lub pobierać z kolejki różne komunikaty. To co zostanie do kolejki wstawione przez jeden proces może być odebrane w innych procesach. Funkcje do operacji na kolejkach komunikatów realizują automatycznie synchronizację na podstawowym poziomie.

Funkcja msgget (IBM AIX 3.2):

prototyp: int msgget ( key_t key, int msgFLG );

header files: <sys/shm.h>, <sys/types.h>, <sys/mode.h>

opis: Funkcja msgget tworzy w systemie strukturę reprezentującą kolejkę komunikatów. Funkcja ta zwraca identyfikator pamięci dzielonej. Identyfikator takiej struktury jest tworzony, podobnie jak w przypadku zbioru semaforowego lub segmentu pamięci dzielonej, na podstawie podanego klucza. Jeżeli zostanie podany taki klucz, dla którego istnieje już kolejka komunikatów utworzona przez jakiś inny proces, to wówczas funkcja będzie próbowała „dołączyć się” do tej istniejącej już kolejki - zwróci jej identyfikator, ale bez tworzenia jej na nowo. Funkcja zawsze będzie próbowała utworzyć nową kolejkę wtedy, gdy jako wartość parametru key zostanie przekazane IPC_PRIVATE. Jest możliwe określenie ustalonego klucza, co spowoduje utworzenie kolejki z ustalonym identyfikatorem. Można taki klucz ustalić przez zrzutowanie pewnej liczby całkowitej na wartość typu key_t lub użyć np. funkcji ftok. W ten sposób różne programy/procesy mogą się dołączać do tej samej kolejki (nie muszą to być procesy związane ze sobą zależnością parent-child). Drugi parametr określa między innymi sposób tworzenia kolejki i uprawnienia procesów w zakresie dostępu do kolejki. Jego wartość powstaje przez wykonanie logicznej operacji „or” na poniższych wartościach:

W przypadku błędu funkcja zwraca wartość -1 i wpisuje kod błędu do globalnej zmiennej errno. Dokładny opis funkcji - polecenie man msgget. Zarówno zbiory semaforowe, segmenty pamięci dzielonej jak i kolejki komunikatów nie są automatycznie usuwane po zakończeniu działania procesu, który je utworzył. Usunięcie kolejki jest realizowane przez funkcję msgctl lub polecenie systemowe ipcrm.

Funkcja msgctl (IBM AIX 3.2):

prototyp: int msgctl ( int msgID, int command, struct msqid_ds * buffer );

header files: <sys/shm.h>, <sys/types.h>

opis: Funkcja msgctl umożliwia realizację operacji sterujących na kolejkach komunikatów. Dzięki niej można między innymi usunąć z systemu kolejkę komunikatów. Można również zmieniać uprawnienia procesów w zakresie dostępu do kolejki. Parametr msgID określa identyfikator kolejki komunikatów (zwrócony przez funkcję msgget). Parametr command określa rodzaj operacji kontrolnej, którą chcemy wykonać na kolejce komunikatów. Ostatni parametr zależy od rodzaju komendy i zawiera niezbędne dane do jej wykonania. Najważniejsze operacje kontrolne:

W przypadku błędu funkcja zwraca wartość -1 i wpisuje kod błędu do globalnej zmiennej errno. Dokładniejsze informacje - polecenie man msgctl.

Funkcja msgsnd (IBM AIX 3.2):

prototyp: int msgsnd ( int msgID, void * msgPtr, size_t msgSize, int msgFLG );

header files: <sys/shm.h>, <sys/types.h>

opis: Funkcja msgsnd umożliwia wstawienie komunikatu do kolejki. Pierwszy parametr określa identyfikator kolejki komunikatów, do której wstawiamy dane. Drugi parametr określa adres, pod którym znajduje się struktura (reprezentująca cały komunikat) zawierająca między innymi wstawiane dane. Trzeci parametr określa rozmiar danych w bajtach, które przekazujemy do wstawienia (rozmiar pola mtext struktury komunikatu). Czwarty parametr określa co system ma zrobić, jeżeli nie jest w danej chwili możliwe wstawienie komunikatu do kolejki.
Aby wstawić dane do kolejki trzeba najpierw utworzyć odpowiednio dużą strukturę o ściśle określonej postaci. Struktura ta musi posiadać dwa pola:
long mtype;
char mtext[ <ROZMIAR> ];
właściwe dane, które przekazuje się do kolejki powinny być wstawione do pola mtext.
Zwracana wartość: W przypadku pomyślnego wstawienia komunikatu do kolejki, funkcja zwraca wartość 0, w przeciwnym przypadku -1, a do zmiennej globalnej errno jest wstawiany kod błędu.

Przykład 1 - fragment kodu funkcji wstawiającej do kolejki napis tekstowy

typedef struct {

long mtype;

char mtext [100];

} komunikat;

void wstawNapis( char * napis ) {

komunikat kom;

strcpy( kom.mtext, napis );

kom.mtype = 1;

msgsnd( msgID, & kom, strlen( kom.mtext ) + 1, 0 );

}

Funkcja msgrcv (IBM AIX 3.2):

prototyp: int msgrcv ( int msgID, void * Ptr, size_t Size, int Type, int msgFLG );

header files: <sys/shm.h>, <sys/types.h>

opis: Funkcja msgrcv umożliwia pobranie komunikatu z kolejki. Pierwszy parametr określa identyfikator kolejki komunikatów, z której pobieramy komunikat. Drugi parametr określa adres, pod którym znajduje się struktura (reprezentująca cały komunikat), do której ma zostać przekopiowany komunikat (musi ona być wystarczająco duża). Trzeci parametr określa rozmiar akceptowanych danych w bajtach (rozmiar pola mtext struktury komunikatu) - jeżeli komunikat jest większy to nastąpi jego obcięcie. Czwarty parametr określa typ komunikatu jaki chcemy odebrać:

Ostatni parametr określa co system ma zrobić, jeżeli nie jest w danej chwili możliwe odczytanie z kolejki komunikatu o zadanym typie.

Aby odebrać dane z kolejki trzeba analogicznie jak w funkcji msgsnd, utworzyć odpowiednio dużą strukturę o ściśle określonej postaci (taka sama struktura jak w funkcji msgsnd). Struktura ta musi posiadać dwa pola:
long mtype;
char mtext[ <ROZMIAR> ];
Pole mtext musi mieć na tyle duży rozmiar, aby pomieścić dane odczytywanego komunikatu.
Zwracana wartość: W przypadku pomyślnego odczytania komunikatu z kolejki, funkcja zwraca wartość 0, w przeciwnym przypadku -1, a do zmiennej globalnej errno jest wstawiany kod błędu.

Przykład 2 - fragment kodu funkcji odczytującej z kolejki komunikat

typedef struct {

long mtype;

char mtext [100];

} komunikat;

void odczytajKomunikat() {

komunikat kom;

msgrcv( msgID, & kom, 100, 1, 0 ); /* maksymalnie 100 bajtów, typ komunikatu 1 */

printf( ”Odczytałem z kolejki: %s\n”, kom.mtext );

}

UWAGI:

Program #1 do realizacji:

  1. Funkcje child(), parent()

  2. Tworzenie kolejki komunikatów

  3. Rozwidlenie procesu

  4. W procesie child'a wywołanie funkcji child()

  5. W procesie parent'a wywołanie funkcji parent(), a następnie usunięcie kolejki komunikatów

Funkcja parent'a powinna realizować serwer, który wykonuje polecenia i odsyła odpowiedzi do child'a - klienta

Systemy operacyjne - konspekt

1

Paweł Łopata, Katedra Telekomunikacji AGH

M

Y

F

I

L

E

T

X

T

a

.

.

...

.

.

0

8

.

...

00

01

02

03

04

05

06

07

08

09

0a

0b

0c

0d

0e

0f

ID

ff

03

04

05

ff

00

00

09

0a

0b

15

00

00

00

00

00

00

00

00

00

00

16

17

ff

00

00

00

00

f7

00

00

00

10

.

.

.

20



Wyszukiwarka

Podobne podstrony:
[8]konspekt new, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki,
konspekt nr8, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, Fi
konspekt 8, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, Fizy
konspekt 9, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, Fizy
konspekt Cw5, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, 5,4 Badanie
[8]konspekt, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, lab
poziomy energetyczne konspekt, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laborato
konspekt3, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, labor
[7]konspekt, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, lab
[6]konspekt, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, lab
konspekt 8 1str, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki,
Konspekt07 forM.B, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, labork
Konspekt05, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, labo
[9]konspekt, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, lab
przerwa energetyczna konspekt, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laborato
[4]konspekt, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, laborki, lab
[8]konspekt new new, Elektrotechnika AGH, Semestr II letni 2012-2013, Fizyka II - Laboratorium, labo

więcej podobnych podstron