Hlavní navigace

Terminály tajemství zbavené: procesy a signály

17. 4. 2014
Doba čtení: 9 minut

Sdílet

Terminálový subsystém TTY je středobodem každého linuxového a obecně i UNIXového systému. Jeho důležitost je ale často přehlížená a je těžké sehnat kvalitní popis vhodný pro začátečníky. V dnešní části našeho třídílného seriálu se podíváme na vztah mezi procesy a úlohami během terminálové relace.

Č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.

CS24_early

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.

Byl pro vás článek přínosný?

Autor článku

Ondřej Caletka vystudoval obor Telekomunikační technika na ČVUT a dnes pracuje ve vzdělávacím oddělení RIPE NCC, mezinárodní asociaci koordinující internetové sítě.