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.2 Správa procesů

Správa procesů zabezpečuje dynamické vytváření a rušení procesů, jejich synchronizaci a plánování, přidělování paměti a popř. odkládání na disk.

10.2.1 Adresové prostory procesu

Provádění procesu v Unixu má dvě fáze. V uživatelské fázi se provádějí instrukce uložené v textu programu (obr. 10.2), tj. v adresovém prostoru procesu. Po volámí jádra

[OBR. 10.2]

se procese přepne do své systémové fáze, do adresového prostoru jádra, a provádí instrukce uložené v textu jádra.

V adresovém prostoru procesu jsou pouze instrukce a data programu. Systémové tabulky jsou umístěny v adresovém prostoru jádra, v jeho datech. Mapování adresových prostorů do fyzické paměti je na obr. 10.2 naznačeno šipkami.

10.2.2 Sdílitelné texty programů

Procesoru pro interpretaci programu stačí instrukce pouze číst (pokud to instrukční repertoár procesoru umožňuje). To tvůrce Unixu vedlo k myšlence, že více procesů může být řízeno totožnou kopií textu programu, která ovšem musí být chráněna proti přepisu (ochranou paměti nebo mapováním). Takto sdílené texty jsou výhodné v interaktivním prostředí s mnoha současnými uživateli téhož programu, neboť v oprerační paměti stačí jediná kopie textu programu a tolik dat a zásobníků, kolik je uživatelů. Dále není třeba sdílitelné texty programu odkládat na disk (v souboru s výkonným programem je stále původní verze textu).

10.2.3 Evidence procesů a textů programů

Proces vznikne voláním jádra fork. Obraz nově vzniklého procesu je přesnou kopií obrazu původního procesu (s jediným rozdílem, totiž vrázeným číslem procesu).

Obrazem procesu zde míníme jeho systémový segment, text, data a zásobník. Novému procesu se vždy okopíruje datová oblast a zásobník. Text se kopíruje jen tehdy, není-li sdílitelný. Okopíruje se dále obsah systémového segmentu, v němž si jádro poznamenává různé vlastnosti procesu, je-li obraz procesu v operační paměti.

V systémovém segmentu je rovněž systémový zásobník. Dále je zde např. seznam procesem otevřených souborů. Tím se snadno vysvětlí dědičnost otevřených souborů voláním fork.

Jádro udržuje evidenci o procesech v tabulce procesů, záznam o procesu v této tabulce je tvořen strukturou procesu

struct proc {
	char status;		/* stav procesu */
	char pri;		/* dispečerská priorita */
	char restime;		/* doba setrvání v paměti */
	char cpu;		/* doba přidělení procesoru */
	char nice;		/* uživatelská priorita */
	short sig;		/* nevyřízené signály */
	short uid;		/* identifikace vlastníka */
	short pgrp;		/* pid vedoucího procesu skupiny */
	short pid;		/* identifikační číslo procesu */
	short ppid;		/* identifikační číslo rodiče */
	short addr;		/* adresa obrazu procesu */
	short size;		/* velikost obrazu */
	caddr_t event;		/* událost, na kterou proces čeká */
	struct text *textp;	/* odkaz na text */
}
která obsahuje důležité údaje o procesu po dobu jeho existence. Mezi ně patří identifikční číslo procesu, plánovací údaje, umístění textu, dat, systémového segmentu. Záznam v tabulce se obsadí při vzniku procesu a uvolní při jeho zániku. Počet záznamů je pevný, avšak volitelný při generování provozní verze jádra.

Tabulka textů programů (textová tabulka) udržuje evidenci o používaných textech. Každá položka ve struktuře text

struct text {
	short daddr;		/* disková adresa textu */
	short caddr;		/* adresa textu v paměti */
	short size;		/* délka textu */
	struct inode *iptr;	/* i-uzel prototypu */
	char count;		/* počet procesů */
	char ccount;		/* z toho v paměti */
	char flag;		/* příznaky */
}
obsahuje umístění textu v operační paměti a na disku, délku textu a počet procesů, které jsou tímto textem řízeny. Klesne-li počet procesů na nulu, lze prostor zabíraný textem i položku v textové tabulce uvolnit.

Jak obě tabulky spolu souvisí, je patrné z obr. 10.3.

[OBR. 10.3]

Systémový segment obsahuje strukturu user (a v ní první položku systémového zásobníku):

struct user {
label_t regs;		/* prostor pro úschovu registrů */
short uid;		/* efektivní identifikační číslo vlastníka */
short gid;		/* efektivní identifikační číslo jeho skupiny */
short ruid;		/* identifikační číslo vlastníka */
short rgid;		/* identifikační číslo jeho skupiny */
struct proc *procp;	/* odkaz na strukturu proc téhož procesu */
struct inode *cdir;	/* ukazatel na inode pracovního adresáře */
struct inode *rdir;	/* ukazatel na inode domovského adresáře */
struct file *ofile[NOFILE];/* pole ukazatelů na otevřené soubory */
caddr_t base;		/* počáteční adresa přenášených slabik */
unsigned count;		/* počet přenášených slabik */
off_t offset;		/* současné ukazovátko */
int arg[5];		/* argumenty volání jádra */
unsigned tsize;		/* délka textu */
unsigned dsize;		/* délka dat */
unsigned ssize;		/* délka zásobníku */
faddr_t signal[NSIG];	/* adresy funcí pro reakci na signály */
time_t utime;		/* uživatelský čas procesu */
time_t stime;		/* systémový čas procesu */
time_t cutime;		/* uživatelský čas všech potomků */
time_t cstime;		/* systémový čas všech potomků */
dev_t ttyd;		/* (major,minor) aktivujícího terminálu */
time_t start;		/* čas vzniku procesu */
short cmask;		/* maska práv pro vytváření souborů */
int cstack[1];		/* systémový zásobník */
}

10.2.4 Přidělování paměti

V té části operační paměti, do které jádro umísťuje obrazy procesů, jsou systémový segment, nesdílitelný text, data a zásobník procesu ukládány souvisle. Je-li text sdílen s jinými procesy, je umístěn v jiné části paměti.

Důvodem je snížení režie jádra při odkládání obrazu procesu na disk. Jak operační paměť, tak diskový prostor (na zařízení /dev/swap) se přiděluje metodou prvního vhodného volného úseku.

Jestliže se obraz zvětší (voláním brk nebo rozšířením zásobníku), resp. se vytváří proces nový (voláním fork), přidělí se potřebná část operační paměti a obsah staré paměti se okopíruje do nově přidělené části. Při rozšiřování procesu se stará paměť uvolní, při vytváření nového procesu nikoliv. Vždy se příslušně upraví tabulky procesů a textů.

Prostor v operační paměti se přiděluje po nejmenších částech, které lze zobrazit mezi adresovými prostory. V původní terminologii se užívá anglického slova click (mlasknutí, cvaknutí), přidržíme se zde termínu sousto. Na PDP-11 je soustem 32 slov. V těchto jednotkách jsou v tabulkách uváděny délky textu, dat a zásobníku.

Nestačí-li prostor v operační paměti, použije se prostor na disku /dev/swap. Obraz procesu se odloží na disk a je-li opět zaveden (swap-in) do operační paměti, je buď delší, nebo jde o obraz nového procesu.

Odkládání na disk a znovuzavádění do paměti provádí proces číslo 0. Činnost tohoto procesu si probereme v odst. 10.2.6, neboť k tomu musíme znát vzájemné působení procesů.

10.2.5 Synchronizace a plánování

Procesu v systémové fázi nemůže být ve standarrdním jádru procesor odebrán. Ke změně identifikace procesu dochází až v okamžiku přepínání ze systémové do uživatelské fáze procesu, kdy je procesor přidělen přpravenému procesu s nejvyšší dispečerskou prioritu. Indikace, že je třeba přeplánovat procesy, se nastaví nenulovou hodnotou proměnné runrun jádra. Má-li runrun nulovou hodnotu, přeplánování se neprovede a pokračuje se v přerušeném procesu. Systémový proces si určuje dispečerskou prioritu sám. Nemůže-li totiž pokračovat dál (např. musí-li počkat na dokončení periferní operace nebo na uvolnění prostředku, obecně na nějakou událost), zablokuje se voláním synchronizační funkce jádra

sleep(event, pri)
kde event je identifikace události, na kterou systémový proces čeká a pri je dispečerská priorita, která bude procesu přidělena v okamžiku jeho reaktivace. Zablokováním systémový proces uvolní procesor.

Událost se identifikuje dohodnutým celým číslem (obvykle je to adresa příslušné tabulky jádra).

Voláním interní synchronizační funkce

wakeup(event)
jsou reaktivovány všechny procesy, které na událost event čekají. Mezi nimi se uplatní nejdříve proces, který si předepsal nejvyšší prioritu. To však není závislé na procesu, ale na typu událostí. Tak např. uvolnění sdílené systémové tabulky nebo dokončení diskové operace mají vysokou prioritu ukončení, terminálové operace nižší prioritu a čekání na uplynutí časového intervalu prioritu nejnižší.

Dispečerská priorita proc.pri uživatelského procesu je vždy nižší než priorita libovolného systémového procesu a určuje se součtem tří složek:

proc.pri = PUSER + proc.nice - NZERO + proc.cpu/16.

Konstanta PUSER (=50) odděluje prioritu systémových procesů (které jsou v intervalu < -32767, 49 >, čím nižší číslo, tím vyšší priorita). Proměnná proc.nice je uživatelská priorita (NZERO <= proc.nice < 2*NZERO).

Uživatel může proc.nice změnit voláním jádra nice(n), kterým se provede proc.nice = proc.nice + n, vždy však s omezením na uvedený interval. Konstatntou NZERO (=20) může správce systému ovlivňovat interval uživatelské priority. Jádro udržuje hodnotu proměnné proc.cpu a udává čas procesoru spotřebovaný procesem. V okamžiku přidělení procesu je hodnota nulová, při každém přerušení časovače (po 20 ms) se zvýší o 1. Dispečerská priorita uživatelského procesu se tedy sníží o 1 každých 320 ms nepřetržitého užívání procesoru.

Všechny uživatelské procesy jsou vytvořeny se stejnou uživatelskou prioritou. Nedochází-li k zablokování procesů z jiných důvodů (např. periferními operacemi), jde o cyklické plánování procesů na nižší úrovni s časovým kvantem 320 ms, tj. o typický systém sdílení času. Interaktivní procesy (s častými terminálovými operacemi) tak mají zabezpečenou vysokou prioritu oproti procesům, které převážně využívají procesor. Procesy s uživatelskou prioritou sníženou na proc.nice = 39 oproti normálnímu stavu proc.nice = 20, mají časové kvantum 6,4 s (20 * 320 ms). Vzorec pro výpočet dispečerské priority závisí na variantě Unixu (uvedený vzorec platí pro Unix v7).

K přeplánování procesů dochází jen v okamžiku přechodu ze systémové do uživatelské fáze. Proto jádro standardního Unixu vnutí přeplánování navíc každou sekundu (redundantní pojistka proti monopolizaci času procesoru jedním procesem). Přeplánování se vnutí zvýšením obsahu proměnné runrun o 1.

10.2.6 Odkládání na disk

Vraťme se nyní k procesu číslo 0, který zajišťuje odkládání obrazů procesů na disk a jejich obnovu v operační paměti. Tento proces (říkejme mu swap proces) je po většinu doby zablokován čekáním na událost runout. Ta nastane, kdykoliv nějaký proces přejde do stavu připraven. Tím se swap proces odblokuje (probudí se) a snaží se obnovit obrazy těch odložených procesů, které jsou připraveny. Pokud takový proces v tabulce najde, přidělí mu místo v operační paměti a načítá systémový segment, data a zásobník procesu do přiděleného místa. Procesům s nesdíleným textem rovněž načítá jejich text.

Nestačí-li prostor v operační paměti, swap proces prohledává tabulku procesů znovu, tentokrát s úmyslem najít proces vhodný k odložení. Nalezne-li ho, vypíše jeho obraz na disk, uvolní místo v operační paměti a vrátí se k obnovování obrazů odložených procesů. Swap proces tedy pracuje ve dvou protichůdných směrech. Nejprve se snaží do operační paměti uložit obrazy všech připravených procesů, pak se snaží operační paměť uvolnit odložením obrazu některého z procesů.

Výběr procesů přesouvaných zpět do operační paměti se provádí podle nejdelší doby odložení. Na disk se vybírá ze zablokovaných procesů. Nejprve se vybírá největší dlouhodobě zablokovaný proces. Je-li jich víc, vybere se ten, který byl v operační paměti déle. Není-li dlouhodobě zablokován žádný proces, vybírá se z ostatních zablokovaných procesů ten, který je v operační paměti nejdéle.

Aby se vyloučilo časté přesouvání téhož procesu, musí proces strávit alespoň 2 s v operační paměti předtím, než může být jeho obraz odložen na disk. Analogicky musí být obraz procesu odložen na disku alespoň 3 s, než může být obnoven v operační paměti.