# Lista 3 ###### tags: `SO2022` ![](https://i.imgur.com/YBT6ZSX.png) ## Deklaracja :::spoiler | Zadanie | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |-----------|---|---|---|---|---|---|---|---| |Deklaracja|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| ::: ## Zadanie 1 ![](https://i.imgur.com/EPkPKRw.png) ``` terminal1: xterm -e 'bash -i' & ps -eo user,pid,sid,ppid,pgid,cmd xterm: sleep 1000 & terminal2: kill -9 BASH_PID lub XTERM_PID ps -eo user,pid,sid,ppid,pgid,cmd ``` * ### signal do Bash'a `sleep` zmienia parent pid na init (lub systemd user init) :::spoiler Przykład ![](https://i.imgur.com/3BS4ypi.png) ![](https://i.imgur.com/mBm5QvZ.png) **bash -i** (lider sesji bo pid == sid): pid 55115 sid 55115 **sleep** sid 55115 ppid * przed sigkillem: 55115 (bash -i) * po: 1 (init) sleep ![](https://i.imgur.com/Xbr3FTJ.png) bash -i ![](https://i.imgur.com/EvKYb7S.png) ::: * ### signal do emulatora Po zabiciu sigkillem xterma do wszystkich procesow dzieci (czyli u nas `bash -i`) zostają wysłane SIGHUP. Bash rozsyła otrzymany SIGHUP dalej: The shell exits by default upon receipt of a 'SIGHUP'. Before exiting, an interactive shell resends the 'SIGHUP' to all jobs, running or stopped. Stopped jobs are sent 'SIGCONT' to ensure that they receive the 'SIGHUP'. To prevent the shell from sending the 'SIGHUP' signal to a particular job, it should be removed from the jobs table with the 'disown' builtin (*note Job Control Builtins::) or marked to not receive 'SIGHUP' using 'disown -h'. :::spoiler Przykład ![](https://i.imgur.com/vajrzp7.png) sleep i xterm: ![](https://i.imgur.com/9hKHwRG.png) ![](https://i.imgur.com/Rs5JEmE.png) ::: ## Zadanie 2 ![](https://i.imgur.com/GjkPyzK.png) * ### Jak zachowuje się sterownik terminala działającego w trybie kanonicznym (czytanie linijkami)? Input terminala jest przetwarzany linijkami, których końce są wyznaczone przez: * znaki nowej linii `\n` * lub end-of-file (EOF) * lub end-of-line (EOL) Limit wielkości linii wyznaczony jest przez `MAX_CANON`. Przetwarzane są również specjalne znaki `ERASE` (usuń znak) (oraz podobne typu `WERASE`, jeżeli flaga `IEXTEN` jest ustawiona) oraz `KILL` (usuń wszystkie dotychczasowe znaki z linii). W większości systemów UNIXowych jest moduł nazywany `terminal line discipline`, który jest pośrednikiem między funkcjami `read` oraz `write` kernela, aby wszystkie urządzenia terminala mogły przetwarzać płynnie tryb kanoniczny. ![](https://i.imgur.com/y5iGQ5S.png) * ### W jaki sposób przetwarza on znaki (w tym kody sterujące) wchodzące do kolejki wejściowej/wyjściowej? ![](https://i.imgur.com/2d1ROei.png) * #### Tryb kanoniczny jak wyżej * #### Tryb niekakoniczny W tym trybie input jest dostępny natychmiastowo (zależnie od ustawień zmiennych odpowiadających za czas i minimalną liczbę wczytanych znaków w strukturze `termios`). Nie da się go edytować - bufor jedynie przyjmuje 4095 znaków (z konieczności zapewnienia miejsca na znak końca w trybie kanonicznym, gdybyśy zdecydowali się na niego przełączyć). Również rozmiar **output queue** jest ograniczony, jednak nie ma dostępu do stałych określających ten rozmiar. Kiedy output queue się zapełnia, kernel usypia proces pisania, aż zwolni się miejsce. Można to wykonać również funkcjami `tcflush` lub ustawieniami `tcsetattr`, które czyszczą input lub output queue. Jeżeli opcja **echo** jest włączona, to znaki z inputu pojawiają się również na outpucie. Dostępne są również **specjalne znaki** (np. `ctrl+c`), które w różny sposób są przetwarzane (np. wysyłanie sygnałów), opisane w tablicy `c_cc` w strukturze `termios` (część z nich można edytować, lub wyłączać specjalnymi flagami) * ### Jak konfigurację terminala powinien zmienić program na czas wpisywania hasła przez użytkownika? Nie powinien wyświetlać wpisywanych znaków. Wyłączamy opcję echo :::spoiler Obrazowanie różnic `stty -a < /dev/pts/[nr]` ![](https://i.imgur.com/vQbtrSC.png) **stty -a normalnie** ``` speed 38400 baud; rows 23; columns 80; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0; -parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten **echo** echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc ``` **podczas wpisywania hasła** ``` speed 38400 baud; rows 23; columns 80; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0; -parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten **-echo** echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc ``` ::: * ### Czemu edytory takie jak `vi` konfigurują sterownik terminala do pracy w trybie niekanonicznym? Ponieważ nie wszystkie instrukcje sterujące programu są zakończone znakami kończącymi wczytanie linii (to by było bardzo niewygodne!), np. `dd` usuwające linie, czy `hjkl` i wiele (chyba większość) innych poleceń w edytorach ## Zadanie 3 ![](https://i.imgur.com/51fwKmU.png) ``` terminal1: stty -a ``` * ### Sygnały związane z **zarządzaniem zadaniami**: - `intr` wysyła sygnał przerwania - `quit` wysyła sygnał zamknięcia - `swtch` włącza inną warstwę powłoki - `start`/`stop` wznawia/wstrzymuje wyświetlanie - `susp` wysyła sygnał stop * ### Sygnały związane z **edycją wiersza** (do zaprezentowania z `cat`): - `erase` kasuje ostatni wprowadzony znak, - `kill` kasuje całą linię - `eof` wysyła znak końca pliku (końca wejścia) - `eol`/`eol2` wysyła znak końca wiersza - `werase` kasuje ostatnie wprowadzone słowo - `rprnt` powtarza bieżący wiersz - `lnext` wprowadza kolejny znak w cudzysłowie * :::spoiler Przykłady ![](https://i.imgur.com/b4Izi3D.png) ![](https://i.imgur.com/1mBSXTE.png) ::: * ### Zmiana rozmiaru okna Program zostaje poinformowany o zmianie rozmiaru okna przy odebraniu sygnału `SIGWINCH` ``` terminal1: trace -e 'trace=!all' -p PID_NANO stty -F WYNIK_TTY columns 20 terminal2: tty nano plik Zminiamy rozmiar okna i obserwujemy sygnały ``` ## Zadanie 4 ![](https://i.imgur.com/CqHeObV.png) ``` terminal1: echo -e '\e[10A';read echo -e '\e[1mBold\e[3mItalic\e[0mReset' cat strzałki wypisują ^[[A ^[[B ^[[C ^[[D type echo wbudowane type cat program ``` * [Kody CSI](https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797) * [Kody CSI Wikipedia](https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences) * :::spoiler Przykłady ![](https://i.imgur.com/zs8Ap93.png) ![](https://i.imgur.com/HJUH27D.png) ![](https://i.imgur.com/4HnN6hN.png) ::: ## Zadanie 5 ![](https://i.imgur.com/PV3SuYI.png) 1. ### Wstrzymaj zadanie pierwszoplanowe `sleep 1000` i przy pomocy wbudowanego polecenia powłoki `bg` przenieś to zadanie do wykonania w tle. Jaki sygnał został użyty do wstrzymania zadania? ``` terminal1: strace -e 'trace=!all' -p PID_SLEEP terminal2: sleep 1000 ctrl+z ps - pozmany pid bg %1 ``` * Zatrymanie `ctrl+z` `SIGSTP` * Wznowienie `bg` `SIGCONT` 2. ### Uruchom `find /`. W trakcie jego działania naciśnij na przemian kilkukrotnie kombinację klawiszy `CTRL+S` oraz `CTRL+Q`. Czemu program zatrzymuje się i wznawia swoją pracę, skoro sterownik terminala nie wysyłał do niego żadnych sygnałów? ``` terminal1: strace -e 'trace=!all' -p PID_FIND terminal2: find / ctrl+z ps - pozmany pid fg ctrl+s ctr+q ``` Są to sekwencje sterowania terminalem Domyślnie w naszym terminalu: `start = ^Q; stop = ^S;` [software_flow_control](https://en.wikipedia.org/wiki/Software_flow_control) [tty_ioctl](https://man7.org/linux/man-pages/man4/tty_ioctl.4.html) * `stop` - zatrzymuje output (sterownik terminala deklaruje że nie jest w stanie przyjąć więcej danych i wysyła `XOFF`) * `start` - ponawia zatrzymany output (wysłaniem `XON`) 3. ### Uruchom w powłoce `bash` polecenie `cat - &`. Czemu zadanie zostało od razu wstrzymane? Jaki sygnał otrzymało? Zakończ to zdanie wbudowanym poleceniem powłoki `kill`. ``` terminal1: strace -e 'trace=!all' -p PID_CAT terminal2: cat - & fg ctrl+z bg kill -9 PID_CAT ``` Zostało wstrzymane sygnałem `SIGTTIN` * `SIGTTIN` Zatrzymuje proces gdy próbuje czytać z Terminala, domyślnie włączone * `SIGTTOU` Zatrzymuje proces gdy próbuje pisać na Terminala, domyślnie wyłączone 4. ### Porównaj działanie polecenia `cat /etc/shells &` przed i po zmianie konfiguracji terminala poleceniem `stty tostop`. Jaki efekt ma włączenie flagi `tostop` na zachowanie sterownika terminala? ``` terminal1: stty -a pokaże konfigurację bierzącego terminala cat /etc/shells & stty tostop SIGTTOU zaczyna być wysyłany cat /etc/shells & stty -a widać zmianę tostop ``` Wynik: po ustawieniu `tostop` zadanie po przeniesieniu na drugi plan, przy próbie zapisu danych na standardowe wyjście otrzymuje `SIGTTOU` :::spoiler Wynik z Terminala ![](https://i.imgur.com/Lj12BIt.png) ::: 5. ### Wykonaj polecenie `stty -echoctl`. Wyjaśnij co zmieniło się w konfiguracji terminala i zaprezentuj na przykładzie programu `cat` pokaż jak zmieniło się przetwarzanie znaków sterujących. ``` terminal1: stty -echoctl wyłącza notację z daszkiem np. ^Z, znaki są przetwarzane cat - możemy chodzić strzałkami po całym terminalu ``` ## Zadanie 6 ![](https://i.imgur.com/qjw4UfE.png) ```x86asm= _JB_RBX = 0 _JB_RBP = 1 _JB_R12 = 2 _JB_R13 = 3 _JB_R14 = 4 _JB_R15 = 5 _JB_RSP = 6 _JB_RIP = 7 .text .globl Setjmp .type Setjmp,@function Setjmp: ; Zapisujemy rejestry o których wartość ; powinna dbać funkcja wywoływana ; oraz wskaźnik na stos i następną instrukcję movq (%rsp),%r11 movq %rbx,(_JB_RBX * 8)(%rdi) movq %rbp,(_JB_RBP * 8)(%rdi) movq %r12,(_JB_R12 * 8)(%rdi) movq %r13,(_JB_R13 * 8)(%rdi) movq %r14,(_JB_R14 * 8)(%rdi) movq %r15,(_JB_R15 * 8)(%rdi) movq %rsp,(_JB_RSP * 8)(%rdi) movq %r11,(_JB_RIP * 8)(%rdi) ; Zracamy 0 xorl %eax,%eax ret .size Setjmp, . - Setjmp .globl Longjmp .type Longjmp,@function Longjmp: ; Przywracamy zrejestry zapisane w setjump movq (_JB_RBX * 8)(%rdi),%rbx movq (_JB_RBP * 8)(%rdi),%rbp movq (_JB_R12 * 8)(%rdi),%r12 movq (_JB_R13 * 8)(%rdi),%r13 movq (_JB_R14 * 8)(%rdi),%r14 movq (_JB_R15 * 8)(%rdi),%r15 movq (_JB_RSP * 8)(%rdi),%rsp movq (_JB_RIP * 8)(%rdi),%r11 ; Przenosimy wartość przekazaną ; do funkcji do rejestru zwracanego przez funkcję movl %esi,%eax ; Zabezpieczamy się przed zwróceniem 0, ; które przeznaczone jest tylko jako powrót z setjmp testl %eax,%eax jnz 1f incl %eax Ustalamy adres powrotu 1: movq %r11,(%rsp) ret .size Longjmp, . - Longjmp ``` ```c= typedef struct { long rbx; long rbp; long r12; long r13; long r14; long r15; void *rsp; void *rip; } Jmpbuf[1]; int Setjmp(Jmpbuf env); noreturn void Longjmp(Jmpbuf env, int val); ``` * ### Dlaczego `Jmpbuf` nie przechowuje wszystkich rejestrów procesora? Część rejestrów jest zapisywana przez funkcję wywołującą, a zęść przez funkcję wywolywaną, i te rejestry zapisujemy Zapisujemy też wskaźnik na stos i następną instrukcję. * ### Czemu `Longjmp` zapisuje na stos wartość przed wykonaniem instrukcji `ret`? Ustalamy miejsce w kodzie do którego chcemy wrócić PRZYKLAD Z PODRECZNIKA ![](https://i.imgur.com/2f4RGbb.png) ## Zadanie 7 ![](https://i.imgur.com/WwM1oY3.png) ![](https://i.imgur.com/nbi7ceE.png) ![](https://i.imgur.com/UqBJQWZ.png) ```c= #include "csapp.h" #include "terminal.h" #undef MAXLINE #define MAXLINE 120 static sigjmp_buf env; static void signal_handler(int signo) { /* TODO: Something is missing here! */ // Skaczemu do readnum, któ©y następnie się kończy siglongjmp(env,signo); // longjmp(env,signo); /* TODO: END*/ } /* If interrupted by signal, returns signal number. Otherwise converts user * provided string to number and saves it under num_p and returns zero. */ static int readnum(int *num_p) { char line[MAXLINE]; int n; /* TODO: Something is missing here! Use Read() to get line from user. */ /* Ustawiamy miejsce skoku funkcji tak, aby zapamiętała maskę sygnałów, Ponieważ wchodzimy do funkcji prosto z handlera, Zatem nigdy się on nie zakończy czyli nie wyczyści po sobie maski */ n=sigsetjmp(env,1); // n=setjmp(env); // Zgodnie z treścią po otrzymaniu sygnału natyczmiast przerywamy działanie if(n) return n; // Ustalamy aby sygnał SIGALRM został wysłany po sekundzie alarm(1); // Wczytujemy odpowiedź Read(STDIN_FILENO,line,MAXLINE); /* TODO: END*/ *num_p = atoi(line); return 0; } static void game(void) { int tty = tty_open(); int timeout = 0, num1 = 0, num2 = 0, sum; int last_sig = 0; int lives = 3; int score = 0; while (lives > 0) { switch (last_sig) { case 0: timeout = 5; num1 = random() % 100; num2 = random() % 100; printf("What is the sum of %d and %d?\n", num1, num2); break; case SIGINT: printf(CHA(1) EL() "Bye bye!\n"); exit(EXIT_FAILURE); case SIGALRM: timeout--; if (timeout < 0) { last_sig = 0; lives--; printf(CHA(1) EL() "Answer was %d!\n", num1 + num2); continue; } break; default: app_error("lastsig = %d not handled!\n", last_sig); break; } /* Rewrite user prompt to show current number of lives and timeout. */ sigset_t set, oldset; sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGALRM); Sigprocmask(SIG_BLOCK, &set, &oldset); int x, y; tty_curpos(tty, &x, &y); dprintf(STDOUT_FILENO, CHA(1) "lives: %d timeout: %d > ", lives, timeout); if (last_sig == SIGALRM) dprintf(STDOUT_FILENO, CHA(%d), y); Sigprocmask(SIG_SETMASK, &oldset, NULL); /* Read a number from user. */ last_sig = readnum(&sum); if (last_sig) continue; /* Line contains user input (a number) terminated with '\0'. */ if (sum == num1 + num2) { printf("Correct!\n"); score++; } else { printf("Incorrect!\n"); lives--; } } Close(tty); printf("Game over! Your score is %d.\n", score); } int main(void) { /* Initialize PRNG seed. */ struct timeval tv; gettimeofday(&tv, NULL); srandom(tv.tv_usec); /* SIGALRM is used for timeouts, SIGINT for graceful exit. */ Signal(SIGALRM, signal_handler); Signal(SIGINT, signal_handler); game(); return EXIT_SUCCESS; } ``` ## Zadanie 8 ![](https://i.imgur.com/gTMVcON.png) ```c= #include "queue.h" #include "csapp.h" #define CORO_STKSIZE 4096 #define CORO_STKALIGN 16 /* As required by SysV ABI ! */ #ifndef EOF #define EOF (-1) #endif #ifndef NOTHING #define NOTHING (-2) #endif typedef struct coro { TAILQ_ENTRY(coro) co_link; const char *co_name; void *co_stack; Jmpbuf co_ctx; } coro_t; static TAILQ_HEAD(, coro) runqueue = TAILQ_HEAD_INITIALIZER(runqueue); static coro_t *running; static Jmpbuf dispatcher; /* Initialize coroutine stucture with stack. */ static void coro_init(coro_t *co, const char *name) { memset(co, 0, sizeof(coro_t)); co->co_name = name; /* Allocates a fresh stack for the coroutine! */ if (posix_memalign(&co->co_stack, CORO_STKALIGN, CORO_STKSIZE) < 0) unix_error("posix_memalign error"); } /* Detach a stack from coroutine structure. */ static void coro_destroy(coro_t *co) { free(co->co_stack); } /* * Switch between subsequent coroutines. * * Dead coroutines, i.e. ones that returned EOF, get removed from the run queue. * Feed next coroutine (value returned from coro_yield) with the result from * previous one (parameter passed to coro_yield). * Return to dispatcher if there're no more coroutines to run. */ static noreturn void coro_switch(int v) { coro_t *curr = running; /* TODO: Use description above to implement the body. */ // Jeśli EOF usuwamy współprogram z kolejki if (v == EOF) { TAILQ_REMOVE(&runqueue, curr, co_link); // Jeśli opróżniliśmy kolejkę wracamy do nadzorcy if (TAILQ_EMPTY(&runqueue)) Longjmp(dispatcher, NOTHING); } // Wybieramy następny współprogram running = TAILQ_NEXT(curr, co_link); // Jeśli nie ma ostatniego elementu to wracamy do pierwszego współprogramu if (running == NULL) running = TAILQ_FIRST(&runqueue); // Skaczemy do następnego współprogramu Longjmp(running->co_ctx, v); /* TODO: END*/ } /* Save caller context and switch back to next coroutine. */ static int coro_yield(int v) { int nv = Setjmp(running->co_ctx); if (nv == 0) coro_switch(v); return nv; } /* Configure coroutine context to be executed. */ static void coro_add(coro_t *co, void (*fn)(int)) { int v = Setjmp(co->co_ctx); if (v) { /* This will get executed when coroutine is entered first time. */ fn(v); /* Coroutine must pass EOF to be removed from runqueue! */ coro_switch(EOF); } /* Coroutine will be running on its private stack! */ co->co_ctx->rsp = co->co_stack + CORO_STKSIZE; TAILQ_INSERT_TAIL(&runqueue, co, co_link); } /* Take first coroutine and feed it with passed value. */ static int coro_run(int v) { running = TAILQ_FIRST(&runqueue); int nv = Setjmp(dispatcher); if (nv == 0) Longjmp(running->co_ctx, v); return nv; } /* * Actual coroutines that perform some useful work. */ static void func_1(int _) { int words = 0; char prev_ch = ' '; char ch; while (Read(0, &ch, 1) > 0) { if (isspace(ch)) { if (isspace(prev_ch)) continue; words++; } coro_yield(ch); prev_ch = ch; } if (!isspace(ch)) words++; dprintf(STDERR_FILENO, "\nfunc_1: words = %d\n", words); } static void func_2(int ch) { int removed = 0; while (ch != EOF) { if (!isalpha(ch)) { removed++; ch = NOTHING; } ch = coro_yield(ch); } dprintf(STDERR_FILENO, "func_2: removed = %d\n", removed); } static void func_3(int ch) { int printed = 0; while (ch != EOF) { if (ch != NOTHING) { printed++; if (islower(ch)) ch = toupper(ch); else if (isupper(ch)) ch = tolower(ch); Write(STDOUT_FILENO, &ch, 1); } ch = coro_yield(NOTHING); } dprintf(STDERR_FILENO, "func_3: printed = %d\n", printed); } int main(void) { coro_t co[3]; coro_init(&co[0], "func_1"); coro_init(&co[1], "func_2"); coro_init(&co[2], "func_3"); coro_add(&co[0], func_1); coro_add(&co[1], func_2); coro_add(&co[2], func_3); coro_run(NOTHING); coro_destroy(&co[0]); coro_destroy(&co[1]); coro_destroy(&co[2]); dprintf(STDERR_FILENO, "Bye, bye!\n"); return EXIT_SUCCESS; } ```