Operační systém UNIX a jazyk C

Jan Brodský
Luděk Skočovský

SNTL Nakladatelství technické literatury Praha 1989, ISBN 80-03-00049-1

10. Implementace


10.7 Proudy

Pro síťové aplikace je obsluha linek terminálovými ovládači zbytečně složitá (přímá a upravená fronta). Kromě výměny linkové disciplíny nelze do jádra doplňovat protokoly jednotlivých vrstev sítě. Oba důvody vedly k úpravám jádra.

V r. 1984 D. Ritchie uveřejnil návrh úprav [2], které byly realizovány ve verzi Unix System V.3 z r. 1987. Základní složkou nového mechanismu je proud (stream). Proud je plně duplexní spojení mezi procesem a zařízením nebo pseudozařízením. Sestává z několika lineárně spojených modulů.

Moduly mezi sebou komunikují výhradně tak, že svému nejbližšímu modulu zašlou zprávu. Na konci proudu, který je nejblíž procesu, jsou k dispozici rutiny jádra, které požadavky typu write a ioctl převedou na zprávy zasílané dál do proudu. Požadavek typu read převezme data z proudu a předá je do procesu. Na opačném konci proudu je ovládač zařízení. Tak data a řídící požadavky jsou zaslány směrem k zařízení, opačně data a stav zařízení jsou ve formě zpráv zaslány proudem do procesu. Další případné mezimoduly proudu mohou data zpracovávat nejrůznějším způsobem.

[OBR. 10.17]

Oba koncové moduly proudu jsou spojeny v okamžiku otevření zařízení (obr. 10.17). Další mezimoduly jsou vkládány dynamicky na žádost procesu požadavkem push, zpět vyjmuty požadavkem pop. Proud tedy může být chápán jako zásobník proudových modulů s vrcholem nejblíže k procesu. Situaci ilustruje obr. 10.18 a 10.19.

[OBR. 10.18]

Každý proudový modul sestává ze dvou protisměrných front. Fronta dále sestává z vlastní fronty znaků, tj. seznamu bloků, dvou funkcí service a put a stavových informací. Funkce put je volána sousedním modulem a ukládá blok do seznamu (zařadí ho do fronty). Obslužná funkce (service) je volána kdykoliv je ve frontě blok.

Struktura fronty je vidět z její deklarace:

struct queue {
	int flags;		/* příznakové bity */
	void (*put)();		/* funkce put */
	void (*service)();	/* funkce service */
	struct queue *next	/* fronta po proudu */
	struct queue *dualq;	/* protisměrná fronta */
	struct block *first;	/* první blok ve frontě */
	struct block *last;	/* poslední blok ve frontě */
	int hiwater;		/* maximální počet znaků ve frontě */
	int lowater;		/* minimální hladina */
	int count;		/* aktuální počet znaků */
} 

Zprávy vyměňované mezi moduly proudu jsou v blocích, dynamicky přidělovaných v různých délkách, typicky 4, 16, 64 nebo 1024 slabik. Typ zprávy může být:

DATA		data,
BREAK		stisknutí DEL,
HANGUP		přerušení linky,
DELIM		oddělovač dat,
IOCTL		řídící požadavek, který musí být potvrzen,
IOCACK		kladné potvrzení,
IOCNAK		záporné potvrzení,
START, STOP	tradiční X/ON, X/OFF protokol.

Zprávy typu IOCTL nenesou data, ale ovlivňují činnost proudových modulů. Ten modul, který zprávě porozumí změní podle ní svou činnost a vyšle zpět potvrzení IOCACK. Jinak zprávu propustí beze změny dále. Nenajde-li se modul, který zprávě rozumí, koncový modul proudu musí vyslat zpět IOCNAK. Počáteční modul proudu čeká na potvrzení a výsledek předává žádajícímu procesu.

[OBR. 10.19]

Proudové moduly mohou dodržovat délku bloků, mohou však rovněž datové bloky libovolně spojovat nebo rozpojovat. Bloky s řídícími zprávami však spojovány být nemohou.

Na rozdíl od klasického jádra Unixu mají proudové moduly charakter korutin, tzn. že jádro při vhodné příležitosti volá obslužnou funkci každé aktivní fronty. Zda fronta je nebo není aktivní, se pozná podle jednoho bitu slova flags. Obslužná funkce zpracuje první blok ve frontě a výsledek zapíše do sousední fronty tak, že vyvolá její funkci put. Tak pokračuje, dokud svou frontu nevyprázdní nebo nezaplní sousední frontou. Pak vrátí řízení zpět jádru. Frontu aktivuje zpravidla sousední modul, zapisuje-li do prázdné fronty.

Opačně, každá fronta má dán maximální počet znaků (hiwater). Je-li překročen, fronta je zaplněna a předchozí modul v proudu je pozastaven. Obslužná funkce naopak při odebírání znaků z fronty může zjistit, že jejich počet (count) klesl pod minimální hladinu (lowater) a obnoví činnost předchozího modulu.

Typické schéma obslužné funkce je:

service(q)
struct queue *q;
{
while("fronta q není prázdná a fronta q->next není plná")
	"odeber blok z fronty q";
	"zpracuj tento blok";
	"ulož ho do fronty q->next";
}

Ukážeme, jak naprogramovat frontu ttyin z obr. 10.18. Je zvykem, že se znaky přicházející z terminálu skládají do řádku s vyřízením opravných znaků. Zde ukážeme jen skládání a jejich opis na obrazovce (echo):

ttyin_put(q, bp)
struct queue *q; struct block *bp;
{
"zařaď blok bp na konec fronty q";
q->dualq->put(q->dualq, bp);	/* echo do protisměru */
if("data bloku bp obsahují CR nebo LF");
	enable(q);		/* aktivuj frontu */
}

ttyin_service(q)
struct queue *q;
{
"vybírej znaky z fronty q až po CR nebo LF, vyřizuj
opravy (erase, kill) a skládej je do pole line";
q->next->put(line);
q->next->put(DELIM);
}

Funkce ttyin_put provádí echo co nejrychleji. Je-li modul tty bezprostředním sousedem ovládače terminálu, je ttyin_put volána z jeho přerušovací funkce. Časově náročné záležitosti, jako je skládání znaků do řádku včetně oprav, se dělají až v ttyin_service, která je aktivována až v okamžiku výskytu nového řádku.

Proudový mechanismus může být použit i pro komunikaci mezi dvěma procesy. Stačí využít speciálního proudového modulu, pseudozařízení. Je to modul, jehož funkce je patrna z obr. 10.20. Obslužná funkce pseudozařízení ukládá bloky ze své fronty nikoliv do fronty svého souseda po proudu, ale do fronty souseda své duální fronty.

[OBR. 10.20]

Další důležitý typ proudového modulu je modul nazvaný message, který transformuje zprávy typu IOCTL na typ DATA a naopak. Předpokládá se přitom jednoznačná korespondence zpráv. Tento modul lze pak např. využít k simulaci zařízení, obr. 10.21.

[OBR. 10.21]

Stejný postup se dá použít, je-li např. terminál připojen k vzdálenému počítači sítí. Řídící požadavky ioctl procesu na vzdáleném počítači (např. pohyb kurzoru) musí být interpretovány až v uzlu terminálu. Proto se ve vzdáleném počítači modulem message převedou na datové zprávy, sítí se přenesou a v uzlu terminálu se analogickým modulem message převedou zpět na zprávy IOCTL a interpretují se v lokálním ovládači terminálu.

Tento mechanismus je rovněž využit pro vytvoření několika virtuálních terminálů na jednom fyzickém bitově mapovaném terminálu Blit [2]. To je mj. odpověď Unixu na grafická okna osobních počítačů.