Článek je překladem zápisku The TTY demystified, který napsal Linus Akesson. Překlad vychází se souhlasem původního autora.
Procesy
Linuxový proces se může nacházet v jednom z následujících stavů:
R
Běžící, nebo připravený k běhu (ve frontě k běhu)
D
Nepřerušitelný spánek (čekání na událost)
S
Přerušitelný spánek (čekání na událost nebo signál)
T
Pozastavený, buď pomocí řízení úloh shellem, nebo během trasování debuggerem.
Z
Zombie proces (někdy také mátoha). Proces byl ukončen, ale rodič si nevyzvedl návratový kód.
Spuštěním příkazu ps l
je možné sledovat, které procesy běží a které právě spí. Pokud proces spí, sloupec WCHAN
(wait channel, tedy jméno čekací fronty) ukazuje, na jakou událost jádra daný proces čeká.
$ ps l
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
0 500 5942 5928 15 0 12916 1460 wait Ss pts/14 0:00 -/bin/bash
0 500 12235 5942 15 0 21004 3572 wait S+ pts/14 0:01 vim index.php
0 500 12580 12235 15 0 8080 1440 wait S+ pts/14 0:00 /bin/bash -c (ps l) >/tmp/v727757/1 2>&1
0 500 12581 12580 15 0 4412 824 - R+ pts/14 0:00 ps l
Název fronty „ wait
“ koresponduje se systémovým voláním wait(2)
, takže tyto procesy budou probuzeny kdykoli dojde ke změně stavu jejich dětských procesů. Existují dva druhy spánku: přerušitelný a nepřerušitelný. Ten nejběžnější přerušitelný znamená, že zatímco proces čeká v čekací frontě, může být přesunut na frontu k běhu, pokud mu přijde signál. Podíváte-li se do zdrojového kódu jádra, zjistíte, že všechny kódy, které čekají na událost, musí po návratu z funkce schedule()
kontrolovat, zda nečeká na vyřízení nějaký signál a případně systémové volání stornovat.
Sloupec STAT
ve výše uvedeném výpisu ps
zobrazuje aktuální stav procesu. Ve stejném sloupci se také zobrazují případné atributy a příznaky:
s
Proces je vedoucím procesem relace.
+
Proces je součástí skupiny procesů na popředí.
Tyto atributy se používají pro řízení úloh.
Relace a úlohy
Stisknete-li ^Z
pro pozastavení programu, nebo spustíte-li program na pozadí přidáním &
, začnete používat funkci řízení úloh. Úloha je totožná se skupinou procesů. Interní příkazy shellu jako jobs
, fg
a bg
mohou být použity ke správě úloh během relace. Každá relace je řízena vedoucím procesem relace, což je shell, úzce spolupracující s jádrem složitým protokolem signálů a systémových volání.
Následující příklad ukazuje vztah mezi procesy, úlohami (job) a relacemi (session):
Tyto interakce shellu…
…vedou k těmto procesům…
…a těmto strukturám v jádru:
-
Ovladač TTY (
/dev/pts/0
)
Velikost: 45×13
Řídicí skupina procesů: (101)
Skupina procesů na popředí: (103)
Konfigurace UART (ignorována, neboť jde o xterm): Přenosová rychlost, parta, délka slova a další
Konfigurace linkové disciplíny: upravený/surový režim, úprava konců řádků, přerušovací sekvence, atd.
Stav linkové disciplíny: editační buffer (nyní prázdný), poloha kurzoru v bufferu, atd. -
Roura
pipe0
Čtecí konec (připojen k PID 104 jako file descriptor 0)
Zapisovací konec (připojen k PID 103 jako file descriptor 1)
Buffer
Základní myšlenka spočívá v tom, že každá kolona (pipeline) je samostatnou úlohou, v rámci které by měly být jednotlivé procesy ovládány (pozastavovány, spouštěny a zabíjeny) současně. Proto volání kill(2)
umožňuje poslat signál celé skupině procesů. Volání fork(2)
ve výchozím nastavení umísťuje nově vzniklý proces do stejné skupiny procesů jako jeho rodič, takže například stisknutí ^C
z klávesnice ovlivní jak rodiče, tak dítě. Jedním z úkolů pro vedoucí shell relace je vytvářet novou skupinu procesů pro každou novou kolonu.
Ovladač TTY udržuje informaci o skupině procesů na popředí, ale pouze pasivně. O aktualizaci této informace se opět stará vedoucí shell. Stejně tak ovladač TTY udržuje informaci o velikosti terminálu, která také musí být nastavena buď terminálovým emulátorem, nebo i přímo uživatelem.
Jak můžete vidět na výše uvedených příkladech, několik procesů má připojeno /dev/pts/0
na svůj standardní vstup. Ale jen úloha na popředí (kolona ls |
sort
) bude vstup z terminálu dostávat. Stejně tak jen úloze na popředí bude dovoleno zapisovat na terminál. Pokud by se na terminál pokusil zapsat proces cat
, bude pomocí signálu pozastaven. (Poznámka překladatele: Záleží na nastavení příznaku tostop
, jehož výchozí nastavení může být různé. Více o tomto příznaku v příštím díle.)
Signálové šílenství
Pojďme se teď blíže podívat, jak ovladače TTY, linkové disciplíny a řadiče UART v jádře komunikují s uživatelskými procesy.
Unixové soubory, včetně speciálních souborů terminálového zařízení, mohou být čteny a zapisovány a může s nimi být dále nakládáno speciálním voláním ioctl(2)
, ve kterém je definována spousta terminálových operací. Volání ioctl
ale musí být zahájena ze strany procesu, nelze je použít k tomu, aby jádro asynchronně komunikovalo s procesem.
Douglas Adams ve Stopařově průvodci po Galaxii popisuje jednu extrémně nudnou planetu obývanou sklíčenými lidmi a určitým druhem zvířat s ostrými zuby, která s lidmi komunikují silným kousnutím do stehna. To je až nápadně podobné přístupu UNIXu, kde jádro s procesy komunikuje zasláním paralyzujících nebo i smrtících signálů. Procesy mohou některé signály zablokovat a zareagovat na vzniklou situaci, ale většina to nedělá.
Signály jsou tedy mechanizmem, pomocí kterého jádro asynchronně komunikuje s procesem. Každý signál je unikátní a je třeba ho zkoumat samostatně. Příkazem kill -l
je možné zjistit, které signály váš systém podporuje. Může to vypadat nějak takto:
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT
17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN
35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4
39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6
59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
Jak je vidět, signály jsou číslovány od jedničky. Nicméně pokud jsou zobrazovány v bitových maskách (jako u výstupu ps s
), nejméně významný bit připadá signálu 1. Nyní se blíže podíváme na některé signály:
SIGHUP
- Výchozí akce: ukončení procesu
- Možné akce: ukončení, ignorování, volání funkce
SIGHUP
posílá ovladač UART celé relaci při detekci zavěšení telefonního modemu. Standardně dojde k ukončení všech procesů. Některé programy jako nohup(1)
nebo screen(1)
se od relace odpojí, takže jejich děti signál zavěšení neobdrží.
SIGINT
- Výchozí akce: ukončení procesu
- Možné akce: ukončení, ignorování, volání funkce
SIGINT
posílá ovladač TTY úloze na popředí kdykoli se ve vstupním bufferu objeví znak interaktivního vyvolání pozornosti (typicky ^C
s ASCII kódem 3), pokud tato funkce nebyla v ovladači TTY vypnuta. Kdokoli s přístupem ke speciálnímu souboru TTY zařízení může tuto funkci vypnout nebo změnit znak pro její vyvolání. Správce relace hlídá nastavení TTY každé úlohy a při každém přepnutí úlohy nastavení aktualizuje.
SIGQUIT
- Výchozí akce: výpis paměti procesu (core dump)
- Možné akce: výpis paměti, ignorování, volání funkce
SIGQUIT
funguje stejně jako SIGINT
, ale výchozí akce je jiná a aktivační znak je typicky ^\
.
SIGPIPE
- Výchozí akce: ukončení procesu
- Možné akce: ukončení, ignorování, volání funkce
SIGPIPE
posílá jádro procesu, který se snaží zapisovat do roury, kterou nikdo nečte. Bez tohoto signálu by kolony jako yes | head
nikdy neskončily.
SIGCHLD
- Výchozí akce: ignoruje se
- Možné akce: ignorování, volání funkce
Když proces skončí nebo se pozastaví, jádro pošle rodičovskému procesu SIGCHLD
. Spolu se signálem se přenáší další informace, jako číslo procesu, uživatele a návratová hodnota. Vedoucí shell relace pomocí tohoto signálu sleduje jednotlivé úlohy.
SIGSTOP
- Výchozí akce: pozastavení
- Možné akce: pozastavení
Tento signál bezpodmínečně zastaví běh příjemce. Jeho obsluhu nelze změnit. Není to ale příkaz, který by jádro posílalo procesu při stisknutí ^Z
. Tím je SIGTSTP
, který může být procesem změněn. Aplikace může například přesunout kurzor na spodní okraj obrazovky a uvést terminál do známého stavu a teprve pak se uspat vyvoláním SIGSTOP
.
SIGCONT
- Výchozí akce: pokračování procesu
- Možné akce: pokračování, pokračování a volání funkce
SIGCONT
probudí pozastavený proces. Typicky ho posílá shell po použití příkazu fg
. Protože signál SIGSTOP
není možné zamaskovat, může neočekávaný signál SIGCONT
sloužit jako indikace, že proces byl na nějakou dobu pozastaven.
SIGTSTP
- Výchozí akce: pozastavení
- Možné akce: pozastavení, ignorování, volání funkce
SIGTSTP
funguje stejně jako SIGINT
a SIGQUIT
, jen používá kombinaci ^Z
a výchozí akcí je pozastavení procesu.
SIGTTIN
- Výchozí akce: pozastavení
- Možné akce: pozastavení, ignorování, volání funkce
Pokud chce proces úlohy na pozadí číst terminál, ovladač TTY celé úloze pošle tento signál. Tím je proces pozastaven.
SIGTTOU
- Výchozí akce: pozastavení
- Možné akce: pozastavení, ignorování, volání funkce
Pokud chce proces úlohy na pozadí zapisovat na terminál, ovladač TTY celé úloze pošle tento signál a úlohu pozastaví. Tuto funkci je možné zablokovat pro každé TTY zařízení samostatně.
SIGWINCH
- Výchozí akce: ignoruje se
- Možné akce: ignorování, volání funkce
Jak již bylo zmíněno, TTY zařízení udržuje informaci o velikosti terminálu, ale tato informace musí být udržována ručně. Kdykoli se velikost změní, ovladač TTY pošle úloze na popředí signál SIGWINCH
. Dobře se chovající interaktivní aplikace, jako třeba editory, si na základě tohoto signálu znovu načtou velikost terminálu z TTY zařízení a překreslí obrazovku.
Příklad
Předpokládejme, že editujete soubor ve svém oblíbeném terminálovém editoru. Kurzor je někde uprostřed obrazovky a editor je zaneprázdněn, například nějakým nahrazováním ve velkém souboru. Nyní stisknete ^Z
. Protože linková disciplína je nastavena, aby tento znak zachytila ( ^Z
je jeden bajt s ASCII kódem 26), nemusíte čekat až editor dokončí svou úlohu a začne číst z TTY zařízení. Linková disciplína okamžitě pošle SIGTSTP
celé skupině procesů na popředí. V této skupině je editor a všechny jeho dětské procesy, které vytvořil.
Editor má vestavěnou obsluhu signálu SIGTSTP
, takže jádro přesune aktuální běh editoru na obsluhu signálu. Ta vysláním speciálních sekvencí na terminál přesune kurzor na poslední řádek. Jelikož je proces editoru stále na popředí, nemá se zápisem na terminál žádný problém. Editor pak pošle SIGSTOP
své vlastní skupině procesů.
Editor je nyní pozastaven. Tato informace je předána vedoucímu shellu relace signálem SIGCHLD
, který zahrnuje i číslo pozastaveného procesu. Vedoucí shell přečte aktuální konfiguraci terminálu a uloží ji pro pozdější obnovení. Správce pak nainstaluje sebe jako úlohu na popředí pomocí příslušného volání ioctl
. Pak vytiskne něco jako [1]+ Pozastavena
, aby dal uživateli najevo, že úloha je pozastavena.
V tuto chvíli příkaz ps(1)
vypíše, že proces editoru je v pozastaveném stavu (stav T
). Pokud se pokusíme editor probudit, buď pomocí vestavěného příkazu bg
nebo posláním SIGCONT
příkazem kill(1)
, editor začne obsluhovat signál SIGCONT
. Během této obsluhy se pravděpodobně pokusí překreslit obrazovku zápisem do terminálu. Jenže to ovladač TTY nedovolí, protože úloha editoru je na pozadí. Ovladač TTY tedy pošle editoru SIGTTOU
, čímž ho zase pozastaví. To opět vyvolá SIGCHLD
směrem k vedoucímu shellu, který opět na terminál vypíše [1]+ Pozastavena
.
Když napíšeme fg
, shell obnoví nastavení linkové disciplíny, které dříve uložil. Tím dá ovladači TTY najevo, že úloha editoru je odteď zase na popředí. Nakonec skupině procesů kolem editoru pošle SIGCONT
. Editor se znovu pokusí překreslit obrazovku, v čemž mu tentokrát nic bránit nebude.
Obsah příští části
V příštím, závěrečném díle probereme řízení toku dat a možnosti nastavení terminálu.