# WIKTORIA Lista 3 ## Zadanie 1 **Czemu fork to fujka?** Prostota fork() stała się mitem. Speyfikacja POSIX wyróżnia 25 specjalnych przypadków określających kopiowanie stanu rodzica do dziecka. Ponadto źle funkcjonuje z abstrakcjami systemu operacyjnego zaimplementowanymi w trybie użytkownika, np. wymagając wyczyszczenia bufora wejścia/wyjścia przed każdym wywołaniem. Jest też także dość problematyczny w procesach wielowątkowych, gdyż może generować zakleszczenia. Domyślnie, fork() kopiuje cały stan rodzica, a zadaniem programisty jest usunięcie, tego, czego dany proces nie potrzebuje. Narusza to zasadę najmniejszego uprzywilejowania, utrudnia randomizację pamięci (mają ten sam układ pamięci), a także poddaje w wątpliwość bezpieczeństwo ze względu na np. umożliwienie odczytu wszystkiego z odziedziczonej pamięci. fork() jest powolny oraz utrudnia skalowalność systemu ze względu na często niepotrzebne współdzielenie zasobów. Ze względu użycie kopiowania przy zapisie używany jest mechanizm 'memory overcommit', który sprawia, że w przypadku fork() kopiowanie przestrzeni adresowej kończy się natychmiastowym sukcesem bez względu na to czy jest wystarczająco pamięci. Skutkuje to więc zakończeniem procesu np. przy próbie zapisu do odziedziczonej strony. ***Jak posix_spawn niweluje te wady?*** Procedura posix_spawn() korzysta z clone() z flagami - CLONE_VM, która sprawia, że zamiast kopiować całą przestrzeń adresową jest ona wspólna dla obu procesów, a to ogranicza błędy braku pamięci spowodowane memory overcommit - CLONE_VFORK, która zatrzymuje działanie procesu-rodzica aż do zwolnienia zasobów pamięci wirtualnej. Ponadto posix_spawn() jest alternatywą dla wywołań fork() + exec() wraz z potrzebnymi operacjami między wywołaniem tych procedur, tym samym upraszczając życie programistom, tak jak i pozwala na większą kontrolę nad atrybutami procesu (takich jak SID, GID, maska sygnałów). ## Zadanie 2 **Proces osierocony** -- aktywny proces, którego rodzic zakończył działanie. **Zadanie drugoplanowe** -- zadanie działające w tle bez ingerencji użytkownika, pozwalające mu na wykonywanie innych zadań w międzyczasie. W przypadku zakończenia procesu sygnałem SIGKILL, osierocone dziecko zostaje przygarnięte przez systemd --user. Natomiast w przypadku wysłania SIGHUP do powłoki, zakończone zostają wszystkie grupy procesów potomnych powłoki. SIGKILL to sygnał rozkazujący natychmiastowe zatrzymanie procesu (proces zostaje osierocony, a jego PPID zostaje zmieniony na PID najbliższego żniwiaża - w moim przypadku systemd --user), natomiast SIGHUP jest sygnałem kontrolującym zamknięcie terminala, stąd "uprzątnięcie grup dzieci" przed zakończeniem procesu. :::spoiler ``` strace -e trace=signal bash ps -o pid,ppid,pgrp,stat,cmd kill -HUP kill -KILL ``` ::: ## Zadanie 4 ``` ~$ strace -e trace=signal cat - & [1] 26894 ~$ --- SIGTTIN {si_signo=SIGTTIN, si_code=SI_KERNEL} --- --- stopped by SIGTTIN --- ``` Polecenie ```cat -``` wypisuje wczytane znaki z terminala. Ponieważ proces nie może przeczytać nic z terminala, gdy działa w tle, to zostaje do niego wysłany sygnał SIGTTIN, którego domyślną akcją jest zatrzymanie procesu. Przed: ``` ~$ cat /etc/shells & [1] 27241 ~$ # /etc/shells: valid login shells /bin/sh /bin/bash /usr/bin/bash /bin/rbash /usr/bin/rbash /bin/dash /usr/bin/dash /usr/bin/tmux ``` Po: ``` ~$ cat /etc/shells & [1] 27321 ~$ ``` Po włączeniu flagi tostop, do powyższego procesu wysyłany zostaje sygnał SIGTTOU, który działa podobnie do SIGTTIN z tą różnicą, że domyślnie zatrzymuje procesy działające w tle próbujące wypisać coś na terminal. SIGCHLD wysyłany jest do procesu-rodzica w przypadku zakończenia bądź zatrzymania procesu. Aby rozróżnić wstrzymanie/kontynuowanie od zakończenia procesu potomnego w waitpid() wystarczy wywołać ją z opcją WEXITED(jeśli chcemy dowiedzieć się czy dziecko zakończyło działanie - ustawiona jest domyślnie), WSTOPPED (dla zatrzymania) oraz WCONTINUED (gdy proces został wznowiony sygnałem SIGCONT). Funkcja biblioteki standardowej do wybrania grupy pierwszoplanowej: [*tcsetpgrp()*](https://linux.die.net/man/3/tcsetpgrp). ## Zadanie 5 ```setjmp``` zachowuje rejestry callee-saved do struktury Jmpbuf. ```longjmp``` przywraca rejestry zachowane przez setjmp. Do Jmpbuf zapisywane są tylko rejestry wymagane przez calling convention. Procedura setjmp jest zwykłą procedurą, stąd funkcją ją wywołująca nie może zakładać, że wszystkie rejestry po powrocie z tej funkcji pozostaną takie same. Przed wykonaniem instrukcji ret longjmp na stosie umieszcza adres powrotu do miejsca na stosie, które zapisała procedura setjmp. Instrukcja ret zdejmuje ją ze stosu a następnie wykonuje skok pod wskazany adres. ## Zadanie 6 **Nielokalny skok** -- skok między dwoma odzielnymi funkcjami. W przeciwieństwie do skoków lokalnych, muszą zadbać o pozostawienie stosu i rejestrów w odpowiednim stanie. ```C= static void signal_handler(int signo) { siglongjmp(env, signo); //przekaż numer sygnału na wyjście } ``` ```C= static int readnum(int *num_p) { char line[MAXLINE]; int n; n = sigsetjmp(env, 1); // Niezerowa wartość drugiego argumentu // skutkuje zachowaniem maski sygnałów if(n){ *num_p = 0; return n; // zwróć numer sygnału } alarm(1); if (read(0, line, MAXLINE)) *num_p = atoi(line); return 0; } ``` POSIX nie obiecuje, że setjmp() zachowa maskę sygnałów dla późniejszego przywrócenia przez longjmp(). Stąd program przestaje działać. ## Zadanie 7 **Zmiana kontekstu** -- proces zachowywania i odtwarzania stanu procesu, aby ułatwić wykonywanie wielu procesów "na raz". **Wielozadaniowość kooperacyjna** -- rodzaj wielozadaniowości, w której zamiast zmiany kontekstu następuje dobrowolna współpraca między procesami. ```C= static noreturn void coro_switch(int v) { coro_t *curr = running; running = TAILQ_NEXT(running, co_link); // wybierz kolejny proces w kolejce if (running == TAILQ_END(&runqueue)){ // Gdy kolejny procces = NULL to wybierz pierwszy running = TAILQ_FIRST(&runqueue); // proces z kolejki (zapętlenie kolejki) } if (v == EOF){ // Usuń współprogram z kolejki po napotkaniu EOF TAILQ_REMOVE(&runqueue, curr, co_link); } if (TAILQ_EMPTY(&runqueue)){ // Jeśli kolejka jest już pusta Longjmp(dispatcher, v); // powróć do dispachera, bo już wszystkie } // współprogramy zakończyły działanie Longjmp(running->co_ctx, v); } ``` ###### tags: `so`