# Lista 2
###### 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:|
:::
## Zadanie 1

* ### Na podstawie rysunku przedstaw stany procesu w systemie Linux.

* **Running**: proces jest wykonywany (**Executing**) lub gotowy do wykonania (**Ready**)
* **Ready**: proces oczekuje na wykonanie
* **Executing**: proces jest właśnie wykonywany
* **Interuptible**: proces jest zablokowany i oczekuje na operacje I/O, dostępność zasobów lub sygnał od innego procesu
* **Uninteruptible** proces jest zablokowany i oczekuje na bezpośrednie instrukcje hardwarowe, nie będzie obsługiwał żadnych przerwań
* **Stopped**: proces został zatrzymany i może zostać wznowiony tylko przez inny proces np. Ctrl+z lub debugger
* **Zombie**: proces został zakończony, ale jego struktura danych cały czas jest w tablicy procesów
:::spoiler

:::
* ### Podaj akcje albo zdarzenia wyzwalające zmianę stanu.
* Oczekiwanie na odpowiedź hardwaru
* Otrzymanie sygnału o przejścu w stan stopped
* Rozpoczęcie procesu
* Zakończenie procesu
* Proces sam oddaje procesor, np. zasypia na x sekund
* Proces oczekuje na inny proces
* itp.
* ### Które przejścia mogą być rezultatem działań podejmowanych przez:
* **Jądro systemu operacyjnego**
* Stopped -> Ready **(signal)**
* Executing -> Stopped **(signal)**
* Executing -> Interuptible
* Executing -> Zombie **(termination)**
* Executing -> Ready **(scheduling)**
* Interuptible -> Ready **(signal)**
* Creation -> Ready
* **Kod sterowników**
* Executing -> Uninteruptible
* Executing -> Interuptible
* Uninteruptible -> Ready
* Interuptible -> Ready
* **Proces użytkownika**
* Executing -> Ready (?)
* Executing -> Interuptible
* ### Wyjaśnij różnice między snem przerywalnym i nieprzerywalnym.
* **Sen nieprzerywalny** czeka na przerwanie sprzętowe, nie odpiera w tym czasie sygnałów. Jedynie odpowiedz hardware'u może go wybudzić
* **Sen przerwywalny** czeka na sygnał, zatem możemy go przerwać wysyłając np. `SIGKILL`
tldr; przerywalny może odebrać sygnał, nieprzerywalny go nie odbierze
* ### Czy proces może **zablokować** lub **zignorować** sygnał `SIGKILL` lub `SIGSEGV`?
* **`SIGKILL`** nie można zignorować ani zablokować
* **`SIGSEGV`** można przechwycić i zablokować sygnał
(tylko `SIGKILL` i `SIGSTOP` nie da się zignorować ani zablokować)
:::spoiler program do testów
```c=
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_handler(int signo)
{
if (signo == SIGXCPU)
printf("received SIGXCPU\n");
}
void sig_handler1(int signo)
{
if (signo == SIGINT)
printf("received SIGINT\n");
}
void sig_handler3(int signo)
{
if (signo == SIGTERM)
printf("received SIGTERM\n");
}
void sig_handler4(int signo)
{
if (signo == SIGSEGV)
printf("received SIGSEGV\n");
}
int main(void)
{
if (signal(SIGXCPU, sig_handler) == SIG_ERR)
printf("\ncan't catch SIGXCPU\n");
// if (signal(SIGINT, sig_handler1) == SIG_ERR)
// printf("\ncan't catch SIGINT\n");
if (signal(SIGTERM, sig_handler3) == SIG_ERR)
printf("\ncan't catch SIGTERM\n");
sigset_t sigs;
sigemptyset (&sigs);
sigaddset(&sigs, SIGSEGV);
if (signal(SIGSEGV, sig_handler4) == SIG_ERR)
printf("\ncan't catch SIGSEGV\n");
// A long long wait so that we can easily issue a signal to this process
int i = 0;
printf("PID: %d\n", (int)getpid());
while(1) {
sigprocmask(SIG_BLOCK, &sigs, NULL);
}
return 0;
}
```
:::
## Zadanie 2

:::spoiler Opis tworzenia procesu w systemie Linux wg tekstu z rozdziału (basically wszystko jest na obrazku na dole)
1. Wchodzimy w tryb jądra i tworzymy struktury danych dla zadania i dodatkowe, np. stos trybu jądra
2. Wyszukujemy wolny PID i aktualizujemy wpis w tablicy identyfikatorów
3. Nadajemy procesowi identyfikator PID, własną mapę pamięci (procesy nie mogą jej współdzielić, oprócz readonly, w Linuxie jest teraz mechanizm COW, tj. do procesu dziecka kopiujemy tylko te strony, które aktualnie próbuje zapisać proces potomny), współdzielony dostęp do plików swojego rodzica
4. Ustawiamy rejestry nowego procesu
5. Po rozpoczęciu wykonywania procesu potomnego wykonuje on wywołanie systemowe `exec`
6. Jądro odnajduje i weryfikuje wskazany plik
7. Jądro kopiuje przekazane argumenty i środowisko
8. Jądro wyzerowuje sygnały i inicjalizuje rejestry wartościami zerowymi
9. Jądro zwalnia starą przestrzeń adresową i tablicę stron
:::
#### Tworzenie nowego procesu w WinNT

* ### Wyjaśnij różnice w tworzeniu procesów w systemie Linux i WinNT.
- Linux najpierw klonuje proces `fork`, a później nadpisuje jego przestrzeń adresową nowym programem za pomocą `execve`. W WinNT od razu jest tworzony nowy proces przez `NtCreateUserProcess`
- W WinNT kiedy nowy proces jest tworzony jest on usypiany gdyż system musi go jeszcze dodać do Win32 subsystem process

- W linuksie kiedy jest tworzony nowy proces żadne strony nie są ładowane do pamięci, zostaną one załadowne dopiero wtedy kiedy nastąpi page fault dla danej strony (COW)
* ### Naszkicuj przebieg najważniejszych akcji podejmowanych przez jądro w trakcie obsługi funkcji `fork(2)` i `execve(2)`.

* ### Załóżmy, że system posiada wywołanie `spawn`, o takich samych argumentach jak `execve`. Zastępuje ono parę wywołań `fork` i `execve`, a realizuje takie samo zadanie. Dlaczego w takim przypadku mielibyśmy problemy z dodaniem do powłoki obsługi przekierowania standardowego wejścia/wyjścia odpowiednio z/do pliku albo łączenia dowolnych procesów potokami?
- Prziekierowanie (potoki) można zaimplementować w następujący sposób:

Po użyciu wywołania systemowego `pipe` zwracane są dwa deskryptory(fd[0] i fd[1]) do nowo utworzonego pipe-a, jeden do zapisu drugi do odczytu. Pipe w takim stanie jest mało użyteczny więc żeby z niego skorzystać i przekierować wyjście/wejście potrzeba stworzyć nowe dziecko za pomocą `fork`, teraz zarówno rodzic i dziecko mają deskryptory do pipe-a.

Teraz w zależności od tego kto ma czytać a kto pisać do pipe-a ojciec i dziecko zamykają odpowiednie deskryptory.

Teraz możemy użyć `dup2` żeby zduplikować file descriptory na standardowe wejście/wyjście (podmienić je tak by program odczytywał/pisał do pipe-a),a
następnie użyć `execve` by podmnienić jeden program na drugi (`execve` zachowuje file descriptory). Widać więc, że przekierowanie wejścia/wyjścia odbywa się pomiędzy `fork`, a `execv`, więc gdybyśmy korzystali ze `spawn` nie moglibyśmy łatwo przekierować IO, odpalane programy musiałyby same próbować podmniać swoje wejście/wyjście w zależności od podanych argumentów albo próbować czegoś innego, więc sprawa się komplikuje.
Kod ze stacka gdzie jest pokazana prosta implementacja przekierowania wejścia/wyjścia:
https://stackoverflow.com/questions/8082932/connecting-n-commands-with-pipes-in-a-shell
Kod z APUE do przekierowania wejścia/wyjścia:


## Zadanie 3

* ### Na podstawie dokumentacji `fork(2)` i `execve(2)` wymień najważniejsze zasoby procesu, które są:
* **Dziedziczone przez proces potomny**
* `fork()`
* **Przekazywane do nowego programu załadowanego do przestrzeni adresowej**
* `execve()`
* ### Czemu przed wywołaniem fork należy opróżnić bufory biblioteki `stdio(3)`?
Proces dziecka dziedziczy po rodzicu wszystko, włącznie z deskryptorami i buforami i ich zawartością. Podczas zakończenia procesu bufor jest opróżniany, przez co możemy niechcący wiele razy powielić zapis do deskryptora pliku (jak w powyższym linku)
**Problem-Ciekawostka**:
https://unix.stackexchange.com/questions/447898/why-does-a-program-with-fork-sometimes-print-its-output-multiple-times
* ### Co jądro robi w trakcie wywołania `execve` z konfiguracją zainstalowanych procedur obsługi sygnałów?
Zainstalowane procedury obsługi sygnałów (signal handlers) są usuwane, załadowywane są handlery z programu który zostaje załadowany do pamięci za pomocą `execve`
### Fork
Proces potomny jest dokładnym duplikatem procesu nadrzędnego z wyjątkiem następujących punktów:
* Odrębny PID
* Dziecko nie dziedziczy blokad pamięci: `mlock(2)` `mlockall(2)`
* Wykorzystanie zasobów procesu i liczniki czasu procesora zostają zresetowane
* Zestaw oczekujących sygnałów dziecka jest początkowo pusty
* Dziecko nie dziedziczy blokad rekordów związanych z procesem od swojego rodzica ( fcntl(2) ). (Z drugiej strony, dziedziczy blokady opisu otwartych plików fcntl(2) i blokady flock(2) od swojego rodzica.)

### Execve


(niceness jeszcze)
## Zadanie 4

* ### Uruchom program «xeyes» po czym użyj na nim polecenia «kill», «pkill» i «xkill». Który sygnał jest wysyłany domyślnie?
* `kill` domyślny sygnał `SIGTERM` jeśli nie pomoże to `SIGKILL`
* `pkill` domyślny sygnał `SIGTERM`
* `xkill`(ten od xorg) domyślny sygnał `SIGKILL`
* ### Co opisują pozostałe pola pliku`/proc/ID/status` dotyczące sygnałów?
* `SigQ` Ilość zakolejkowanych sygnałów
* `SigPnd` Sygnały oczekujące dla wątków
* `ShdPnd` Sygnały oczekujące dla procesów
* `SigBlk` Sygnały zablokowane
* `SigIng` Sygnały ignorowane
* `SigCgt` Sygnały przechwytywane
:::spoiler
```
SigPnd, ShdPnd
Mask (expressed in hexadecimal) of signals pending for thread and for process as
a whole (see pthreads(7) and signal(7)).
SigBlk, SigIgn, SigCgt
Masks (expressed in hexadecimal) indicating signals being blocked, ignored, and
caught (see signal(7)).
```
:::
* ### Który sygnał zostanie dostarczony jako pierwszy po wybudzeniu procesu?
Z tego co rozumiem to moga się pojawić wyścigi sygnałów, ale nie powinny bo syganły są ustawione w jakiś konkretny sposób i czytane są pokolei z maski
np: `«SIGUSR2»` << `«SIGUSR1»` << `«SIGINT»` << `«SIGHUP»` (pierwszy)
## Zadanie 5

**[Sinit - Link](https://git.suckless.org/sinit/files.html)**
:::danger
dlaczego mamy sigalarm kiedy mamy sigchild??? ani PPo tego nie wie ani nikt, ale prawdopodobnie dlatego, bo jak mamy reparenting do inita to on moze nie dostac wtedy sigchild i moze nie wiedziec ze mamy zombie do pogrzebania
wlasciwie to PPo nie umie korzystac z gita'
https://git.suckless.org/sinit/commit/170d599d58efee6c9be675a85c6e435d68e8a2de.html
:::
:::spoiler
```c=
/* See LICENSE file for copyright and license details. */
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define LEN(x) (sizeof (x) / sizeof *(x))
#define TIMEO 30
static void sigpoweroff(void);
static void sigreap(void);
static void sigreboot(void);
static void spawn(char *const []);
static struct {
int sig;
void (*handler)(void);
} sigmap[] = {
{ SIGUSR1, sigpoweroff },
//proces dziecka się zatrzymał lub zakończył
{ SIGCHLD, sigreap },
//alarm co 30 sekund
{ SIGALRM, sigreap },
{ SIGINT, sigreboot },
};
#include "config.h"
static sigset_t set;
//sigprocmask(2) - zarządzanie maską sygnałów wątku
//sigwait(3) - zatrzymuje wykonanie wątku do czasu, aż jeden z
//sygnałów określonych w signal set będzie w stanie pending,
//usuwa go z pending i zwraca jego numer w sig
int
main(void)
{
int sig;
size_t i;
//error jeżeli nie jest pierwszym procesem
if (getpid() != 1)
return 1;
chdir("/");
//set - zbiór wszystkich sygnałów
sigfillset(&set);
//blokuj wszystkie sygnały
sigprocmask(SIG_BLOCK, &set, NULL);
//wywołaj rc.init
spawn(rcinitcmd);
while (1) {
//wygeneruj SIGALRM dla procesu po 30 sekundach
alarm(TIMEO);
//czekaj na sygnał występujący w set
//i zapisz jego numer w sig
sigwait(&set, &sig);
//sprawdzamy dla każdego sygnału w sigmapie na górze
for (i = 0; i < LEN(sigmap); i++) {
//czy właśnie przechwyciliśmy ten sygnał
if (sigmap[i].sig == sig) {
//jeżeli tak, to wywołaj procedurę obsługi tego sygnału
sigmap[i].handler();
break;
}
}
}
/* not reachable */
return 0;
}
static void
sigpoweroff(void)
{
//rc.shutdown poweroff
spawn(rcpoweroffcmd);
}
static void
sigreap(void)
{
//WNOHAND - wróć natychmiast, jeżeli żadne dziecko
//się nie zakończyło
//argument -1 - czekaj na którekolwiek z dzieci
//return val zwraca PID dziecka, którego stan się zmienił,
//lub z WNOHANG jeżeli żadne nie zmieniło stanu to 0
//-1 gdy error
//tldr; oczyszczanie dzieci co 30 sekund
while (waitpid(-1, NULL, WNOHANG) > 0)
;
alarm(TIMEO);
}
static void
sigreboot(void)
{
//rc.shutdown reboot
spawn(rcrebootcmd);
}
static void
spawn(char *const argv[])
{
switch (fork()) {
case 0: //child process
//odblokuj sygnały
sigprocmask(SIG_UNBLOCK, &set, NULL);
//uruchom program w nowej sesji
setsid();
execvp(argv[0], argv);
//wypisz error
perror("execvp");
_exit(1);
case -1:
perror("fork");
}
}
```
:::
* ### Opowiedz jakie zadania pełni minimalny program rozruchowy `sinit`.
* Dba o to aby być pierwszym procesem
* Przechwytuje wszystkie sygnały
* Wywołuje restart systemu
* Wywołuje wyłączenie systemu
* Wywołuje oczyszczenie dzieci
* ### Jakie akcje wykonuje pod wpływem wysyłania do niego sygnałów wymienionych w tablicy `sigmap`?
* `SIGUSR1` - wyłącza urządzenie
* `SIGCHLD` - grzebanie dzieci
* `SIGALRM` - grzebanie dzieci
* `SIGINT` - ponowne uruchomienie
* ### Do czego służą procedury
* `sigprocmask(2)` ustawianie aktualnie blokowanych procesów
* `sigwait(3)` oczekuje na wywołanie jednego z sygnałów wskazanych w masce
* ### W jaki sposób grzebie swoje dzieci?
* przy uruchomieniu zostaje zaplanowane pierwsze czyszczenie dzieci
* proces za pomocą waitpid(-1, NULL, WNOHANG) w pętli czeka aż nie będzie dzieci do pogrzebania po czym ponownie planuje wykonanie czyszczenia
## Zadanie 6

```c=
#include "csapp.h"
static pid_t spawn(void (*fn)(void)) {
pid_t pid = Fork();
if (pid == 0) {
fn();
printf("(%d) I'm done!\n", getpid());
exit(EXIT_SUCCESS);
}
return pid;
}
// Możliwe że zbędne
void sig_handler1(int signo)
{}
static void grandchild(void) {
printf("(%d) Waiting for signal!\n", getpid());
/* TODO: Something is missing here! */
signal(SIGINT, sig_handler1); // Możliwe że zbędne
pause();
/* TODO: END */
printf("(%d) Got the signal!\n", getpid());
exit(1); // możliwe że zbędne
}
static void child(void) {
pid_t pid;
/* TODO: Spawn a child! */
Setpgid(0, 0); // Standardowo proces jest w grupie rodzica, a teraz ma zostać w swojej własnej
pid = spawn(grandchild);
/* TODO: END */
printf("(%d) Grandchild (%d) spawned!\n", getpid(), pid);
}
/* Runs command "ps -o pid,ppid,pgrp,stat,cmd" using execve(2). */
static void ps(void) {
/* TODO: Something is missing here! */
char* const argv[] = { "/usr/bin/ps", "-o", "pid,ppid,pgrp,stat,cmd", '\0'};
execve(argv[0], argv, NULL);
/* TODO: END */
}
int main(void) {
/* TODO: Make yourself a reaper. */
/* TODO: END */
#ifdef LINUX
Prctl(PR_SET_CHILD_SUBREAPER, 1);
#endif
printf("(%d) I'm a reaper now!\n", getpid());
pid_t pid, pgrp;
int status;
/* TODO: Start child and grandchild, then kill child!
* Remember that you need to kill all subprocesses before quit. */
pgrp = spawn(child);
Waitpid(pgrp, NULL, 0);
pid = spawn(ps);
Waitpid(pid, NULL, 0);
Kill(-pgrp, SIGINT); // - jest dla grup
printf("(%d) SIGINT sent to group %d!\n", getpid(), pgrp);
Waitpid(-1, &status, 0);
printf("(%d) Grandchild exit code is %d!\n", getpid(), WEXITSTATUS(status));
/* TODO: END */
return EXIT_SUCCESS;
}
```
## Zadanie 7

Nie jestem przekonany ponieważ nigdy dobrze nie startuje :( i wydaje mi się że możemy pominąć sigprocmask
```c=
#include "csapp.h"
static void signal_handler(int signum, siginfo_t *info, void *data) {
if (signum == SIGINT) {
safe_printf("(%d) Screw you guys... I'm going home!\n", getpid());
_exit(0);
}
}
static void play(pid_t next, const sigset_t *set) {
for (;;) {
printf("(%d) Waiting for a ball!\n", getpid());
Sigsuspend(set); // tutaj sobie czekamy na sygnal (pilke), zawieszamy proces do otrzymania kolejnego sygnalu
usleep((300 + random() % 400) * 1000);
Kill(next, SIGUSR1);
printf("(%d) Passing ball to (%d)!\n", getpid(), next);
}
}
int main(int argc, char *argv[]) {
if (argc != 2)
app_error("Usage: %s [CHILDREN]", argv[0]);
int children = atoi(argv[1]);
if (children < 4 || children > 20)
app_error("Give number of children in range from 4 to 20!");
/* Register signal handler for SIGUSR1 */
struct sigaction action = {.sa_sigaction = signal_handler};
Sigaction(SIGINT, &action, NULL);
Sigaction(SIGUSR1, &action, NULL);
/* TODO: Start all processes and make them wait for the ball! */
sigset_t set; //Maska podawana do sigprocmask która blokuje opa sygnały
sigset_t set2;// maska dla sigsuspend-a która nie blokuje dwóch sygnałów
sigfillset(&set2);
sigdelset(&set2, SIGINT);
sigdelset(&set2, SIGUSR1);
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGUSR1);
Sigprocmask(SIG_BLOCK,&set,NULL);
pid_t next_pid = getpid();
for (int i = 0; i < children; i++)
{
pid_t pid = Fork();
if (!pid)
play(next_pid, &action.sa_mask);
else
next_pid = pid;
}
Kill(next_pid, SIGUSR1);
play(next_pid, &set2);
return EXIT_SUCCESS;
}
```
### Czy piłki w końcu skleją się w jedną?
Tak, ponieważ w pending dla danego procesu może być max 1 sygnał o danym numerze - reszta jest ignorowana. Ze względu na randomowy timer oczekiwania kiedyś może dojść do sytuacji, że w krótkim odstępie czasu proces otrzyma kilka piłek na raz - wtedy tylko jedna z nich pozostaje w grze, reszta jest ignorowana.
## Zadanie 8



`/usr/include/x86_64-linux-gnu/sys/ucontext.h` na fedorce w
`/usr/include/sys/ucontext.h`
```c=
// #define _GNU_SOURCE
// #include <sys/ucontext.h>
#include "csapp.h"
/* First address of handled region. */
#define ADDR_START ((void *)0x10000000)
/* Last address of handled region (not inclusive). */
#define ADDR_END ((void *)0x10010000)
static size_t pagesize;
/* Maps anonymouse page with `prot` access permissions at `addr` address. */
static void mmap_page(void *addr, int prot)
{
Mmap(addr, pagesize, prot, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
}
/* Changes protection bits to `prot` for page at `addr` address. */
static void mprotect_page(void *addr, int prot)
{
Mprotect(addr, pagesize, prot);
}
static void sigsegv_handler(int signum, siginfo_t *info, void *data)
{
ucontext_t *uc = data;
intptr_t rip;
/* TODO: You need to get value of instruction pointer register from `uc`.
* Print all useful data from `info` and quit in such a way that a shell
* reports program has been terminated with SIGSEGV. */
// rip = uc->uc_mcontext.gregs[REG_RIP]; //nie można użyć bo sie kłóci z csapp/h
rip = uc->uc_mcontext.gregs[16];
void *access_addr = info->si_addr; // Adres który miał zostać odczytany
int err_code = info->si_code; // Kod błedu dla SIGSEGV
if (access_addr > ADDR_START && access_addr < ADDR_END)
{
void *page_addr = (void *)((long)access_addr - ((long)access_addr % PAGE_SIZE));
if (err_code == SEGV_MAPERR)
{
safe_printf("Fault at rip=%lx accessing %lx! Map missing page at %lx.\n",
(long)rip, (long)access_addr, (long)page_addr);
mmap_page(page_addr, PROT_WRITE);
}
else if (err_code == SEGV_ACCERR)
{
safe_printf("Fault at rip=%lx accessing %lx! Make page at %lx writeable.\n",
(long)rip, (long)access_addr, (long)page_addr);
mprotect_page(page_addr, PROT_WRITE);
}
}
else
{
safe_printf("Fault at rip=%lx accessing %lx! Address not mapped - terminating!\n",
(long)rip, (long)access_addr);
exit(11);
}
/* TODO: END */
}
int main(int argc, char **argv)
{
pagesize = sysconf(_SC_PAGESIZE);
/* Register signal handler for SIGSEGV */
struct sigaction action = {.sa_sigaction = sigsegv_handler,
.sa_flags = SA_SIGINFO};
sigaction(SIGSEGV, &action, NULL);
/* Initially all pages in the range are either not mapped or readonly! */
for (void *addr = ADDR_START; addr < ADDR_END; addr += pagesize)
if (random() % 2)
mmap_page(addr, PROT_READ);
/* Generate lots of writes to the region. */
volatile long *array = ADDR_START;
long nelems = (ADDR_END - ADDR_START) / sizeof(long);
for (long i = 0; i < nelems * 2; i++)
{
long index = random() % nelems;
array[index] = (long)&array[index];
}
/* Perform off by one access - triggering a real fault! */
array[nelems] = 0xDEADC0DE;
return EXIT_SUCCESS;
}
```