Wątek 2 - Wojciech Bieniecki

Report
Systemy operacyjne
Wykład 6b
Wątki, mechanizmy IPC
dr inż. Wojciech Bieniecki
Instytut Nauk Ekonomicznych
i Informatyki
http://wbieniec.kis.p.lodz.pl/pwsz
1
Wielowątkowość
Jeden proces może wykonywać się w wielu współbieżnych wątkach (ang. thread).
Każdy wątek (inna nazwa: proces lekki, ang. lightweight process)
– Ma swój własny stan (Aktywny, Gotowy, Zablokowany, ... )
– Ma swoje wartości rejestrów i licznika rozkazów.
– Ma swój własny stos (zmienne lokalne funkcji).
– Ma dostęp do przestrzeni adresowej, plików i innych zasobów procesu.
Wszystkie wątki procesu współdzielą te zasoby.
Procesy są od siebie izolowane, wątki nie !
Operacje zakończenia, zawieszenia procesu dotyczą wszystkich wątków.
Przełączanie pomiędzy równoprawnymi wątkami jest „tanie” (szybsze) w
porównaniu z przełączaniem pomiędzy tradycyjnymi procesami (nie trzeba
przełączać kontekstu pamięci).
Wątki zyskują na popularności ponieważ – mając pewne cechy ciężkich procesów
2
– są efektywniejsze w działaniu.
Schemat procesu i wątków
Proces
Przestrzeń adresowa
Otwarte pliki
Procesy potomne
Obsługa sygnałów
Sprawozdawczość
Zmienne globalne
Wątek 1
Licznik rozkazów
Rejestry
Stos i wskaźnik stosu
Stan
Wątek 2
Licznik rozkazów
Rejestry
Stos i wskaźnik stosu
Stan
Wątek 2
Licznik rozkazów
Rejestry
Stos i wskaźnik stosu
Stan
3
Proces z wątkami
Standardowy Unix
MS-DOS
Linux, MS-Windows, POSIX,
OS/2, Solaris
4
Cechy wątków
Zalety
– Utworzenie i zakończenie wątku zajmuje znacznie mniej czasu niż w
przypadku procesu
– Możliwość szybkiego przełączania kontekstu pomiędzy wątkami tego
samego procesu
– Możliwość komunikacji wątków bez pośrednictwa systemu operacyjnego
– Możliwość wykorzystania maszyn wieloprocesorowych
Wady
Źle zachowujący się wątek może zakłócić pracę innych wątków tego
samego procesu. W przypadku dwóch procesów o odrębnych
przestrzeniach adresowych nie jest to możliwe
5
Wątki na poziomie użytkownika
ang. user-level threads
System operacyjny nie jest świadom istnienia
wątków.
Zarządzanie wątkami jest przeprowadzane
przez bibliotekę w przestrzeni użytkownika.
Przykład: Wątek A wywołuje funkcję read.
Standardowo funkcja systemowa read jest
synchroniczna (usypia do momentu
zakończenia operacji). Jednak implementacja
w bibliotece wywołuje wersję asynchroniczną i
przełącza się do wątku B.
Rozwiązanie to jest szybkie, ma jednak wady:
– Dwa wątki nie mogą się wykonywać współbieżnie na dwóch różnych
procesorach.
– Nie można odebrać procesora jednemu wątkowi i przekazać drugiemu
6
Wątki na poziomie jądra
ang. kernel-level threads
Wątek jest jednostką systemu
operacyjnego.
Wątki podlegają szeregowaniu
przez jądro.
W systemie SMP* wątki mogą się
wykonywać na różnych
procesorach – przetwarzanie
równoległe.
Windows i Linux wykorzystują tę
metodę.
*) SMP (ang. Symmetric Multiprocessing, przetwarzanie symetryczne) - architektura komputerowa, która
pozwala na znaczne zwiększenie mocy obliczeniowej systemu komputerowego poprzez wykorzystanie dwóch
7
lub więcej procesorów do jednoczesnego wykonywania zadań.
Przykład użycia wątków: Serwer WWW
while(TRUE) {
getNextRequest(&buf);
handoffWork(&buf);
}
while(TRUE) {
waitForWork(&buf);
lookForPageInCache(&buf,&page);
if(pageNotInCache(&page)) {
readPageFromDisk(&buf,&page);
}
returnPage(&page);
}
Technika puli wątków: Zamiast tworzyć nowy wątek do obsługi kolejnego żądania, mamy
zbiór wątków stale gotowych do działania.
8
Nie ponosimy kosztu tworzenia i usuwania wątków.
Możliwe problemy z wątkami
Przykład:
errno to uniksowy mechanizm zgłaszania błędów przez funkcje libc,
a w szczególności jądra.
Jeśli funkcja zakończy się błędem, sygnalizuje to zwracając zwykle -1
lub NULL.
Program powinien wtedy zajrzeć do zmiennej globalnej errno, żeby
dowiedzieć się jaki dokładnie błąd wystąpił.
Jeśli funkcja zakończy się pomyślnie zawartość errno nie jest
zdefiniowana.
9
Możliwe problemy z wątkami
#include
#include
#include
#include
<stdio.h>
<errno.h>
<stdlib.h>
<string.h>
/*
/*
/*
/*
fprintf */
errno */
malloc, free, exit */
strerror */
extern int errno;
int main( void )
{
/* deklaracja wskaźnika do tablicy o pojemności 2GB
*/
char *ptr = malloc( 2000000000UL );
if ( ptr == NULL ){
puts("malloc failed");
puts(strerror(errno));
}
else
{
/* Pomyślnie zaalokowano tablicę
*/
free( ptr );
}
exit(EXIT_SUCCESS); /* exiting program */
}
10
Możliwe problemy z wątkami
W standardowej bibliotece C, w wersji wielowątkowej, errno jest implementowane jako
prywatna zmienna globalna (nie współdzielona z innymi wątkami)
Gdy kilka wątków jednocześnie wywołuje funkcje malloc/free - może dojść do
uszkodzenia globalnych struktur danych (listy wolnych bloków pamięci)
Potrzeba synchronizacji => może prowadzić do spadku wydajności
11
Inne problemy z wątkami
Proces otrzymuje sygnał:
– Wszystkie wątki otrzymują sygnał
– Wybrany wątek otrzymuje sygnał
– Wątek aktualnie aktywny otrzymuje sygnał
Proces wykonuje fork.
– Czy duplikować jedynie działający wątek, czy też wszystkie wątki ?
Proces wywołuje exit.
– Zakończyć proces czy też jedynie aktywny wątek ?
Anulowanie wątku (ang. cancellation).
– Wykonać natychmiast .
– Wątek co jakiś czas sprawdza czy nie został anulowany
Standard POSIX zawiera odpowiedzi na powyższe problemy.
12
Implementacje wątków – POSIX
void *thread(void *param) {
// tu kod wątku
// możemy przekazać wynik
return NULL;
}
int main()
{
pthread_t id;
// Parametr przekazywany wątkowi
void *param=NULL;
pthread_create(&id,NULL,&thread,param);
// Funkcja thread w odrębnym wątku współbieżnie
// z main. id przechowuje identyfikator wątku
void *result;
// Czekaj na zakończenie wątku
pthread_join(id,&result);
// Wynik w result, zamiast &result można
// przekazać NULL
}
Funkcja pthread_create tworzy
nowy wątek. Rozpoczyna on pracę
od funkcji, której adres przekazano
jako trzeci argument.
Funkcja pthread_join usypia
wywołujący ją wątek do
momentu, kiedy wątek o
identyfikatorze przekazanym
jako pierwszy argument
zakończy pracę.
Zakończenie pracy wątku –
powrót z funkcji, która go
rozpoczyna.
13
Obsługa wątków w POSIX
W Linuxie wątki mogą być tworzone poprzez funkcję pthread_create().
Podobnie jak fork() funkcja pthread_create() tworzy nowy kontekst wykonywania.
Nowy wątek dzieli z tworzącym go procesem PID, przestrzeń adresową, deskryptory plików
itp.
Wątek kończy swoje działanie w momencie kiedy skończy się wykonywanie funkcji
przekazanej jako parametr do pthread_create().
W Linuxie wątki mogą być także tworzone poprzez funkcję clone().
Różnica pomiędzy pthread_create() a clone(). Można wskazać:
które typy zasobów będą a które nie będą współdzielone (np. identyfikatory plików,
uchwyty sygnałów itp.).
jaki będzie posiadała PID procesu macierzystego
jaki sygnał (jeśli w ogóle) będzie dostarczał informacji twórcy wątku o zakończeniu
wątku .
gdzie będzie umieszczony stos nowego wątku.
Wątek kończy swoje działanie w momencie kiedy skończy się wykonywanie funkcji
przekazanej jako parametr do clone().
14
Włókna – Win32 API
Włókna (ang. fibers) koncepcyjnie są to miniwątki, podobnie jak wątki dysponują
własnym kontekstem wykonawczym – posiadają własny stos i chronią zawartość
rejestrów procesora.
Włókna w odróżnieniu od wątków nie są wywłaszczane przez system operacyjny z
czasu procesora – za przełączanie włókien jest odpowiedzialna sama aplikacja.
15
Zadania - Windows
W systemie Windows 2000 i późniejszych istnieje możliwość zgrupowania pewnej liczby
procesów i zarządzania nimi w ramach tak zwanego zadania (ang. job).
Funkcje pozwalające zarządzać zadaniami dają większe
standardowe funkcje dedykowane procesom.
możliwości kontroli niż
Zadaniom można nakładać ograniczenia takie jak określenie maksymalnego czasu, w
trybie użytkownika, jaki jest przyznawany każdemu procesowi (proces używający więcej
czasu jest kończony), maksymalny czas, w trybie użytkownika, przyznany wszystkim
procesom zadania itp.
Do umieszczenia procesu w zadaniu służy specjalna funkcja systemu:
BOOL AssignProcessToJobObject(HANDLE hJob, HANDLE hProcess);
Umieszczenie kilku procesów w zadaniu umożliwia między innymi ich równoczesne
zakończenie.
Dodatkowo istnieje możliwość powiadamiania o tym co się dzieje w zadaniu, np. który z
procesów się zakończył i czemu.
16
Powinowactwo
Powinowactwo (ang. thread affinity) – powiązanie procesu/wątku ze zdefiniowaną
jednostką centralną
Istnieją dwa modele powinowactwa:
słabe powinowactwo – nie ma gwarancji, że wątek, który wykonywał się na
procesorze X będzie ponownie wykonywany na tym samym procesorze;
silne powinowactwo – system gwarantuje, że dany proces będzie się wykonywał
tylko na procesorach wskazanych przez użytkownika.
System Windows 2000 standardowo stosuje słabe powinowactwo. Istnieje grupa funkcji
systemowych, która pozwala wymusić silne powinowactwo. Silne powinowactwo jest
szczególnie pożądane w komputerach o architekturze NUMA (ang. Non-Uniform Memory
17
Access – nie jednolity dostęp do pamięci).
Metody komunikacji międzyprocesowej
Komunikacja międzyprocesowa (ang. Inter-Process Communication
— IPC) – sposoby komunikacji pomiędzy procesami systemu
operacyjnego.
Pojęcie IPC może odnosić się do wymiany informacji w systemach
rozproszonych (klastrów, systemów odległych połączonych siecią).
Mechanizmy IPC opierają się na budowaniu w pamięci lub na dysku
dynamicznych struktur, używanych do transmisji komunikatów
pomiędzy procesami
18
Metody komunikacji międzyprocesowej
Lista metod IPC 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.
19
Pliki zwykłe w UNIX/LINUX
Jądro systemu operacyjnego UNIX udostępnia dwie operacje na plikach
realizowane odpowiednio przez funkcje systemowe
Odczyt – funkcja read
Zapis – funkcja write
Z punktu widzenia jądra w systemie UNIX plik nie ma żadnej struktury – jest
traktowany jako tablica bajtów. Operacje odczytu lub zapisu mogą dotyczyć
dowolnego fragmentu pliku, określonego z dokładnością do bajtu.
Plik w UNIX identyfikowany jest przez nazwę.
Podawanie nazwy pliku przy każdym odwołaniu do niego wymagałoby
przeszukiwania katalogów w celu jego odnalezienia.
Dlatego wprowadzono została funkcję systemową open, której zadaniem jest
zaalokowanie niezbędnych zasobów w jądrze, umożliwiających wykonywanie
dalszych operacji na pliku bez potrzeby przeszukiwania katalogów.
Pliki zwykłe w UNIX
Funkcja open zwraca deskryptor (liczba), który będzie przekazywany jako
parametrem dla funkcji systemowych związanych z operacjami na otwartych
plikach.
Standardowo zajęte są deskryptory:
0 – standardowe wejście,
1 – standardowe wyjście
2 –standardowe wyjście diagnostyczne
Przy otwieraniu pliku przekazywany jest tryb otwarcia, określający
dopuszczalne operacje, jakie można wykonać w związku z tym otwarciem, np.
tylko zapis, tylko odczyt lub zapis i odczyt.
Tryb otwarcia może mieć również wpływ na sposób wykonania tych operacji,
np. każda operacja zapisu
Jądro systemu operacyjnego dostarcza też mechanizm tworzenia plików –
dostępny przez funkcję systemową creat, która tworzy plik i otwiera go w
trybie do zapisu, zwracając odpowiedni deskryptor.
Pliki zwykłe w UNIX
Tworzenie i otwieranie plików realizowane jest za pomocą funkcji:
open - otwarcie pliku (uogólniona funkcja open umożliwia również
utworzenie pliku)
creat - utworzenie pliku i otwarcie do zapisu
dup - utworzenie kopii deskryptora i nadanie jej pierwszego wolnego
numeru z tablicy otwartych plików
dup2 - utworzenie kopii deskryptora, umożliwiające określenie jej
identyfikatora przez użytkownika
close - zamknięcie deskryptora otwartego pliku,
unlink - usunięcie dowiązania do pliku
Operacje na plikach realizowane są za pomocą funkcji:
read - odczyt fragmentu pliku,
write - zapis fragmentu pliku,
lseek - przesunięcie wskaźnika bieżącej pozycji
Powyższe funkcje zdefiniowane są w pliku nagłówkowym fcntl.h.
Struktury danych w jądrze systemu związane z
otwartymi plikami
23
Łącza komunikacyjne
Łącza w UNIX są plikami specjalnymi.
Są podobne do plików zwykłych – posiadają swój i-węzeł, posiadają bloki z danymi, na
otwartych łączach można wykonywać operacje zapisu i odczytu.
Czym różnią się od plików zwykłych
• ograniczona liczba bloków – łącza mają rozmiar 4KB – 8KB w zależności od
konkretnego systemu,
• dostęp sekwencyjny – na łączach można wykonywać tylko operacje zapisu i odczytu,
ale nie można wykonywać funkcji lseek)
• sposób wykonywania operacji zapisu i odczytu – dane odczytywane z łącza są
zarazem usuwane (nie można ich odczytać ponownie)
• proces jest blokowany w funkcji read na pustym łączu i w funkcji write, jeśli w łączu
nie ma wystarczającej ilości wolnego miejsca, żeby zmieścić zapisywany blok
Łącza komunikacyjne – cechy wspólne i
różnice
Łącze nazwane (tzw kolejki FIFO).
Łącze nienazwane (tzw. potok)
Posiada dowiązanie w systemie plików
(istnieje jako plik w jakimś katalogu)
nie ma dowiązania w systemie plików –
istnieje tylko tak długo, jak jest otwarte
Może być identyfikowane przez nazwę
Jest identyfikowane tylko przez deskryptory
Po zamknięciu pozostaje przydzielony iwęzeł, ale wszystkie jego bloki na dysku są
zwalniane.
Po zamknięciu wszystkich jego
deskryptorów przestaje istnieć (zwalniany
jest jego i-węzeł i wszystkie bloki)
Procesy, które chcą komunikować się za pomocą łącza nienazwanego, muszą znać jego
deskryptor.
Przykład użycia łącza nazwanego
Utworzone metodą mkfifo łącze musi zostać otwarte przez użycie funkcji open.
Funkcja ta musi zostać wywołana przynajmniej przez dwa procesy w sposób
komplementarny, tzn. jeden z nich musi otworzyć łącze do zapisu, a drugi do odczytu.
Odczyt i zapis danych z łącza nazwanego odbywa się za pomocą funkcji read i write
mkfifo("kolejka",0666);
desc = open("kolejka" , O_RDONLY);
desc = open("kolejka" , O_WRONLY);
read(desc);
write(desc);
close(desc);
close(desc);
unlink("kolejka");
26
Użycie łączy nienazwanych
27
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
28
Sygnały w systemie Unix
Funkcje obsługujące sygnały zawarte są w bibliotece <signal.h>
29
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.
30
Ważniejsze funkcje systemowe dla
sygnałów
31
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.).
32
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.
33
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.
34
Pamięć współdzielona
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ł.
35
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
36
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
37
Przypadki wykorzystania semaforów
38
Literatura
Ważniak – laboratorium z systemów operacyjnych
http://wazniak.mimuw.edu.pl/index.php?title=Systemy_operacyjne#Laboratorium
Wojciech Kwedlo, Wykład z Systemów Operacyjnych Wydział Informatyki Politechniki Białostockiej
http://aragorn.pb.bialystok.pl/~wkwedlo/OS-Slides-new.html
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/
Beej's Guide to Unix IPC
http://beej.us/guide/bgipc/
Robert Love: Linux. Programowanie systemowe. Helion 2008
Stevens R.W.: Programowanie w środowisku systemu UNIX. WNT, 2002
Havilland K., Gray D., Salama B.: Unix - programowanie systemowe. ReadMe, 1999
39

similar documents