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.3 Ovládání periferních zařízení

10.3.1 Reprezentace periferních zařízení

Priferní zařízení jsou prostřednictvím speciálního souboru (kap. 2) reprezentována dvojicí identifikačních čísel (hlavní, vedlejší) a třídou zařízení (blokové, znakové). Hlavní číslo určuje typ zařízení k identifikaci příslušného ovládače. Pro každou třídu je v jádru tabulka zařízení obsahující adresy jednotlivých ovládačů. Hlavní číslo je pak indexem do tabulky blokových nebo znakových zařízení. Vedlejší číslo udává konkrétní zařízení v rámci typu určeného hlavním číslem (např. číslo diskové mechaniky, podkanál multiplexoru, přes který jsou připojeny terminály apod.), předává se jako argument do ovládače. Může též udávat zakódovaně vlastnosti konkrétního zařízení.

Tabulky zařízení (bdevsw a cdevsw) jsou jediným pojítkem mezi kódem jádra a ovládači. Je tedy poměrně snadné doplnit ovládač nového zařízení.

Mezi daty na zařízení a uživatelským procesem stojí systémové vyrovnávací paměti.

[OBR. 10.4]

Mezi touto vyrovnávací pamětí a adresovým prostorem procesu se přenášejí znaky v počtu zadaném ve volání read, write jádra (nezávisle na třídě zařízení). Parametry datové operace (tj. adresa a délka) se uloží do složek user.base a user.count.

Přenos mezi systémovou vyrovnávací pamětí a zařízením probíhá u znakového zařízení po znacích, u blokového zařízení po blocích pevné délky (obr. 10.4). Délka bloku se v různých implementacích Unixu liší. Nejčastěji bývá 512 slabik (standardní délka pro výměnu dat na magnetické pásce), u větších systémů 1024 slabik.

Protože např. pro archivaci disků na magnetickou pásku je přenos po blocích zdlouhavý, umožňuje jádro Unixu zacházet s blokovým zařízením jako se zařízením znakovým. Pak je vyloučena systémová vyrovnávací paměť a přenos se děje přímo mezi zařízením a adresovým prostorem procesu v počtu slabik určeném voláním read, write jádra (obr. 10.5). V terminologii Unixu se mluví o přímém ovládání (angl. raw device interface).

[OBR. 10.5]

10.3.2 Blokové zařízení a vyrovnávací paměti

V Unixu je hlavním představitelem blokového zařízení disk (ať již klasický pevný disk, disk s výměnnými svazky, pružný disk nebo disk typu Winchester). Blokově se někdy ovládají i magnetické pásky.

Nezávisle na vlastnostech použitých disků je každý svazek jádrem chápán jako množina bloků pevné délky (512 slabik nebo více), očíslovaných od 0. Ovládač disku přitom musí respektovat technické vlastnosti použitých disků. Jsou-li např. použity pružné disky se sektory délky 256 slabik, jsou za blok brány dva po sobě jdoucí sektory apod.

Blok je tedy na disku jednoznačně určen hlavním a vedlejším číslem zařízení a číslem bloku v tomto zařízení.

Jádro ve svém adresovém prostoru udržuje sadu vyrovnávacích pamětí, každé vyrovnávací paměti přísluší identifikační část (obsahující stavové příznaky a identifikaci bloku, je-li jím obsazena) a vlastní datová část. Počet vyrovnávacích pamětí se stanoví pevně při generování provozní verze, zpravidla jsou to desítky až stovky.

Zpočátku jsou všechny vyrovnávací paměti zařazeny v seznamu (dvojitě řetězeném) dostupných vyrovnávacích pamětí, se kterými žádný ovládač nepracuje a které mohou být použity pro datové operace s blokovým zařízením; tomuto seznamu se říká av-seznam.

Uvolňované vyrovnávací paměti se vracejí na jeho konec, nová vyrovnávací paměť se přiděluje z jeho začátku.

Dokončí-li se datová operace, uvolní se vyrovnávací paměť a zařadí se na konec av-seznamu. Vyrovnávací paměti však zůstává označena blokem, který je v ní umístěn. Na začátek av-seznamu se tak postupně dostávají vyrovnávací paměti s nejdéle nepoužitým blokem.

Každá již použitá vyrovnávací paměť je navíc zařazena v seznamu (opět dvojitě řetězeném) vyrovnávacích pamětí pro určité zařízení. Každému blokovému zařízení tedy jádro udržuje seznam propůjčených pamětí (d-seznam).

Do d-seznamu zařízení se vyrovnávací paměť zařadí datovou operací a zůstává v něm, dokud není třeba jako první vyrovnávací paměť z av-seznamu pro jinou datovou operaci. Pak se přesune do d-seznamu příslušného zařízení.

Možná situace je na obr. 10.6. Vyrovnávací paměť číslo 3 nebyla dosud použita a je tedy zařazena pouze v av-seznamu. Vyrovnávací paměti číslo 0 a 1 byly použity ukončenými datovými operacemi na zařízení (major, minor), jsou tedy zařazeny jak v av-seznamu, tak v d-seznamu zařízení. Vyrovnávací paměť číslo 2 byla přidělena zařízení (major, minor), ale operace ještě neskončila. Je tedy pouze v d-seznamu tohoto zařízení.

V d-seznamu jsou vyrovnávací paměti dokončených i nedokončených datových operací. Nedokončené tvoří frontu požadavků na přenos. Protože nejsou v av-seznamu, ukazatel do av-seznamu (na obr. 10.6 plná šipka) odkazuje na popis požadované operace. Popis sestává z indikace (zda jde o čtení nebo zápis), čísla bloku a adresy vyrovnávací paměti.

[OBR. 10.6]

10.3.3 Implementace blokových operací

Uvažme nejprve čtení. Žádá-li proces o čtení bloku na zařízení (major, minor), prohlédne se příslušný d-seznam. Je-li v něm vyrovnávací paměť s požadovaným blokem, není třeba blok číst z disku a požadovaný počet slabik se přenese z vyrovnávací paměti do adresového prostoru procesu.

Není-li v d-seznamu požadovaný blok obsažen, vyřadí se první blok z av-seznamu, zařadí se do d-seznamu zařízení a spustí se disková operace čtení. Proces se zablokuje až do ukončení čtení, pak zařadí vyrovnávací paměť na konec av-seznamu (v d-seznamu zůstává), a tím se čtení převede na předchozí případ.

Překračuje-li délka žádaná procesem hranici bloku, načte se i blok obsahující pokračování souboru. Jak uvidíme v čl. 10.4, nemusí to být blok, který následuje v jejich přirozeném očíslování.

Pro zápis je situace obdobná. Je-li blok v d-seznamu, přesunou se data z adresového prostoru procesu do vyrovnávací paměti, a ta se označí jako modifikovaná (angl. dirty). Skutečný zápis na disk se provádí asynchronně, až je nezbytně třeba. Tato situace nastane, je-li modifikovaná vyrovnávací paměť vyřazována jako první z av-seznamu. Podle normy X/OPEN lze vyžádat i okamžitý zápis na disk. Děje se tak při orevírání souboru (režim činnosti je zadán jako O_SYNCW). Pak jádro zaručuje, že při skončení write jsou data skutečně zapsána na disk.

Efektivita vyrovnávacích pamětí je založena na tom, že procesy často žádají čtení a zápis počtu slabik, který je menší než délka bloku (tím se snižuje počet diskových operací).

Jde však o slabé místo Unixu. Problém je v tom, že z hlediska procesů je zápis ukončen, avšak ve skutečnosti zápis ukončen být nemusí, buď pro chybu disku, nebo jen proto, že dosud na vyrovnávací paměť nepřišla řada. Dojde-li k neočekávanému zastavení systému (výpadkem napětí, chybou procesoru, ...), mohou být obsahy disků jiné, než by měly být. Jak se tento problém řeší, objasníme v kap. 11.

Voláním sync jádra se provede zápis všech modifikovaných vyrovnávacích pamětí na disk. Proces update tuto činnost vyvolá každých 30s; tím se riziko sníží, ale problém neřeší.

10.3.4 Znakové zařízení a vyrovnávací paměti

Jako znaková se ovládají všechna zařízení, která nevyhovují modelu blokového zařízení. Jde zejména o terminály (komunikační linky), tiskárny a zařízení obdobného charakteru. Terminály jsou připojeny plným duplexem (lze současně číst i zapisovat).

Pro snadnější tvorbu ovládačů znakových zařízení existují společné funkce. Týkají se zejména práce se znakovými frontami, což jsou systémové vyrovnávací paměti, vložené mezi zařízení a uživatelský proces (obr. 10.7).

Znaková fronta je organizována jako řetězený seznam bloků po několika znacích, tzv. c-seznam. Každé znakové zařízení má svůj c-seznam. Prostor pro ně se přiděluje a uvolňuje dynamicky v adresovém prostoru jádra.

Výstupní operace (např. na tiskárnu) probíhá tak, že se znaky z adresového prostoru procesu přenesou do fronty. Překročí-li přitom její délka předem stanovenou horní mez, proces se zablokuje.

[OBR. 10.7]

Ovládač aktivuje zařízení s příchodem prvního znaku do fronty a výstup dalších znaků z fronty na zařízení je řízen přerušením od zařízení. To se děje tak dlouho, dokud se fronta nevyprázdní a ovládač uvede zařízení do klidového stavu.

Klesne-li během vyprazdňování fronty počet znaků pod určitou mez, proces se odblokuje.

Výstup znaků na zařízení tedy probíhá rychlostí zařízení. Znakové fronty se plní zpravidla v dávkách vždy na maximum a než počet znaků ve frontě klesne pod dolní mez, je proces zablokován a uvolní procesor. V anglické terminologii se pro horní a dolní mez používají výrazy: high/low water mark. Horní a dolní mez závisí na zařízení (např. 100 a 50 pro tiskárnu). Je-li dolní mez polovinou horní meze, jde o klasické střídání vyrovnávacích pamětí.

Analogickým způsobem se zachází s jednoduchým vstupním zařízením (snímač děrné pásky). Ovládač při otevírání odpovídajícího speciálního souboru odstartuje čtení znaku ze zařízení a znaky, jejichž úspěšné načtení se hlásí přerušením, ukládá do vstupní fronty. Po dosáhnutí horní meze ovládač další čtení znaků ze zařízení zastaví.

Požádá-li uživatelský proces jádro o načtení určitého počtu znaků z tohoto vztupního zařízení, jsou mu předány ze vstupní fronty. Při vyprazdňování fronty se počet znaků ve frontě sníží pod dolní mez a jádro opět odstartuje čtení znaku ze zařízení atd.

10.3.5 Ovládání terminálů

Terminál sestává z klávesnice a obrazovky. Jde tedy o dvě zařízení, která však nelze ovládat samostatně. Prvním důvodem je opisování znaků zadaných z klávesnice na obrazovku (echo). V tom případě musí ovládač zajistit přesun znaků mezi dvěma c-seznamy, přesněji ze vstupní do výstupní fronty. Druhý důvod spočívá v tom, že znaky na klávesnici zadává chybující uživatel. Je výhodné, má-li uživatel možnost svou chybu napravit ihned, nejlépe tak, aby se chybně zadané znaky nenačetly do procesu, který si čtení z klávesnice vyžádal.

S terminálem jsou proto v Unixu spojeny tři znakové fronty. Jedna výstupní, se kterou se zachází způsobem popsaným v předchozím odstavci, a dvě vstupní fronty, přímá (raw) a upravená (cooked).

Do přímé fronty (obr. 10.8) se ukládají všechny znaky z klávesnice. Ovládač terminálu dále interpretuje speciální znaky (např. pro výmaz předchozího znaku a řádku) a převádí víceznakové posloupnosti znaků (např. \007) na kód znaku. Výsledek interpretace ukládá do upravené fronty a opisuje na obrazovce, tj. ukládá do výstupní fronty. Teprve až uživatel zadá znak nového řádku, je obsah přímé fronty konečný a datová operace končí.

[OBR. 10.8]

Procesy obvykle čtou z upravené fronty. Některé programy (např. vi) však potřebují číst každý znak zadaný uživatelem a nemohou čekat na celý řádek. Pak mají možnost číst z přímé fronty, což je na obr. 10.8 znázorněno tečkovanou šipkou.

Přepnutí na čtení z přímé fronty se provede příkazem

$ stty raw
což je poněkud nebezpečné, protože pro návrat do původního stavu musíte bezchybně zadat
$ stty cooked
a to se nemusí podařit.

Každý terminál je pro jádro a ovládač kromě speciálního souboru reprezentován tabulkou (strukturou tty), v níž jsou kromě hlav c-seznamů uloženy i charakteristiky terminálů. K nim patří rychlost přenosu, parita, znaky pro výmaz předchozího znaku, řádku, znak, který generuje SIGINT apod. Ze struktury tty uvedeme jen některé její složky:

#define NCC 8
struct tty {
struct clist t_rawq;		/* přímá fronta */
struct clist t_cookedq;		/* upravená fronta */
struct clist t_outq;		/* výstupní fronta */
...
ushort t_iflag;			/* režim vstupu */
ushort t_oflag;			/* režim výstupu */
ushort t_cflag;			/* vlastnosti terminálu */
...
char t_term;			/* typ terminálu */
...
unsigned char t_cc[NCC];	/* speciální znaky */
}

Obsah některých položek této tablky se dá přečíst nebo změnit příkazem stty shellu a programově voláním ioctl jádra.

Funkce použité v ovládačích každého terminálu jsou soustředěny ve správě terminálů (obr. 10.1) a tvoří tzv. linkovou disciplínu (předpokládáme, že treminály jsou připojeny i vzdáleně přes modem na sériové linky). Vstupní body do správy terminálů jsou uloženy v tabulce linesw, která bude popsána v odst. 10.3.6.

10.3.6 Ovládače

Vraťme se nyní k tabulkám zařízení. Jsou pro každou třídu číslovány zvlášť a v obou případech jde o pole struktur. V tabulce blokového zařízení

extern struct bdevsw {
	int (*bopen)	();
	int (*bclose)	();
	int (*bstrategy)();
	struct buf *d_list;
	} bdevsw[];
jsou uvedeny adresy funkcí ovládače příslušného zařízení. Jsou to funkce pro otevření, uzavření a strategii, tedy pro zařazení požadavku na přenos bloku do d-seznamu. Strategii proto, že ovládač může při výběru požadavků, čekajících v d-seznamu, uplatnit různé startegie výběru, např. u disků s cílem minimalizovat pohyb raménka. Zbývající položkou struktury je právě odkaz na d-seznam příslušný blokovému zařízení.

Ukázkou obsazení tabulky blokových zařízení může být např. (major je hlavní číslo):

struct bdevsw bdevsw [] = { 		/* major, zařízení */
rkopen, rkclose, rkstrategy, &rktab,	/* 0, disk RK */
dyopen, dyclose, dystrategy, &dytab,	/* 1, diskety DY */
0					/* konec tabulky */
};

Obdobně pro znaková zařízení jsou v tabulce cdevsw

extern struct cdevsw {
	int (*copen)	();
	int (*cclose)	();
	int (*cread)	();
	int (*cwrite)	();
	int (*cioctl)	();
	struct tty *ctty;
	} cdevsw[];
uvedeny adresy funkcí ovládače. Jsou to funkce pro otevření a uzavření, čtení, zápis a řídící operace, které jsou třeba pro implementaci volání ioctl jádra (odst. 6.3.3). Mezi ně patří např. přepnutí na jinou linkovou disciplínu, což je specialita Unixů z Berkeley, aktivace skupiny procesů pro řízení prací (odst. 7.2.3) apod. Součástí tabulky je rovněž odkaz na strukturu tty.

Konkrétní obsazení obou znakových tabulek se dá ilustrovat třeba takto:

struct cdevsw cdevsw [] = { 				/* major, zařízení */
klopen, klclose, klread, klwrite, klioctl,0		/* 0, konzola */
syopen, none, syread, sywrite, syioctl, 0		/* 1, tty */
none, none, mmread, mmwrite, none, 0,			/* 2, mem */
lpopen, lpclose, none, lpwrite, none, 0,		/* 3, tiskárna */
dzopen, dzclose, dzread, dzwrite, dzioctl, dztty, 0,	/* 4, multiplexor */
rkopen, rkclose, rkread, rkwrite, none, 0,		/* 5, RK disk */
dyopen, dyclose, dyread, dywrite, none, 0,		/* 6, DY diskety */
0							/* konec */
};

Obsah tabulek je vcelku srozumitelný. Funkce, které nejsou pro určité zařízení třeba, jsou nahrazeny prázdnou funkcí none.

Konzola je terminál vyhrazený pro režim jednoho (super) uživatele. V pozdějších verzích Unixu funkci konzoly převezme ten terminál, u kterého se superuživatel přihlásí. Zařízení tty je vždy ten terminál, u kterého se uživatel přihlásí. Funkce syopen, syread, ... totiž pouze vyvolávají odpovídající funkce ovládače přískušného terminálu. Jeho hlavní číslo zjistí v systémovém segmentu procesu. Je to buď konzola, nebo multiplexor, na kterém jsou jednotlivé terminály odlišeny vedlejším číslem. Tak např. funkce pro čtení je realizována takto:

syread(dev)
dev_t dev;
{
	(*cdevsw[major(user.ttyd)].read)(user.ttyd)
}
čímž se z tabulky cdevsw vyvolá funkce uvedená jako její složka read. Této funkci se předá identifikace terminálu aktivujícího proces. Řádek tabulky cdevsw se určí podle hlavního čísla; major je zde makro
#define major(x) (int)(((unsigned) x >> 8))

Zařízení mem umožňuje pracovat s fyzickou operační pamětí a zasahovat tak do adresových prostorů jiných procesů (i do vlastního). Ovládač mem není ovládačem žádného zařízení, jde o vnitřní modul jádra. Obsluhuje i prázdný speciální soubor /dev/null, koš na bity.

Disky a diskety mají v blokové tabulce jiné hlavní číslo než v tabulce znakové (mohou mít i stejné).

Linková disciplína (tj. funkce používané ovládači terminálů pro ovládání linky a znakových front) je popsána strukturou linesw, která se v různých verzích Unixu liší a může vypadat např. takto:

extern struct linesw {
	int (*lopen)	();	/* otevření */
	int (*lclose)	();	/* uzavření */
	int (*lread)	();	/* čtení */
	int (*lwrite)	();	/* zápis */
	int (*lioctl)	();	/* řízení */
	int (*lrint)	();	/* obsluha přerušení */
	int (*modem)	();	/* obsluha modemu */
	} linesw[];

Její nastavení může být např.

struct linesw linesw[] = {
ttyopen, none, ttyread, ttywrite, none, ttyinput, ttystart,
0
};

V tomto příkladu je uvedena pouze jediná linková disciplína (což je obvyklé).

Naplnění tabulek je součástí generování provozní verze. Ve verzích Unixu s dostupnými zdrojovými texty pobíhá jejich kompilací (odst. 11.3.2), v ostatních verzích se tabulky naplňují konfiguračními programy.

Hlavní a vedlejší čísla zařízení zjistíme výpisem adresáře /dev (ve shodě s předchozím obsahem tabulek bdevsw a cdevsw:

$ cd /dev
$ ls -l console kmem mem null *rk* root swap tty*

crw-rw-rw-  1 root    root           0,0  Sep 21 12:49 console
crw-------  1 sysinfo sysinfo        2,0  Oct 11 1984  kmem
crw-------  1 sysinfo sysinfo        2,1  Oct 11 1984  mem
crw-------  1 sysinfo sysinfo        2,2  Oct 11 1984  null
brw-------  3 bin     bin            0,0  Oct 23 1984  rk0
brw-------  1 bin     bin            0,1  Oct 23 1984  rk1
brw-------  1 bin     bin            0,0  Oct 23 1984  root
crw-------  1 bin     bin            5,0  Oct 23 1984  rrk0
crw-------  1 bin     bin            5,1  Oct 23 1984  rrk1
brw-------  1 bin     bin            0,0  Oct 23 1984  swap
crw-rw-rw-  1 root    root           6,0  Sep 21 12:49 tty00
crw-rw-rw-  1 root    root           6,1  Sep 21 12:49 tty01
crw-rw-rw-  1 root    root           6,2  Sep 21 12:49 tty02
crw-rw-rw-  1 root    root           6,3  Sep 21 12:49 tty03
$
Pozn.: Tento výpis nesouvisí s obdobným výpisem v kap. 2.