V této kapitole je popsáno co to jsou procesy a jak jádro Linuxu procesy vytváří, spravuje a ruší.
Procesy provádějí v operačním systému úlohy. Program je souhrn strojových instrukcí a dat uložených ve spustitelném obrazu na disku a je to v zásadě pasivní entita. Naproti tomu proces můžeme chápat jako program v akci.
Jedná se o dynamickou entitu, která se trvale mění tak jak procesor vykonává jednotlivé strojové instrukce. Kromě instrukcí a dat programu jsou součástí procesu také čítač instrukcí a všechny registry procesoru a dále zásobník, který obsahuje dočasně odložená data jako parametry rutin, návratové adresy a uložené proměnné. Momentálně spuitiný program nebo proces zahrnuje všechny aktivity procesoru. Linux je multiprocesový operační systém. Procesy jsou oddělené úlohy, každý má vlastní práva a vlastní zodpovědnost. Pokud havaruje jeden proces, nezpůsobí to havár" jiného procesu v systému. Každý jeden proces běží ve svém vlastním virtuálním adresovém prostoru a není schopen komunikovat s ostatními procesy jinak než pomocí bezpečných, jádrem řízených mechanismů.
V době svého života proces používá řadu systémových prostředků. Používá procesor k vykonávání svých instrukcí a fyzickou paměť systému k uložení sebe sama a svých dat. Otevírá a používá soubory v souborovém systému a může přímo či nepřímo používat fyzická zařízení systému. Linux musí sledovat jednak proces a jednak prostředky, které proces využívá, aby mohl zajistit spravedlivé rozdělení prostředků mezi všemi procesy v systému. Nebylo by vůči ostatním procesům spravedlivé, kdyby si jeden proces monopolizoval většinu fyzické paměti nebo procesorového času.
Nejcennějším prostředkem systému je procesor, protože je v něm obvykle pouze jeden. Linux je multiprocesový operační systém a jeho cílem je zajistit, aby v každém okamžiku běžel na každém procesoru nějaký proces, aby se procesorů využilo co nejvíce. Pokud je procesů více než procesorů (což obvykle bývá), musejí ostatní procesy s během počkat než bude procesor volný. Multiprocessing je jednoduchá myýlenka - proces běží dokud nemusí čekat, obvykle na nějaký systémový prostředek, pokud prostředek má, může běžet dále. V jednoprocesovém systému, jako je například DOS, by procesor po dobu čekání na prostředek zůstal nečinný a mrhal by časem. V multiprocesovém systému je v paměti současně přítomno více procesů.
Kdykoliv musí proces čekat, operační systém mu odebere procesor a předá jej jinému procesu, který jej potřebuje více. Rozhodování o tom, který proces má příýtě běžet, provádí plánovač úloh. Linux používá řadu různých plánovacích strategií, aby se zajistilo spravedlivé rozdělení procesoru.
Linux podporuje různé formáty spustitelných souborů, jedním je ELF, dalším může být Java a všechny tyto formáty musejí být spravovány transparentně tak, jak procesy využívají sdílené knihovny systému.
Aby mohl Linux jednotlivé procesy v systému spravovat, je každý proces reprezentován datovou strukturou task_struct (termíny "úlohaiy (task) a "procesiu jsou v Linuxu rovnocenné). Vektor task je pole ukazatelů na všechny struktury task_struct v systému.
Znamená to, že maximální počet procesů v systému je omezen velikostí vektoru task, který má implicitně 512 položek. Při vzniku procesu se v paměti alokuje nová struktura task_struct a přidá se vektoru task. Aby se usnadnilo vyhledávání, na aktuální proces je možno odkazovat se ukazatelem current.
Kromě normálních procesů Linux dále podporuje takzvané procesy reálného času. Tyto procesy musejí velmi rychle reagovat na externí události (proto procesy "reálného časuin) a plánovač s nimi zachází odlišně od normálních uživatelských procesů. Přestože struktura task_struct je poměrně rozsáhlá a složitá, jednotlivé položky je možno rozdělit do několika funkčních oblastí:
Při běhu procesu se podle okolností mění jeho status. Procesy v Linuxu mohou mít následující status:
Tyto informace potřebuje plánovač k rozhodování, který proces si nejvíce zaslouží spustit. Identifikátory Každý proces má svůj identifikátor procesu. Identifikátor není indexem do vektoru task, je to prostě číslo. Každý proces má dále identifikátory uživatele a skupiny, které slouží k řízení přístupových práv procesu k souborům a zařízením v systému.
Linux podporuje klasické unixovské komunikační mechanismy jako signály, roury a semafory a dále mechanismy Systemu V pro sdílení paměti, semafory a fronty zpráv. Mechanismy pro meziprocesovou komunikaci jsou popsány v kapitole Meziprocesová komunikace.
V Linuxu není žádný proces nezávislý na ostatních procesech. Všechny procesy vyjma iniciálního procesu mají svůj rodičovský proces. Nové procesy nevznikají, kopírují se, nebo přesněji klonují, z předchozích procesů. Každá struktura task_struct každého procesu obsahuje ukazatele na jeho rodičovský proces a na jeho sourozence (ostatní procesy se stejným rodičovským procesem) a dále ukazatele na jeho synovské procesy. Rodinné vztahy mezi procesy v systému můžete zjistit pomocí příkazu pstree:
init(1)-+-crond(98)
|-emacs(387)
|-gpm(146)
|-inetd(110)
|-kerneld(18)
|-kflushd(2)
|-klogd(87)
|-kswapd(3)
|-login(160)---bash(192)---emacs(225)
|-lpd(121)
|-mingetty(161)
|-mingetty(162)
|-mingetty(163)
|-mingetty(164)
|-login(403)---bash(404)---pstree(594)
|-sendmail(134)
|-syslogd(78)
\-update(166)
Navíc jsou všechny procesy v systému svázány obousměrně propojeným seznamem, jehož kořenem je datová struktura task_struct procesu init. Tento seznam jádru umožňuje dohled nad všemi procesy v systému. Navíc je potřebný pro podporu příkazů jako jsou ps nebo kill.
Jádro udržuje údaj o čase spuštění procesu i o celkovém času procesoru, který proces doposud spotřeboval. S každým tikem hodin jádro inkrementuje časový údaj o tom, kolik času proces strávil v systému a v uživatelském režimu. Linux navíc podporuje intervalové časovače procesů; proces může pomocí systémových volání tyto časovače nastavit tak, aby po uplynutí určité doby poslaly procesu signál. Tyto časovače mohou být jednorázové nebo periodické.
Procesy mohou podle potřeby otevírat a zavírat soubory a struktura task_struct procesu obsahuje ukazatele na deskriptory otevřených souborů a dále ukazatele na dva VFS inody.
Inody jednoznačně popisují soubor nebo adresář v souborovém systému a představují také uniformní rozhraní k nižiím vrstvám souborového systému. Podpora souborových systémů v Linuxu je vysvětlena v kapitole Souborový systém. První ukazatel je na inode kořene procesu (jeho domovského adresáře), druhý ukazuje na jeho aktuální či pracovní (pwd) adresář.
Označení pwd je odvozeno od příkazu pwd systému Unix, který vypisuje pracovní adresář procesu. Pro inody je vedeno počitadlo count, které říká, že se na ně jeden či více procesů odkazují.
Většina procesů má nějakou virtuální paměť (nemají ji pouze démony a vlákna jádra) a jádro Linuxu musí sledovat, jak se virtuální paměť mapuje na fyzickou paměť systému.
Proces můžeme chápat jako souhrn celého aktuálního stavu systému. Vždy, když proces běží, používá registry procesoru, zásobník a podobně. To všechno je kontext procesu a když dojde k pozastavení procesu, musí se celý kontext procesu uložit do struktury task_struct. Když plánovač proces opět spustí, kontext procesu se odtud obnoví.
Linux, stejně jako systém Unix, používá ke kontrole přístupových práv k souborům identifikátory uživatelů a skupin. Každý soubor a adresář v Linuxu má své vlastníky a svá oprávnění, která popisují práva uživatelů systému k tomuto souboru či adresáři. Základními oprávněními jsou práva čtení, zápisu a spuštění, která se přiřazují třem třídám uživatelů: vlastníkovi souboru, procesům patřícím do nějaké skupiny a všem procesům v systému. Každá třída uživatelů může mít jiná oprávnění, takže je třeba možné nastavit oprávnění tak, že vlastník bude moci soubor číst i zapisovat, skupina souboru jej bude moci jenom číst a všechny ostatní procesy v systému nebudou mít žádná přístupová práva.
Skupiny představují způsob přiřazení privilegií k souborům a adresářům skupinám uživatelů a ne jen jednomu uživateli nebo všem uživatelům v systému. Můžete například vytvořit skupinu pro všechny uživatele podílející se na nějakém projektu a nastavit ji tak, že bude moci číst i modifikovat zdrojové kódy projektu. Proces může patřit do více skupin (maximální počet je implicitně 32) a tyto skupiny se ukládají ve vektoru groups struktury task_struct každého procesu. Pokud má nějaká skupina přístupová práva k nějakému souboru a proces do této skupiny patří, má k danému souboru práva této skupiny.
Ve struktuře task_struct procesu se udržují čtyři dvojice uživatelských a skupinových identifikátorů procesu:
uid, gid Identifikátor uživatele a skupiny toho uživatele, jehož jménem proces běží.
Všechny procesy běží částečně v uživatelském režimu a částečně v systémovém režimu. Nízkoúrovňová hardwarová podpora těchto režimů může být různá, obecně ale platí, že existují nějaké bezpečnostní mechanismy při přechodu z uživatelského režimu do systémového a zpět. V uživatelském režimu má proces výrazně menší privilegia než v režimu systémovém.
Vždy při použití systémového volání se proces přepíná z uživatelského režimu do systémového režimu a pokračuje v práci. V té době pracuje jménem procesu jádro. V Linuxu procesy nemohou vynuceně přerušit aktuálně běžící proces, nemohou jeho běh pozastavit, aby mohly běžet samy. Každý proces se dobrovolně vzdává procesoru, na němž běží v době, kdy musí čekat na nějakou systémovou událost. Proces řekněme čeká například na načtení znaku ze souboru. Toto čekání se provádí využitím systémového volání, v systémovém režimu. Proces použije knihovních funkcí k otevření a čtení souboru a poté následně používá systémových volání ke čtení bajtů z otevřeného souboru. V takovém případě je čekající proces pozastaven a umožní se běh jinému, potřebnějšímu procesu.
Procesy používají systémových volání často, takže mohou být také často přinuceny čekat. Ale i v této situaci může nějaký proces bez čekání běžet příliý dlouho a spotřebovávat nepřiměřené množství procesorového času, proto Linux používá preemptivní plánování úloh. Při využití tohoto schématu je každému procesu umožněno běžet malý objem času, 200 ms, a jakmile tento čas vyprší, naplánuje se běh jiného procesu a původní proces musí čekat na další příležitost ke běhu. Tento krátký přidělovaný časový úsek se označuje jako časové kvantum (time-slice).
Rozhodování o tom, který proces právě nechat běžet, je úkolem plánovače.
Spustitelný proces je takový, který čeká pouze na přidělení procesoru. Linux používá rozumně jednoduchý na prioritě založený plánovací algoritmus, který volí aktuální procesy. Když zvolí nový aktuální proces, uloží status momentálně aktuálního procesu, procesorově specifické registry a další kontextové informace do datové struktury task_struct. Poté obnoví status nově naplánovaného procesu (což je opět procesorově závislá operace) a předá mu řízení systému. Aby mohl plánovač spravedlivě rozdělovat procesorový čas mezi spustitelné procesy v systému, ukládá si do struktury task_struct každého procesu následující informace:
Plánovač je spouštěn z několika míst v jádře. Spouští se po převedení aktuálního procesu do fronty čekajících procesů, může být zavolán po ukončení systémového volání, těsně před přepnutím procesu ze systémového režimu do uživatelského režimu. Dalším důvodem spuštění může být, že systémový časovač dodekrementoval hodnotu counter procesu na nulu.
Při každém spuštění provede plánovač následující úkoly:
Úlohy jádra Plánovač spustí bottom-half obsluhu a zpracuje frontu úloh plánovače. Tato "odlehčená" vlákna jádra jsou podrobně popsána v kapitole Mechanismy jádra. Aktuální proces Před výběrem dalšího procesu musí být spuštěn aktuální proces. Pokud se používá plánovací strategie round robin, uloží se proces na konec fronty spustiných procesů.
Pokud je úloha v přerušitelném (INTERRUPTIBLE) stavu a od posledního naplánování obdržela signál, přepne ji plánovač do stavu RUNNING.
Pokud vypršel aktuální proces, dostává se do stavu RUNNING.
Pokud je aktuální proces ve stavu RUNNING, zůstává v něm.
Procesy, které nejsou ani RUNNING, ani INTERRUPTIBLE se odstraní z fronty spustiných procesů. Znamená to, že plánovač nebude tyto procesy posuzovat při výběru nejvhodnějšího procesu pro spuštění.
Přepnutí procesů je poslední operace prováděná plánovačem. Uložený kontext předchozího procesu je tedy snímek hardwarového kontextu systému v okamžiku, kdy byl tento proces na konci plánovače.
Obdobně tedy když dojde k nahrání nového procesu, v jeho snímku je rovněž zachycena situace, kdy se proces nacházel na konci plánovače, včetně obsahu instrukčního čítače a registrů.
Pokud předchozí proces nebo nový aktuální proces používají virtuální paměť, může být zapotřebí změnit údaje ve stránkovacích tabulkách. I tato akce je závislá na architektuře. Procesory jako Alpha AXP, které používají překladové tabulky a uložení položek tabulky stránek ve vyrovnávací paměti, musejí zneplatnit ty údaje ve vyrovnávací paměti, které patřily předchozímu procesu.
Víceprocesorové systémy jsou ve světě Linuxu poměrně zřídkavé, bylo však vynaloženo dost úsilí při budování Linuxu jako operačního systému typu SMP (Symmetric Multi-Processing).
Symetrický multiprocesorový systém je takový, který je schopen stejnoměrně rozdělovat práci mezi všechny procesory v systému. Toto vyvažování je nejzřetelnější právě v plánovači.
Ve víceprocesorovém systému doufáme, že každý procesor provádí nějaký proces. Každý z procesorů spouští plánovač separátně podle toho, kdy jeho proces vyčerpá své časové kvantum nebo když musí čekat na systémové prostředky. První zajímavá věc v SMP systému je ta, že v něm není pouze jediný nečinný proces. V jednoprocesorovém systému je nečinným procesem první úloha ve vektoru task, v SMP systému existuje pro každý procesor jeden nečinný proces, navíc můžete mít několik nečinných procesorů. Navíc existuje jeden aktuální proces pro každý procesor, takže SMP systém musí vést záznamy o aktuálních a nečinných procesech pro každý procesor.
V SMP systému obsahuje struktura task_struct každého procesu číslo procesoru, na němž proces právě běží (processor), a rovněž číslo procesoru, na němž běžel naposledy (last_processor). Neexistuje žádný důvod, proč by proces nemohl při každém naplánování běžet na jiném procesoru, Linux však může provádění procesu omezit pouze na jeden nebo několik procesorů v systému pomocí masky processor_mask. Pokud je v ní nastaven N-tý bit, může proces běžet na N-tém procesoru. Když plánovač rozhoduje o naplánování nového procesu pro určitý procesor, nebude brát v úvahu ty, které nemají pro tento procesor nastaven příslušný bit v masce processor_mask. Plánovač navíc vždy částečně upřednostňuje procesy, které naposledy běžely na stejném procesoru, protože převedení procesu na jiný procesor s sebou nese jistou rež" navíc.
Na obrázku 4.1 vidíme, že v systému jsou pro každý proces dvě datové struktury, které popisují procesově specifické informace souborového systému. První z nich, struktura fs_struct, obsahuje ukazatele na VFS inody procesu a jeho hodnotu umask. Hodnota umask je implicitní hodnota pro vytváření nových souborů procesem a je možno ji změnit pomocí systémových volání.
Druhá datová struktura, struktura files_struct, obsahuje informace o všech souborech, které proces momentálně používá. Program čte ze standardního vstupu a zapisuje na standardní výstup. Všechna chybová hlášení se posílají na standardní chybový výstup. Fyzicky to mohou být soubory, terminálové linky nebo reálná zařízení, z pohledu programu jsou to ale vždy soubory. Každý soubor má svůj vlastní deskriptor a struktura files_struct obsahuje ukazatele na maximálně 256 datových struktur file, z nichž každá popisuje jeden soubor používaný procesem. Položka f_mode obsahuje režim vytvoření souboru: pro čtení, pro zápis i čtení nebo pouze pro zápis. f_pos obsahuje pozici v souboru, kde se provede následující operace čtení nebo zápisu. f_inode ukazuje na inode souboru a f_ops je ukazatel na vektor adres rutin, jedna pro každou funkci, kterou je možno se souborem provádět. Může to být například funkce pro zápis dat. Tato abstraktnost rozhraní je velmi mocná a umožňuje Linuxu podporovat široké spektrum různých typů souborů. Například roury se v Linuxu podporují, jak uvidíme později, právě tímto mechanismem.
Vždy při otevření souboru se jeden z volných ukazatelů struktury files_struct použije jako ukazatel na novou strukturu file. Procesy v Linuxu očekávají při svém spuštění tři otevřené deskriptory souborů. Označují se jako standardní vstup, standardní výstup a chybový výstup a obvykle se dědí z vytvářejícího rodičovského procesu. Všechny přístupy k souborům se provádějí pomocí standardních systémových volání, která přebírají nebo vracejí deskriptory souborů. Tyto deskriptory jsou indexy do vektoru fd procesu, takže standardní vstup a výstup a chybový výstup mají deskriptory 0, 1 a 2. Všechny přístupy k souborům používají ke splnění svých požadavků operační rutiny datové struktury file spolu s inody VFS.
Virtuální paměť procesu obsahuje spustitelný kód a data z mnoha zdrojů. Prvním zdrojem je nahraný obraz spustitelného programu, řekněme příkaz jako ls. Tento příkaz se, stejně jako všechny spustitelné obrazy, skládá jak ze spustitelného kódu, tak i z dat. Obraz obsahuje všechny informace potřebné k zavedení spustitelného kódu a s ním souvisejících dat do virtuální paměti procesu. Proces dále může alokovat (virtuální) paměť pro potřeby své práce, řekněme k uložení obsahu souborů, které načítá. Tato nově alokovaná virtuální paměť se musí navázat do stávající virtuální paměti procesu tak, aby ji bylo možno využít. Dále procesy používají knihovny obecně užitečných rutin, například rutin pro manipulaci se soubory. Nemá smysl, aby měl každý proces svou vlastní kop" knihovny, a proto Linux používá sdílené knihovny, které může používat více běžících procesů najednou. Kód a data těchto sdílených knihoven musí být rovněž přítomny ve virtuální paměti procesu a také všech ostatních procesů, které knihovnu používají.
V určitém časovém úseku proces nemusí používat veškerý kód a data obsažená ve své virtuální paměti. Může obsahovat části kódu, které se využívají jenom v určitých situacích, například v době inicializace nebo při zpracování určité události. Může využívat pouze některé rutiny ze sdílených knihoven. Bylo by plýtváním nahrávat do fyzické paměti veškerý kód a data, aby tam potom ležely nevyužity. Vynásobme takovéto plýtvání počtem procesů v systému a měli bychom systém, který by pracoval velmi neefektivně. Proto Linux používá techniku zvanou stránkování na žádost, kdy se virtuální paměť procesu zavádí do fyzické paměti pouze v okamžiku, kdy ji proces potřebuje. Namísto přímého nahrání veýkerého kódu a dat do paměti tedy Linux pouze modifikuje tabulku stránek procesu tak, že jednotlivé oblasti virtuální paměti označí jako používané, ale v paměti neexistující. Když se pak proces pokusí o přístup k těmto částem kódu či dat, hardware systému detekuje výpadek stránky a předá řízení jádru Linuxu, které situaci napraví. Proto musí Linux pro každou oblast virtuální paměti v adresovém prostoru procesu vědět, odkud kód pochází a jak jej do paměti dostat, aby mohl takovéto výpadky stránek ošetřit.
Jádro Linuxu tedy potřebuje spravovat všechny tyto oblasti virtuální paměti. Obsah virtuální paměti každého procesu je popsán datovou strukturou mm_struct, na kterou se ukazuje ze struktury task_struct procesu. Datová struktura mm_struct každého procesu obsahuje také informace o nahraném spustitelném obrazu a ukazatel na tabulku stránek procesu.
Dále obsahuje ukazatele na seznam datových struktur vm_area_struct, z nichž každá reprezentuje jednu oblast virtuální paměti procesu.
Tento seznam je uspořádán vzestupně podle pořadí oblastí ve virtuální paměti. Na obrázku 4.2 vidíme rozvržení virtuální paměti jednoduchého procesu včetně datových struktur jádra, které virtuální paměť spravují. Protože jednotlivé oblasti virtuální paměti pocházejí z různých zdrojů, používá Linux abstraktní rozhraní, kdy struktura vm_area_struct obsahuje sadu ukazatelů (vm_ops) na rutiny obsluhy virtuální paměti. Díky tomu je možno celou virtuální paměť procesu obsluhovat konzistentním způsobem bez ohledu na to, že služby nižií úrovně se mohou pro jednotlivé oblasti lišit. Existuje zde například rutina, která se bude volat v případě, že se proces pokusí o přístup k oblasti paměti, která neexistuje; tímto mechanismem se obsluhují výpadky stránek.
Se seznamem struktur vm_area_struct jádro trvale pracuje, když vytváří nové oblasti virtuální paměti procesu nebo když opravuje odkazy na virtuální oblasti, které nejsou přítomny ve fyzické paměti procesu. Díky tomu se doba, strávená hledáním správné struktury vm_area_struct, stává kritickou pro celý výkon systému. Aby se přístup zrychlil, organizuje Linux struktury vm_area_struct také do takzvaného AVL (Adelson-Velskii a Landis) stromu. Strom je organizován tak, že každá struktura vm_area_struct (nebo též uzel) obsahuje levý a pravý ukazatel na své sousedy ve stromu. Levý ukazatel ukazuje na uzel s nižií počáteční virtuální adresou, pravý ukazatel ukazuje na uzel s vyšťí počáteční virtuální adresou. Při hledání správného uzlu začne Linux od kořene stromu a pohybuje se z každého uzlu buť vlevo, nebo vpravo, až najde správný uzel. Pochopitelně nic není zadarmo a v tomto případě platíme za efektivní hledání vyšťí náročností vložení nové struktury vm_area_struct do stromu.
Při alokování nové oblasti virtuální paměti Linux neprovádí skutečnou alokaci fyzické paměti.
Pouze popíše nově vzniklou virtuální oblast vytvořením nové struktury vm_area_struct.
Tato struktura se zapojí do seznamu oblastí virtuální paměti procesu. Když se proces pokusí o zápis na virtuální adresu v nově vytvořené oblasti, dojde k výpadku stránky. Procesor se pokusí o dekódování virtuální adresy, protože však pro danou adresu neexistuje platná položka tabulky stránek, generuje výpadek stránky a přenechá řízení jádru systému. Linux se podívá, zda se požadovaná adresa nachází v existující oblasti virtuální paměti procesu. Pokud ano, vytvoří Linux patřičnou položku tabulky stránek a alokuje fyzickou paměťovou stránku. Pak může být nutné přenést stránku do fyzické paměti z diskového souboru nebo z odkládacího souboru. Poté je možno proces znovu spustit na stejné instrukci, která vyvolala výpadek stránky, a protože stránka už nyní existuje, proces může dále pokračovat.
Když se systém spustí, běží v režimu jádra a existuje pouze jediný, iniciální proces. Stejně jako všechny ostatní procesy, i iniciální proces má svůj strojový stav (kontext) reprezentovaný hodnotami registrů, zásobníkem a podobně. Když se vytvoří a spustí další procesy, bude tento stav uložen v datové struktuře task_struct iniciálního procesu. Na konci inicializace systému spustí iniciální proces vlákno jádra (zvané init) a pak nečinně čeká a nic nedělá.
Kdykoliv není nic jiného na práci, spustí plánovač tento pozastavený proces. Struktura task_struct tohoto procesu jako jediná není alokována dynamicky, je staticky definována přímo v jádře a poněkud matoucně se jmenuje init_task.
Vlákno jádra či proces init má identifikátor procesu 1, protože se jedná o první faktický proces v systému. Provede nějaké počáteční nastavení systému (například otevření systémové konzoly a připojení kořenového souborového systému) a pak spustí inicializační program systému. Podle konkrétního systému se jedná o program /etc/init, /bin/init nebo /sbin/init. Při vytváření nových procesů v systému používá program init jako skriptový soubor /etc/inittab. Tyto nové procesy pak mohou samy vytvářet další nové procesy. Například proces getty může při pokusu o přihlášení vytvořit proces login. Všechny procesy v systému jsou (přímo či nepřímo) potomky procesu init.
Nové procesy se vytvářejí klonováním starých procesů, přesněji řečeno klonováním aktuálního procesu. Nová úloha se vytvoří systémovým voláním (fork nebo clone) a samotné klonování se odehraje v jádře v režimu jádra. Ve fyzické paměti systému se alokuje nová datová struktura task_struct a jedna nebo více fyzických stránek pro zásobník klonovaného procesu. Může se vytvořit identifikátor nového procesu, který musí být odl"ný od identifikátorů všech existujících procesů. Je však možné, že nově vytvořený proces si ponechá identifikátor svého rodičovského procesu. Do vektoru task se vloží nová struktura task_struct a obsah struktury task_struct starého (aktuálního) procesu se zkopíruje do nové struktury.
Klonováním procesů umožňuje Linux dvěma procesům sdílet prostředky. Týká se to souborů, obsluhy signálů a virtuální paměti. Když se prostředky sdílejí, hodnota jejich počitadla count se zvyýuje, takže Linux příslušný prostředek neuvolní do té doby, dokud jej nepřestanou používat oba procesy. Pokud například klonovaný proces sdílí virtuální paměť se svým rodičem, jeho struktura task_struct bude obsahovat ukazatel na strukturu mm_struct rodičovského procesu a počitadlo count v této struktuře bude inkrementováno, aby se ukázalo, kolik procesů strukturu momentálně sdílí.
Klonování virtuální paměti je poněkud více rafinované. Musí se vytvořit nová sada datových struktur vm_area_struct, dále na ně ukazující struktura mm_struct a tabulka stránek nového procesu. V tomto okamžiku se ještě žádná virtuální paměť nekopíruje. Bylo by to dost obtížné a zdlouhavé vzhledem k tomu, že část virtuální paměti může ležet ve fyzické paměti, část ve spustitelných obrazech na disku a část třeba v odkládacím souboru. Namísto toho používá Linux techniku zvanou "kopírování při zápisuiv, což znamená, že ke kopírování virtuální paměti dojde pouze v případě, že jeden z procesů do ní bude zapisovat. Virtuální paměť, do níž se nezapisuje (i když by k tomu mohlo dojít), se může mezi oběma procesy sdílet bez jakéhokoliv nebezpečí. Aby technika "kopírování při zápisuiv fungovala, jsou v tabulce stránek sdílené zapisovatelné stránky označeny jako "pouze pro čteníin a ve struktuře vm_area_struct je uvedeno, že se jedná o stránce chráněné kopírováním při zápisu.
Pokud se jeden z procesů pokusí o zápis do této oblasti virtuální paměti, dojde k výpadku stránky. Teprve v této fázi Linux vytvoří kop" paměti a upraví tabulky stránek a datové struktury virtuální paměti obou procesů.
Jádro si pamatuje čas vytvoření procesu a také objem času procesu, který proces po dobu svého života spotřeboval. S každým tikem hodin aktualizuje jádro čas, který proces existuje, a čas, který strávil v uživatelském režimu. Tyto časy se měří v jednotkách zvaných jiffies.
Kromě těchto účetních časovačů podporuje Linux ještě procesově závislé intervalové časovače.
Proces si může pomocí těchto časovačů nechat posílat různé signály. Linux podporuje tři typy časovačů:
V dané chvíli může běžet jeden nebo více časovačů a Linux má všechny potřebné informace o časovačích uloženy v datové struktuře task_struct procesu. Pomocí různých systémových volání je možno časovače nastavovat, spouštět, zastavovat a přečíst jejich momentální hodnoty. Virtuální a profilové časovače se obsluhují stejným způsobem.
S každým tikem hodin se dekrementují časovače procesu a když doběhnou, pošle se příslušný signál.
Reálné časovače fungují poněkud jinak a při jejich obsluze používá Linux časovací mechanismy popsané v kapitole Mechanismy jádra. Každý proces má svou vlastní datovou strukturu timer_list a pokud používá reálné časovače, přidávají se do fronty systémových časovačů. Když časovač doběhne, odstraní jej bottom-half handler z fronty a zavolá obsluhu intervalového časovače.
Tím se generuje signál SIGALARM, časovač se restartuje a znovu se přidává do fronty systémových časovačů.
V Linuxu se, stejně jako v Unixu, programy a příkazy normálně spouštějí pomocí interpretu příkazů. Příkazový interpret je uživatelský proces jako každý jiný a označuje se termínem shell.
V Linuxu existuje mnoho příkazových interpretů, nejpopulárnějšími jsou sh, bash a tcsh.
S výjimkou několika vestavěných příkazů jako cd nebo pwd jsou ostatní příkazy spustitelné binární soubory. Při zadání příkazu prohledává příkazový interpret adresáře ve vyhledávací cestě procesu, uložené v proměnné prostředí PATH, a hledá spustitelný obraz zadaného jména. Pokud soubor najde, nahraje jej a spustí. Příkazový interpret vytvoří svůj klon pomocí výše popsaného mechanismu fork a nový proces nahradí binární obraz příkazového interpretu obsahem právě nahraného spustitelného obrazu. Za normálních okolností příkazový interpret čeká na dokončení příkazu, přesněji řečeno na skončení synovského procesu. Příkazový interpret můžete znovu vyvolat tím, že synovský proces přesunete do pozadí stiskem CONTROL-Z, čímž se synovskému procesu pošle signál SIGSTOP a proces se zastaví. Příkazem bg příkazového interpretu pak můžete přesunout do pozadí příkazový interpret, který synovskému procesu pošle signál SIGCONT a tak obnoví jeho běh. Příkazový interpret pak zůstává v pozadí, dokud synovský proces neskončí nebo dokud nepotřebuje terminálový vstup či výstup.
Spustitelný soubor může mít mnoho formátů nebo se může jednat o skript. Skriptové soubory je nutné rozeznat a nechat je provést příslušným interpretem, například skripty příkazového interpretu se provádějí programem /bin/sh. Spustitelné objektové soubory obsahují spustitelný kód a data doplněná o další informace, které umožňují operačnímu systému nahrát soubor do paměti a spustit jej. Nejběžnějiím formátem spustitelného souboru v Linuxu je formát ELF, teoreticky je však Linux natolik pružný, že může obsloužit prakticky jakýkoliv objektový formát.
Stejně jako u souborových systémů jsou i binární formáty buť součástí jádra Linuxu nebo se dají dohrát jako moduly. Jádro udržuje seznam podporovaných binárních formátů (viz obrázek 4.3) a když dojde k pokusu o spuštění souboru, vyzkouší se každý binární formát dokud některý z nich nebude fungovat.
Linux běžně podporuje formáty a.out a ELF. Spustitelné soubory nemusejí být nahrány v paměti celé, používá se metoda zvaná vynucené nahrávání. Jednotlivé části spustitelného obrazu se zavádějí do paměti podle toho, jak je proces potřebuje. Nepoužívané části je možno z paměti uvolnit.
Objektový souborový formát ELF (Executable and Linkable Format) navržený v Unix System Laboratories je dnes zaveden jako nejčastěji používaný formát v Linuxu. Přestože v porovnání s jinými objektovými formáty jako ESCOFF a a.out má tento formát poněkud vyšší výkonnostní režii, je daleko pružnějií. Spustitelné soubory ve formátu ELF obsahují spustitelný kód, někdy označovaný jako text, a data. Tabulky ve spustitelném obraze říkají, jak má být proces umístěn do virtuální paměti procesu. Staticky linkované obrazy jsou sestaveny linkerem (ld) do jediného obrazu, který obsahuje veškerý kód a data potřebná ke spuštění tohoto obrazu. Obraz dále specifikuje své rozvržení v paměti a adresu, od níž se má kód začít vykonávat.
Na obrázku 4.4 vidíme uspořádání staticky linkovaného spustitelného obrazu ve formátu ELF.
Jedná se o jednoduchý program v jazyce C, který vytiskne text "hello worldin a ukončí se. Hlavička souboru říká, že se jedná o obraz ELF se dvěma fyzickými hlavičkami (hodnota e_phnum je 2), které začínají 52 bajtů (e_phoff) od počátku souboru. První fyzická hlavička popisuje spustitelný kód obrazu. Patří na virtuální adresu 0x8048000 a je celkem 65 532 bajtů dlouhý. Velikost je dána tím, že jde o staticky linkovaný obraz, který obsahuje celý knihovní kód funkce printf(). Vstupní bod obrazu, tedy adresa první instrukce programu, není na počátku obrazu, ale na virtuální adrese 0x8048090 (položka e_entry). Kód začíná ihned za druhou fyzickou hlavičkou. Tato druhá hlavička popisuje data programu a nahrává se do virtuální paměti na adresu 0x8059BB8. Data je možno číst i zapisovat. Můžete si všimnout, že velikost dat je 2 200 bajtů (p_filesz), zatímco velikost paměti dat je 4 248 bajtů.
Je to dáno tím, že prvních 2 200 bajtů obsahuje předinicializovaná data, zatímco zbývajících 2 048 bajtů obsahuje data, která se budou inicializovat až při běhu kódu.
Když Linux nahrává spustitelný obraz ve formátu ELF do virtuálního paměťového prostoru procesu, neprovádí skutečné nahrávání obrazu.
Nastaví datové struktury virtuální paměti, strom struktur vm_area_struct a tabulky stránek. Když se program začne provádět, dojde k výpadku stránky, což způsobí, že se kód a data programu načtou do fyzické paměti. Nepoužité části programu se do paměti nikdy nenahrají.
Jakmile zavaděč binárního formátu ELF zjistí, že nahrávaný obraz je platným obrazem ve formátu ELF, odstraní z virtuální paměti procesu obraz stávajícího prováděného programu. Protože proces je klonem obrazu (všechny procesy jsou klonem), starý obraz je obraz programu prováděného rodičovským procesem, například tedy obrazem interpretu příkazů. Odstranění starého spustitelného obrazu zruší staré struktury virtuální paměti a tabulky stránek. Dojde rovněž k vymazání všech handlerů signálů a k zavření všech otevřených souborů. Na konci rušení je proces připraven přijmout nový spustitelný obraz. Bez ohledu na formát spustitelného obrazu se struktura mm_struct procesu nastavuje vždy stejnými informacemi. Musí totiž obsahovat ukazatele na začátek a konec kódu a dat obrazu. Tyto hodnoty se načtou z fyzických hlaviček formátu ELF a jimi určené oblasti programu se mapují do virtuálního adresového prostoru procesu. Zde se rovněž nastaví datové struktury vm_area_struct a provede se inicializace tabulky stránek procesu. Datová struktura mm_struct dále obsahuje ukazatele na parametry předávané programu a na proměnné prostředí procesu.
Sdílené knihovny ve formátu ELF
Dynamicky linkované obrazy neobsahují veškerý kód a data, která ke své činnosti potřebují.
Některé části jsou umístěny ve sdílených knihovnách, které se zavádějí až v okamžiku spuštění programu. Tabulky sdílených knihoven formátu ELF dále slouží dynamickému linkeru při linkování sdílených knihoven do běžícího programu. Linux používá několik dynamických linkerů, ld.so.1, libc.so.1 a ld-linux.so.1, všechny jsou umístěny v adresáři /lib. Knihovny obsahují běžně používaný kód, například internacionální podporu. Bez použití dynamického linkování by každý program musel obsahovat vlastní kop" těchto knihoven a bylo by tak zapotřebí podstatně více diskového prostoru a virtuální paměti. Při dynamickém linkování jsou v tabulkách obrazu ELF uloženy informace pro každou knihovní funkci, na níž se program odkazuje. Tyto informace dynamickému linkeru říkají jak nalézt knihovní funkci a jak ji vlinkovat do adresového prostoru programu.
Skriptové soubory jsou spustitelné soubory, které ke svému běhu potřebují interpret. V Linuxu existuje celá řada interpretů, například wish, perl a příkazové interprety jako tcsh.
Linux používá standardní konvenci systému Unix, kdy první řádek skriptu obsahuje jméno interpretu. Typický skript bude tedy začínat třeba takto:
#!/usr/bin/wish
Zavaděč skriptu se pokusí nalézt interpret skriptu.
Provádí to tak, že se pokusí otevřít spustitelný soubor, uvedený v prvním řádku skriptu. Pokud se mu jej podaří otevřít, má ukazatel na jeho VFS inode a může přejít k dalšímu kroku a nechat interpret vykonat skriptový soubor. Jméno skriptového souboru je parametrem číslo nula (tedy prvním parametrem) interpretu a všechny ostatní parametry se posouvají o jednu pozici vzad (původně první parametr se stává druhým a tak dále). Nahrání interpretu se provádí stejným mechanismem, jakým Linux nahrává všechny spustitelné soubory. Linux opět zkouší všechny registrované binární formáty dokud nenalezne vyhovující formát. Znamená to, že teoreticky můžete na sebe stavět několik interpretů a binárních formátů. Obsluha spustitelných souborů v Linuxu je tedy velice pružná.