Program je posloupnost počítačových instrukcí, které vykonávají nějakou úlohu. Program může být napsán v assembleru, počítačovém jazyce nízké úrovně, nebo v nějakém vyšťím, na platformě nezávislém jazyce, jako je například C. Operační systém je speciální program, který uživatelům umožňuje spouštět aplikace jako jsou tabulkové procesory a textové editory. Tato kapitola představuje úvod do základních programovacích principů a vysvětluje úkol a funkci operačního systému.
Instrukce, které CPU načítá z paměti a provádí, nejsou pro člověka vůbec srozumitelné. Jedná se o strojový kód, který počítači přesně říká, co má dělat. -estnáctkové číslo 0x89e5 je pro procesor Intel 80486 instrukce, která zkopíruje obsah registru ESP do registru EBP. Jedním z prvních programátorských nástrojů vyvinutých už pro první počítače byl assembler, program, který bere pro člověka srozumitelný zdrojový kód a přeloží jej do strojového kódu. Jazyk assembler provádí manipulace s registry a daty explicitně, je jiný pro každý mikroprocesor. Assembler pro procesory Intel x86 se výrazně liší od assembleru pro mikroprocesor Alpha AXP. Následující ukázka assembleru procesoru Alpha AXP ukazuje, co může takový program dělat:
První příkaz (na řádku 1) naplní registr 16 hodnotou na adrese, uložené v registru 15. Druhý příkaz naplní registr 17 hodnotou z následující adresy. Třetí příkaz porovná hodnotu registrů 16 a 17 a pokud se shodují, skočí na návěští 100. Pokud registry neobsahují stejnou hodnotu, pokračuje se řádkem 4, na němž se obsah registru 17 uloží do paměti. Pokud registry obsahují stejnou hodnotu, není nutné ji ukládat. Programy v assembleru se píší obtížně a zdlouhavě, navíc se v nich snadno dělají chyby. Pouze velmi malá část jádra Linuxu je napsána přímo v assembleru. Tyto části jsou závislé na konkrétním procesoru a důvodem použití assembleru je dosažení maximálního výkonu.
Psát v assembleru větší programy je obtížné a zdlouhavé. Často vznikají chyby a výsledné programy nejsou přenositelné, protože jsou vázány na určitou rodinu procesorů. Daleko lepší je použít strojově nezávislý jazyk, jako je například C. Jazyk C umožňuje popisovat program pomocí jeho logického algoritmu a pomocí dat, nad nimiž operuje. Speciální programy zvané kompilátory čtou program v jazyce C a překládají jej do assembleru, generují z něj konkrétní strojový kód. Dobrý kompilátor dokáže překládat tak, že výsledný kód je téměř stejně efektivní jako kdyby byl napsán zkuieným programátorem přímo v assembleru. Většina jádra Linuxu je napsána v jazyce C. Vezměme si následující příkaz jazyka C:
if (x !- y) x - y ;
Tento příkaz provádí přesně stejnou operaci jako výše uvedený příklad v assembleru. Pokud se obsah proměnné x neshoduje s obsahem proměnné y, překopíruje se obsah proměnné y do x. Program v jazyce C je organizován do rutin, které provádějí jednotlivé funkce. Každá rutina může vracet jakoukoliv hodnotu nebo datový typ podporovaný jazykem C. Velké programy jako například jádro Linuxu se mohou skládat z mnoha samostatných C-modulů, každý z nich má své vlastní rutiny a datové struktury. Jednotlivé moduly zdrojového kódu plní příbuzné logické funkce, jako například obsluhu souborového systému.
Jazyk C podporuje mnoho typů proměnných; proměnná je místo v paměti, na něž se dá odkazovat symbolickým jménem. Ve výše uvedeném fragmentu programu označují x a y místa v paměti. Programátor se nestará o to, kam v paměti budou data uložena, to je záležitostí linkeru (viz dále). Některé proměnné obsahují různé typy dat, jako například celá nebo reálná čísla, jiné představují ukazatele.
Ukazatel je proměnná, která obsahuje adresu - místo v paměti, kde jsou uložena jiná data.
Představme si proměnnou x. Ta může být v paměti uložena na adrese 0x80010000. Můžete mít ukazatel, řekněme px, který bude ukazovat na x. Ukazatel px může "bydleti, na adrese 0x80010030. Hodnota ukazatele px bude 0x8001000, tedy adresa proměnné x.
Jazyk C umožňuje shromažťovat do jedné datové struktury spolu související proměnné. Například takto:
struct {
int i ;
char b ;
} my_struct ;
Toto je datová struktura nazvaná my_struct, která obsahuje dva prvky, celé číslo (32 bitů) zvané i a znak (8 bitů) zvaný b.
Linker je program, který "slinkuje" dohromady několik modulů a knihoven a výsledkem je jediný program. Moduly obsahují strojový kód vygenerovaný assemblerem nebo překladačem a najdeme v nich jednak spustitelný kód a data a dále informace, které říkají linkeru, jak jednotlivé moduly zkombinovat do výsledného programu. Jeden modul může například obsahovat všechny databázové funkce programu, jiný modul může obsahovat funkce pro obsluhu řádkových příkazů. Linker propojí odkazy mezi těmito moduly v místech, kde se rutina nebo data, na něž se jeden modul odkazuje, nacházejí v modulu jiném. Jádro Linuxu je jediný velký program, vzniklý slinkováním řady modulů.
Bez příslušného softwaru je počítač jenom hromada elektroniky, která vydává teplo. Pokud je hardware srdcem počítače, je software jeho duší. Operační systém je soubor systémových programů, které uživateli umožňují spouštět aplikační programy. Operační systém abstrahuje od skutečného hardwaru systému a přináší uživatelům a aplikacím virtuální stroj. V zásadě platí, že teprve operační systém představuje charakteristiku počítače. Většina uživatelů PC používá jeden nebo více operačních systémů, z nichž každý může mít úplně jiný vzhled a chování. Linux je tvořen řadou samostatných dílů, které dohromady tvoří operační systém. Nutným dílem Linuxu je jeho jádro, ani to by však nebylo k ničemu bez knihoven nebo příkazových interpretů.
Abychom si lépe ukázali, co to operační systém vlastně je, zkusme si představit co se stane, když zadáte jednoduchý příkaz:
$ ls
Mail c images perl
docs tcl
$
Znak $ je prompt přihlaýovacího příkazového interpretu (v tomto případě bash). Znamená to, že se čeká na vás, na uživatele, až zadáte nějaký příkaz. Když zadáváte příkaz ls, ovladač klávesnice rozpozná, že jsou zadávány nějaké znaky. Ovladač klávesnice je předá příkazovému interpretu, který příkaz zpracuje tak, že se pokouší najít spustitelný soubor stejného jména.
Najde jej jako soubor /bin/ls. Zavolají se služby jádra, které přemístí kód obsažený v souboru do virtuální paměti a zahájí jeho provádění. Příkaz ls volá souborové služby jádra a zjiýšuje, jaké soubory existují. Souborový systém může využít informací uložených ve vyrovnávací paměti souborového systému, nebo použije ovladač diskového zařízení a načte údaje z disku. Může dojít i k tomu, že síťový ovladač začne komunikovat se vzdáleným počítačem aby zjistil podrobnosti o vzdálených souborech, k nimž má váý systém přístup (souborové systémy je možné vzdáleně připojit pomocí služby Network File System, NFS). Aš už se informace zjistí jakýmkoliv způsobem, ls je vypíše a ovladač displeje je zobrazí na obrazovce.
Celé to vypadá dost komplikovaně, nicméně takovýto jednoduchý příklad nám ukazuje, že operační systém je ve skutečnosti řada spolupracujících funkcí, které dohromady dávají uživateli souvislý pohled na systém.
Pokud bychom měli neomezené prostředky, řekněme paměť, řada funkcí, které operační systém provádí, by byla zbytečných. Jedním ze základních triků každého operačního systému je zajistit, aby se malý objem fyzické paměti choval jako paměť daleko větší. Této obrovské paměti říkáme virtuální paměť. Vtip je v tom, že programy, které v systému běží, se domnívají, že mají k dispozici celou tuto velikou paměť. Systém v době běhu rozděluje paměť na snadno manipulovatelné stránky a tyto stránky podle potřeby odkládá na disk. Programy si ničeho nevšimnou díky dalšímu triku, multiprocessingu.
Proces můžeme chápat jako spuitiný program. Každý proces je samostatná entita, která vykonává nějaký program. Pokud se podíváte na procesy v Linuxu, uvidíte jich celou řadu. Například zadáním příkazu ps uvidíte následující seznam procesů:
$ ps
PID TTY STAT TIME COMMAND
158 pRe 1 0:00 -bash
174 pRe 1 0:00 sh /usr/X11R6/bin/startx
175 pRe 1 0:00 xinit /usr/X11R6/lib/X11/xinit/xinitrc
-178 pRe 1 N 0:00 bowman
182 pRe 1 N 0:01 rxvt -geometry 120x35 -fg white -bg black
184 pRe 1 < 0:00 xclock -bg grey -geometry
-1500-1500 -padding 0
185 pRe 1 < 0:00 xload -bg grey -geometry -0-0 -label xload
187 pp6 1 9:26 /bin/bash
202 pRe 1 N 0:00 rxvt -geometry 120x35 -fg white -bg black
203 ppc 2 0:00 /bin/bash
1796 pRe 1 N 0:00 rxvt -geometry 120x35 -fg white -bg black
1797 v06 1 0:00 /bin/bash
3056 pp6 3 < 0:02 emacs intro/introduction.tex
3270 pp6 3 0:00 ps
$
Pokud by můj systém měl mnoho procesorů, mohl by každý proces (přinejmenším teoreticky) běžet na jednom procesoru. Bohužel ve skutečnosti mám pouze jediný procesor, takže operační systém se uchyluje k dalším trikům a nechává každý proces běžet pouze krátkou dobu. Tento čas se označuje jako časové kvantum. Celému triku se říká multiprocessing nebo také plánování a způsobuje, že každý proces si myslí, že on je jediný proces v systému. Procesy jsou jeden před druhým chráněny, takže když jeden proces zhavaruje nebo začne pracovat chybně, neovlivní to nijak ostatní procesy. Operační systém toho dosahuje tím, že každému procesu přidělí samostatný adresový prostor a proces může manipulovat pouze s ním.
Ovladače zařízení představují důležitou část jádra Linuxu. Stejně jako ostatní části operačního systému pracují v silně privilegovaném prostředí a pokud by něco dělaly špatně, mohly by způsobit závažné problémy. Ovladače zařízení řídí interakci mezi operačním systémem a tím hardwarovým zařízením, které ovládají. Například při zápisu na disk IDE používá souborový systém obecné rozhraní blokového zařízení. Ovladač se stará o detaily a zajišťuje provedení operací, specifických pro dané zařízení. Ovladače zařízení jsou určeny vždy pro konkrétní čip řadiče, s nímž spolupracují, takže pokud například máte SCSI řadič NCR810, budete potřebovat ovladač pro řadič NCR810.
V Linuxu, stejně jako v systému Unix, se k samostatným souborovým systémům, které systém může používat, nepřistupuje pomocí identifikátorů zařízení (jako jsou čísla nebo jména disků). Namísto toho jsou všechna zařízení zkombinována do jediné stromové hierarchické struktury, která reprezentuje souborový systém jako celek. Do tohoto jediného stromu přidává Linux nový souborový systém pokaždé, jakmile dojde k jeho připojení do připojovacího adresáře, řekněme /mnt/cdrom. Jednou z důležitých vlastností Linuxu je podpora řady rozdílných souborových systémů. Díky tomu je celý systém velmi pružný a je schopen spolupracovat s jinými operačními systémy. Nejoblíbenější souborový systém v Linuxu je systém ext2, který podporuje většina distribucí Linuxu.
Souborový systém dává uživateli rozumný pohled na soubory a adresáře na discích bez ohledu na typ souborového systému nebo jiné vlastnosti samotného fyzického zařízení. Linux transparentně podporuje řadu rozdílných souborových systémů (například MS-DOS a ext2) a všechny připojené soubory a souborové systémy představuje jako jediný integrovaný virtuální souborový systém. Obecně tedy platí, že uživatelé a procesy nepotřebují vědět typ souborového systému, v němž je uložen nějaký soubor, prostě jej jenom používají.
Blokové ovladače zařízení skrývají rozdíl mezi fyzickými typy blokových zařízení (například IDE a SCSI) a, z pohledu libovolného souborového systému, představují každé zařízení pouze jako lineární seznam bloků dat. Velikosti bloků se mohou pro různá zařízení lišit, například pro disketová zařízení je typický blok o velikosti 512 bajtů, pro zařízení IDE je typický blok o velikosti 1024 bajtů a tyto podrobnosti jsou před uživatelem systému skryty. Souborový systém ext2 vypadá vždy stejně, bez ohledu na to, na jakém zařízení je fyzicky uložen.
Operační systém musí udržovat řadu informací o momentálním stavu systému. Jak se v systému různé věci mění, je nutné tyto datové struktury modifikovat tak, aby odrážely skutečnost.
Když se například přihlásí uživatel, může se vytvořit nový proces. Jádro musí vytvořit datovou strukturu reprezentující nový proces a zapojit jej mezi datové struktury, reprezentující všechny ostatní procesy v systému.
Tyto datové struktury povětšinou existují pouze ve fyzické paměti a přistupovat k nim může pouze jádro a jeho subsystémy. Datové struktury obsahují data a ukazatele - adresy jiných datových struktur nebo rutin. Všechny dohromady mohou datové struktury používané jádrem Linuxu působit zmatečně. Každá datová struktura však má svůj účel a přestože některé jsou používány několika subsystémy jádra, jsou podstatně jednodušťí, než by mohlo vypadat na první pohled.
Pochopení jádra Linuxu je vázáno pochopením jeho datových struktur a funkcí, k nimž je jádro Linuxu používá. Tato kniha zakládá popis jádra Linuxu na jeho datových strukturách.
O každém subsystému jádra se zde hovoří v termínech jeho algoritmů, metod, jakými plní svou funkci, a způsobu použití datových struktur jádra.
Při údržbě svých datových struktur používá Linux řadu technik softwarového inženýrství.
V řadě příležitostí používá propojené nebo zřetězené datové struktury. Pokud každou datovou strukturu chápeme jako jednu instanci nebo výskyt něčeho, řekněme procesu nebo síťového zařízení, musí být jádro schopno nalézt všechny instance. V lineárním seznamu obsahuje kořenový ukazatel adresu první datové struktury (prvku), v seznamu a každá struktura obsahuje ukazatel na následující prvek seznamu. Ukazatel posledního prvku má hodnotu 0 nebo NULL, čímž se signalizuje konec seznamu. V obousměrně propojeném seznamu obsahuje každý prvek ukazatel jednak na následující prvek a jednak také ukazatel na předchozí prvek seznamu. Pomocí obousměrně propojených seznamů se usnadňuje přidávání a odstraňování prvků uprostřed seznamu, je k tomu ale zapotřebí více paměti. To je typické dilema každého operačního systému: rozhodování mezi zatížením paměti a procesoru.
Lineární seznamy jsou užitečný nástroj jak organizovat datové struktury, průchod takovýmto seznamem ale může být neefektivní. Pokud hledáte určitý prvek, může se vám snadno stát, že budete muset procházet celým seznamem. K obejití tohoto omezení používá Linux další techniku, hashování. Hashovací tabulka je pole nebo vektor ukazatelů. Pole nebo vektor je jednoduše soustava věcí, které leží v paměti jedna za druhou. Knihovnu můžeme chápat jako pole knih. K polím se přistupuje pomocí indexu, který představuje offset v poli. Pokud se budeme dále držet analogie s knihovničkou, můžete každou knihu popsat její pozicí v knihovně, můžete například chtít pátou knihu.
Hashovací tabulka je pole ukazatelů na datové struktury, přičemž její index se odvozuje od informací v těchto strukturách. Pokud budete mít datovou strukturu obsahující informace o obyvatelích nějaké vesnice, můžete jako index použít věk obyvatel. Při hledání dat o určité osobě použijete její věk jako index do hashovací tabulky obyvatel a pak už budete pouze sledovat ukazatel na datovou strukturu obsahující informace o určité osobě. Bohužel je velmi pravděpodobné, že určitý věk bude mít více obyvatel ve vesnici, takže ukazatel v hashovací tabulce je ukazatelem na řetězec či seznam datových struktur, které popisují obyvatele stejného věku. Nicméně průchod těmito kratšími seznamy je stále rychlejší než prohledávání všech datových struktur.
Hashovací tabulky urychlují přístup k často používaným datovým strukturám, Linux používá hashovací tabulky velmi často při implementaci vyrovnávacích pamětí. Vyrovnávací paměť obsahuje užitečné informace, k nimž je nutné přistupovat rychle, a často obsahuje pouze podmnožinu všech dostupných dat. Datové struktury se ukládají a udržují ve vyrovnávacích pamětech, protože k nim jádro přistupuje velmi často. Vyrovnávací paměti mají ovšem i nevýhodu, protože jejich použití a údržba je mnohem složitějií než prostá manipulace s lineárními seznamy nebo hashovacími tabulkami. Pokud se nějakou datovou strukturu podaří ve vyrovnávací paměti najít (takzvaný zásah), udělá to radost. Pokud tam ale požadovaná struktura není, je nutné prohledat všechny příslušné struktury, a pokud požadovaná struktura existuje, musí se přidat do vyrovnávací paměti. Při přidávání nové struktury do vyrovnávací paměti může být nezbytné odstranit z ní nějakou jinou strukturu. Linux musí rozhodnout o tom, kterou strukturu odstranit, přičemž hrozí nebezpečí, že odstraněnou strukturu bude potřebovat hned vzápětí.
Jádro Linuxu používá velmi často abstraktních rozhraní. Rozhraní je soubor rutin a datových struktur, které nějakým způsobem fungují. Například všechny ovladače síťových zařízení musejí nabízet určité rutiny manipulující nad určitými datovými strukturami. Řešením mohou být obecné vrstvy kódu, které používají služeb (rozhraní) nižiích vrstev. Síťová vrstva je obecná a ve spolupráci s kódem pro konkrétní zařízení vytváří standardní rozhraní.
Velmi často se nižií vrstvy při zavádění systému registrují u vyšťích vrstev. Registrace obvykle obnáší přidání nějaké datové struktury do nějakého seznamu. Například každý souborový systém vestavěný v jádře se v jádru registruje při zavádění systému nebo, pokud používáte moduly, při prvním použití daného souborového systému. Které souborové systémy jsou registrovány můžete zjistit v souboru /proc/filesystems. Registrační datová struktura velmi často obsahuje ukazatele na jednotlivé funkce. Jedná se o adresy programových funkcí, které zajišťují určité úkony. Když použijeme opět jako příklad registraci souborového systému, v datové struktuře, kterou každý souborový systém předává jádru při své registraci, je obsažena adresa rutiny specifické pro daný souborový systém, která musí být zavolána vždy, když se systém připojuje.