Hlavní navigace

Terminály tajemství zbavené: řízení toku a konfigurace

24. 4. 2014
Doba čtení: 7 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í závěrečné části našeho seriálu se podíváme na řízení toku a na možnosti konfigurace terminálového ovladače.

Článek je překladem zápisku The TTY demystified, který napsal Linus Akesson. Překlad vychází se souhlasem původního autora.

Blokující vstup/výstup a řízení toku

Spustíte-li příkaz yes v okně terminálu xterm, před očima vám proběhne spousta řádků se znakem y. Příkaz yes přirozeně generuje řádky s ypsilony mnohem rychleji, než je xterm stíhá přečíst, aktualizovat framebuffer, požádat X server o překreslení okna a tak dále. Jak spolu tyto dva programy dokážou spolupracovat?

Odpověď se jmenuje blokující vstup/výstup (blocking I/O). Pseudoterminál je schopen udržet pouze omezené množství dat ve svém bufferu. Když se buffer naplní a yes  zavolá funkci write(2) , pak se obsluha této funkce zablokuje, čímž je proces yes přesunut do stavu přerušitelného spánku. V něm setrvá do doby, než xterm odebere nějaká data a buffer se uvolní.

Stejný postup se uplatní u sériových linek. yes dokáže data posílat mnohem rychleji než řekněme 9600 bitů za sekundu, ale protože rychlost sériového portu je omezena, fronta v jádře se brzy naplní a další voláníwrite(2) zablokují proces, případně selžou s chybovým kódem EAGAIN, je-li požadován neblokující V/V.

Co když vám řeknu, že je možné cíleně vyvolat blokování u TTY zařízení, aniž by byl buffer plný? Všechny pokusy o zápis pak povedou k zablokování procesu. K čemu může být taková funkce dobrá?

Představme si, že komunikujeme s nějakým starým terminálem VT-100 rychlostí 9600 bitů za sekundu. Terminálu jsme právě poslali nějakou složitou ovládací sekvenci, pomocí které má být obrazovka rolována. V tu chvíli je terminál plně vytížen rolováním displeje, takže nemůže přijímat žádná data. Fyzicky ale sériová linka stále běží plnou rychlostí 9600 bitů za sekundu. V bufferu terminálu ale není dostatek místa pro přijaté znaky. To je dobrý okamžik pro aktivaci blokování TTY. Jak toho ale dosáhnout z terminálu?

V minulých dílech jsme se už setkali s tím, že TTY subsystém dokáže dávat některým datovým bajtům speciální význam. Ve výchozím nastavení například znak ^C nebude předán aplikaci funkcí read(2) , ale místo toho skupina procesů (úloha) na popředí obdrží signál SIGINT. Stejným způsobem je možné nakonfigurovat TTY, aby reagovalo na bajty zastavení tokuspuštění toku. Typicky jde o znaky ^S (ASCII kód 19) a ^Q (ASCII kód 17). Staré hardwarové terminály tyto bajty vysílaly automaticky a očekávaly, že na jejich základě operační systém omezí přísun nových dat. Takto tedy funguje řízení toku a je to taky důvod, proč občas xterm vypadá zaseklý, stisknete-li omylem  ^S.

Je zde ale důležitý rozdíl: Zápis dat do TTY zařízení, který je pozastaven z důvodu řízení toku, blokuje pouze proces, který se snaží zapisovat, zatímco zápis do terminálu procesem z pozadí (je-li nastaveno tostop, pozn. překl.), způsobí vyslání signálu SIGTTOU celé skupině procesů. Nevím proč se návrháři UNIXu vydali cestou signálů SIGTTOU a SIGTTIN, když by stačilo spolehnout se na blokující V/V. Odhaduji, že ovladač TTY zodpovědný za řízení úloh byl navržen pro ovládání celých úloh, nikdy jen samostatných procesů.

Nastavení TTY zařízení

Pro zjištění řídicího terminálu pro daný shell je možné prohlédnout výstup příkazu ps l, nebo použít příkaz tty(1) .

Proces může číst a měnit konfiguraci otevřeného TTY zařízení pomocí volání ioctl(2) . API je popsáno v tty_ioctl(4) . Jde o součást binárního rozhraní mezi aplikacemi a Linuxem, které zůstává stejné napříč verzemi Linuxu. Portabilní aplikace by však raději měly využívat POSIXové wrappery popsané v manuálu termios(3) .

Nebudeme zacházet do detailů rozhraní termios(3) , nicméně pokud píšete program v C a rádi byste obsloužili ^C před příchodem SIGINT, zakázali editaci řádku nebo lokální odezvu, změnili bitovou rychlost nebo vypnuli řízení toku, vše potřebné najdete v této manuálové stránce.

Existuje také nástroj stty(1) , pomocí kterého lze ovládat TTY zařízení z příkazového řádku. Používá také API termios(3) .

Pojďme ho vyzkoušet!

$ stty -a
speed 38400 baud; rows 73; columns 238; 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; flush = ^O; min = 1; time = 0;
-parenb -parodd 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

Přepínač -a způsobí zobrazení všech nastavení. Standardně je použit řídicí terminál shellu, ve kterém je nástroj spušten, jiné zařízení je možné vybrat přepínačem -F. Některá nastavení ovlivňují parametry UARTu, některá nastavují linkovou disciplínu a další řízení úloh. Vše smícháno v jednom kbelíku pro pána. Podívejme se na první řádek:

speed UART Bitová rychlost. Ignorováno u pseudoterminálů.
rows, columns ovladač TTY Čísi představa o velikosti terminálu připojeného k TTY zařízení. Jde vlastně o dvojici proměnných v jádře, kterou si můžete libovolně nastavovat a číst. Při každém nastavení vyšle ovladač TTY SIGWINCH úloze na popředí.
line linková disciplína Číslo linkové disciplíny připojené k TTY zařízení. Nula znamená N_TTY. Všechny platné hodnoty jsou vypsány v /proc/tty/ldiscs. Jiné hodnoty se zdají být aliasem k N_TTY, ale nespoléhejte na to.

Tohle zkuste: spusťte xterm. Poznamenejte si název TTY zařízení (výstup příkazu tty) a jeho velikost (kterou hlásí stty -a). Nyní spusťte vim nebo podobný celoobrazovkový program. Editor si zjistí velikost terminálu z TTY zařízení a vyplní celé okno. Teď zkuste z jiného shellu:

$ stty -F <TTY zařízení> rows <polovina výšky>

Tím aktualizujete datovou strukturu TTY zařízení v jádře, čímž jádro odešle editoru SIGWINCH. Ten na signál zareaguje překreslením obrazovky do horní poloviny okna.

Druhý řádek výstupu stty -a obsahuje seznam speciálních znaků. Spusťte nový xterm a vyzkoušejte:

$ stty intr o

Tím se znak „ o “, namísto ^C stal znakem přerušení, generujícím SIGINT úloze na popředí. Zkuste spustit cat a ověřte, že jej není možné ukončit pomocí ^C. Pak zkuste napsat  hello.

Čas od času se dostanete k unixovému systému, kde nefunguje klávesa backspace. To je způsobeno tím, že terminál odesílá kód backspace (buď ASCII 8 ^H, nebo ASCII 127 ^~), který nesouhlasí s nastavením erase v TTY zařízení. Problém je možno odstranit zapsáním stty erase ^H, případně stty erase ^~. Mějte ale na paměti, že spousta aplikací používá knihovnu readline, která přepíná linkovou disciplínu do nezpracovaného režimu, takže není touto změnou ovlivněna.

Ve výpisu stty -a je také několik přepínačů. Opět jsou namíchány bez zvláštního pořadí. Některé se vztahují k nastavení UARTu, jiné ovlivňují chování linkové disciplíny, další ovlivňují řízení toku a řízení úloh. Pomlčka ( -) před názvem přepínače znamená, že je vypnutý; jinak je zapnutý. Význam všech přepínačů je popsán v manuálu stty(1) , my se zmíníme jen o několika:

icanon přepíná kanonický režim (tedy upravený režim čtení po řádcích). Zkuste toto v novém terminálu:

$ stty -icanon; cat

Všimněte si, že všechny znaky pro editaci jako backspace a ^U  přestaly fungovat. Také si všimněte, že cat dostává a vzápětí zpátky vypisuje každý znak samostatně namísto celých řádků.

echo povoluje odezvu terminálu a je ve výchozím stavu zapnuto. Povolte znovu kanonický režim ( stty icanon) a zkuste tohle:

$ stty -echo; cat

Jak píšete, terminálový emulátor posílá stisknuté klávesy jádru. Jádro obvykle vrací stejné znaky zpět na terminál, abyste viděli, co píšete. S vypnutou odezvou text vidět nemůžete, ale upravený režim je stále funkční, takže editační klávesy fungují. Až stisknete Enter, linková disciplína přenese editační buffer příkazu cat, který jej obratem vytiskne na terminál.

tostop určuje, zda je úlohám na pozadí povoleno zapisovat na terminál. Nejdříve zkuste toto:

$ stty tostop; (sleep 5; echo ahoj světe) &

Znak & způsobí spuštění druhého příkazu na pozadí. Po pěti sekundách se tato úloha pokusí zapsat na terminál. Ovladač TTY ji uspí signálem SIGTTOU a shell tuto událost oznámí, buď bezprostředně, nebo při zobrazení další výzvy. Zabijte teď úlohu na pozadí a zkuste toto:

$ stty -tostop; (sleep 5; echo ahoj světe) &

Opět dostanete okamžitě zpátky výzvu shellu, tentokrát ale úloha na pozadí po pěti sekundách vypíše ahoj světe doprostřed čehokoli, co zrovna píšete.

Konečně stty sane obnoví konfiguraci TTY zařízení do rozumného základu.

root_podpora

Shrnutí

Doufám, že vám tento seriál poskytl dostatek informací o TTY zařízeních a linkových disciplínách a jakým způsobem to všechno souvisí s terminály, editací řádku a řízení úloh. Další podrobnosti jsou k nalezení v odkazovaných manuálových stránkách, a také v manuálu glibc.

Poznámka k překladu: Při překladu jsem vycházel především z terminologie, kterou zavedl Luděk Skočovský v knize UNIX, POSIX, PLAN9. Tuto knihu také vřele doporučuji k podrobnějšímu studiu chování unixových systémů.

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