# SO Lista 9+1 ## Zadanie 1 ![](https://i.imgur.com/zJcdy57.png) ### przetwarzanie równoległe <ul> <li>polega na tworzeniu podprocesów</li> <li>operuje na innych address space'ach</li> <li>cieżej jest współdzielić dane i zasoby programu</li> <li>jednocześnie przetwarzanie wielu zadań (na osobnych procesach)</li> </ul> ### przetwarzanie współbieżne <ul> <li>polega na tworzeniu nowych "unit of execution" w istniejącym już procesie</li> <li>operuje na tym samym address space</li> <li>współdzielenie danych nie wymaga żadnych specjalnych operacji</li> <li>też jednoczesne przetwarzanie zadań, ale na 1 procku, tworzymy po prostu więcej wątków egzekucji procesu, które będą realizowane naprzemiennie </li> </ul> ![](https://i.imgur.com/ir7iNzx.png) ### procedury wielobieżne (miliony definicji podobno ale) funckcje które można bezpiecznie wywołać ponownie (`re-enter`), po tym jak ich wykonanie zostanie przerwane np. przez sygnał, wywołanie innych funkcji w niej itp. Recap kilku zasad <ul> <li>nie zaleca się używać statycznych i globalnych mutowalnych zmiennych bez synchronizacji</li> <li>locków też nie mogą używać -> przykład z wykładu</li> </ul> ### procedury thread-safe taka której wolno nam używać na kilku wątkach jednocześnie ### a) wielobieżna, nie wielowątkowo-bezpieczna ```clike int c; void incr(){ c++; } /* jest wielobieżna, bo wywołanie jej jeszcze raz nie zrobi nic złego, ale MT-safe nie jest, bo race do zmiennej */ ``` ### b) wielowątkowo-bezpieczna, nie wielobieżna ```clike printf() //używa locków ``` tu nie wiem czy jakiś komentarz potrzebny czy po prostu fakt tych locków + wiedza z wykładu? ### Kiedy w jednowątkowym procesie uniksowym może wystąpić współbieżność? Możemy zasymulować współbieżność używając `setjmp()` i `longjmp()` oraz musimy stworzyć stosy samodzielnie na każdy symulowany `kontekst wykonania` (aka nasz wątek) ![](https://i.imgur.com/9iF8agh.jpg) ![](https://i.imgur.com/cHpmYPV.jpg) ## Zadanie 2 ![](https://i.imgur.com/WNphB4m.png) ### deadlock sytuacja, gdzie 2 wątki (lub więcej) czekają na siebie nawzajem aż np. zwolnią zasób, zasygnalizują zakończenie zadania itp. ::: info A, B -> wątki X, Y -> zasoby A rzuca locka na zasób X B rzuca locka na zasób Y A requestuje zasób Y B requestuje zasób X ::: ![](https://i.imgur.com/0d0cogV.png) ### livelock taki deadlock tylko stan procesów się zmienia. 2 wątki, aby uniknąć deadlocka, wyłączają swoje działanie lub podejmująinne akcje, ale robią to w tym samym momencie, więc i tak nikt z nich nie idzie dalej (np. jak 2 zasoby chcą się przepuścić przez drzwi nawzajem) ::: info A, B -> wątki X -> zasób A chce by B skorzystał z zasobu pierwszy B chce by A skorzystał z zasobu pierwszy zmieniają ciągle stan, ale nie idą do przodu (być może zmieniają jakąś zmienną owner_of_zasób czy coś) ::: ![](https://i.imgur.com/HXE0cKm.png) ### starvation sytuacja, gdzie wątki o większym priorytecie mają dużo do roboty lub jest ich po prostu bardzo dużo i w wyniku tego pojedyncze wątki o mniejszym prio nie dostają czasu procesora ::: info A, B -> wątki X -> zasób A pracuje na X w inf loopie (np. nasz echo server) B nie ma dostępu do X, bo jest zajęty przez A ::: ![](https://i.imgur.com/W8A4wuO.png) ![](https://i.imgur.com/6VFITmU.jpg) ![](https://i.imgur.com/95I1Nlk.jpg) ## Zadanie 2 #### Deadlock 2 wątki lub więcej chcą jakichś zasobów i czekają na siebie nawzajem. Przykład: dwa wątki (A i B) mają locki odpowiednio do dwóch zasobów (X i Y). Wątek A chce dostęp do zasobu X, a potem do Y. Wątek B - najpierw do - Y, a potem do X #### Livelock 2 wątki/więcej chcą dostęp do jednego zasobu i muszą czekać na siebie nawzajem. Przykład: Jakieś zwykłe dodawanie z lockiem na zmiennej suma #### Starvation Wątki o wyższy priorytecie mają stale dostęp do jakichś zasobów przez co jakiś wątek musi czekać aż tamte się ogarną (jest na głodzie) Przykład: Tak jak pawlos napisał - A pracuje w infinite loopie (np echoserver) i B nie ma do niego dostępu i jest na głodzie ### Jak coś to pawlos ma więcej pisane są notatki wojta ## Zadanie 3 ![](https://i.imgur.com/gucE28l.png) ### Can any1 check on that? Po deassemblacji fragment kodu odpowiedzialny za inkrementację tally wyglada następująco ![](https://i.imgur.com/ITflL20.png) Możemy zauważyć, że najpierwsz wczytujemy wartość tally z jego lokalizacji w pamięci, natępnie dodajemy 1 i ponownie umieszczamy wartość pod adresem tally. Oznacza to, że może zaistnieć sytuacja kiedy następująco: Proc1 odczytuje wartość tally=0; Proc2 n-1 razy odczytuje wartość tally, zwiększa ją o 1 i wpisuje wynik pod adres tej zmiennej. Wtedy tally = n-1 Proc1 zwiększa wartość którą odczytał o 1 (czyli 0) i wpisuje wynik pod adres zmiennej. Wtedy tally = 1. Proc2 (wykonując n-tą iteracje pętli) odczytuje tally. Proc1 wykonuje n-1 razy odczytuje, inkrementuje i zapisuje tally. Proc2 wpisuje i zwięksa wartość tally którą odczytał (1) o 1 i dokonuje zapisu. Wtedy wartość tally zwiększa się tylko o 2. Natomiast jeśli działanie procesów odbyło się jeden po drugim to ostatecznie wartość tally byłaby równa 2n. Jeśli wystartujemy K procesów może nastąpić podobny przeplot. Tak samo wykonuje się krok 1 i 2. Następnie zwoją pracę wykonują Proc3 - ProcK. Później mają miejsce kroki 3-6. Wtedy końcowa wartość tally = 2. Jeśli działanie procesów odbyłoby się jeden po drugim to ostatecznie wartość tally byłaby równa Kn. ## Zadanie 4 ![](https://i.imgur.com/6iSq2vq.png) APUE 430 PDF ### tabelka ![](https://i.imgur.com/3kdYGKa.png) #### fork 1. Fork działa w odrębie całego programu, gdzie pthread_create w jednej funkcji. 2. Fork tworzy proces, pthread operuje na istniejącym #### exit 1. Exit kończy proces, a pthread_exit() wątek 2. Exit wywołuje funkcje atexit(), a drugi te z pthread_cleanup_push Kiedy wątek wywoła pthread_exit() wszystkie sharowane zasoby nie są zwalniane #### waitpid 1. Waitpid musi być zawołany, aby zwolnić pamięć w kernelu Pthread_join wołamy, aby zwolnić zasoby w procesie (dopóki nie odczytamy to wisiało gdzieśtam) 2. Waitpid może być zawołany przez rodzica, gdzie drugi może wołać każdy wątek i czekać na inne #### atexit 1. Atexit nie możemy przekazać argumentów, a w drugim możemy 2. Funkcje są wywoływane przy exit() i return w przypadku atexit(), a w wątkowym wariancie tylko przy pthread_exit() #### abort możemy abortować inne wątki w wątkowym wariancie, a w procesowym tylko siebie ### wątek złączalny domyślny mode wątku, join potrzebny, aby uwolnić zasoby itd. ### wątek odczepiony wątek po zakończeniu działania od razu ma zwolnione zasoby, raczej nie obchodzi nas co zwraca funkcja ### Kto odpowiada za usunięcie segmentu stosu z przestrzeni użytkownika, gdy wątek złączalny albo odczepiony zakończy pracę? Jeśli wątek jest <b>odczepiony</b> to w `pthread_exit()` jego stack jest zwalniany (plik pthread_create.c 165 linijka) Inaczej jego stack jest czyszczony w `pthread_join()` ![](https://i.imgur.com/WuChsQk.jpg) ![](https://i.imgur.com/ZeBI7CT.jpg) ## Zadanie 5 ogólnie chyba nieco dziwne ![](https://i.imgur.com/bUkVySO.png) ### Jeden z wątków zawoła jedną z funkcji #### fork skopiuje address space i dziecko odziedziczy stan wszystkich mutexów, locków i cond. variable. W dziecku będzie jeden wątek. Dziecko nie wie które locki trzeba odblokować, żeby się nie zablokować. Można to naprawić stosując pthread_atfork() #### execve replace całego addres space'u. Niszczymy też wszystkie wątki. Jedyny problem to może być to, że nie wiemy tak naprawdę kiedy nasze wątki przestaną istnieć, bo nie zaczekamy na ten co robi execve, to potencjalnie deskryptor pliku trafi do nowego programu #### exit_group() Who knows? ### W kontekście którego wątek zostanie obsłużony? W docsach i APUE czytamy: ::: info zostanie wybrany arbitralny(?) wątek, który dostanie sygnał ::: therefore -> randomowo ### Rury wtedy dostaniemy sigpipe i wszystkie wątki w procesie umrą #### Czytanie z tego samego deskryptora (APUE 496 pdf) Jako, że mamy jeden kursor to musimy używać lseek(), ale wtedy załóżmy, że A robi lseek(); read() i B robi to samo. Załóżmy, że A wykona lseek(), a potem będzie contex switch na B i ten wykona lseek() i read(), potem znów switch na A i ten wykona read(). Tym sposobem przeczytaliśmy ten sam fragment pliku ![](https://i.imgur.com/Sz5QeoS.jpg) ![](https://i.imgur.com/mdMmtse.jpg) ## Zadanie 6 ![](https://i.imgur.com/qZdpdb4.png) ### Jaka motywacja za wprowadzeniem poll? Gdy program operuje na dużej liczbie deskryptorów (wiele połączeń przychodzących/wychodzących), nie chcemy zgadywać narażać się na długość blokowanie na jednym z nich (read()/write()), tylko chcemy mieć pewność, że coś jest do zrobienia na jednym lub więcej z nich i przetworzyć to. Istnieją inne opcje do tego problemu, ale nie są tak efektywne lub są skomplikowane ### Czemu lepiej konstruować oprogramowanie w oparciu o poll? Odpytywanie deskryptorów zużywa dużo cykli procesora, ponieważ odpytujemy po kolei nieblokującym `readem` każdy z nich, jeśli są dane to git, jeśli nie to idź dalej. Zazwyczaj tych danych nie będzie, a syscalle są kosztowne Używanie sygnałów po pierwsze jest nieustandaryzowane do końca. Poza tym działają tylko fd terminala i sieciowe. Jeśli dostaniemy sygnał to nawet nie wiemy, który fd ma coś dla nas tylko, że któryś ma ### Na jakich fd działa poll? Regularne pliki, terminale, rury, FIFO, sockety (APUE mówi, że każde działa, ale stosuje się do tych) ### Czemu fd muszą być nieblokujące? (z pollem) 1. Jak poll wróci to mamy pewność, że fd X może być przeczytany <b>raz</b> bez blokowania. To znaczy, że na każdy read przypada 2 polle. Jeśli mamy nieblokujące fd to czytamy ile wlezie i ignorujemy EWOULDBLOCK na końcowym `readzie` 2. Jest jeszcze problem taki, że poll wróci, bo mamy klienta do zaakceptowania. Wtedy klient się rozłącza, a my blokujemy się w accept() ### Jak zapewnić, żeby wywołania nie blokowały się w jądrze? fcntl ustawić flagę O_NONBLOCK na jakimś fd używanym w tych funkcjach ### Co musi być w revents, żeby po powrocie z poll nie było errora o blokowaniu? ![](https://i.imgur.com/GprHQrk.png) ![](https://i.imgur.com/jmvlXmO.png) ![](https://i.imgur.com/qCMoXLz.jpg) ![](https://i.imgur.com/IIyZbYz.jpg) ## Zadanie 7 ![](https://i.imgur.com/K0vxmZt.png) ```clike= /* TODO: Start threads and wait for them to finish. */ pthread_t tid[nthreads]; for (int i = 0; i < nthreads; i++) Pthread_create(&tid[i],NULL,thread,NULL); for (int i = 0; i < nthreads; i++) Pthread_join(tid[i],NULL); ``` ## Zadanie 8 ![](https://i.imgur.com/tOaJsje.png) ```clike= /* TODO: If listening descriptor ready, add new client to the pool. */ if(fds[0].revents & POLLIN){ char host[MAXLINE],port[MAXLINE]; socklen_t clientlen = sizeof(struct sockaddr_storage); struct sockaddr_storage clientaddr; int connfd; connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); Getnameinfo((SA *)&clientaddr, clientlen, host, MAXLINE, port, MAXLINE, 0); addclient(connfd,host,port); nready--; } /* TODO: Echo a text line from each ready connected descriptor. * Delete a client when end-of-file condition was detected on socket. */ int i = 1; while (nready > 0) { if(fds[i].revents & POLLIN){ int n; n = clientread(i); if(n == 0){ delclient(i); i--; } nready--; } i++; } ```