# Procesy, čili co je to vlastně spuštěný program

„Program" a „proces" se běžně používají zaměnitelně a je to zdroj nedorozumění, který se táhne až do produkce. Program je soubor na disku. Leží tam, nedělá nic a bude tam ležet i za rok.

Proces je běžící instance toho souboru. Stejný program může běžet padesátkrát naráz a každá instance má vlastní paměť, vlastní stav a vlastní osud. Napiš si `ps aux | grep firefox` - neuvidíš jeden proces, ale dvacet a víc, jeden rodičovský a k tomu další pro karty, grafiku a zvuk. „Jeden Firefox" je kolonie.

Stránka je o tom, co proces doopravdy je, jak vzniká, jak umírá a co se na tom láme. Neřeší, kdo dostane procesor - to je [plánování procesů](Planovani-procesu) - ani mechaniku [přepínání kontextu](Prepinani-kontextu). Předpokládá [systémová volání](Systemova-volani) a [rozdíl mezi režimem jádra a uživatelským](Jadro-a-uzivatelsky-rezim).

## Tři věci, nic víc

Tohle je nejdůležitější věc na celé stránce: **proces je adresní prostor plus stav procesoru plus tabulka otevřených souborů.**

To je celé. Zbytek stránky je jen rozvedení téhle věty.

**Adresní prostor** je mapa toho, co proces vidí v paměti - kód, data, halda, zásobník, namapované soubory. Detaily má [správa paměti](Sprava-pameti) a [stránkování](Virtualni-pamet-a-strankovani).

**Stav procesoru** jsou registry, hlavně ukazatel instrukcí a ukazatel zásobníku. Odchází z procesoru - uloží se. Vrací se - obnoví se.

**Tabulka otevřených souborů** je pole deskriptorů: 0 vstup, 1 výstup, 2 chyby, dál všechno, co si proces otevřel.

Všechno, co jádro s procesy dělá, je ukládání a obnovování téhle trojice.

## Co si jádro o procesu pamatuje

Struktura, kde je ta trojice zapsaná, se jmenuje PCB (Process Control Block), v Linuxu `struct task_struct`. Je v ní PID a PPID, stav, uložené registry, ukazatel na tabulky stránek, tabulka deskriptorů, UID, priorita, naběhaný čas, maska a fronta signálů a ukazatel na rodiče. Na x86-64 vyjde řádově na deset kilobajtů podle konfigurace jádra a ke každému úkolu patří navíc 16 KiB zásobníku pro běh v jádře.

V Linuxu je `task_struct` na jeden **úkol**, ne na proces. Proces je skupina úkolů, které sdílejí adresní prostor - proto se procesy i [vlákna](Vlakna) vytvářejí týmž voláním `clone`. Přečteš si ji přes `cat /proc/1234/status`.

### Stavy procesu a přechody mezi nimi

Klasických stavů je pět: nový, připravený, běžící, čekající a ukončený. Procesor je jeden na jádro, takže drtivá většina procesů nikdy neběží, jen čeká.

```mermaid
flowchart TD
    N[Nový] --> P[Připravený]
    P -->|plánovač vybere| B[Běžící]
    B -->|vyprší kvantum| P
    B -->|čeká na I/O nebo zámek| C[Čekající]
    C -->|data dorazila| P
    B -->|exit| U[Ukončený, čeká na rodiče]
    U -->|rodič zavolá wait| K[Uklizeno]
```

Nejzajímavější je poslední hrana: proces po `exit` ještě chvíli existuje. V `ps` odpovídají stavům písmena ve sloupci `STAT`:

**`R`** - běží nebo je připravený; `ps` to nerozlišuje.

**`S`** - přerušitelný spánek, sem patří skoro všechno.

**`D`** - nepřerušitelný spánek uvnitř jádra.

**`T`** - zastavený po `Ctrl+Z`, rozběhne ho `kill -CONT`.

**`Z`** - zombie: skončil a nikdo nepřečetl návratový kód.

**`I`** - nečinné jádrové vlákno, do zátěže se nepočítá.

Stav `D` se vyplatí vysvětlit, protože se o něj lidi pravidelně zabíjejí. Proces sedí v místě jádra, které signály vůbec nekontroluje, typicky uprostřed čtení z disku. `kill -9` na něj **funguje jen zdánlivě**: signál se uloží do fronty a čeká, až se proces vrátí do uživatelského režimu. Dokud operace neskončí nebo neselže, nevrátí se a nezabiješ ho ničím.

**Proces v `D` není zaseknutý program, je to zaseknuté zařízení.** Umírající NVMe, odpojený NFS server - hledej příčinu v [I/O](Vstup-a-vystup). Do zátěže se `D` počítá, takže load average 40 na nevytíženém procesoru je skoro vždycky tohle.

## Jak proces vzniká

Nový proces vyrobí jedině jiný proces - kromě toho jediného, který nastartuje jádro při [startu systému](Start-systemu).

### fork a exec: proč dvě volání a ne jedno

`fork` udělá kopii volajícího procesu. `exec` v aktuálním procesu zahodí adresní prostor a nahradí ho jiným programem. Spuštění programu je tedy `fork` a po něm `exec`.

`fork` kopíruje adresní prostor, tabulku deskriptorů, pracovní adresář, masku signálů a prostředí. Nekopíruje PID, ostatní vlákna, zámky souborů ani naběhaný čas.

Adresní prostor se přitom doopravdy nekopíruje. Obě strany dostanou tytéž fyzické stránky jen pro čtení a teprve zápis vyrobí soukromou kopii jedné stránky. Tomu se říká **copy-on-write** a je to důvod, proč jde forknout i proces s osmi gigabajty haldy.

Návratová hodnota je celý trik: **potomek dostane nulu, rodič PID potomka.** Jedno volání, dva návraty.

```c
pid_t pid = fork();
if (pid == 0) {
    close(1);                         // zavře standardní výstup
    open("vystup.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);  // dostane deskriptor 1
    execlp("ls", "ls", "-l", NULL);   // odsud dál je to jiný program
    _exit(127);                       // sem se dojde, jen když exec selhal
}
waitpid(pid, &status, 0);             // rodič si vyzvedne návratový kód
```

A tady je pointa. Mezi `fork` a `exec` je okamžik, kdy potomek existuje, ale nový program ještě neběží - a v něm se dá s procesem cokoliv udělat. Přesně to dělá shell u `ls -l > vystup.txt`. Program `ls` o přesměrování neví a nemusí umět nic než psát na výstup.

### Co tím Windows ztrácí

Windows nemá `fork`. Má `CreateProcess`, které postaví proces a rovnou do něj nahraje program - jedno volání s deseti parametry. Přesměrování se řeší vyplněním struktury `STARTUPINFO` a povolením dědění deskriptorů.

Ztrácí se ten mezikrok: co nejde vyjádřit parametry `CreateProcess`, nejde vůbec. Model `fork` a `exec` je obecnější, ale platí se za něj kopírováním tabulek stránek.

## Jak proces končí

### Zombie a sirotci

Když proces skončí, jádro uvolní paměť, zavře soubory a zahodí adresní prostor. Nechá si ale `task_struct` s návratovým kódem, protože ta informace patří rodiči. Do té doby je proces **zombie**.

Zombie se nedá zabít, protože už je mrtvý; `kill -9` na něj nemá žádný efekt. Uklidí ho jediná věc: rodič zavolá `wait` nebo `waitpid` a přečte si návratový kód.

Když rodič skončí dřív než potomek, je z potomka **sirotek** a jádro ho přepojí pod PID 1, který volá `wait` ve smyčce. Odtud plyne zdánlivý paradox: hromada zombie zmizí, jakmile zabiješ jejich rodiče.

### Proč v kontejneru zůstávají zombie

V kontejneru je PID 1 tvoje aplikace, ne init. Node ani Java o té roli nic nevědí, `wait` ve smyčce nevolají, a tak se pod nimi zombie hromadí, dokud nevyčerpají kvótu úkolů.

Druhá půlka problému je horší: **PID 1 má potlačené výchozí obsluhy signálů.** Bez vlastní obsluhy `SIGTERM` aplikaci `docker stop` nezabije a čeká deset vteřin na `SIGKILL`. Řešení je pro obojí stejné - dej jako vstupní bod `docker run --init` nebo `tini`. Zbytek má [virtualizace a kontejnery](Virtualizace-a-kontejnery).

## Strom procesů, skupiny a sezení

Každý proces má rodiče, takže procesy tvoří strom. Kořenem je PID 1, na Debianu systemd; podívej se na něj přes `pstree -p 1234`.

Nad stromem leží druhé členění, o kterém většina lidí neví, dokud je nekopne. Procesy se sdružují do **skupin** a skupiny do **sezení**. Jedna roura je jedna skupina - `sort data.txt | uniq -c | head` jsou tři procesy v jedné skupině a `Ctrl+C` míří na celou skupinu popředí. Proto se roura zastaví celá.

Sezení je svázané s terminálem. Když spadne SSH, dostane skupina popředí `SIGHUP` a implicitně umře. Proto existuje `nohup`, který `SIGHUP` ignoruje, a `setsid`, který proces posadí do vlastního sezení. U dlouhoběžících věcí je ale správná odpověď udělat z toho službu systemd.

Signály jsou nejjednodušší způsob, jak procesu něco říct: jedno číslo, žádná data. `SIGTERM` znamená ukonči se, `SIGKILL` nejde odchytit, `SIGCHLD` ohlásí konec potomka. Výklad je na [meziprocesové komunikaci](Meziprocesova-komunikace).

## Kolik proces stojí a kdy sáhnout po vlákně

Prázdný proces stojí v jádře řádově desítky kilobajtů. Triviální céčkový program přidá pár set kilobajtů obsazené paměti a vytvoření vyjde na desítky až stovky mikrosekund.

| Vlastnost | Proces | Vlákno |
|---|---|---|
| Adresní prostor | vlastní | sdílený |
| Tabulka deskriptorů | kopie při `fork` | sdílená |
| Cena vytvoření | desítky až stovky µs | jednotky až desítky µs |
| Pád jedné části | ostatní běží dál | **strhne celý proces** |

**Doporučení: procesy tam, kde spolu části komunikují málo, vlákna jen tam, kde sdílejí velkou datovou strukturu.** Nginx obsluhuje spojení procesy, jeden na jádro - pád workeru nepoloží server. Výpočet nad dvougigabajtovou maticí naopak dělej [vlákny](Vlakna).

## Fork bomba a proč pořád funguje

Nejznámější ukázka toho, že proces je zdroj jako každý jiný, se vejde na řádek:

```
:(){ :|:& };:
```

Funkce `:` zavolá dvakrát sama sebe a obě volání pošle na pozadí. Počet procesů roste exponenciálně, tabulka procesů se zaplní a systém přestane reagovat. Zabít to nejde, protože i `kill` potřebuje ke spuštění `fork`, který už neprojde.

**2. listopadu 1988** vypustil Robert Tappan Morris červa, který se šířil po tehdejším internetu. Ochranu proti opakované nákaze měl, ale schválně děravou - v jednom případě ze sedmi se nainstaloval i tam, kde už kopie běžela. Stroje se plnily kopiemi, dokud nebyly nepoužitelné; postihlo to zhruba 6 000 z nich. Poučení: **nepoložila je chyba v kódu, ale nekontrolované vytváření procesů.**

Obrana je dvojí a zapni obojí. `ulimit -u` omezí počet procesů na uživatele, trvale přes `/etc/security/limits.conf`. Služby a kontejnery hlídá kontrolér `pids` v cgroups - v systemd `TasksMax=`, v Dockeru `--pids-limit`.

```bash
systemd-run --user --scope -p TasksMax=50 bash  # shell s tvrdým stropem, na vyzkoušení
```

## Co se dnes už nedělá

**Démonizace dvojitým `fork`.** Návody pořád ukazují dvojí `fork`, `setsid` a zápis PID do souboru. Pod systemd napiš `Type=simple` a zůstaň na popředí.

**`kill $(cat /var/run/foo.pid)`.** PID se recykluje a číslo může mezitím dostat někdo jiný; strop ukáže `sysctl kernel.pid_max`. Používej `systemctl stop`, v kódu `pidfd_open` od jádra 5.3 (rok 2019).

## Diagnostika

| Příznak | Kde je problém |
|---|---|
| Proces v `D`, `kill -9` nezabírá | čeká v jádře na I/O, signál dojde až po návratu |
| Tisíce zombie | rodič nevolá `wait` |
| Zombie v kontejneru | aplikace je PID 1 a nesklízí; nasaď `--init` |
| Proces po odhlášení zmizel | dostal `SIGHUP`; chce to `nohup` nebo službu |
| `kill` zabil něco jiného | recyklovaný PID, nespoléhej na PID soubory |
| `ps` a `top` hlásí jiný počet | `ps aux` počítá procesy, `top -H` vlákna |
| `fork: Resource temporarily unavailable` | vyčerpaný `ulimit -u` nebo `pids.max` |

Tři příkazy pokryjí většinu případů:

```bash
ps -eo pid,ppid,stat,wchan:20,comm --sort=stat   # kdo v jakém stavu a v čem visí
ps -eo stat= | sort | uniq -c                    # rozložení stavů, hledej D a Z
cat /proc/1234/status                            # stav, rodič, počet vláken, signály
```

Sloupec `wchan` říká, ve které funkci jádra proces čeká, a u stavu `D` je to jediná užitečná informace. Zbytek má [nástroje a diagnostika](Nastroje-a-diagnostika).

## Co si odnést

**Proces je adresní prostor, stav procesoru a tabulka deskriptorů.** Zbytek je jen správa téhle trojice.

**Program je soubor, proces je jeho instance.** Jeden program, padesát procesů, padesát osudů.

**`fork` vrací nulu potomkovi a PID rodiči.** Mezikrok před `exec` je důvod, proč v shellu funguje přesměrování.

**Copy-on-write dělá `fork` použitelným.** Paměť se kopíruje až při zápisu a po stránkách.

**Zombie není proces, je to nevyzvednutá účtenka.** Sklidí ji `wait` rodiče, nebo po jeho smrti PID 1.

**Stav `D` znamená zaseknuté zařízení.** `kill -9` je tam ztráta času.

**Počet procesů je zdroj a musí mít strop.** `ulimit -u` a `pids` v cgroups nastav předem.

## Kam dál

- **[Vlákna](Vlakna)** - co se změní, když si adresní prostor sdílí víc úkolů
- **[Plánování procesů](Planovani-procesu)** - kdo z připravených dostane procesor a podle čeho
- **[Meziprocesová komunikace](Meziprocesova-komunikace)** - signály, roury a sdílená paměť pořádně
- **[Start systému](Start-systemu)** - odkud se vezme PID 1 a co má na starosti
