Pobieranie prezentacji. Proszę czekać

Pobieranie prezentacji. Proszę czekać

Systemy operacyjne Wykład 6b Wątki, mechanizmy IPC dr inż. Wojciech Bieniecki Instytut Nauk Ekonomicznych i Informatyki

Podobne prezentacje


Prezentacja na temat: "Systemy operacyjne Wykład 6b Wątki, mechanizmy IPC dr inż. Wojciech Bieniecki Instytut Nauk Ekonomicznych i Informatyki"— Zapis prezentacji:

1 Systemy operacyjne Wykład 6b Wątki, mechanizmy IPC dr inż. Wojciech Bieniecki Instytut Nauk Ekonomicznych i Informatyki 1

2 Wielowątkowość 2 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. 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 – są efektywniejsze w działaniu. Procesy są od siebie izolowane, wątki nie !

3 Schemat procesu i wątków 3 Proces Przestrzeń adresowa Otwarte pliki Procesy potomne Obsługa sygnałów Sprawozdawczość Zmienne globalne 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 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 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

4 Proces z wątkami 4 Standardowy Unix MS-DOS Linux, MS-Windows, POSIX, OS/2, Solaris

5 Cechy wątków 5 Zalety – Utworzenie i zakończenie wątku zajmuje znacznie mniej czasu niż w przypadku procesu 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 – 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

6 Wątki na poziomie użytkownika ang. user-level threads 6 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

7 Wątki na poziomie jądra ang. kernel-level threads 7 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 lub więcej procesorów do jednoczesnego wykonywania zadań.

8 Przykład użycia wątków: Serwer WWW 8 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. Nie ponosimy kosztu tworzenia i usuwania wątków.

9 Możliwe problemy z wątkami 9 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.

10 Możliwe problemy z wątkami 10 #include /* fprintf */ #include /* errno */ #include /* malloc, free, exit */ #include /* strerror */ extern int errno; int main( void ) { /* deklaracja wskaźnika do tablicy o pojemności 2GB */ char *ptr = malloc( UL ); if ( ptr == NULL ){ puts("malloc failed"); puts(strerror(errno)); } else { /* Pomyślnie zaalokowano tablicę */ free( ptr ); } exit(EXIT_SUCCESS); /* exiting program */ }

11 Możliwe problemy z wątkami 11 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

12 Inne problemy z wątkami 12 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.

13 Implementacje wątków – POSIX 13 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.

14 Obsługa wątków w POSIX 14 W Linuxie wątki mogą być tworzone poprzez funkcję pthread_create(). W Linuxie wątki mogą być także tworzone poprzez funkcję clone(). 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(). Różnica pomiędzy pthread_create() a clone(). Można wskazać: Wątek kończy swoje działanie w momencie kiedy skończy się wykonywanie funkcji przekazanej jako parametr do clone(). 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.

15 Włókna – Win32 API 15 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.

16 Zadania - Windows 16 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 możliwości kontroli niż standardowe funkcje dedykowane procesom. 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.

17 Powinowactwo 17 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 Access – nie jednolity dostęp do pamięci).

18 Metody komunikacji międzyprocesowej 18 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

19 Metody komunikacji międzyprocesowej 19 pliki i blokady – najprostsza i najstarsza forma IPC Lista metod IPC obejmuje: 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.

20 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. 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. Plik w UNIX identyfikowany jest przez nazwę.

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

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

23 Struktury danych w jądrze systemu związane z otwartymi plikami 23

24 Łą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

25 Łącza komunikacyjne – cechy wspólne i różnice Łącze nazwane (tzw kolejki FIFO). Procesy, które chcą komunikować się za pomocą łącza nienazwanego, muszą znać jego deskryptor. Łącze nienazwane (tzw. potok) Posiada dowiązanie w systemie plików (istnieje jako plik w jakimś katalogu) Może być identyfikowane przez nazwę Po zamknięciu pozostaje przydzielony i- węzeł, ale wszystkie jego bloki na dysku są zwalniane. nie ma dowiązania w systemie plików – istnieje tylko tak długo, jak jest otwarte Jest identyfikowane tylko przez deskryptory Po zamknięciu wszystkich jego deskryptorów przestaje istnieć (zwalniany jest jego i-węzeł i wszystkie bloki)

26 Przykład użycia łącza nazwanego 26 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); close(desc); unlink("kolejka"); write(desc); close(desc);

27 Użycie łączy nienazwanych 27

28 Sygnały 28 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

29 Sygnały w systemie Unix 29 Funkcje obsługujące sygnały zawarte są w bibliotece

30 Sygnały czasu rzeczywistego 30 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: 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. Sygnały są kolejkowane

31 Ważniejsze funkcje systemowe dla sygnałów 31

32 Kolejka komunikatów 32 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.).

33 Kolejka komunikatów 33 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. 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

34 Obsługa kolejki komunikatów 34 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 : /* 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.

35 Pamięć współdzielona 35 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ł.

36 Semafory 36 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. W Uniksie mamy dostępne Semafory Systemu V Semafory Posiksowe nazwane Semafory Posiksowe w pamięci wspólnej 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

37 Porównanie semaforów i muteksów 37 pthread_mutex_lock(&mutex); /*********************/ /* sekcja krytyczna */ /*********************/ pthread_mutex_unlock(&mutex); pthread_mutex_lock(&mutex); /*********************/ /* sekcja krytyczna */ /*********************/ pthread_mutex_unlock(&mutex); sem_wait(&sem); /*********************/ /* sekcja krytyczna */ /*********************/ sem_post(&sem); 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

38 Przypadki wykorzystania semaforów 38

39 Literatura 39 Ważniak – laboratorium z systemów operacyjnych Wojciech Kwedlo, Wykład z Systemów Operacyjnych Wydział Informatyki Politechniki Białostockiej dr inż. Jerzy Ułasiewicz Programowanie aplikacji współbieżnych – Sygnały i ich obsługa dr Anna Kobusińska, Politechnika Poznańska. Materiały dydaktyczne dr inż. Paweł Paduch, Katedra Informatyki Politechniki Świętokrzyskiej w Kielcach Wykład z Programowania współbieżnego Beej's Guide to Unix IPC 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


Pobierz ppt "Systemy operacyjne Wykład 6b Wątki, mechanizmy IPC dr inż. Wojciech Bieniecki Instytut Nauk Ekonomicznych i Informatyki"

Podobne prezentacje


Reklamy Google