# Lista 3
###### tags: `SO2022`

## 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

```
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


**bash -i** (lider sesji bo pid == sid):
pid 55115
sid 55115
**sleep**
sid 55115
ppid
* przed sigkillem: 55115 (bash -i)
* po: 1 (init)
sleep

bash -i

:::
* ### 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

sleep i xterm:


:::
## Zadanie 2

* ### 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.

* ### W jaki sposób przetwarza on znaki (w tym kody sterujące) wchodzące do kolejki wejściowej/wyjściowej?

* #### 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]`

**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

```
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


:::
* ### 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

```
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



:::
## Zadanie 5

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

:::
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

```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

## Zadanie 7



```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

```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;
}
```