Programowanie systemowe w Linux: UNIX jesień/zima 2013 Wykład 7 Programowanie systemowe w Linux: Sygnały Kolejki komunikatów, Pamięć współdzielona, Semafory dr inż. Wojciech Bieniecki Instytut Nauk Ekonomicznych i Informatyki http://wbieniec.kis.p.lodz.pl/pwsz
Metody komunikacji międzyprocesowej Komunikacja międzyprocesowa (ang. Inter-Process Communication — IPC) – sposoby komunikacji pomiędzy procesami systemu operacyjnego. Mechanizmy IPC opierają się na budowaniu w pamięci lub na dysku dynamicznych struktur, używanych do transmisji komunikatów pomiędzy procesami Lista metod IPC ta obejmuje: pliki i blokady – najprostsza i najstarsza forma IPC sygnały (ang. signals) – czasami znane jako przerwania programowe łącza nienazwane (ang. pipes) – znane też jako łącza komunikacyjne łącza nazwane (ang. named pipes) – znane też jako nazwane łącza komunikacyjne kolejki komunikatów (ang. message queues) umożliwiają przekazywanie określonych porcji danych pamięć dzielona (ang. shared memory) umożliwiają współdzielenie kilku procesom tego samego fragmentu wirtualnej przestrzeni adresowej semafory (ang. semaphores) umożliwiają synchronizacje procesów w dostępie do współdzielonych zasobów (np. do pamięci współdzielonej) gniazda Uniksa (ang. Unix domain sockets) gniazda (ang. sockets) RPC (ang. Remote Procedure Call) – zdalne wywoływanie procedur.
Mechanizmy IPC Systemu V zdefiniowane w <sys/ipc.h> działanie Kolejka komunikatów Pamięć współdzielona semafor Rezerwowanie obiektu IPC, uzyskiwanie do niego dostępu msgget shmget semget Sterowanie obiektem IPC, uzyskiwanie informacji o stanie modyfikowanych obiektów IPC, usuwanie obiektów IPC msgctl shmctl semctl Operacje na obiektach IPC: wysyłanie i odbieranie komunikatów, operacje na semaforach, rezerwowanie i zwalnianie segmentów pamięci wspólnej msgsnd, msgrcv shmat, shmdt semop
Mechanizmy IPC POSIX Kolejka komunikatów Pamięć wspólna Semafor plik nagłówkowy <mqueue.h> <sys/mman.h> <semaphore.h> funkcja do tworzenia, otwierania lub usuwania mq_open mq_close mq_unlink shm_open shm_unlink sem_open sem_close sem_unlink sem_init semdestroy funkcje operacji sterujących mq_getattr mq_setattr ftruncate fstat funkcje komunikacji mq_send mq_receive mq_notify mmap munmap sem_wait sem_trywait sem_post sem_getvalue
Wywołania systemowe …get Wywołania systemowe …get (msgget, shmget, semget) są stosowane do tworzenia nowych obiektów IPC, lub do uzyskania dostępu do obiektów istniejących. int msgget(key_t key, int msgflg) key_t key – klucz (liczba long) umożliwiający generowanie identyfikatorów IPC. Procesy, poprzez podanie tej samej wartości klucza uzyskują dostęp do konkretnego mechanizmu IPC. Wartość klucza określamy samodzielnie, lub poprzez stałą IPC_PRIVATE, która spowoduje utworzenie obiektu IPC o niepowtarzalnej wartości identyfikatora (umożliwia komunikację jedynie procesów spokrewnionych) int msgflg – znacznik komunikatu, określa prawa dostępu do tworzonego obiektu IPC. IPC_CREAT – zwraca identyfikator obiektu IPC, a jeżeli nie istniał – utworzenie obiektu. IPC_CREAT|IPC_EXCL spowoduje utworzenie obiektu, a jeżeli obiekt IPC dla danej wartości klucza już istnieje, wywołanie funkcji get zakończy się błędem. Dzięki połączeniu tych dwóch flag użytkownik posiada gwarancję, że jest on twórcą danego obiektu IPC. Funkcje …get zwracają wartości całkowitoliczbowe, nazywane identyfikatorami IPC, które identyfikują obiekty IPC. Od strony systemu operacyjnego identyfikator IPC jest indeksem w systemowej tablicy zawierającej struktury z danymi dotyczącymi uprawnień do obiektów IPC.
Tworzenie obiektów IPC
Wywołania systemowe …ctl Funkcje …ctl (msgctl, shmctl, semctl), używane są w celu przeprowadzenia operacji kontrolnych na obiektach IPC. Wywołania funkcji …ctl posiadają dwa argumenty wspólne: identyfikator obiektu IPC (otrzymany w wyniku wywołania odpowiedniej funkcji …get), oraz następujące stałe: • IPC_STAT – zwraca informację o stanie danego obiektu IPC • IPC_SET – zmienia właściciela, grupę i tryb obiektu IPC • IPC_RMID – usuwa obiekt IPC z systemu
Dostęp do IPC z powłoki systemowej Na poziomie systemowym dane znajdujące się w obiekcie IPC pobiera się za pomocą polecenia ipcs. Informacje na temat obiektów: kolejek komunikatów, pamięci współdzielonej i semaforów otrzymamy stosując odpowiednio przełączniki -q, -m, -s. Dodatkowo przełącznik -b pozwala uzyskać informację nt. maksymalnego rozmiaru obiektów IPC, czyli ilości bajtów w kolejkach, rozmiarów segmentów pamięci współdzielonej i ilości semaforów w zestawach. ipcs -q <msgid> – informacja nt. kolejki komunikatów o identyfikatorze msgid ipcs -m <shmid> – informacja nt. segmentu pamięci współdzielonej o identyfikatorze shmid ipcs -s <semid> – informacja nt. zestawu semaforów o identyfikatorze semid obiekt IPC USUWAMY wykonując polecenie systemowe ipcrm: ipcrm -q <msgid> – usunięcie kolejki komunikatów o identyfikatorze msgid ipcrm -m <shmid> – usunięcie segmentu pamięci współdzielonej o identyfikatorze shmid ipcrm -s <semid> – usunięcie zestawu semaforów o identyfikatorze semid
Sygnały Sygnałem nazywamy asynchroniczne zdarzenie skierowane do procesu. Otrzymanie sygnału przez proces oznacza, że pojawiło się jakieś zdarzenie wyjątkowe, wymagające natychmiastowej reakcji ze strony procesu. od innego procesu (np. SIGINT, SIGKILL) od jądra (np. SIGPIPE, SIGCHLD, SIGALRM) od jądra, ale przez wyjątek sprzętowy (np. SIGSEGV, SIGBUS) Reakcja procesu na otrzymany sygnał Wykonanie akcji domyślnej – najczęściej zakończenie procesu z ewentualnym zrzutem zawartości segmentów pamięci na dysk, czasami zignorowanie sygnału, Zignorowanie sygnału Przechwycenie sygnału tj. podjęcie akcji zdefiniowanej przez użytkownika
Sygnały w systemie Unix Funkcje obsługujące sygnały zawarte są w bibliotece <signal.h>
Sygnały czasu rzeczywistego W Posix dostępne są również sygnały czasu rzeczywistego (realtime, RT) W linuxie mają one wartości 34 – 64. Sygnały czasu rzeczywistego charakteryzują się tym, że: ● Sygnały są kolejkowane ● Wielokrotne wystąpienie tego samego sygnału nie powoduje „zlepiania” w jeden sygnał ● Gdy do kolejki trafiają wielokrotne, nieblokowane sygnały czasu rzeczywistego to sygnały z niższym numerem mają wyższy priorytet. ● Sygnały RT przekazują nie tylko nr sygnału ale także: strukturę siginfo_t oraz kontekst.
Ważniejsze funkcje systemowe dla sygnałów
Sygnały systemu Unix - funkcje Funkcja kill - wysłanie sygnału int kill(int pid, int signum) pid – identyfikator procesu, do którego chcemy wysłać sygnał pid > 0 oznacza proces o identyfikatorze pid, pid == 0 oznacza grupę procesów do których należy proces wysyłający, pid == -1 oznacza wszystkie procesy, których rzeczywisty identyfikator użytkownika jest równy efektywnemu (obowiązującemu) identyfikatorowi procesu wysyłającego sygnał, pid < -1 oznacza procesy należące do grupy o identyfikatorze -pid. signum – numer wysyłanego sygnału Wynik: -1 w przypadku błędu (szczegóły w errno) 0 OK.
Sygnały systemu Unix - funkcje Funkcja signal - Określenie sposobu obsługi sygnału void (*signal(int signum, void (*f)()))() signum – numer sygnału, którego obsługa ma zostać zmieniona f – może obejmować jedną z trzech wartości: SIG_DFL (wartość 0) – standardowa reakcja na sygnał SIG_IGN (wartość 1) – ignorowanie sygnału Wskaźnik do funkcji – wskaźnik na funkcję, która będzie uruchomiona w reakcji na sygnał Wynik: -1 w przypadku błędu (szczegóły w errno) Wskaźnik na poprzednią procedurę obsługi sygnału, lub SIG_IGN, SIG_DFL UWAGA 1: Nie można przechwytywać, ani ignorować sygnałów SIGKILL i SIGSTOP. UWAGA 2: Gdy w procesie macierzystym ustawiony jest tryb ignorowania sygnału SIGCLD to po wywołaniu funkcji exit przez proces potomny, proces zombi nie jest zachowywany i miejsce w tablicy procesów jest natychmiast zwalniane
Sygnały systemu Unix - funkcje Funkcja pause – oczekiwanie na sygnał void pause() Zawiesza wywołujący proces aż do chwili otrzymania dowolnego sygnału. Jeśli sygnał jest ignorowany przez proces, to funkcja pause też go ignoruje. Najczęściej sygnałem, którego oczekuje pause jest sygnał pobudki SIGALARM. Funkcja ALARM Funkcja wysyła sygnał SIGALARM po upływie czasu podanym przez użytkownika. unsigned alarm ( unsigned int sek ) Wynik: 0 w przypadku gdy alarm nie był wcześniej ustawiony liczba dodatnia – liczba sekund pozostała do wysłania sygnału gdy poprzednio ustawiono alarm, a sygnał nie został jeszcze wysłany
void func (int signo, siginfo_t * info, void *context); Sygnały POSIX Procedura obsługi sygnału zdefiniowana jest następująco: void func (int signo, siginfo_t * info, void *context); gdzie signo numer sygnału info struktura: typedef struct { int si_signo; /*numer sygnału*/ int si_errno; /* jeśli !=0 numer błędu związany z tym sygnałem*/ int si_code; /*SI_{USER, QUEUE, TIMER, KERNEL, ASYNCIO, MESGQ, SIGIO, TKILL, ASYNCNL} */ union sigval si_value; } siginfo_t; context kontekst zależny od implementacji.
Generowanie sygnałów RT w POSIX Sygnały RT są generowane przez następujące zdarzenia i identyfikowane w polu si_code struktury siginfo_t: SI_ASYNCIO – sygnał został wygenerowany przez zakończenie asynchronicznego zlecenia wejścia wyjścia (funkcje z serji aio_*) np drivery USB SI_MESGQ – sygnał wygenerowany przy umieszczeniu komunikatu w pustej kolejce komunikatów SI_QUEUE – sygnał wysłany przez funkcję sigqueue SI_TIMER – sygnał wygenerowany przez zakończenie pracy czasomierza ustawionego za pomocą timer_settim SI_USER – sygnał wysłany przez funkcję kill SI_TKILL – sygnał wysłany przez funkcję tkill SI_KERNEL – sygnał wysłany przez funkcję jądra SI_SIGIO – sygnał wysłany przez SIGIO
Określanie procedury obsługi sygnału void sigaction(int numer_sygnalu, struct sigaction* akcja, struct sigaction* poprzednia_akcja); struct sigaction { void (*sa_handler)(int); sigset_t sa_mask; unsigned long sa_flags; void (*sa_sigaction)(int,siginfo_t, void*); void (*sa_restorer)(void); }; sa_handler – jeżeli sa_flags nie jest ustawione na SA_SIGINFO to jest to adres klasycznej funkcji obsługi sygnału. Możliwe też SIG_DFL, SIG_IGN dzięki którym przywracamy domyślną obsługę lub ignorujemy. sa_mask – dodatkowe sygnały do zablokowania sa_flags – flagi sygnału SA_NOCLDSTOP – nie wysyłaj SIGCLD nawet gdy potomek się zakończy SA_NOCLDWAIT – nie twórz „zombie” po śmierci potomków. SA_SIGINFO – użyj trzyargumentowej funkcji obsługi sygnału sa_sigaction zamiast sa_handler SA_NOMASK – w czasie wykonywania sygnału nie jest on automatycznie blokowany SA_ONESHOT – Kiedy sygnał jest wysłany, obsługa sygnału jest zerowana do SIG_DFL SA_RESTART – Kiedy sygnał jest wysłany do procesu, w czasie, gdy ten wykonuje wolną funkcję systemową, funkcja systemowa jest wznawiana po powrocie z funkcji sa_sigaction – jeżeli SA_SIGINFO to określa adres funkcji obsługi sygnału
Obsługa zbioru sygnałów sigset_t: Funkcje systemowe POSIX do obsługi sygnałów operują na zbiorach sygnałów. int sigemptyset(sigset_t *set); - zeruje zbiór sygnałów int sigfillset(sigset_t *set); - dodaje do zbioru wszystkie dostępne sygnały int sigaddset(sigset_t *set,int sig); - dodaje do zbioru sygnał int sigdelset(sigset_t *set,int sig); - usuwa ze zbioru sygnał int sigismeber(sigset_t *set,int sig); - Jeśli sygnał w jest w zbiorze zwróci != 0 int sigpending(sigset_t *set); - Uzyskuje informacje, które z sygnałów są aktualnie niezałatwione int sigsuspend(sigset_t *mask); - Wstrzymanie działania programu dopóki proces nie otrzyma sygnału. Na ten czas ustawia maskę sygnałów. dostępne sygnały void pause(); - wstrzymuje działanie procesu do nadejścia jakiegokolwiek sygnału
Maskowanie sygnałów W kodzie programu mogą istnieć sekcje które nie powinny być przerywane sygnałami. Stąd w systemie istnieją funkcje służące do blokowania sygnałów. Zablokowany sygnał jest pamiętany - może on być obsłużony gdy zostanie odblokowany. Standardowo tylko jeden nie obsłużony sygnał może być pamiętany, ale sygnały mogą być kolejkowane gdy ustawiona jest flaga SA_SIGINFO (funkcja sigaction()) Przykład: maska sygnałów blokuje dostarczanie sygnałów do procesu P1
Maskowanie sygnałów w POSIX Do maskowania sygnałów wykorzystujemy zbiory sygnałów int sigprocmask(int how, sigset_t *set, sigset_t *oset) how – sposób blokowania SIG_SETMASK – blokowanie sygnałów ze zbioru set SIG_UNBLOCK – odblokowanie sygnałów ze zbioru set SIG_BLOCK – blokowane sygnały są unią bieżąco zablokowanych sygnałów i wyspecyfikowanych w zbiorze set set Zbiór sygnałów oset Poprzedni zbiór sygnałów Użycie w sekcji krytycznej
Wysłanie sygnału POSIX int sigqueue(pid_t pid, int sig, const union sigval value); pid – komu chcemy wysłać sygnał sig – nr sygnału value – dodatkowe informacje union sigval { int sival_int; void *sival_ptr; };
Uwaga do używania sygnałów Sygnały z założenia są narzędziem działającym asynchronicznie. Nie można robić żadnych założeń co do stanu wykonywanego programu. Należy pamiętać by w procedurze obsługi sygnału nie zmieniać globalnych danych. Jeżeli już to czynimy to tylko wtedy gdy inna część naszego oprogramowania z nich nie korzysta. Należy np. na moment użycia zmiennych globalnych, czy innych współdzielonych z procedurą obsługi sygnału zasobów zablokować sygnał, który tą obsługę mógł by wywołać.
Przykłady użycia sygnałów // Program obsługujący sygnał SIGINT #include <signal.h> #include <stdlib.h> #include <setjmp.h> int sigcnt = 0; int sig = 0; void sighandler(int signum) { // Procedura obsługi sygnału sigcnt++; sig = signum; } main() { int i; i = 0; printf("Start programu \n"); signal(SIGINT, sighandler); do { printf(" %d %d %d \n", i, sigcnt, sig); sleep(1); i++; } while(1);
Przykłady użycia sygnałów // Sygnał ustala limit czasowy na podanie hasła #include <stdio.h> #include <signal.h> int time_out; void time_sig(int signo) { time_out = 1; } main() { char passwd[16]; signal(SIGALRM, time_sig); for(i=0;i<5;i++) { time_out = 0; printf("Podaj haslo:\n"); alarm(5); gets(passwd); alarm(0); if( time_out == 0) break; // dalszy ciąg programu
Obsługa sygnałów przy pomocy funkcji sigaction #include <stdlib.h> #include <signal.h> #include <unistd.h> static int count, numer, kod, value = 0; void handler( int signo, siginfo_t *info, void * o ) { count++; numer = signo; kod = info->si_code; value = info->si_value.sival_int; } int main( int argc, char * argv[] ) { int i,pid,kod,wart; union sigval sig; sigset_t set; struct sigaction act; sigemptyset( &set ); sigaddset( &set, SIGUSR1 ); act.sa_flags = SA_SIGINFO; act.sa_mask = set; act.sa_handler = &handler; // VERTE
Obsługa sygnałów przy pomocy funkcji sigaction sigaction( SIGUSR1, &act, NULL ); if((pid = fork()) == 0) { // Tu – proces potomny pid = getppid(); for(i=0;i<10;i++) { printf("Wysylam: sygnal= %d \n", i); // Wartość sygnalu sig.sival_int = i; sigqueue(pid, SIGUSR1, sig); // wrzucamy do kolejki sleep(1); } exit(0); // Proces Macierzysty ----------- while(1) { printf("Odbior: licz= %d num= %d kod= %d val= %d\n", count, numer, kod, value); if(value == 9) break; return EXIT_SUCCESS;
Sprawdzenie sygnałów dostarczonych do procesu ale zablokowanych #include <stdio.h> #include <signal.h> #include <unistd.h> void main() { sigset_t set, oset, pset; sigemptyset( &set ); sigaddset( &set, SIGINT ); // Ustawienie maski sigprocmask( SIG_BLOCK, &set, &oset ); printf( "stary zbiór %8.8ld.\n", oset ); // Sprawdzanie sygnałów oczekujących sigpending( &pset ); printf( "Zbiór sygnałów oczekujących %8.8ld.\n", pset ); kill( getpid(), SIGINT ); printf( "Pending set is %8.8ld.\n", pset ); sigprocmask( SIG_UNBLOCK, &set, &oset ); /* Program zatrzymuje się z SIGINT */ }
Oczekiwanie na sygnał #include <stdio.h> #include <signal.h> #include <unistd.h> void main() { sigemptyset( &set ); sigaddset( &set, SIGINT ); printf("Program zawieszony i odporny na przerwanie.\n" ); printf("Sygnał SIGALRM zatrzyma program za 10 sekund\n" ); alarm( 10 ); sigsuspend( &set ); } Funkcja sigsuspend tymczasowo zastępuje bieżącą maskę sygnałów przez sigmask i oczekuje na sygnał. Następuje zablokowanie procesu. Gdy sygnał jest obsługiwany odblokowanie procesu następuje po zakończeniu obsługi sygnału i maska sygnału zostaje przywrócona do poprzedniej.
Kolejka komunikatów Kolejki komunikatów to specjalne listy (kolejki) w jądrze, zawierające odpowiednio sformatowane dane i umożliwiające ich wymianę poprzez dowolne procesy w systemie. Istnieje możliwość umieszczania komunikatów w określonych kolejkach (z zachowaniem kolejności ich wysyłania przez procesy) oraz odbierania komunikatu na parę rożnych sposobów (zależnie od typu, czasu przybycia itp.).
Kolejka komunikatów Za każdą kolejkę komunikatów odpowiada jedna struktura typu msqid_ds Komunikaty danej kolejki przechowywane są na liście, której elementami są struktury typu msg każda z nich posiada informacje o typie komunikatu, wskaźnik do następnej struktury msg oraz wskaźnik do miejsca w pamięci, gdzie przechowywana jest właściwa treść komunikatu Dodatkowo, każdej kolejce komunikatów przydziela sie dwie kolejki typu wait_queue, na których śpią procesy zawieszone podczas wykonywania operacji czytania bądź pisania do danej kolejki.
Struktury danych kolejki Struktura msqid_ds (include/linux/msg.h): struct msqid_ds {/* jedna struktura msg dla kazdej kolejki w systemie */ struct ipc_perm msg_perm; Jest to instancja struktury ipc_perm (prawa dostępu) struct msg *msg_first; /* pierwszy komunikat w kolejce */ struct msg *msg_last; /* ostatni komunikat w kolejce */ __kernel_time_t msg_stime; /* czas ostatniego msgsnd */ __kernel_time_t msg_rtime; /* czas ostatniego msgrcv */ __kernel_time_t msg_ctime; /* czas ostatniej zmiany */ struct wait_queue *wwait; /* dwie kolejki typu wait_queue, na ktorych spia */ struct wait_queue *rwait; /*procesy zawieszone podczas wykonywania operacji odpowiednio czytania oraz pisania w danej kolejce komunikatow*/ unsigned short msg_cbytes; /* liczba bajtow w kolejce */ unsigned short msg_qnum; /* liczba komunikatow w kolejce */ unsigned short msg_qbytes; /* maksymalna liczba bajtow w kolejce */ __kernel_ipc_pid_t msg_lspid; /* pid ostatniego msgsnd */ __kernel_ipc_pid_t msg_lrpid; /* pid ostatniego receive*/ };
Struktury danych kolejki Struktura msg (include/linux/msg.h): /* jedna struktura msg dla każdego komunikatu */ struct msg { struct msg *msg_next; /* następny komunikat w kolejce */ long msg_type; char *msg_spot; time_t msg_stime; /* czas wysłania tego komunikatu */ short msg_ts; /* długość właściwej treści komunikatu */ }; Dodatkowe wyjaśnienia: msg_type Typ przechowywanego komunikatu. Wysyłanemu do kolejki komunikatowi nadawca przypisuje dodatnia liczbę naturalna, stająca sie jego typem. Przy odbiorze komunikatu można zażądać komunikatów określonego typu. msg_spot Wskaźnik do miejsca w pamięci, gdzie przechowywana jest właściwą tresc komunikatu. Na każdy komunikat przydzielane jest oddzielne miejsce w pamięci.
Obsługa kolejki komunikatów Kolejki komunikatów umożliwiają przesyłanie pakietów danych, nazywanych komunikatami, pomiędzy różnymi procesami. Sam komunikat jest zbudowany jako struktura msgbuf, jego definicja znajduje się w pliku <sys/msg.h>: /* message buffer for msgsnd and msgrcv calls */ struct msgbuf { long mtype; /* typ komunikatu */ char mtext[1]; /* tresc komunikatu */ }; Każdy komunikat ma określony typ i długość. Typ komunikatu nadaje proces inicjujący komunikat. Komunikaty są umieszczane w kolejce w kolejności ich wysyłania. Nadawca może wysyłać komunikaty, nawet gdy żaden z potencjalnych odbiorców nie jest gotów do ich odbioru. Komunikaty są w takich przypadkach buforowane w kolejce oczekiwania na odebranie. Przy odbiorze komunikatu, odbiorca może oczekiwać na pierwszy przybyły komunikat lub na pierwszy komunikat określonego typu. Komunikaty w kolejce są przechowywane nawet po zakończeniu procesu nadawcy, tak długo, aż nie zostaną odebrane lub kolejka nie zostanie zlikwidowana.
Obsługa kolejki komunikatów int msgsnd(int msgid, struct msgbuf *msgp, int msgs, int msgflg) służy do wysłania komunikatu do kolejki. Argumenty funkcji: msgid – identyfikator kolejki komunikatów msgp – wskaźnik na komunikat do wysłania msg – rozmiar właściwej treści komunikatu (w bajtach) msgflg – flagi specyfikujące zachowanie się funkcji w warunkach nietypowych. Wartość ta może być ustawiona na 0 lub IPC_NOWAIT Wartości zwracane: poprawne wykonanie funkcji: 0 zakończenie błędne: -1 Możliwe kody błędów (errno) w przypadku błędnego zakończenie funkcji: EACCES – kolejka skojarzona z wartością msgid, istnieje, ale proces wołający funkcję nie ma wystarczających praw dostępu do kolejki EAGAIN – kolejka jest pełna, a flaga IPC_NOWAIT była ustawiona EFAULT – niepoprawny wskaźnik msgp EIDRM – kolejka została przeznaczona do usunięcia EINTR – otrzymano sygnał podczas oczekiwania na operację zapisu EINVAL – zły identyfikator kolejki, lub ujemny typ wiadomości, lub zły rozmiar wiadomości
Obsługa kolejki komunikatów Odbieranie komunikatów. Odebrany komunikat jest usuwany z kolejki int msgrcv (int msgid, struct msgbuf *msgp, int msgs, long msgtyp, int msgflg) Argumenty funkcji: msgid – identyfikator kolejki komunikatów msgp – wskaźnik do obszaru pamięci w którym ma zostać umieszczony pobrany komunikat msgs – rozmiar właściwej treści komunikatu msgtyp – określa typ komunikatu który ma być odebrany z kolejki. Możliwe są następujące wartości zmiennej msgtyp: • msgtyp > 0 – pobierany jest pierwszy komunikat typu msgtyp • msgtyp < 0 – pobierany jest pierwszy komunikat którgo wartość typu jest mniejsza lub równa wartości msgtyp • msgtyp = 0 – typ komunikatu nie jest brany, tzn. funkcja pobiera pierwszy komunikat dowolnego typu msgflg – flaga specyfikujące zachowanie się funkcji w warunkach nietypowych. Wartość ta może być ustawiona na 0, IPC_NOWAIT lub MSG_NOERROR Wartości zwracane: poprawne – liczba odebranych bajtów; błędne: -1 Możliwe kody błędów (errno) – analogicznie do funkcji msgsnd
Przykład: producenci i konsumenci // Program producenta #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #define MAX 10 //zakładamy ograniczony bufor komunikatów struct buf_elem { //nasza struktura komunikatu long mtype; int mvalue; }; #define PUSTY 1 #define PELNY 2
Przykład: producenci i konsumenci // Program producenta – c.d. main(){ int msgid, i; struct buf_elem elem; msgid = msgget(45281, IPC_CREAT|IPC_EXCL|0600); //tworzymy kolejke if (msgid == -1){ //jeżeli kolejka już istniała msgid = msgget(45281, IPC_CREAT|0600); // pobieramy jej id if (msgid == -1) exit(1); //błąd utworzenia lub pobrania kolejki }else{ elem.mtype = PUSTY; for (i=0; i<MAX; i++) if (msgsnd(msgid, &elem, sizeof(elem.mvalue), 0) == -1) exit(1); // Błąd wyslania pustego komunikatu } for (i=0; i<10000; i++){ if (msgrcv(msgid,&elem,sizeof(elem.mvalue),PUSTY, 0)== -1) exit(1); //Błąd odebrania pustego komunikatu elem.mvalue = i; elem.mtype = PELNY; exit(1); //Błąd wyslania elementu }}
Przykład: producenci i konsumenci main(){ int msgid, i; struct buf_elem elem; msgid = msgget(45281, IPC_CREAT|IPC_EXCL|0600); if (msgid == -1){ msgid = msgget(45281, IPC_CREAT|0600); if (msgid == -1) exit(1); //błąd utworzenia kolejki komunikatów }else{ elem.mtype = PUSTY; for (i=0; i<MAX; i++) if (msgsnd(msgid, &elem, sizeof(elem.mvalue), 0) == -1) exit(1); //błąd wyslania pustego komunikatu } for (i=0; i<10000; i++){ if (msgrcv(msgid,&elem,sizeof(elem.mvalue),PELNY,0) == -1) exit(1); // błąd odebrania elementu printf("Numer: %5d Wartosc: %5d\n", elem.mvalue); exit(1); // błąd wyslania pustego komunikatu„
Kolejki Komunikatów Posix Podstawowe różnice w stosunku do komunikatów SystemV – Odczyt przekazuje zawsze najstarszy komunikat o najwyższym priorytecie, z systemu V można odczytać najstarszą wiadomość dowolnego typu – Kolejki komunikatów w posiksie mogą generować sygnał lub tworzyć wątek w chwili umieszczenia komunikatu Każdy komunikat w kolejce ma następujące parametry – Priorytet – liczba całkowita bez znaku lub liczbą całkowitą typu long – Rozmiar obszaru danych w komunikacie >=0 – Dane (jeśli rozmiar > 0) Uwaga: Pisząc program w C wykorzystujemy plik nagłówkowy <mqueue.h> Podczas linkowania trzeba dodać przełącznik -lrt
Tworzenie i zamykanie kolejki Posix Tworzenie kolejki komunikatów mqd_t mq_open(const char *name, int oflag,... /* mode_t mode, struct mq_attr *atr */); – name – nazwa zaczynająca się od '/' – oflag – flagi, przy tworzeniu musi być ustawione O_CREATE) – mode – słowo trybu dostępu – atr - dodatkowe parametry, gdy NULL brana wartość domyślna Zamknięcie kolejki int mq_close(mqd_t *mqdes); mqdes – identyfikator kolejki Jeżeli proces się kończy, to wszystkie otwarte przez niego kolejki są zamykane, nie zostają one jednak usunięte!
Usunięcie kolejki Posix int mq_unlink(const char *name); name – nazwa kolejki Funkcja ta usuwa nazwę name wtedy gdy ilość otwarć jest > 0 kolejka nie zostanie jednak usunięta z systemu do czasu ostatniego wywołania mq_close // mq_unlink.c #include <stdlib.h> #include <stdio.h> #include <mqueue.h> int main (int argc, char **argv) { if (0 > mq_unlink("/kolejka")) perror("blad usuwania kolejki"); exit(1); } exit(0);
Odczyt i ustawienie atrybutów kolejki Posix int mq_getattr(mqd_t mqdes, struct mq_attr *attr); int mq_setattr(mqd_t mqdes, const struct mq_attr *attr, struct mq_attr *old_attr); mqdes – identyfikator kolejki attr – atrybuty kolejki old_attr – jeśli != NULL zapisane zostaną stare atrybuty kolejki Atrybuty kolejki tworzą strukturę struct mq_attr { long mq_flags; /*znaczniki kolejki np. O_NONBLOCK*/ long mq_maxmsg; /* maksymalna liczba komunikatów */ long mq_msgsize; /*maksymalny rozmiar komunikatu w bajtach*/ long mq_curmsgs; /* bieżaca liczba kokmunikatów */ }; Wskaźnik do tej struktury można przekazać jako czwarty parametr mq_open i tam ustawić mq_maxmsg i mq_msgsize. mq_setattr może jedynie ustawić mq_flags. Liczby komunikatów nie możemy zmienić za pośrednictwem tych funkcji.
Przykład tworzenia kolejki Posix // mq_create.c #include <stdlib.h> #include <stdio.h> #include <mqueue.h> int main (int argc, char **argv){ int flags; mqd_t mqd; struct mq_attr mqat; flags = O_RDWR | O_CREAT ; * flags |= O_EXCL; */ mqat.mq_maxmsg = 8; mqat.mq_msgsize = 4096; if (0 > (mqd = mq_open("/kolejka",flags,0666,&mqat))) { perror("blad tworzenia kolejki"); exit(1); } mq_getattr(mqd,&mqat); printf("mq_maxmsg: %lu\nmq_msgsize: %lu\nmq_curmsgs: %lu\n", mqat.mq_maxmsg, mqat.mq_msgsize, mqat.mq_curmsgs); mq_close(mqd); exit(0);
Wysyłanie komunikatu do kolejki Posix int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio); mqdes – identyfikator kolejki ptr - początek danych len - rozmiar danych prio – priorytet, liczba całkowita bez znaku. Posix wymaga by można było ustawiać conajmniej 32 priorytety. Wartość ta to MQ_PRIO_MAX w pliku limits.h w przypadku linuxa 32768. Odbiór komunikatu int mq_receive(mqd_t mqdes, char *ptr, size_t len, unsigned int *prio); mqdes – identyfikator kolejki ptr – adres bufora len - rozmiar danych, musi być co najmniej o wielkości maksymalnej wielkości komunikatu. Dlatego często przed odbiorem komunikatu powinniśmy odczytać pole mq_msgsize struktury mq_attr. prio – priorytet. Z kolejki Posix zawsze odbierane są komunikaty o najwyższym priorytecie i najstarsze. Dopuszcza się by przekazać 0 bajtów.
Przykład wysłania komunikatu do kolejki Posix #include <stdlib.h>/* Wysłanie komunikatu mq_send.c */ #include <stdio.h> #include <mqueue.h> #include <string.h> int main (int argc, char **argv){ int flags; mqd_t mqd; struct mq_attr mqat; char msg[256] = "Tekst komunikatu"); flags = O_RDWR | O_CREAT ; if (0 > (mqd = mq_open("/kolejka",flags,0666,NULL))){ perror("blad tworzenia kolejki"); exit(1); } if (0 > mq_send(mqd,msg,sizeof(msg),10)){ perror("blad wyslania komunikatu"); exit(1); } mq_getattr(mqd,&mqat); printf("mq_maxmsg: %lu\nmq_msgsize: %lu\nmq_curmsgs: %lu\n", mqat.mq_maxmsg, mqat.mq_msgsize, mqat.mq_curmsgs); mq_close(mqd); exit(0); }
Przykład odebrania komunikatu z kolejki Posix #include <stdlib.h>/* Odbiór komunikatu mq_receive.c */ #include <stdio.h> #include <mqueue.h> #include <string.h> int main (int argc, char **argv){ int flags; mqd_t mqd; struct mq_attr mqat; char msg[4096]; unsigned int prio = 0; flags = O_RDWR | O_CREAT ; if (0 > (mqd = mq_open("/kolejka",flags,0666,NULL))){ perror("blad tworzenia kolejki"); exit(1);} if (0 > mq_receive(mqd,msg,4096,&prio)){ perror("blad odbioru komunikatu"); exit(1);} printf("Otrzymalem komunikat o priorytecie %u\n",prio); printf("[%s]\n",msg); mq_getattr(mqd,&mqat); printf("mq_maxmsg: %lu\nmq_msgsize: %lu\nmq_curmsgs: %lu\n", mqat.mq_maxmsg, mqat.mq_msgsize, mqat.mq_curmsgs); mq_close(mqd); }
Powiadomienie o komunikacie int mq_notify(mqd_t mqdes, const stuct sigevent *n); n – struktura związana z sygnałami czasu rzeczywistego Posix.1 struct sigevent { int sigev_notify; //SIGEV_{NONE, SIGNAL, THREAD, THREAD_ID} int sigev_signo; // jeżeli SIGEV_SIGNAL to nr sygnału union sigval sigev_value; //przekazywane procedurze obsługi albo wątkowi void (*sigev_notify_function)(union sigval); //używane gdy SIGEV_THREAD pthreaed_attr_t *sigev_notify_attributes; //używane gdy SIGEV_THREAD } union sigval { int sival_int; void *sival_ptr; }; Gdy n!=0 to proces chce być powiadamiany kiedy w określonej kolejce pojawia się komunikat, a kolejka ta była pusta. Gdy n == 0 a proces był zarejestrowany do powiadamiania to informacja ta jest kasowana. Dla jednej kolejki, w danej chwili tylko jeden proces może być zarejestrowany do powiadomienia. Powiadomienie nie jest wysyłane gdy proces został zablokowany na mq_receive czekając na odbiór z danej kolejki komunikatów. Gdy zostanie wysłane powiadomienie to rejestracja jest kasowana i proces powinien znowu wywołać mq_notify. Informowanie po raz drugi nie wystąpi zanim kolejka nie będzie pusta dlatego ponowne mq_notify powinno być przed mq_receive.
Przykład z powiadomieniem (1) #include <stdlib.h> #include <stdio.h> #include <mqueue.h> #include <string.h> #include <signal.h> mqd_t mqd; int flags; char *msg; struct sigevent sigev; struct mq_attr mqat; static void sig_usr1(int signo) { ssize_t n; mq_notify(mqd,&sigev); n = mq_receive(mqd, msg, mqat.mq_msgsize, NULL); printf("SIGUSR! odebrał %ld bajtów\n", (long) n); printf("[%s]\n",msg); } // . . . . .
Przykład z powiadomieniem (1) // c. d. int main (int argc, char **argv){ f = O_RDWR | O_CREAT ; if ( 0> ( mqd = mq_open("/kolejka",f,0666,NULL))) { perror("blad tworzenia kolejki"); exit(1); } mq_getattr(mqd, &mqat); printf("mq_maxmsg: %lu\nmq_msgsize: %lu\nmq_curmsgs: %lu\n", mqat.mq_maxmsg, mqat.mq_msgsize, mqat.mq_curmsgs); msg = malloc(mqat.mq_msgsize); signal(SIGUSR1, sig_usr1); sigev.sigev_notify = SIGEV_SIGNAL; sigev.sigev_signo = SIGUSR1; mq_notify(mqd, &sigev); for (;;) sleep(1); printf("czekam\n"); mq_close(mqd); exit(0);
Przykład z powiadomieniem W procedurze obsługi sygnału w standardzie Posix.1 powinny znaleźć się jedynie funkcje określane jako async-signal-safe a tak nie jest. W poniższym przykładzie funkcja obsługi sygnału będzie zmieniała zmienną globalną, która jest odpowiedzialna za pobranie komunikatu. Sygnał jest generowany tylko w momencie pojawienia się pierwszego komunikatu w pustej kolejce. Dlatego podczas nadejścia komunikatu zostanie uruchomiony wątek, który go odbierze. W tym celu trzeba sigev_notify ustawić na SIGEV_THREAD
Przykład z powiadomieniem (2) // deklaracje #include <stdlib.h> #include <stdio.h> #include <mqueue.h> #include <errno.h> #include <pthread.h> #include <unistd.h> extern int errno; mqd_t mqd; int flags; struct sigevent sigev; struct mq_attr mqat;
Przykład z powiadomieniem (2) // funkcja obslugi wątku static void notify_thread(union sigval arg) { ssize_t n; char *msg; printf("notify_thread START\n"); msg = malloc(mqat.mq_msgsize); mq_notify(mqd,&sigev); while (( n = mq_receive(mqd,msg, mqat.mq_msgsize, NULL)) >= 0) printf("THREAD odebrał %ld bajtów\n", (long) n); printf("[%s]\n",msg); } if (errno != EAGAIN) perror("błąd odbioru"); free(msg); pthread_exit(NULL); printf("notify_thread STOP\n"); return;
Przykład z powiadomieniem (2) //Pętla główna programu int main (int argc, char **argv){ flags = O_RDWR | O_CREAT ; if (0 > (mqd = mq_open("/kolejka",flags,0666,NULL))) { perror("blad tworzenia kolejki"); exit(1); } mq_getattr(mqd,&mqat); printf("mq_maxmsg: %lu\nmq_msgsize: %lu\nmq_curmsgs:%lu\n", mqat.mq_maxmsg,mqat.mq_msgsize,mqat.mq_curmsgs); sigev.sigev_notify = SIGEV_THREAD; sigev.sigev_value.sival_ptr = NULL; sigev.sigev_notify_function = notify_thread; sigev.sigev_notify_attributes = NULL; mq_notify(mqd,&sigev); for (;;) sleep(1); printf("czekam\n"); mq_close(mqd);
Pamięć współdzielona schemat korzystania z pamięci współdzielonej jest specjalnie utworzonym segmentem wirtualnej przestrzeni adresowej, do którego dostęp może mieć wiele procesów. Jest to najszybszy sposób komunikacji pomiędzy procesami. schemat korzystania z pamięci współdzielonej jeden z procesów tworzy segment pamięci współdzielonej, dowiązuje go powodując jego odwzorowanie w bieżący obszar danych procesu, opcjonalnie zapisuje w stworzonym segmencie dane. inne procesy mogą odczytywać i/lub zapisywać wartości w pamięci współdzielonej. Każdy proces uzyskuje dostęp do pamięci współdzielonej względem miejsca wyznaczonego przez jego adres dowiązania, stąd każdy proces korzystając z tych samych danych używa innego adresu dowiązania. Kończąc korzystanie z segmentu pamięci proces może ten segment odwiązać, czyli usunąć jego dowiązanie. Kiedy wszystkie procesy zakończą korzystanie z segmentu pamięci współdzielonej, za jego usunięcie najczęściej odpowiedzialny jest proces, który segment utworzył.
Funkcje systemowe obsługujące pamięć współdzieloną int shmget (key_t key, size_t size, int shmflags) Funkcja służy do tworzenia segmentu pamięci współdzielonej lub do uzyskiwania dostępu do już istniejących segmentów pamięci. W drugim przypadku wartością parametru size może być 0, ponieważ rozmiar segmentu został już wcześniej zadeklarowany przez proces, który go utworzył. key – wartość klucza, który identyfikuje segment pamięci współdzielonej (podobnie jak w przypadku kolejek komunikatów może to być dowolna liczba lub stała IPC_PRIVATE) size – wielkość segmentu pamięci współdzielonej (w bajtach) shmflags – prawa dostępu do pamięci współdzielonej (z prawami dostępu mogą zostać użyte znaczniki IPC_CREAT, IPC_EXCL, ich działanie jest analogiczne jak w przypadku funkcji msgget) Wartości zwracane: poprawne wykonanie funkcji: identyfikator segmentu pamięci współdzielonej zakończenie błędne: -1
Funkcje systemowe obsługujące pamięć współdzieloną int shmctl (int shmid, int cmd, struct shmid_ds *buf) Funkcja odpowiada funkcji msgctl. Przy próbie usunięcia segmentu odwzorowanego na przestrzeń adresową procesu system odpowiada komunikatem o błędzie. Jeśli w wywołaniu funkcji użyje się stałej IPC_RMID, to wartość argumentu buf należy wyzerować, rzutując 0 na typ (shmid_ds *). shmid – identyfikator pamięci współdzielonej cmd – stała specyfikująca rodzaj operacji cmd == IPC_STAT – pozwala uzyskać informację o stanie pamięci współdzielonej cmd == IPC_SET – pozwala zmienić parametry segmentu pamięci cmd == IPC_RMID – pozwala usunąć segment pamięci współdzielonej z systemu buf - wskaźnik na zmienną strukturalną przez którą przekazywane są parametry operacji Wartości zwracane: poprawne wykonanie funkcji: 0 zakończenie błędne: -1
Funkcje systemowe obsługujące pamięć współdzieloną char* shmat (int shmid, char* shmaddr, int shmflg) Funkcja zwraca wskaźnik do segmentu danych, do którego jest dowiązana pamięć Domyślnie dowiązane segmenty są dostępne w trybie do zapisu i odczytu. shmid – identyfikator pamięci współdzielonej zwracany przez funkcję shmget shmaddr – adres dla tworzonego segmentu pamięci współdzielonej lub wartość NULL, która powoduje, że segment dołączany jest w miejscu wybranym przez system (użytkownik nie musi znać rozmieszczenia programu w pamięci) shmflg – określa uprawnienia do segmentu pamięci współdzielonej i specjalne warunki dowiązania char* shmdt (char* shmaddr) Funkcja powoduje odłączenie segmentu pamięci współdzielonej. Argumentem funkcji jest adres stworzonego segmentu pamięci współdzielonej. Odłączenie to powinno nastąpić po zakończeniu pracy z danym segmentem. Po wywołaniu funkcji shmdt licznik dołączeń do segmentu jest zmniejszany o 1.
Przykład korzystania z pamięci współdzielonej //Program 1 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define MAX 10 main(){ int shmid, i; int *buf; shmid = shmget(45281, MAX*sizeof(int), IPC_CREAT|0600); //tworzymy segment 40 bajtów o kluczu 45281 właściciel ma pełne prawa if (shmid == -1){ perror("błąd utworzenia segmentu pamieci wspoldzielonej"); exit(1); } buf = (int*)shmat(shmid, NULL, 0); //przyłączamy segment if (buf == NULL){ perror("błąd przylaczenia segmentu pamieci wspoldzielonej"); for (i=0; i<10000; i++) //cyklicznie zapisujemy liczby do bufora buf[i%MAX] = i;
Przykład korzystania z pamięci współdzielonej //Program 2 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define MAX 10 main(){ int shmid, i; int *buf; shmid = shmget(45281, MAX*sizeof(int), IPC_CREAT|0600); if (shmid == -1) exit(1); //utworzenia/odczytania segmentu pamięci wpsółdzielonej buf = (int*)shmat(shmid, NULL, 0); if (buf == NULL){ exit(1); // błąd przylaczenia segmentu pamieci wspoldzielonej"); } for (i=0; i<10000; i++) printf("Numer: %5d Wartosc: %5d\n", i, buf[i%MAX]);
Przykłady korzystania z POSIX POSIX umożliwia skojarzenie segmentu pamięci współdzielonej: ● ze zwykłym plikiem, aby uzyskać wejście/wyjście oparte na odwzorowaniu w pamięci ● ze specjalnym plikiem, aby uzyskać anonimowe odwzorowanie pamięci ● z funkcją shm_open, aby uzyskać posiksową pamięć wspólną, z której mają korzystać niezależne procesy
Przykłady korzystania z POSIX shm_create.c // Program 1: utworzenie segmentu pamięci współdzielonej #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> int main (int argc, char **argv){ int fd,flags; char *ptr; off_t length; flags = O_RDWR | O_CREAT ; /* flags |= O_EXCL; */ length = 1024; if (-1 == (fd = shm_open("/pamiec_shm",flags,0666))) perror("blad shm_open"); if (-1 == (ftruncate(fd,length))) perror("blad ftruncate"); if (MAP_FAILED == (ptr = mmap(NULL,length,PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0))) perror("blad mmap"); exit(0); }
Przykłady korzystania z POSIX shm_write.c // Program 2: zapis do segmentu pamięci współdzielonej #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int main (int argc, char **argv){ int ii,fd; struct stat stat; unsigned char *ptr; if (-1 == (fd = shm_open("/pamiec_shm",O_RDWR, 0666))) perror("blad shm_open"); if (-1 == (fstat(fd, &stat))) perror("blad fstat"); if (MAP_FAILED == (ptr = mmap(NULL,stat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0))) perror("blad mmap"); if (-1 == close(fd)) perror("blad close"); for (ii = 0;ii<stat.st_size;ii++) *ptr++=ii%256; exit(0); }
Przykłady korzystania z POSIX shm_read.c //Program 3: odczyt z pamięci współdzielonej // #include j. w. int main (int argc, char **argv){ int ii,fd; struct stat stat; unsigned char c, *ptr; if (-1 == (fd =shm_open("/pamiec_shm",O_RDONLY,0666))) perror("blad shm_open"); if (-1 == fstat(fd, &stat)) perror("blad fstat"); if (MAP_FAILED == (ptr = mmap(NULL,stat.st_size,PROT_READ,MAP_SHARED, fd, 0))) perror("blad mmap"); if (-1 == close(fd)) //sprawdzenie czy faktycznie jest tak jak zapisaliśmy for (ii = 0; ii < stat.st_size; ii++){ c = *ptr++; if (!(ii%16)) printf("\n"); if (!(ii%256)) printf("\n"); printf("%.2X ",c); if (c != (ii%256)) printf("Jednak nie są równe"); } printf("\n");exit(0);
Przykłady korzystania z POSIX shm_unlink.c //Program 4: usunięcie obiektu pamięci współdzielonej #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <sys/mman.h> int main (int argc, char **argv) { if (!shm_unlink("/pamiec_shm")) printf("Ok nazwa obiektu usunięta\n"); else perror("Błąd odlinkowywania pamięci"); exit(0); }
Semafory Semafor to struktura danych wspólna dla kilku procesów. Służy do synchronizowania kilku procesów korzystających ze wspólnego zasobu – zapobiega sytuacjom hazardowym – może zapobiec zakleszczeniu lub zagłodzeniu procesów. Semafor to liczba całkowita nieujemna przechowywana w jądrze systemu skojarzona z zasobem o wartości równej liczbie dostępnych zasobów tego typu. Proces, który żąda zasobu sprawdza wartość semafora – wartość dodatnia – zasób jest dostępny. Przed rozpoczęciem korzystania z zasobu proces zmniejsza wartość semafora. – wartość zerowa – nie ma wolnych zasobów i proces musi czekać. – zwolnienie zasobu przez proces – zwiększenie wartości semafora i wysłanie powiadomienia do kolejki procesów oczekujących na zasób W Uniksie mamy dostępne Semafory Systemu V Semafory Posiksowe nazwane Semafory Posiksowe w pamięci wspólnej
Porównanie semaforów i muteksów pthread_mutex_lock(&mutex); /*********************/ /* sekcja krytyczna */ pthread_mutex_unlock(&mutex); sem_wait(&sem); /*********************/ /* sekcja krytyczna */ sem_post(&sem); Niemal identyczne zachowanie. Jednak odblokowanie muteksu może dokonać tylko wątek który go zablokował. Wywołanie odblokowania muteksu więcej niż 1 raz nie jest pamiętane. Muteksy używamy tylko do synchronizacji wątków. Semafory używamy do synchronizacji wątków i procesów
Przypadki wykorzystania semaforów
Metody semaforów Posix
Obsługa semaforów w Posix Utworzenie / otwarcie obiektu semafora sem_t* sem_open(const char*nam, int oflg, mode_t mod, unsigned int val); nam – nazwa zgodna z normą posix (zaczyna się od „/” ) oflg – czy tworzymy czy tylko otwieramy (O_CREAT | O_EXCL) mod – jeżeli tworzymy to podajemy bity uprawnień val – jeżeli tworzymy, wartość początkowa semafora Wartość zwracana: gdy OK zwróci wskaźnik do semafora, gdy błąd zwróci wartość SEM_FAILD = 1. zamknięcie obiektu semafora sem_t *sem_close(sem_t *sem); usunięcie obiektu semafora sem_t *sem_unlink(const char *name);
Sygnalizowanie i sprawdzanie wartości int sem_post(sem_t *sem); Działanie: Gdy wartość semafora > 0, zwiększy ją o 1 Gdy wartość semafora == 0, zwiększy o jeden i obudzi jeden z wątków/procesów czekających na wartość >0. Wartość zwracana -1 błąd 0 OK Sprawdzanie wart. int sem_getvalue(sem_t *sem, int *valp); Działanie: Zwraca wartość semafora pod wskaźnik valp. Jeżeli *valp<0 to -*valp oznacza liczbę procesów czekających w kolejce na semafor. Wartość zwracana -1 błąd 0 OK
Przykład użycia semaforów: sem_create.c // Tworzenie obiekyu semafora nazwanego #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <semaphore.h> int main (int argc, char **argv) { int flags; sem_t *sem; unsigned int value; flags = O_RDWR | O_CREAT ; /* flags |= O_EXCL; */ value = 1; if (0 > (sem = sem_open("/semafor",flags,0666,value))) perror("blad tworzenia semafora"); exit(1); } printf("Ok semafor utworzony\n"); sem_close(sem); printf("Ok semafor zamknięty\n"); exit(0);
Przykład użycia semaforów: sem_getvalue.c // Pobranie wartości semafora nazwanego // Polecenia #include – jak wyżej int main (int argc, char **argv) { sem_t * sem; int value; int flags = 0; flags = O_RDWR | O_CREAT ; /* flags |= O_EXCL; */ value = 1; if (0 > (sem = sem_open("/semafor",flags,0666,value))) perror("blad otwierania semafora"); exit(1); } printf("Ok semafor otwarty\n"); sem_getvalue(sem,&value); printf("Wartość: %d\n",value); sem_close(sem); printf("Ok semafor zamknięty\n"); exit(0);
Przykład użycia semaforów: sem_wait.c // Przykład czekania na semaforze // Polecenia #include – jak wyżej int main (int argc, char **argv){ sem_t* sem; int value; int flags = 0; flags = O_RDWR | O_CREAT ; /* flags |= O_EXCL; */ value = 1; if (0 > (sem = sem_open("/semafor",flags,0666,value))) { perror("blad otwierania semafora"); exit(1); } printf("Ok semafor otwarty\n"); sem_wait(sem); printf("Ok semafor podniesiony\n"); sem_getvalue(sem,&value); printf("Wartość: %d\n",value); sem_close(sem); printf("Ok semafor zamknięty\n"); exit(0);
Przykład użycia semaforów: sem_signal.c // Przykład sygnalizowania // Polecenia #include – jak wyżej int main (int argc, char **argv) { sem_t * sem; int value; int flags = 0; flags = O_RDWR | O_CREAT ; /* flags |= O_EXCL; */ value = 1; if (0 > (sem = sem_open("/semafor",flags,0666,value))) perror("blad otwierania semafora"); exit(1); } printf("Ok semafor otwarty\n"); sem_post(sem); printf("Ok semafor opuszczony\n"); sem_getvalue(sem,&value); printf("Wartość: %d\n",value); sem_close(sem); printf("Ok semafor zamknięty\n"); exit(0);
Przykład użycia semaforów: sem_unlink.c // Kasowanie semafora #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <semaphore.h> int main (int argc, char **argv) { sem_unlink("/semafor"); printf("Ok semafor usunięty\n"); exit(0); }
Semafory nienazwane Posix Tworzenie semafora int sem_init(sem_t *sem, int shared, unsigned int value); sem – semafor zaalokowany przez użytkownika shared jeśli 0 to wspólny tylko dla wątków jeśli <> 0 to dla procesów i sem musi być w pamięci wspólnej. value wartość początkowa semafora Usuwanie semafora int sem_destroy(sem_t *sem); Należy zwrócić uwagę by sem_init wywoływać tylko raz // Uwaga – niepoprawny kod sem_t mysem; sem_init(mysem, 1, 0); if (fork() == 0){ sem_post(mysem); } sem_wait(&mysem); Jeżeli sem przeznaczony jest do pamięci dzielonej trzeba uważać by nie operować na kopiach semaforów!
Literatura dr inż. Jerzy Ułasiewicz Programowanie aplikacji współbieżnych – Sygnały i ich obsługa http://www.zak.ict.pwr.wroc.pl/ulasiewicz/ dr Anna Kobusińska, Politechnika Poznańska. Materiały dydaktyczne http://www.cs.put.poznan.pl/akobusinska/ dr inż. Paweł Paduch, Katedra Informatyki Politechniki Świętokrzyskiej w Kielcach Wykład z Programowania współbieżnego http://achilles.tu.kielce.pl/Members/ppaduch/ Ważniak – laboratorium z systemów operacyjnych http://wazniak.mimuw.edu.pl/index.php?title=Systemy_operacyjne#Laboratorium