Systemy Operacyjne Linux Komunikacja pomiędzy procesami (IPC)


Jerzy Pejaś: Systemy operacyjne - Linux
LINUX
Komunikacja
między
procesami
(IPC)
- 1 -
Jerzy Pejaś: Systemy operacyjne - Linux
Komunikacja IPC
Wprowadzenie
q Aby dwa procesy komunikowały się ze sobą, muszą obydwa się na
to zgodzić, a system operacyjny musi dostarczyć narzędzi
przeznaczonych do komunikacji między procesami (ang.
Interprocess communication, IPC).
q Komunikacja między procesami nie dotyczy jedynie wymiany
informacji pomiędzy procesami w sieci, ale przede wszystkim
procesów wykonywanych w jednym systemie w jednym systemie
komputerowym (patrz rys.1).
Rys.1 Komunikacja między dwoma procesami w jednym systemie
q Widzimy, że komunikacja między dwoma procesami odbywa się
za pośrednictwem jądra. Jest to sytuacja typowa, ale nie jest to
wymóg.
q Komunikacja między procesami w tym samym systemie może być
realizowana na kilka różnych sposobów:
" Pół-dupleksowe łącza komunikacyjne (ang. half-duplex UNIX pipes),
" Kolejki FIFO (łącza nazwane, ang. named pipes),
" Kolejki komunikatów (ang. SYS V style message queues),
" Zbiory semaforów (ang. SYS V style semaphore sets),
" Pamięć współdzielona (ang. SYS V shared memory segments),
- 2 -
Jerzy Pejaś: Systemy operacyjne - Linux
" Pełno-dupleksowe łącza komunikacyjne (ang. Full-duplex pipes,
STREAMS pipes).
q Komunikacja między procesami wykonywanymi w różnych
systemach przy użyciu jakiejś sieci łączącej systemy może
wyglądać tak jak na rys.2.
Rys.2 Komunikacja między dwoma procesami w różnych systemach
q Komunikacja między procesami znajdującymi się w różnych
systemach realizowana jest za pośrednictwem gniazd (ang
networking sockets, Berkley style).
Ł ącza komunikacyjne (ang. pipes)
q Łącze komunikacyjne jest metodą, która umożliwia połączenie
standardowego wyjścia jednego z procesów do standardowego
wejścia innego lub tego samego procesu. Łącze komunikacyjne
umożliwia przepływ danych tylko w jednym kierunku (stąd
nazwa pół-duplex).
Rys.3 Łącze komunikacyjne w jednym procesie
- 3 -
Jerzy Pejaś: Systemy operacyjne - Linux
q Schemat zastosowania łącza komunikacyjnego w jednym i tym
samym procesie pokazano na rys.3. Zasady czytania danych z
łącza, w którym nie ma żadnych danych oraz pisanie do łącza
wówczas, gdy jest zapełnione podamy dalej, przy okazji
omawiania łączy nazwanych.
Rys.4 Łącze komunikacyjne w jednym procesie bezpośrednio po
wywołaniu funkcji fork
q Możliwość wymiany przez proces informacji tylko z sobą jest mało
interesująca, chociaż czasami może być przydatna, np. w razie
konieczności kolejkowania informacji.
q Typowym zastosowaniem łączy komunikacyjnych jest
komunikowanie się dwóch różnych procesów w następujący
sposób. Najpierw proces tworzy łącze komunikacyjne, następnie
zaś wywołuje funkcje systemową fork, aby utworzyć swoją kopię
(patrz rys.4).
- 4 -
Jerzy Pejaś: Systemy operacyjne - Linux
Rys.5 Łącze komunikacyjne między dwoma procesami
q Następnie proces macierzysty zamyka np. koniec łącza służący do
czytania, a proces potomny zamyka koniec łącza służący do
pisania. Powstaje w ten sposób jednokierunkowy przepływ
informacji między dwoma procesami (rys.5).
q Gdy użytkownik wprowadzi na przykład z poziomu shell' a
następujące polecenie:
who | sort | lpr
Wówczas shell utworzy po kolei trzy procesy i dwa łącza
pomiędzy nimi. Utworzony w ten sposób tzw. potok (ang.
pipeline) przedstawiony jest na rys.6.
Rys.6 Łącza komunikacyjne między dwoma procesami tworzą potok
q Wszystkie omawiane łącza były jednokierunkowe, a więc
umożliwiały przepływ danych tylko w jedną stronę. Jeśli chcemy
uzyskać przepływ danych w obie strony, to musimy stworzyć dwa
łącza komunikacyjne skierowane przeciwnie. Trzeba w tym celu
wykonać następujące kroki:
" Utwórz łącze 1, utwórz łącze 2,
" Wywołaj funkcję systemową fork,
" Przodek zamyka łącze 1 do czytania,
" Przodek zamyka łącze 2 do pisania,
" Potomek zamyka łącze 1 do pisania,
- 5 -
Jerzy Pejaś: Systemy operacyjne - Linux
" Potomek zamyka łącze 2 do czytania.
q Schemat konstrukcji przedstawiono na rys.7. Od tego momentu
oba procesy posiadają pseudo pełno-duplexowe łącze
komunikacyjne.
Rys.7 Dwa łącza komunikacyjne umożliwiają dwukierunkowy przepływ
informacji
Tworzenie łączy komunikacyjnych w języku C
q Standardowo łącze komunikacyjne na poziomie języka C tworzy
się za pomocą funkcji systemowej pipe. Funkcja pobiera
pojedynczy parametr, będący wektorem dwóch liczb całkowitych,
i zwraca (jeśli wywołanie kończy się pomyślnie) w nich dwa
nowe deskryptory wykorzystywane przy konstrukcji potoku.
Wywołanie systemowe: pipe();
Prototyp: int pipe( int fd[2] );
RETURNS: 0 on success
-1 on error: errno = EMFILE (no free descriptors)
EMFILE (system file table is full)
EFAULT (fd array is not valid)
Uwaga: fd[0] jest deskryptorem czytania, fd[1] 
deskryptorem pisania
q Szkic programu wywołującego funkcje pipe może mieć postać:
#include
#include
#include
main()
- 6 -
Jerzy Pejaś: Systemy operacyjne - Linux
{
int fd[2];
pipe(fd);
.
.
}
q Ustanowiwszy łącze komunikacyjne możemy utworzyć nowy
proces:
#include
#include
#include
main()
{
int fd[2];
pid_t childpid;
pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
.
.
}
q Jeśli przodek chce otrzymywać dane od potomka powinien
zamknąć deskryptor fd[1], zaś potomek powinien zamknąć fd[0].
Jeśli z kolei przodek chce przesyłać dane do potomka, wtedy musi
zamknąć fd[0], zaś potomek powinien zamknąć fd[1]. Jest to
istotne z praktycznego punktu widzenia, ponieważ EOF nie
będzie nigdy zwrócony jeśli zbędne końce łącza nie zostaną
jawnie zamknięte.
/* "Linux Programmer s Guide - Chapter 6" */
#include
#include
#include
int main(void)
{
int fd[2], nbytes;
pid_t childpid;
char string[] = "Hello, world!\n";
char readbuffer[80];
pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
- 7 -
Jerzy Pejaś: Systemy operacyjne - Linux
}
if(childpid == 0)
{
/* Proces potomny zamyka wejściową stronę łącza */
close(fd[0]);
/* Wysyła "string" poprzez wejście do łącza */
write(fd[1], string, strlen(string));
exit(0);
}
else
{
/* Proces przodka zamyka wyjściową stronę łącza */
close(fd[1]);
/* Czyta  string z łącza */
nbytes = read(fd[0], readbuffer,
sizeof(readbuffer));
printf("Odebrano łańcuch: %s", readbuffer);
}
return 0;
}
q Często deskryptory potomka są duplikowane po to, aby
wskazywały na standardowe wejście lub wyjście:
Wywołanie systemowe: dup();
Prototyp: int dup( int olffd);
RETURNS: 0 on success
-1 on error: errno = EBADF (oldfd is not a valid descriptor)
EBADF (newfd is out of range)
EMFILE (too many descriptors for the
process)
Uwaga: stary deskryptor nie jest zamknięty; oba mogą być
używane zamiennie
q Zwykle po to duplikujemy deskryptory, aby zamknąć najpierw
standardowy strumień (wej/wyj). Dzieje się tak dzięki temu, że
wywołanie systemowe dup() przydzielając nowy deskryptor
wykorzystuje nieużywany (wolny) deskryptor o najniższym
numerze. Rozważmy następujący fragment:
childpid = fork();
if(childpid == 0)
{
/* Zamknij standardowe wejście procesu potomnego */
close(0);
/* Duplikuj wejściową stronę łącza i przydziel ją do
stdin */
dup(fd[0]);
execlp("sort", "sort", NULL);
.
- 8 -
Jerzy Pejaś: Systemy operacyjne - Linux
}
q Procedura execlp uruchamia nowy proces sort (standardowe
polecenie shell' a). Ponieważ nowo uruchomiony proces
dziedziczy standardowy strumień od swego stwórcy, stąd w
naszym przypadku odziedziczy wejście do łącza jako swoje
standardowe wejście. Od tego momentu każda informacja
wysyłana przez przodka do łącza będzie przekazywana do
procesu sortującego.
q Istnieje także inna odmiana procedury dup(), występująca pod
nazwą dup2().
Wywołanie systemowe: dup2();
Prototyp: int dup( int olffd, int newfd);
RETURNS: new descriptor on success
-1 on error: errno = EBADF (oldfd is not a valid descriptor)
EBADF (newfd is out of range)
EMFILE (too many descriptors for the
process)
Uwaga: stary deskryptor jest zamykany przez dup2()
q Funkcja dup2() jest niepodzielna, tzn. że proces duplikacji oraz
zamykania deskryptora nie może być przerwany przez
napływające sygnały. W przypadku użycia dup() zachodzi
konieczność użycia następnie close(). Pomiędzy tymi dwoma
wywołaniami może minąć trochę czasu i jeśli w tym czasie
nadejdzie sygnał proces duplikowania może zakończyć się
błędem (deskryptor 0 może zająć inny proces).
childpid = fork();
if(childpid == 0)
{
/* Zamknij stdin, duplikuj wejściową stronę łącza
i przydziel ją do stdin */
dup2(0, fd[0]);
execlp("sort", "sort", NULL);
.
}
Prostszy sposób tworzenia łączy komunikacyjnych w
języku C
q W standardowej bibliotece wejścia/wyjścia systemu Linux istnieje
funkcja, która tworzy łącze komunikacyjne oraz inicjuje
- 9 -
Jerzy Pejaś: Systemy operacyjne - Linux
wykonywanie drugiego procesu po to, aby albo czytał z łącza albo
do niego pisał. Ta funkcja tworzy pół-dupleksowy potok poprzez
wewnętrzne wywołanie funkcji pipe().
Funkcja biblioteczna: popen();
Prototyp: FILE *popen ( char *command,
char *type);
RETURNS: new file stream on success, NULL on
unsuccessful fork() or pipe() call
Uwaga: tworzy łacze oraz wykonuje fork/exec wykorzystując
 command
q Argument command oznacza wiersz polecenia. Funkcja ta
wywoływana jet przez shell, stąd używa się zmiennej
środowiskowej PATH do zlokalizowania polecenia.
q Kierunek przepływu danych określony jest przez parametr type.
Jeśli wartością tego argumentu jest r, to proces wywołujący
funkcję będzie czytać z wejścia standardowego dla polecenia
określonego przez argument command. Jeśli zaś wartością jest w,
to proces wywołujący funkcję będzie pisać do wyjścia
standardowego dla polecenia command.
q Łącze otwarte przy pomocy popen() musi być zamknięte przy
pomocy funkcji pclose().
Funkcja biblioteczna: pclose();
Prototyp: int pclose(FILE *stream);
RETURNS: exit status of wait4() call
-1 if "stream" is not valid, or if wait4()
fails
Uwaga: czeka aż proces potokowy zakończy się i nastepnie
zamyka strumień.
q Rozważmy przykład:
#include
#define MAXSTRS 5
int main(void)
{
int cntr;
FILE *pipe_fp;
char *strings[MAXSTRS] = { "echo",
"bravo", "alpha", "charlie", "delta"};
/* Utwórz jednokierunkowy potok, wywołując popen() */
- 10 -
Jerzy Pejaś: Systemy operacyjne - Linux
if (( pipe_fp = popen("sort", "w")) == NULL)
{
perror("popen");
exit(1);
}
/* Przetwarzaj w pętli */
for(cntr=0; cntr {
fputs(strings[cntr], pipe_fp);
fputc( \n , pipe_fp);
}
/* Zamknij łącze */
pclose(pipe_fp);
return(0);
}
q Ponieważ popen() korzysta z shella, stąd dostępne są wszystkie
wykorzystywane w nim znaki specjalne, przekierunkowywanie,
potoki, itp. Oznacza to, że możliwe są następujące wywołania
funkcji popen():
popen("ls scottb", "r");
popen("sort > /tmp/foo", "w");
popen("sort | uniq | more", "w");
q Kolejny przykład otwiera dwa łącza (jedno związane z poleceniem
ls, drugie z poleceniem sort), listuje zawartość katalogu bieżącego
i przekazuje na wejście sort:
#include
int main(void)
{
FILE *pipein_fp, *pipeout_fp;
char readbuf[80];
/* Utwórz jednokierunkowy potok, wywołując popen() */
if (( pipein_fp = popen("ls", "r")) == NULL)
{
perror("popen");
exit(1);
}
/* Utwórz jednokierunkowy potok, wywołując popen() */
if (( pipeout_fp = popen("sort", "w")) == NULL)
{
perror("popen");
exit(1);
}
/* Przetwarzaj w pętli */
while(fgets(readbuf, 80, pipein_fp))
fputs(readbuf, pipeout_fp);
/* Zamknij łącze */
- 11 -
Jerzy Pejaś: Systemy operacyjne - Linux
pclose(pipein_fp);
pclose(pipeout_fp);
return(0);
}
q Poniższy przykład szkicuje program, który otwiera potok
pomiędzy przekazywanym poleceniem, a plikiem:
/* Plik tPopen.c */
#include
int main(int argc, char *argv[])
{
FILE *pipe_fp, *infile;
char readbuf[80];
if( argc != 3)
{
fprintf(stderr, "USAGE: popen3 [command]
[filename]\n");
exit(1);
}
/* Otwórz plik wejściowy */
if (( infile = fopen(argv[2], "rt")) == NULL)
{
perror("fopen");
exit(1);
}
/* Utwórz jednokierunkowy potok, wywołując popen() */
if (( pipe_fp = popen(argv[1], "w")) == NULL)
{
perror("popen");
exit(1);
}
/* Przetwarzaj w pętli */
do
{
fgets(readbuf, 80, infile);
if(feof(infile)) break;
fputs(readbuf, pipe_fp);
} while(!feof(infile));
fclose(infile);
pclose(pipe_fp);
return(0);
}
q Załóżmy, że po skompilowaniu i zlinkowaniu uzyskaliśmy plik
wykonywalny o nazwie tPopen. Można go uruchamiać wówczas
np. tak:
tPopen sort tPopen.c
tPopen cat tPpopen.c
tPopen more tPopen.c
tPopen cat tPopen.c | grep main
- 12 -
Jerzy Pejaś: Systemy operacyjne - Linux
Ł ącza nazwane (FIFO)
q Łącza nazwane pracują podobnie jak zwykłe łącza, ale można
zauważyć kilka istotnych różnic:
" Łącze nazwane istnieje w systemie plikowym jako specjalny
plik urządzenia,
" Procesy o różnym poziomie pokrewieństwa mogą wymieniać
dane poprzez łącze,
" Jeśli wszystkie operacje I/O realizowane są przez
współpracujące procesy, wtedy łącze nazwane pozostaje w
systemie plikowym i może być wykorzystywane pózniej.
q Istnieje kilka sposobów utworzenia łącza nazwanego. Dwa
pierwsze mogą być zrealizowane na poziomie poleceń shell'a.
mknod MYFIFO p
mkfifo a=rw MYFIFO
q Powyższe polecenia działają podobnie, z jedną różnicą: mkfifo
umożliwia natychmiastowe określenie upoważnień do łącza (np.,
a = rw), w przypadku mknod należy uczynić to przy pomocy
polecenia chmod.
q Aby sprawdzić, że łącze nazwane jest rzeczywiście plikiem
posłużmy się poleceniem ls:
$ ls  l MYFIFO
prw-rw-rw- 1 root root 0 Dec 07 10:20 MYFIFO|
q Aby utworzyć łącze FIFO z poziomu języka C należy skorzystać z
funkcji mknod():
Funkcja biblioteczna: popen();
Prototyp: int mknod( char *pathname,
mode_t mode, dev_t dev);
RETURNS: 0 on success,
-1 on error: errno = EFAULT (pathname invalid)
EACCES (permission denied)
ENAMETOOLONG (pathname too long)
ENOENT (invalid pathname)
ENOTDIR (invalid pathname)
(see man page for mknod for others)
Uwaga: tworzy węzeł systemu plikowego (file, device file,
or FIFO)
- 13 -
Jerzy Pejaś: Systemy operacyjne - Linux
q Argument pathname oanacza normalna nazwę ścieżki i będzie
nazwą kolejki, mode ą określa tryb dostępu do pliku, które będzie
zsumowane logicznie ze znacznikiem S_IFIFO z pliku
wskazującym, że ma być utworzona kolejka FIFO.
W przypadku tworzenia łącza FIFO pomija się argument dev
(urządzenie).
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
q Należy pamiętać, że wynikowe prawa dostępu do łącza nazwanego
są wynikiem logicznego złożenia żądanych praw dostępu oraz
maski wynikającej z praw procesu, który wywołuje operację
mknod():
final_umask = requested_permissions & original_umask
q Powszechnie stosowany trick:
umask(0);
mknod("/tmp/MYFIFO", S_IFIFO|0666, 0);
q Funkcja umask() ustawia tzw. maskę trybu dostępu do pliku.
q Gdy powstanie już kolejka FIFO, wówczas trzeba ja otworzyć do
czytania lub pisania przy użyciu funkcji systemowej open(), lub
jednej z funkcji standardowego wejścia-wyjścia ą fopen() lub
freopen(). Dalej przy obsłudze łącza FIFO będziemy otwierać je
przy pomocy funkcji fopen(), a zamykać przy pomocy fclose().
Zauważmy, że open i close są odwołaniami systemowymi
(funkcjami systemowymi), podczas gdy fopen i fclose ą
funkcjami bibliotecznymi.
q Rozważmy proces, który jest serwerem:
/* fifoserver.c */
#include
#include
#include
#include
#include
#define FIFO_FILE "MYFIFO"
int main(void)
{
FILE *fp;
char readbuf[80];
/* Utwórz FIFO jeśli nie istnieje */
umask(0);
- 14 -
Jerzy Pejaś: Systemy operacyjne - Linux
mknod(FIFO_FILE, S_IFIFO|0666, 0);
while(1)
{
fp = fopen(FIFO_FILE, "r");
fgets(readbuf, 80, fp);
printf("Otrzymany łańcuch: %s\n", readbuf);
fclose(fp);
}
return(0);
}
q Ponieważ łącza FIFO są zawsze domyślnie blokowane (patrz
dalej), stąd po skompilowaniu możemy napisać polecenie:
$ fifoserver&
q Dla naszego prostego serwera można teraz dopisać klienta:
/* fifoclient.c */
#include
#include
#define FIFO_FILE "MYFIFO"
int main(int argc, char *argv[])
{
FILE *fp;
if ( argc != 2 )
{
printf("Stosuj: fifoclient [string]\n");
exit(1);
}
if((fp = fopen(FIFO_FILE, "w")) == NULL)
{
perror("fopen");
exit(1);
}
fputs(argv[1], fp);
fclose(fp);
return(0);
}
Ł ącza nazwane (FIFO) ą blokowanie działań
q Blokowanie jest standardowym stanem łącza FIFO. Oznacza to, że
jeśli FIFO jest otwarte do czytania, to proces który to zrobił jest
blokowany dotąd dopóki jakiś inny proces nie otworzy tego łącza
do pisania (i vice-versa). Jeśl tego typu zachowanie jest zbędne,
wtedy w funkcji open lub fopen należy użyć flagi
O_NONBLOCK.
- 15 -
Jerzy Pejaś: Systemy operacyjne - Linux
q Czytanie danych złączy komunikacyjnych i kolejek FIFO oraz
zapisywanie w nich danych przebiega wg następujących reguł:
" Jeśli proces żąda przeczytania mniejszej porcji danych niż
wynosi bieżąca zawartość łącza komunikacyjnego lub kolejki
FIFO, to będzie pobranych dokładnie tyle danych ile
zażądano.
" Jeśli proces żąda przeczytania większej porcji danych niż
wynosi bieżąca zawartość łącza komunikacyjnego lub kolejki
FIFO, to funkcja read przekaże tylko tyle danych ile ich
znajduje się w łączu.
" Jeśli w łączu komunikacyjnym lub kolejce FIFO nie ma
danych, a żaden proces nie otworzył ich do pisania, to
wartością funkcji systemowej read będzie zero, oznaczające
koniec pliku. Jeśli proces, który wywołuje funkcję read
ustawił flagę O_NONBLOCK, to nie wiadomo, czy
przekazane przez funkcję zero oznacza, że nie ma danych, czy
też, że nie ma żadnego procesu, który mógłby je pisać.
" Jeśli proces zapisze mniejszą porcję danych niż wynosi
pojemność łącza komunikacyjnego lub kolejki FIFO, to jest
zagwarantowane, że pisanie jest operacją niepodzielną. Jeśli
porcja danych jest z kolei większa, wówczas nie można
zagwarantować, że operacja będzie niepodzielna.
" Jeśli proces wywołuje funkcję write, aby zapisać dane do
łącza komunikacyjnego lub kolejki FIFO, ale istnieje proces,
który otworzył je do czytania, to do procesu będzie wysłany
sygnał SIGPIPE, a funkcja write przekaże w wyniku zero oraz
w zmiennej globalnej errno umieści wartość stałej EPIPE.
Komunikacja między procesami w Systemie V
q Przypomnijmy, że w Systemie V występują trzy rodzaje
komunikacji między procesowej:
" Kolejki komunikatów (ang. SYS V style message queues),
" Zbiory semaforów (ang. SYS V style semaphore sets),
- 16 -
Jerzy Pejaś: Systemy operacyjne - Linux
" Pamięć współdzielona (ang. SYS V shared memory segments),
q Każdy obiekt IPC ma przypisany unikalny identyfikator IPC (IP
ID). Unikalność ta zagwarantowana jest w ramach jądra.
Umożliwia to proste odwoływanie się do IPC obiektów, np.
pamięci współdzielonej.
q W celu utworzenia unikalnego ID należy użyć klucza. Klucz musi
być wzajemnie uzgodniony zarówno przez klienta, jak i serwer.
q Klucz może być przez cały czas taki sam, np. wbudowany na stałe
w aplikację. Nie jest to zbyt korzystne, ponieważ istnieje zawsze
możliwość, że jest on już w użyciu. Wygodniej jest posłużyć się
funkcją ftok(), która generuje wartość klucza dla serwera i klienta:
Funkcja biblioteczna: ftok();
Prototyp: key_t ftok(char *pathname, char proj );
RETURNS: new IPC key value if successful,
-1 if unsuccessful, errno set to return of stat() call
q Zwracana wartość klucza jest kombinacją numeru i-węzła i
numeru urządzenia, związanych z plikiem podanym jako
argument pathname oraz parametru projektowego (np. nazwy
aplikacji). Rozwiązanie takie nie gwarantuje unikalności, ale
aplikacja może wykryć kolizje i operacje powtórzyć.
q Przykłady generowania klucza:
key_t mykey;
mykey = ftok("/tmp/myapp",  a );
lub
key_t mykey;
mykey = ftok(".",  a );
Polecenie ipcs
q Polecenie ipcs umożliwia określenie statusu wszystkich obiektów
IPC (System V). Jego składnia ma postać:
ipcs -q: Show only message queues
ipcs -s: Show only semaphores
ipcs -m: Show only shared memory
ipcs --help: Additional arguments
- 17 -
Jerzy Pejaś: Systemy operacyjne - Linux
q Domyślnie pokazywane są wszystkie trzy kategorie obiektów.
Popatrzmy na następujący wynika działania polecenia ipcs:
------ Shared Memory Segments --------
shmid owner perms bytes nattch status
------ Semaphore Arrays --------
semid owner perms nsems status
------ Message Queues --------
msqid owner perms used-bytes messages
0 root 660 5 1
q Widać, że istnieje jedna kolejka komunikatów o identyfikatorze 0.
Jej właścicielem jest root i posiada prawa dostępu 660 lub ąrw-
rw---. W kolejce znajduje się jedna wiadomość o długości 5
bajtów.
Polecenie ipcrm
q Polecenie ipcrm umożliwia usunięcie obiektu IPC (System V) z
jądra. Chociaż IPC obiekty można usuwać przy pomocy funkcji
systemowych, to często wygodnie jest usunąć je także z poziomu
poleceń shella (ręcznie). Polecenie ma postać:
ipcrm
q W poleceniu należy określić typ usuwanego obiektu: msg ą kolejka
komunikatów, sem ą semafor lub shm ą segment pamięci
współdzielonej oraz IPC ID (można go otrzymać przy pomocy
polecenia ipcs). UWAGA! NALEŻY ZAWSZE PODAWAĆ
TYP OBIEKTU, PONIEWAŻ JDRO GWARANTUJE
UNIKALNOŚĆ IPC ID TYLKO W RAMACH DANEGO TYPU
OBIEKTU.
Kolejki komunikatów
q Kolejka komunikatów jest wewnętrznie powiązaną listą zarządzana
przez jądro. Wiadomość można przesłać do kolejki, jak również
można ją stamtąd pobrać.
q Aby dobrze rozumieć kolejki komunikatów należy dokładnie
zapoznać się z wewnętrznymi strukturami stosowanymi do ich
opisu.
- 18 -
Jerzy Pejaś: Systemy operacyjne - Linux
q Bufor wiadomości. Struktura ta o nazwie msgbuf jest wzorcem
danych wiadomości i określona jest w pliku linux/msg.h:
struct msgbuf
{
long mtype; /* typ wiadmości */
char mtext[1]; /* tekst wiadomości */
};
q gdzie:
" type ą typ komunikatu (musi być liczba dodatnią),
" mtext - wiadomość
q Uwaga. Wzorzec msgbuf można redefiniować, np. w taki sposób:
struct my_msgbuf
{
long mtype; /* typ wiadmości */
long request_id; /* identyfikator żądania */
struct client_info; /* tekst wiadomości */
};
q Struktura (jądra) msg. Jądro przechowuje każdą wiadomość w
kolejce wewnątrz struktury msg (patrz plik linux/msg.h):
/* jedna struktura msg na każda wiadomość */
struct msg
{
struct msg *msg_next; /* następna wiadomość w kolejce */
long msg_type;
char *msg_spot; /* adres tekstu wiadomości */
short msg_ts; /* rozmiar tekstu wiadomości */
};
q gdzie:
" msg next ą wskaznik na następną wiadomość w kolejce
jednokierunkowej w obszarze adresowym jądra,
" msg_type ą typ wiadomości, określony w strukturze
użytkownika msgbuf,
" msg_spot ą wskaznik na początek głównej części wiadomości,
" msg_ts ą długość tekstu lub głównej części wiadomości.
q Struktura jądra msqid_ds. Każdy z trzech typów IPC obiektów
posiada wewnętrzna strukturę danych zarządzaną przez jądro. W
- 19 -
Jerzy Pejaś: Systemy operacyjne - Linux
przypadku kolejki wiadomości nazywa się ona msqid_ds (patrz
linux/msg.h):
/* jedna struktura msqid na każdą wiadomość w systemie*/
struct msqid_ds
{
struct ipc_perm msg_perm;
struct msg *msg_first; /*pierwsza wiadom. w kolejce*/
struct msg *msg_last; /* ostatnia wiadomość */
time_t msg_stime; /* czas ostat. wysłania msgsnd */
time_t msg_rtime; /* czas ostatniego otrzym. msgrcv */
time_t msg_ctime; /* czas ostatniej zmiany */
struct wait_queue *wwait;
struct wait_queue *rwait;
ushort msg_cbytes;
ushort msg_qnum;
ushort msg_qbytes; /* max liczba bajtów w kolejce */
ushort msg_lspid; /* pid ostatniej msgsnd */
ushort msg_lrpid; /* pid ostatnio odebranej msg */
};
q gdzie:
" msg perm ą egzemplarz struktury ipc_perm, zdefiniowanej w
linux/ipc.h; przechowuje informację o uprawnieniach do
kolejki, włączając prawa dostępu oraz twórcy kolejki (uid,
etc.),
" msg_first ą wskaznik na pierwszą wiadomość w kolejce
(głowa listy),
" msg_last - wskaznik na ostatnią wiadomość w kolejce (ogon
listy),
" msg_stime ą czas ostatnio umieszczonej wiadomości w
kolejce,
" msg_rtime ą czas ostatnio pobranej wiadomości z kolejki,
" wwait i rwait ą wskazniki do systemowej kolejki wait
(oczekiwania); są one wykorzystywane wtedy, gdy operacja
wykonywana na kolejce wiadomości uważa, że proces
powinien przejść w statn uśpienia (tj. kolejka jest pełna i
proces oczekuje na otwarcie),
" msg_cbytes ą całkowita liczba bajtów w kolejce,
" msg_qnum ą liczba wiadomości w kolejce,
- 20 -
Jerzy Pejaś: Systemy operacyjne - Linux
" msg_qbytws ą maksymalna liczba bajtów w kolejce,
" msg_lspid - PID procesu, który ostatnio przesłał wiadomość
do kolejki,
" msg_lrpid - PID procesu, który ostatnio odebrał wiadomość z
kolejki
q Struktura jądra ipc_perm. W tej strukturze jądro przechowuje
informacje o uprawnieniach obiektów IPC (patrz linux/msg.h):
struct ipc_perm
{
key_t key; /* klucz */
ushort uid; /* euid oraz egid właściciela */
ushort gid;
ushort cuid; /* euid oraz egid twórcy */
ushort cgid;
ushort mode; /* tryby dostępu */
ushort seq; /* numer kolejny */
};
q Wywołanie systemowe msgget(). Funkcji msgget() używa się albo
do utworzenia nowej kolejki albo dostępu do kolejki istniejącej.
Wywołąnie systemowe: msgget();
Prototyp: int msgget ( key_t key, int msgflg );
RETURNS: message queue identifier on success,
-1 on error: errno = EACCESS (permission denied)
EEXIST (Queue exists, cannot create)
EIDRM (Queue is marked for deletion)
ENOENT (Queue does not exist)
ENOMEM (Not enough memory to create queue)
ENOSPC (Maximum queue limit exceeded)
q Pierwszy argument msgget() jest wartością klucza (zwracaną przez
ftok()). Wartość tego klucza jest następnie porównywana z
istniejącymi wartościami klucza, które znajdują się w wewnątrz
jądra dla innych kolejek wiadomości. Wynik operacji otwarcia lub
dostępu do kolejki zależy od zawartości argumentu msgflag,
którego 9 najmniej znaczących bitów określa tryb dostępu do
kanału komunikacji między procesowej (w tym przypadku do
kolejki komunikatów):
" IPC_CREAT ą tworzy kolejkę, jeśli nie istnieje w jądrze; jeśli
kolejka istnieje zwróci jej identyfikator
- 21 -
Jerzy Pejaś: Systemy operacyjne - Linux
" IPC_EXCL ą jeśli użyta razem z IPC_CREAT zwraca błąd w
przypadku, gdy kolejka już istnieje (użycie samego
IPC_EXCL nie wywołuje żadnych działań); jeśli kolejka nie
istnieje ą zostanie utworzona.
q Zawsze wtedy, gdy tworzony jest nowy kanał komunikacyjny,
wywołując funkcję systemową msgget (także semget lub shget -
patrz dalej) z ustawionym znacznikiem IPC_CREAT, wtedy 9
najmniej znaczących bitów argumentu msgflag inicjuje słowo
trybu dostępu, czyli pole mode w strukturze ipc_perm. Ponadto w
polach cuid i cgid tej są umieszczane odpowiednio obowiązujące
identyfikatory użytkownika i grupy dla procesu wywołującego
funkcję. Podobnie jest w przypadku w pól uid i gid struktury
ipc_perm.
q Te dwa ostatnie identyfikatory nazywane są identyfikatorami
właściciela kanału, zaś dwa pierwsze ą identyfikatorami twórcy
kanału. Identyfikatory twórcy nigdy nie zmieniają się, podczas
gdy proces może zmienić ID właściciela, wywołując funkcje
systemową (w przypadku kolejek wiadomości jest to funkcja
msgctl()).
q Przykład funkcji otwierającej lub tworzącej kolejkę wiadomości
(zwróć uwagę na jawnie określone prawa dostępu 0660:
int open_queue(key_t keyval)
{
int qid;
if((qid = msgget( keyval, IPC_CREAT | 0660 )) == -1)
{
return(-1);
}
return(qid);
}
q Wywołanie systemowe msgsnd(). Funkcji msgsnd() używa się do
umieszczenia wiadomości w kolejce
Wywołanie systemowe: msgsnd();
Prototyp: int msgsnd ( int msqid,
struct msgbuf *msgp, int msgsz, int msgflg );
RETURNS: 0 on success
-1 on error: errno =
- 22 -
Jerzy Pejaś: Systemy operacyjne - Linux
EAGAIN (queue is full, and IPC_NOWAIT was asserted)
EACCES (permission denied, no write permission)
EFAULT (msgp address isn t accessable - invalid)
EIDRM (The message queue has been removed)
EINTR (Received a signal while waiting to write)
EINVAL (Invalid message queue identifier, nonpositive
message type, or invalid message size)
ENOMEM (Not enough memory to copy message buffer)
q Argument msqid) jest identyfikatorem kolejki, zwrócony przez
msgget(), msgp ą wskaznik do redeklarowanego i załadowanego
bufora wiadomości, msgsz ą określa rozmiar wiadomości w
bajtach, z pominięciem długości typu wiadomości (4 bajty).
Argument msgflag ustawiany jest na 0 (ignorowany) lub:
" IPC_NOWAIT ą jeśli kolejka jest pełna, wtedy wiadomość nie
jest zapisywana do kolejki i sterowanie zwracane jest
procesowi wołającemu; jeśli parametr ten nie występuje,
wtedy proces wołający zawisa (jest blokowany) dotąd, aż
będzie mógł pisać.
q Przykład funkcji wysyłającej wiadomość:
int send_message( int qid, struct mymsgbuf *qbuf )
{
int result, length;
/*Długość jest rozmiarem struktury minus sizeof(mtype)
length = sizeof(struct mymsgbuf) - sizeof(long);
if((result = msgsnd( qid, qbuf, length, 0)) == -1)
{
return(-1);
}
return(result);
}
q Przykład użycia funkcji obsługi kolejki wiadomości:
#include
#include
#include
#include
main()
{
int qid;
key_t msgkey;
struct mymsgbuf
{
long mtype; /* typ wiadomości */
int request; /* roboczy numer żądania */
double salary; /* pensja pracownika */
- 23 -
Jerzy Pejaś: Systemy operacyjne - Linux
} msg;
/* Utwórz wartość klucza IPC */
msgkey = ftok(".",  m );
/* Open/create kolejkę */
if(( qid = open_queue( msgkey)) == -1)
{
perror("open_queue");
exit(1);
}
/* Wypełnij wiadomość dowolnymi danymi testowymi */
msg.mtype = 1; /* wiadomość musi mieć dodatni typ! */
msg.request = 1; /* #1 element danych */
msg.salary = 1000.00; /* #2 element danych */
/* Wyślij! */
if((send_message( qid, &msg )) == -1)
{
perror("send_message");
exit(1);
}
}
q Po skompilowaniu programu uruchom go, a następnie użyj
polecenie ipcs, aby obejrzeć status naszej kolejki.
q Wywołanie systemowe msgrcv(). Funkcji msgrcv() używa się do
odbioru wiadomości z kolejki.
Wywołanie systemowe: msgrcv();
Prototyp: int msgrcv ( int msqid, struct msgbuf
*msgp, int msgsz, long mtype, int mflag);
RETURNS: Number of bytes copied into message buffer
-1 on error: errno =
E2BIG (Message length is greater than msgsz)
EACCES (No read permission)
EFAULT (Address pointed to by msgp is invalid)
EIDRM (Queue was removed during retrieval)
EINTR (Interrupted by arriving signal)
EINVAL (msgqid invalid, or msgsz less than 0)
ENOMSG (IPC_NOWAIT asserted, and no message exists
in the queue to satisfy the request)
q Argument msqid jest identyfikatorem kolejki, zwrócony przez
msgget(), msgp ą wskaznik do bufora, w którym będzie
przechowywana pobrana wiadomość, msgsz ą określa rozmiar
bufora wiadomości w bajtach, z pominięciem długości typu
wiadomości (4 bajty), mtype ą określa typ pobieranej z kolejki
wiadomości (jądro poszukuje najstarszej wiadomości o
- 24 -
Jerzy Pejaś: Systemy operacyjne - Linux
wskazanym typie; jeśli przekazana wartość mtype jest równa 0,
wówczas z kolejki pobierana jest najstarsza wiadomość
niezależnie od jej typu).
q Jeśli jako ostatni argument mflag przekazywana jest wartość
IPC_NOWAIT i wiadomość jest niedostępna, wtedy zwracany jest
błąd ENOMSG. W przeciwnym przypadku proces jest blokowany,
aż do momentu otrzymania wiadomości. Jeśli podczas
oczekiwania na wiadomość kolejka zostanie usunięta, wówczas
zwracany jest błąd EIDRM.
q Przykład funkcji pobierającej wiadomość:
int read_message( int qid, long type,
struct mymsgbuf *qbuf )
{
int result, length;
/*Długość jest rozmiarem struktury minus sizeof(mtype)
length = sizeof(struct mymsgbuf) - sizeof(long);
if((result = msgrcv(qid, qbuf, length, type, 0))== -1)
{
return(-1);
}
return(result);
}
q Po pobraniu wiadomości z kolejki jest ona z niej fizycznie
usuwana.
q Ustawienie bitu MSG NOERROR w argumencie mtype oznacza, że
jeśli porcja danych otrzymanych w komunikacie jest większa niż
msgsz, to nadmiar jest pomijany; jeśli bit ten nie jest ustawiony,
wówczas sygnalizowany jest błąd E2BIG. Ta cech umożliwia na
napisanie funkcji, która powoli nam na zaglądanie do środka
kolejki i sprawdzić, czy nadeszła już wiadomość, która nas
interesuje:
int peek_message( int qid, long type )
{
int result, length;
if((result = msgrcv(qid,NULL,0,type,IPC_NOWAIT))== -1)
{
if(errno == E2BIG)
return(TRUE);
}
return(FALSE);
}
- 25 -
Jerzy Pejaś: Systemy operacyjne - Linux
q Wywołanie systemowe msgctl(). Funkcji msgctl() umożliwia
sterowanie operacjami wykonywanymi na kolejce wiadomości.
Wywołanie systemowe: msgctl();
Prototyp: int msgctl (int msgqid, int cmd,
struct msqid_ds *buf);
RETURNS: 0 on success
-1 on error: errno =
EACCES (No read permission and cmd is IPC_STAT)
EFAULT (Address pointed to by buf is invalid
IPC_STAT commands)
EIDRM (Queue was removed during retrieval)
EINVAL (msgqid invalid, or msgsz less than 0)
EPERM (IPC_SET or IPC_RMID command was issued,
calling process does not have write (alter)
access to the queue)
q Poza parametrem cmd pozostałe są takie samie, jak już omawiane.
q Argument cmd umożliwia wykonywanie poleceń na kolejce
wiadomości:
" IPC_STAT ą pobiera strukturę msqid_ds z kolejki i umieszcza
ja pod adresem określonym przez buf.
" IPC_SET ą ustawia wartość ipc_perm w strukturze msqid_ds;
wartość tą pobiera z buf.
" IPC_RMID ą usuwa kolejkę z jądra.
q Używając IPC_STAT można odczytać egzemplarz struktury
msqid_ds (taki egzemplarz istnieje dla każdej kolejki, która
istnieje w systemie). Przykład:
int get_queue_ds( int qid, struct msgqid_ds *qbuf )
{
if( msgctl( qid, IPC_STAT, qbuf) == -1)
{
return(-1);
}
return(0);
}
q Mając kopię wewnętrznej struktury msqid_ds., możemy
zmodyfikować uprawnienia do niej:
int change_queue_mode( int qid, char *mode )
{
struct msqid_ds tmpbuf;
/* uzyskaj kopię wewnętrznej struktury danych */
- 26 -
Jerzy Pejaś: Systemy operacyjne - Linux
get_queue_ds( qid, &tmpbuf);
/* zmień uprawnienia */
sscanf(mode, "%ho", &tmpbuf.msg_perm.mode);
/* uaktualnij structure wewnętrzną */
if( msgctl(qid, IPC_SET, &tmpbuf) == -1)
{
return(-1);
}
return(0);
}
q Po każdym pomyślnym pobraniu wiadomości z kolejki,
wiadomość jest z niej usuwana. Obiekty IPC pozostają jednak w
systemie (także kolejki wiadomości) dopóki nie zostaną jawnie
usunięte lub system zostanie zrestartowany. Kolejkę wiadomości
jawnie usuwa się korzystając z flagi IPC_RMID:
int remove_queue( int qid )
{
if( msgctl( qid, IPC_RMID, 0) == -1)
return(-1);
return(0);
}
Przykład: interaktywny manipulator kolejką wiadomości
q Przedstawiony poniżej kod interpretera pozwala na interakcyjne
operacje na kolejkach wiadomości z poziomu shella. Zakładamy,
że po kompilacji program ma nazwę msgtool.
q Składnia poleceń:
Nadawanie wiadomości: msgtool s (type) "text"
Pobieranie wiadomości: msgtool r (type)
Zmiana uprawnień: msgtool m (mode)
Usuwanie kolejki: msgtool d
Przykłady wywołań:
msgtool s 1 test
msgtool s 5 test
msgtool s 1 "This is a test"
msgtool r 1
msgtool d
msgtool m 660
q Kod programu:
#include
#include
#include
#include
- 27 -
Jerzy Pejaś: Systemy operacyjne - Linux
#include
#include
#define MAX_SEND_SIZE 80
struct mymsgbuf {
long mtype;
char mtext[MAX_SEND_SIZE];
};
void send_message(int qid,
struct mymsgbuf *qbuf, long type, char *text);
void read_message(int qid,
struct mymsgbuf *qbuf, long type);
void remove_queue(int qid);
void change_queue_mode(int qid, char *mode);
void usage(void);
int main(int argc, char *argv[])
{
key_t key;
int msgqueue_id;
struct mymsgbuf qbuf;
if(argc == 1)
usage();
/* Utwórz unikalny klucz, wołając ftok() */
key = ftok(".", 'm');
/* Otwórz/utwórz kolejkę */
if((msgqueue_id = msgget(key, IPC_CREAT|0660)) == -1)
{
perror("msgget");
exit(1);
}
switch(tolower(argv[1][0]))
{
case 's':
send_message(msgqueue_id,
(struct mymsgbuf *)&qbuf, atol(argv[2]), argv[3]);
break;
case 'r':
read_message(msgqueue_id, &qbuf, atol(argv[2]));
break;
case 'd': remove_queue(msgqueue_id);
break;
case 'm': change_queue_mode(msgqueue_id, argv[2]);
break;
default: usage();
}
return(0);
}
void send_message(int qid, struct mymsgbuf *qbuf,
long type, char *text)
{
- 28 -
Jerzy Pejaś: Systemy operacyjne - Linux
/* Wyślij wiadmość do kolejki */
printf("Wysyłanie wiadomości ...\n");
qbuf->mtype = type;
strcpy(qbuf->mtext, text);
if((msgsnd(qid, (struct msgbuf *)qbuf,
strlen(qbuf->mtext)+1, 0)) ==-1)
perror("msgsnd"); exit(1);
}
void read_message(int qid, struct mymsgbuf *qbuf,
long type)
{
/* Czytaj wiadomość z kolejki */
printf("Czytanie wiadomości ...\n");
qbuf->mtype = type;
msgrcv(qid,(struct msgbuf*)qbuf,MAX_SEND_SIZE,type,0);
printf("Typ: %ld Tekst: %s\n",qbuf->mtype, qbuf->mtext);
}
void remove_queue(int qid)
{
/* Usuń kolejke */
msgctl(qid, IPC_RMID, 0);
}
void change_queue_mode(int qid, char *mode)
{
struct msqid_ds myqueue_ds;
/* Pobierz aktualna informację */
msgctl(qid, IPC_STAT, &myqueue_ds);
/* Zmień i załaduj uprawnienia */
sscanf(mode, "%ho", &myqueue_ds.msg_perm.mode);
/* Uaktualnij uprawnienia */
msgctl(qid, IPC_SET, &myqueue_ds);
}
void usage(void)
{
fprintf(stderr, "msgtool  Narzędzie do  majstrownia
przy kolejkach wiadomości\n");
fprintf(stderr, "\nSkładnia: msgtool (s)end
\n");
fprintf(stderr, " (r)ecv \n");
(d)elete\n");
fprintf(stderr, "
frintf(stderr, " (m)ode \n");
exit(1);
}
Semafory
q Semafor będziemy rozważać jako zmienną całkowitą, będącą
licznikiem zasobów. Wartość zmiennej w dowolnej chwili określa
liczbę dostępnych egzemplarzy zasobów.
- 29 -
Jerzy Pejaś: Systemy operacyjne - Linux
q Ponieważ używamy semaforów do synchronizowania różnych
procesów, dlatego bieżąca wartość semafora musi być pamiętana
w jądrze systemu (patrz rys.7).
Rys.7 Wartość semafora pamiętana w jądrze systemu
q Przyjrzyjmy się pokrótce wewnętrznym strukturom stosowanymi
do opisu operacji na semaforach i zarządzanych przez jądro.
q Struktura (jądra) semid_ds. Podobnie jak w przypadku kolejki
wiadomości, jądro zarządza specjalna wewnętrzną strukturą
oddzielna dla każdego zbioru semaforów (patrz plik linux/msg.h):
/* jedna struktura semid na każdy zbiór semaforów
w systemie*/
struct semid_ds
{
struct ipc_perm sem_perm; /* uprawnienia */
time_t sem_otime; /* czas ostatniej operacji na sem */
time_t sem_ctime; /* czas ostatniej zmiany */
struct sem *sem_base; /* wskażnik na pierwszy semafor
w tablicy */
struct wait_queue *eventn;
struct wait_queue *eventz;
struct sem_undo *undo; /* żądanie anulowania do tej
tej tablicy */
ushort sem_nsems; /* liczba semaforów w tablicy */
};
q przy czym znaczenie niektórych pól jest nastepujące:
" sem_perm ą egzemplarz struktury ipc_perm, zdefiniowanej w
linux/ipc.h; przechowuje informację o uprawnieniach do zbioru
- 30 -
Jerzy Pejaś: Systemy operacyjne - Linux
semaforów, włączając prawa dostępu oraz twórcy zbioru
semaforów (uid, etc.),
" sem_otime ą czas ostatniej operacji semop() (patrz dalej),
" sem_ctime ą czas ostatniej zmiany w tej strukturze,
" sem_base ą wskaznik do pierwszego semafora w tablicy (patrz
następna struktura),
" sem_undo ą liczba żądań undo w tej tablicy (patrz dalej),
" sem_nsems ą liczba semaforów w zbiorze semaforów (w tablicy).
q Struktura (jądra) sem. W strukturze semid_ds.znajduje się
odwołanie (wskaznik) do tablicy semaforów. Każdy semafor
opisany jest z kolei przy pomocy struktury sem (patrz plik
linux/msg.h):
/* jedna struktura na każdy semafor w systemie*/
struct sem
{
short sempid; /* pid ostatniej operacji */
ushort semval; /* bieżąca wartość */
ushort semncnt; /* liczba procesów oczekujących na
zwiekszenie semval
ushort semzcnt; /* liczba procesów oczekujących
wartości semval = 0 */
};
q gdzie:
" sem_pid ą PID procesu, który ostatnio dokonał operacji na
semaforze,
" sem_semval ą aktualna wartość semafora,
" sem_semncnt ą liczba procesów oczekujących na zwolnienie
zasobu,
" sem_semzcnt ą liczba procesów oczekujących na 100%
wykorzystanie zasobu.
q Wywołanie systemowe semget(). Funkcji semget() tworzy nowy
zbiór semaforów lub udostępnia zbiór już istniejący.
Wywołanie systemowe: semget();
Prototyp: int semget ( key_t key, int nsems, int
- 31 -
Jerzy Pejaś: Systemy operacyjne - Linux
semflg );
RETURNS: semaphore set IPC identifier on success
-1 on error: errno =
EACCESS (permission denied)
EEXIST (set exists, cannot create (IPC_EXCL))
EIDRM (set is marked for deletion)
ENOENT (set does not exist, no IPC_CREAT was
ENOMEM (Not enough memory to create new set)
ENOSPC (Maximum set limit exceeded)
q Pierwszy argument msgget() jest wartością klucza (zwracaną przez
ftok()). Wartość tego klucza jest następnie porównywana z
istniejącymi wartościami klucza, które znajdują się w wewnątrz
jądra dla innych zbiorów semaforów. Wynik operacji otwarcia lub
dostępu do kolejki zależy od zawartości argumentu semflag,
którego 9 najmniej znaczących bitów określa tryb dostępu do
kanału komunikacji między procesowej (w tym przypadku do
zbioru semaforów):
" IPC_CREAT ą tworzy zbiór semaforów, jeśli nie istnieje w jądrze;
jeśli zbiór istnieje, to zwróci jej identyfikator
" IPC_EXCL ą jeśli użyta razem z IPC_CREAT zwraca błąd w
przypadku, gdy kolejka już istnieje (użycie samego IPC_EXCL nie
wywołuje żadnych działań); jeśli kolejka nie istnieje ą zostanie
utworzona.
q Zawsze wtedy, gdy tworzony jest nowy zbiór semaforów,
wywołując funkcję systemową semget (także msgget lub shget) z
ustawionym znacznikiem IPC_CREAT, wtedy 9 najmniej
znaczących bitów argumentu semflag inicjuje słowo trybu
dostępu, czyli pole mode w strukturze ipc_perm. Ponadto w
polach cuid i cgid tej są umieszczane odpowiednio obowiązujące
identyfikatory użytkownika i grupy dla procesu wywołującego
funkcję. Podobnie jest w przypadku w pól uid i gid struktury
ipc_perm.
q Przykład funkcji otwierającej lub tworzącej zbiór semaforów
(zwróć uwagę na jawnie określone prawa dostępu 0660:
int open_semaphore_set( key_t keyval, int numsems )
{
int sid;
- 32 -
Jerzy Pejaś: Systemy operacyjne - Linux
if ( ! numsems )
return(-1);
if((sid = semget(mykey,numsems,IPC_CREAT|0660))==-1)
{
return(-1);
}
return(sid);
}
q Wywołanie systemowe semoop(). Funkcji semop() umożliwia
wykonywanie operacji na zbiorze semaforów:
Wywołanie systemowe: semop();
Prototyp: int semop ( int semid, struct sembuf
*sops, unsigned nsops);
RETURNS: 0 on success (all operations performed)
-1 on error: errno =
E2BIG (nsops greater than max number of ops allowed
EACCESS (permission denied)
EAGAIN (IPC_NOWAIT asserted, operation could not
EFAULT (invalid address pointed to by sops argument)
EIDRM (semaphore set was removed)
EINTR (Signal received while sleeping)
EINVAL (set doesn t exist, or semid is invalid)
ENOMEM (SEM_UNDO asserted, not enough memory to
undo structure necessary)
ERANGE (semaphore value out of range)
q Argument semid jest identyfikatorem zbioru semaforów, zwrócony
przez semget(), zaś nops określa liczbę elementów tablicy struktur
sembuf, na którą wskazuje argument sops.
q Struktura sembuf jest zadeklarowana w pliku linux/semh i ma
postać:
struct sembuf
{
ushort sem_num; /* indeks semafora w tablicy */
short sem_op; /* operacja semaforowa */
short sem_flg; /* znaczniki operacyjny */
};
q gdzie:
" sem_num ą numer semafora, na którym chcemy wykonać operację,
" sem_op ą operacja na semaforze (dodatnia, ujemna lub zero),
" sem_flg ą znaczniki operacyjne.
- 33 -
Jerzy Pejaś: Systemy operacyjne - Linux
q Jeśli sem_op jest ujemna, wtedy proces wywołujący funkcję
semop() chce czekać, aż wartość semafora stanie się większa niż
(lub taka sama jak) wartość bezwzględna tego pola. Następnie
wartość bezwzględną tego opla odejmuje się od bieżącej wartości
semafora (odpowiada to przydzieleniu zasobu).
q Jeśli sem_op jest dodatnia, to będzie ona dodana do bieżącej
wartości semafora. Odpowiada to operacji uwolnienia zasobów,
chronionych przez semafor.
q Jeśli sem_op jest zerem, wtedy proces wywołujący funkcję
semop() chce czekać, aż wartością semafora stanie się zero.
q Funkcja semop() może używać różnych znaczników, operacji
(patrz struktura sembuf). Jeśli np. znacznikowi temu przypiszemy
wartość IPC_NOWAIT, wtedy oznacza to, iż nie chcemy, aby
proces czekał na zakończenie operacji.
q Przykład. Załóżmy, że dana jest jedna drukarka i wiele procesów.
Semafor powinien zapewnić wzajemne wykluczanie w realizacji
dostępu do drukarki.. Strukturę sembuf należy zainicjować
następująco:
struct sembuf sem_lock = { 0, -1, IPC_NOWAIT };
Zapis ten oznacza, że wartość -1 zostanie dodana do semafora
numer 0. Użycie IPC_NOWAIT oznacza powrót z błędem, jeśli
już jakiś proces korzysta z drukarki. Fragment kodu, który
korzysta z tej informacji ma postać:
if((semop(sid, &sem_lock, 1) == -1)
perror("semop");
Jeśli proces zakończy korzystanie z drukarki, powinien wykonać
operację odrotną:
struct sembuf sem_unlock = { 0, 1, IPC_NOWAIT };
if((semop(sid, &sem_lock, 1) == -1)
perror("semop");
q Wywołanie systemowe semctl(). Funkcji msgctl() umożliwia
sterowanie operacjami wykonywanymi na kolejce wiadomości.
Wywołanie systemowe: semctl();
Prototyp: int semctl (int semid, int semnum, int
- 34 -
Jerzy Pejaś: Systemy operacyjne - Linux
cmd, union semun arg );
RETURNS: positive integer on success
-1 on error: errno =
EACCESS (permission denied)
EFAULT (invalid address pointed to by arg argument)
EIDRM (semaphore set was removed)
EINVAL (set doesn t exist, or semid is invalid)
EPERM (EUID has no privileges for cmd in arg)
ERANGE (semaphore value out of range)
NOTES: Performs control operations on a semaphore set
q Argument semid jest identyfikatorem zbioru semaforów, zwrócony
przez semget(), zaś semnum numerem semafora, na którym
chcemy wykonać operację.
q Argument cmd umożliwia wykonywanie poleceń na zbiorze
semaforów:
" IPC_STAT ą pobiera strukturę semid_ds z kolejki i umieszcza ja
pod adresem bufora buf określonego w unii semun.
" IPC_SET ą ustawia wartość ipc_perm w strukturze semid_ds;
wartość tą pobiera z bufora buf określonego w unii semun.
" IPC_RMID ą usuwa kolejkę z jądra,
" GETVAL ą pobiera wartość semafora,
" GETALL ą pobiera wartości wszystkich semaforów w zbiorze,
" SETVAL ą nadaje semaforowi wartość określona przez pole val
unii,
" SETALL ą nadaje wartości wszystkim semaforom w zbiorze,
określonym przez pole val unii.
q Argument arg jest zmienną typu semun, określoną w pliku
linux/sem.h i ma postać:
union semun
{
int val; /* wartość dla SETVAL */
struct semid_ds *buf; /* bufor dla IPC_STAT&IPC_SET */
ushort *array; /* tablica dla GETALL & SETALL */
struct seminfo *__buf; /* bufor dla IPC_INFO */
void *__pad;
};
q gdzie:
- 35 -
Jerzy Pejaś: Systemy operacyjne - Linux
" val ą używana wtedy, gdy wykonane jest polecenie SETVAL.
Określa wartość przypisywaną semaforowi.
" buf ą stosowany w przypadku polecenia IPC_STAT/IPC_SET.
Reprezentuje kopię wewnętrznej struktury danych semafora
używanej przez jądro.
" array ą wskaznik stosowany w przypadku polecenia
GETALL/SETALL. Powinna wskazywać na tablicę wartości
całkowitych, które mają być ustawione lub pobrane.
q Używając GETVAL można odczytać wartość semafora. Przykład:
int get_sem_val( int sid, int semnum )
{
return( semctl(sid, semnum, GETVAL, 0));
}
q Przykład. Aby określić status np. pięciu drukarek można użyć
następującego kodu:
#define MAX_PRINTERS 5
printer_usage()
{
int x;
for(x=0; x printf("Printer %d: %d\n\r", x, get_sem_val(sid,x));
}
q Przykład. Aby zainicjować nową wartość semafora:
void init_semaphore( int sid, int semnum, int initval)
{
union semun semopts;
semopts.val = initval;
semctl( sid, semnum, SETVAL, semopts);
}
q Przykład. Aby zmienić uprawnienia, można to zrobić np. tak:
void changemode(int sid, char *mode)
{
int rc;
struct semid_ds mysemds;
/* Pobierz bieżące wartości - wskaż najpierw na
lokalną kopię struktury wewnetrznej */
semopts.buf = &mysemds;
/* Spróbuj to zrobic jeszcze raz */
if((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1)
{
perror("semctl");
- 36 -
Jerzy Pejaś: Systemy operacyjne - Linux
exit(1);
}
printf("Poprzednie uprawnienia %o\n",
semopts.buf->sem_perm.mode);
/* Zmień uprawnienia do semafora */
sscanf(mode, "%o", &semopts.buf->sem_perm.mode);
/* Uaktualnij wewnetrzna strukturę */
semctl(sid, 0, IPC_SET, semopts);
printf("Uaktualnianie...\n");
}
Przykład: interaktywny manipulator semaforem
q Przedstawiony poniżej kod interpretera pozwala na interakcyjne
operacje na zbiorze semaforów. Zakładamy, że po kompilacji
program ma nazwę semtool.
q Składnia poleceń:
Utworzenie zbioru semaforów:
semtool c (liczba semaforów w zbiorze)
Zajmowanie semafora:
semtool l (numer zajmowanego semafora)
Zwalnianie semafora:
semtool u (numer zwalnianego semafora)
Zmiana uprawnień:
semtool m (tryb)
Usuwanie kolejki:
semtool d
Przykłady wywołań:
semtool c 5
semtool l
semtool u
semtool m 660
semtool d
q Kod programu:
#include
#include
#include
#include
#include
#include
/* Początkowe wartości wszystkich */
#define SEM_RESOURCE_MAX 1
void opensem(int *sid, key_t key);
void createsem(int *sid, key_t key, int members);
void locksem(int sid, int member);
- 37 -
Jerzy Pejaś: Systemy operacyjne - Linux
void unlocksem(int sid, int member);
void removesem(int sid);
unsigned short get_member_count(int sid);
int getval(int sid, int member);
void dispval(int sid, int member);
void changemode(int sid, char *mode);
void usage(void);
int main(int argc, char *argv[])
{
key_t key;
int semset_id;
if(argc == 1) usage();
/* Utwórz unikalny klucz wywołując ftok() */
key = ftok(".",  s );
switch(tolower(argv[1][0]))
{
case  c : if(argc != 3)
usage();
createsem(&semset_id, key, atoi(argv[2]));
break;
case  l : if(argc != 3)
usage();
opensem(&semset_id, key);
locksem(semset_id, atoi(argv[2]));
break;
case  u : if(argc != 3)
usage();
opensem(&semset_id, key);
unlocksem(semset_id, atoi(argv[2]));
break;
case  d : opensem(&semset_id, key);
removesem(semset_id);
break;
case  m : opensem(&semset_id, key);
changemode(semset_id, argv[2]);
break;
default: usage();
}
return(0);
}
void opensem(int *sid, key_t key)
{
/* Otwórz zbiór semaforów  nie twórz go! */
if((*sid = semget(key, 0, 0666)) == -1)
{
printf("Zbiór semaforów nie istnieje!\n");
exit(1);
}
}
void createsem(int *sid, key_t key, int members)
{
int cntr;
- 38 -
Jerzy Pejaś: Systemy operacyjne - Linux
union semun semopts;
if(members > SEMMSL)
{
printf("Przekroczono max.liczbę semaforów w
zbiorze\n",SEMMSL);
exit(1);
}
printf("Próba utworzenia nowego zbioru semaforów o
%d elementach\n",members);
if((*sid=semget(key,members,
IPC_CREAT|IPC_EXCL|0666)) == -1)
{
fprintf(stderr, "Zbiór semaforów już istnieje!\n");
exit(1);
}
semopts.val = SEM_RESOURCE_MAX;
/* Inicjuj wszystkie elementy zbioru (można także
zrobić przy pomocy polecenia SETALL) */
for(cntr=0; cntr semctl(*sid, cntr, SETVAL, semopts);
}
void locksem(int sid, int member)
{
struct sembuf sem_lock={ 0, -1, IPC_NOWAIT};
if( member<0 || member>(get_member_count(sid)-1))
{
fprintf(stderr, "element semafora %d spoza
zakrsu\n", member);
return;
}
/* Próba zajęcia zbioru semaforów */
if(!getval(sid, member))
{
fprintf(stderr, "Zasoby semafora wyczerpane (brak
możliwości zajęcia zasobu)!\n");
exit(1);
}
sem_lock.sem_num = member;
if((semop(sid, &sem_lock, 1)) == -1)
{
fprintf(stderr, "Błąd zajęcia\n"); exit(1);
}
else
printf("Zasoby semafora zmniejszone o jeden\n");
dispval(sid, member);
}
void unlocksem(int sid, int member)
{
struct sembuf sem_unlock={ member, 1, IPC_NOWAIT};
int semval;
if( member<0 || member>(get_member_count(sid)-1))
- 39 -
Jerzy Pejaś: Systemy operacyjne - Linux
{
fprintf(stderr, "element semafora %d spoza
zakrsu\n", member);
return;
}
/* Czy zbiór semaforów jest zajety? */
semval = getval(sid, member);
if(semval == SEM_RESOURCE_MAX)
{
fprintf(stderr, "Semafor nie zajęty!\n");
exit(1);
}
sem_unlock.sem_num = member;
/* Próba zwolnienia zbioru semaforów */
if((semop(sid, &sem_unlock, 1)) == -1)
{
fprintf(stderr, "Błąd zwolnienia\n");
exit(1);
}
else
printf("Zasoby semafora zwiększone o jeden\n");
dispval(sid, member);
}
void removesem(int sid)
{
semctl(sid, 0, IPC_RMID, 0);
printf("Semafor usunięty\n");
}
unsigned short get_member_count(int sid)
{
union semun semopts;
struct semid_ds mysemds;
semopts.buf = &mysemds;
/* Zwróć luczbe elementów w zbiorze semaforów */
return(semopts.buf->sem_nsems);
}
int getval(int sid, int member)
{
int semval;
semval = semctl(sid, member, GETVAL, 0);
return(semval);
}
void changemode(int sid, char *mode)
{
int rc;
union semun semopts;
struct semid_ds mysemds;
/* Pobierz aktualną wartość struktury wewnętrznej */
semopts.buf = &mysemds;
rc = semctl(sid, 0, IPC_STAT, semopts);
- 40 -
Jerzy Pejaś: Systemy operacyjne - Linux
if (rc == -1)
{
perror("semctl");
exit(1);
}
printf("Poprzednie uprawnienia %o\n",
semopts.buf->sem_perm.mode);
/* Zmień uprawnienia do semafora */
sscanf(mode, "%ho", &semopts.buf->sem_perm.mode);
/* Uaktualnij wewnętrzną strukturę danych */
semctl(sid, 0, IPC_SET, semopts);
printf("Uaktualnianie...\n");
}
void dispval(int sid, int member)
{
int semval;
semval = semctl(sid, member, GETVAL, 0);
printf("semval dla elementu %d is %d\n",
member, semval);
}
void usage(void)
{
fprintf(stderr, "semtool  Narzędzie do  majstrownia
przy semaforach\n");
fprintf(stderr, "\nUżycie: semtool (c)reate
\n");
fprintf(stderr, " (l)ock \n");
fprintf(stderr, " (u)nlock \n");
fprintf(stderr, " (d)elete\n");
fprintf(stderr, " (m)ode \n");
exit(1);
}
Przykład: semstat - program towarzyszący programowi semtool
q Program semstat wyświetla wartości każdego z semaforów
utworzonych przy pomocy semtool.
q Kod programu:
#include
#include
#include
#include
#include
int get_sem_count(int sid);
void show_sem_usage(int sid);
int get_sem_count(int sid);
void dispval(int sid);
int main(int argc, char *argv[])
{
- 41 -
Jerzy Pejaś: Systemy operacyjne - Linux
key_t key;
int semset_id;
/* Utwórz unikalny klucz wywołując ftok() */
key = ftok(".",  s );
/* Otwórz zbiór semaforów  nie twórz go! */
if((semset_id = semget(key, 1, 0666)) == -1)
{
printf("Zbiór semaforów nie istnieje\n");
exit(1);
}
show_sem_usage(semset_id);
return(0);
}
void show_sem_usage(int sid)
{
int cntr=0, maxsems, semval;
maxsems = get_sem_count(sid);
while(cntr < maxsems)
{
semval = semctl(sid, cntr, GETVAL, 0);
printf("Semafor #%d: --> %d\n", cntr, semval);
cntr++;
}
}
int get_sem_count(int sid)
{
int rc;
struct semid_ds mysemds;
union semun semopts;
/* Pobierz aktualną wartość struktury wewnętrznej */
semopts.buf = &mysemds;
if((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1)
{
perror("semctl");
exit(1);
}
/* Zwróć liczbę semaforów w zbiorze */
return(semopts.buf->sem_nsems);
}
void dispval(int sid)
{
int semval;
semval = semctl(sid, 0, GETVAL, 0);
printf("Wartość semval wynosi %d\n", semval);
}
Literatura:
- 42 -
Jerzy Pejaś: Systemy operacyjne - Linux
[1] Sven Goldt, Sven van der Meer, Scott Burkett, Matt Welsh The Linux
Programmer's Guide, 1995 by Sven Goldt
[2] W.Richard Stevens Programowanie zastosowań sieciowych w
systemie Unix, WNT, Warszawa 1995
- 43 -


Wyszukiwarka

Podobne podstrony:
System operacyjny Linux Podręcznik
System operacyjny Linux (podstawy)
systemy operacyjne cw linux strumienie procesy
systemy operacyjne cw linux apache mysql
Systemy Operacyjne Unix Linux solarka2
6 Systemy Operacyjne 30 11 2010 Zarządzanie procesami2
Systemy Operacyjne Unix Linux solaris1
5 Systemy Operacyjne 23 11 2010 Zarządzanie procesami
systemy operacyjne cw linux instalacja(1)
Systemy Operacyjne Unix Linux solarka4
sołtys,Systemy operacyjne, Zarządzanie procesami

więcej podobnych podstron