Linux documentation Project (CS) / Průvodce jádrem operačního systému Linux
Previous Content Next Up

 

5. Meziprocesorová komunikace

    Při vzájemné koordinaci svých aktivit komunikují procesy navzájem mezi sebou a jádrem. Linux podporuje řadu mechanismů pro meziprocesovou komunikaci (IPC). Dvěma z nich jsou signály a roury, Linux však dále podporuje IPC mechanismy Systemu V, pojmenované podle verze Unixu, kde byly poprvé zavedeny.

5.1 Signály

    Signály jsou jednou z nejstarších metod meziprocesové komunikace zavedených v systémech Unix. Slouží k signalizaci asynchronních událostí jednomu nebo více procesům. Signál může být generován přerušením klávesnice nebo chybovými okolnostmi, jako například pokusem o přístup k neexistující oblasti virtuální paměti. Signály jsou rovněž využívány v příkazových interpretech k signalizaci řídících příkazů v synovských procesech.

    Je definována jakási množina signálů, které může generovat jádro nebo jiné procesy za předpokladu, že mají dostatečná privilegia. Seznam všech existujících signálů můžete získat pomocí příkazu kill (kill -l), na mém systému (s procesorem Intel) dostávám následující seznam:

1) SIGHUP 2) SIGINT 3) SIGQUIT   4) SIGILL
5) SIGTRAP 6) SIGIOT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR

   Pro platformu Alpha se signály mohou lišit. Proces se může rozhodnout, že většinu generovaných signálů bude ignorovat. Existují ale dvě výjimky: signál SIGSTOP, který způsobení pozastavení činnosti procesu, a signál SIGKILL, který způsobí ukončení procesu. Ve všech ostatních případech může proces pro signály zvolit libovolnou obsluhu. Procesy mohou signály zablokovat nebo, pokud je neblokují, mohou zvolit vlastní obsluhu nebo mohou obsluhu signálu ponechat na jádru. Pokud je signál obsluhován jádrem, používá jádro implicitní obslužné mechanismy příslušného signálu. Například implicitní akce při obsluze signálu SIGFPE (výjimka operace v pohyblivé řádové čárce) je vytvoření souboru core a ukončení procesu. Signály nemají žádnou vzájemnou prioritu. Pokud dojde ve stejném okamžiku k vygenerování dvou signálů, mohou se v procesu objevit v jakémkoliv pořadí. Obdobně neexistuje žádný mechanismus pro obsluhu více stejných signálů. Neexistuje způsob jak může proces říci, zda obdržel jeden signál SIGCONT nebo 42 těchto signálů.

    Linux implementuje signály pomocí informací uložených ve struktuře task_struct procesu. Počet podporovaných signálů je omezen velikostí slova procesoru. Procesory se slovem o velikosti 32 bitů mohou mít maximálně 32 různých signálů, zatímco 64bitové procesory, jako například Alpha AXP, mohou mít až 64 signálů. Momentálně aktivní signály jsou uloženy v položce signal s maskou blokovaných signálů uloženou v položce blocked. S výjimkou signálů SIGSTOP a SIGKILL je možno blokovat všechny signály. Pokud je generován blokovaný signál, zůstává platný až do doby, než dojde k jeho odblokování. Linux dále udržuje informace o obsluze všech možných signálů, které jsou uloženy v datové struktuře sigaction, na níž ukazuje struktura task_struct. Kromě dalšího obsahuje buť adresu obslužné rutiny signálu, nebo příznak, který Linuxu říká, zda si proces přeje signál ignorovat, nebo zda jej má obsluhovat jádro. Proces může implicitně nastavenou obsluhu signálů modifikovat pomocí různých systémových volání, která následně modifikují strukturu sigaction a také hodnotu masky blocked.

    Ne každý proces v systému může posílat signály všem ostatním procesům, to může pouze jádro a superuživatel. Normální procesy mohou posílat signály pouze procesům se stejným uid a gid nebo procesům ve stejné skupině procesů. Signály jsou generovány nastavením příslušného bitu v položce signal struktury task_struct. Pokud proces signál nezablokoval a čeká v přerušitelném stavu, dojde k jeho probuzení přepnutím stavu na spuitiný a zajištěním, že se bude nacházet ve frontě spustiných procesů. Díky tomu jej bude plánovač v dalším kole plánovaní považovat za kandidáta na spuštění. Pokud je požadována implicitní obsluha, je Linux schopen obsluhu signálu optimalizovat. Pokud se například objeví signál SIGWINCH (změna fokusu X okna) a požaduje se implicitní obsluha, pak není potřeba udělat vůbec nic.

    Signály se procesům nepředávají bezprostředně po jejich výskytu, musejí počkat až do příýtího spuštění procesu. Vždy když proces opouští systémové volání, kontrolují se jeho položky signal a blocked a pokud se v nich nachází nějaký neblokovaný signál, je možno jej nyní doručit. Může se to jevit jako velmi nespolehlivá metoda, avšak každý proces v systému trvale používá systémová volání, například při zápisu znaku na terminál. Proces se může v případě potřeby rozhodnout čekat na signál, pak je uveden do pozastaveného, nicméně přerušitelného stavu až do doby, než se signál objeví. Kód obsluhy signálů v Linuxu pak pro každý neblokovaný přítomný signál vyhodnocuje údaje ve struktuře sigaction.

    Pokud je obsluha signálu nastavena na implicitní obsluhu, obslouží signál jádro. Implicitní obsluha signálu SIGSTOP převede proces do zastaveného stavu a nechá plánovač spustit další proces. Implicitní obsluha signálu SIGFPE provede výpis jádra a ukončí proces. Proces však může zvolit vlastní obsluhu signálu. Jedná se o rutinu, jejíž adresa je uložena ve struktuře sigaction a která se bude volat v okamžiku výskytu signálu. Jádro musí zavolat obslužnou rutinu procesu, což je akce závislá na konkrétní platformě, systém se ale vždy musí vyrovnávat s faktem, že proces se momentálně nachází v režimu jádra a následujícím krokem je návrat do uživatelského režimu, z nějž byla volána nějaká systémová rutina nebo rutina jádra. Tento problém se řeší pomocí manipulace se zásobníkem a registry procesu. Ukazatel instrukcí programu se nastaví na adresu rutiny obsluhy signálu a parametry předávané rutině se uloží na zásobník nebo zapíší do příslušných registrů. Když proces obnoví svou činnost, vypadá to, jako kdyby byla obslužná rutina signálu volána běžnými mechanismy.

    Linux je kompatibilní s normou POSIX, takže procesy mohou při volání obslužné rutiny signálu měnit blokování signálů. Obnáší to změnu masky blocked při volání obsluhy signálu.

Maska blocked musí být po skončení obslužné rutiny nastavena zpět na původní hodnotu.

    Linux proto na zásobník přidává adresu úklidové rutiny, která zajistí obnovu původní hodnota masky. Linux rovněž provádí optimalizaci v případě, kdy se musí volat více obslužných rutin, a to tak, že jejich adresy najednou naskládá na zásobník, takže když jedna obsluha skončí, vrací se přímo do další obslužné rutiny a až poslední skáče do úklidové rutiny.

5.2 Roury

Všechny klasické příkazové interprety Linuxu podporují přesměrování. Například:

$ ls | pr | lpr

    Tento příkaz předává rourou výpis obsahu adresáře pořízený příkazem ls na standardní vstup příkazu pr, který jej rozděluje na stránky. Nakonec se standardní výstup příkazu pr předává na standardní vstup příkazu lpr, který výsledek vytiskne na tiskárně. Roury jsou jednosměrné bajtové proudy, které propojují standardní výstup jednoho procesu se standardním vstupem druhého procesu. "ádný z procesů si tohoto přesměrování není vědom a všem funguje jako normálně. Dočasné roury mezi procesy obsluhuje příkazový interpret.

    V Linuxu se roury implementují pomocí dvou datových struktur file, které obě ukazují na stejný dočasný inode VFS, který sám o sobě ukazuje na fyzickou stránku v paměti. Na obrázku 5.1 vidíme, že každá ze struktur file obsahuje ukazatele na rozdílné vektory souborových rutin; jedna obsahuje ukazatel na operaci zápisu do roury, druhá ukazatel na operaci čtení z roury.

    Tím se před obecnými systémovými voláními zajišťujícími čtení a zápis do souborů skrývají implementační detaily. Když první proces zapisuje do roury, kopírují se bajty na sdílenou datovou stránku, když druhý proces čte z roury, bajty se kopírují ze sdílené stránky. Linux musí synchronizovat přístup k rouře. Musí zajistit, aby čtenář i písař roury byli v koordinaci, k zajištění jejich zamykání používá fronty a signály.

    Když písař zapisuje do roury, používá standardní knihovní funkce zápisu. Všem těmto funkcím se předávají deskriptory, které jsou indexy do množiny datových struktur file procesu, každý deskriptor reprezentuje jeden otevřený soubor nebo, jako v tomto případě, otevřenou rouru. Zápisová rutina pak používá k zajištění požadavků na zápis informace uložené v inodu VFS, který reprezentuje danou rouru.

    Dokud je dostatek místa k zápisu bajtů do roury a dokud není roura zamčena čtenářem, Linux ji zamyká pro písaře a kopíruje bajty zapisované z adresového prostoru písaře do sdílené datové stránky. Když si rouru zamkne čtenář nebo když v ní není dostatek místa pro data, aktuální proces se uspí a čeká v čekací frontě inodu roury. Plánovač pak naplánuje spuštění jiného procesu. Proces je poeru"telný, takže může přijímat signály a bude čtenářem probuzen až bude dostatek místa pro zápis dat nebo když dojde k odemčení roury. Po zapsání všech dat se inode roury odemkne a bude probuzen čtenář, čekající ve frontě tohoto inodu.

    Čtení dat z roury je velmi podobné jejich zápisu. Procesům je povoleno neblokovací čtení (závisí to na režimu otevření souboru či roury) a v takovém případě pokud nejsou žádná data k načtení nebo pokud je roura uzamčená, vzniká chyba. Znamená to, že proces může pokračovat. Druhá možnost je čekat ve frontě inodu roury tak dlouho, dokud zápisová operace neskončí. Když oba procesy skončí s používáním roury, zruší se její inode i sdílená datová stránka.

    Linux rovněž podporuje pojmenované roury, zvané také FIFO, protože zřetězují operace na principu First In, First Out. První data zapsaná do roury budou také jako první přečtena.

    Na rozdíl od normálních rour nejsou FIFO dočasné objekty, představují trvalé entity v souborovém systému a vytvářejí se příkazem mkfifo. Procesy mohou FIFO používat, pokud k nim mají příslušná přístupová práva. Způsob otevření FIFO se poněkud liší od rour. Roura (tedy její dvě datové struktury file, její inode a sdílená datová stránka) se vytvářejí jednorázově za běhu, zatímco FIFO existuje trvale a uživatelé si je otevírají a zavírají. Linux musí ošetřovat situace, kdy čtenář otevře FIFO dříve než písař nebo kdy se čtenář pokouší číst, přestože písař ještě nic nezapsal. Až na tyto výjimky se FIFO obsluhují prakticky úplně stejně jako roury a používají stejné datové struktury a operace.

5.3 Sokety

5.3.1 IPC mechanismy Systemu V

    Linux podporuje tři mechanismy pro meziprocesovou komunikaci, které se poprvé objevily v Unixu System V v roce 1983. Jedná se o fronty zpráv, semafory a sdílenou paměť. Tyto IPC mechanismy Systemu V všechny používají shodné autentifikační metody. Procesy mohou k těmto prostředkům přistupovat pouze když jádru pomocí systémových volání předají jedinečný referenční identifikátor. Přístup k IPC objektům Systemu V se řídí pomocí přístupových oprávnění velmi podobně, jako se řídí přístup k souborům. Přístupová práva k IPC objektům Systemu V nastavuje tvůrce objektu pomocí systémových volání. Referenční identifikátor objektu používají všechny tyto mechanismy jako index do tabulky prostředků. Nejde ovšem přímo o index, k vygenerování indexu jsou nutné ještě pomocné výpočty.

    Všechny datové struktury reprezentující IPC objekty Systemu V se v Linuxu ukládají ve struktuře ipc_perm, která obsahuje uživatelské a skupinové identifikátory procesů, které objekt vytvořily a vlastní, dále přístupový režim tohoto objektu (pro vlastníka, skupinu a ostatní) a konečně klíč IPC objektu. Tento klíč slouží k určení referenčního identifikátoru IPC objektu. Podporují se dva typy klíčů: veřejný a privátní. Pokud je klíč veřejný, může referenční identifikátor objektu zjistit kterýkoliv proces v systému, pokud k tomu má dostatečná práva. S IPC objekty se nikdy nemanipuluje pomocí klíče, vždy pouze prostřednictvím referenčního identifikátoru.

5.3.2 Fronty zpráv

    Fronty zpráv umožňují jednomu nebo více procesům posílat zprávy, které jeden nebo více procesů bude číst. Linux udržuje seznam front zpráv, vektor msgque, jehož každý prvek ukazuje na jednu strukturu msqid_ds, jež plně popisuje jednu frontu zpráv. Když se vytváří fronta zpráv, alokuje se v systémové paměti nová datová struktura msgid_ds a přidá se do vektoru.

    Každá datová struktura msgid_ds obsahuje datovou strukturu pic_perm a ukazatele na zprávy v této frontě. Kromě toho Linux ukládá časy modifikace fronty jako například čas posledního zápisu do fronty a podobně. Struktura msgid_ds obsahuje dále dvě čekací fronty, jednu pro písaře fronty zpráv a druhou pro její čtenáře.

    Vždy když se proces pokusí o zápis zprávy do fronty, porovnají se jeho efektivní uživatelské a skupinové ID s hodnotami v datové struktuře ipc_perm fronty. Pokud proces má právo zápisu do fronty, může se zpráva zkopírovat z adresového prostoru procesu do datové struktury msg, která se přidá na konec fronty zpráv. Může se nicméně stát, že pro zprávu nebude dostatek volného místa, protože Linux omezuje počet a délku zpráv, které je možno zapsat. V takovém případě se proces umístí do čekací fronty písařů fronty a plánovač naplánuje spuštění dalšího procesu. K probuzení písaře dojde po přečtení jedné nebo více zpráv z fronty zpráv.

    Čtení z fronty je podobný proces. Nejprve se opět kontrolují přístupová práva k frontě. Čtenář si může zvolit přečtení první zprávy ve frontě bez ohledu na její typ, nebo může vybrat zprávu určitého typu. Pokud zadaným kritériím nevyhovuje žádná zpráva, čtenář bude přesunut do čekací fronty čtenářů a plánovač spustí jiný proces. Když dojde k zápisu nové zprávy do fronty, proces bude probuzen a znovu spuštěn.

5.3.3 Semafory

    Ve své nejjednodušťí podobě je semafor místo v paměti, které může více procesů testovat a nastavovat. Operace testování a nastavování je z hlediska procesu nepřerušitelná, atomická - jakmile je jednou vyvolána, nic ji nemůže zastavit. Výsledkem operace testování a nastavení je součet aktuální hodnoty semaforu a nastavované hodnoty, která může být kladná nebo záporná. Podle výsledku operace může být proces uspán dokud hodnota semaforu nebude změněna jiným procesem. Semafory se dají použít k implementaci kritických sekcí, kritických oblastí kódu, které může v jednom okamžiku vykonávat pouze jediný proces.

    Řekněme, že máte mnoho spolupracujících procesů, které všechny čtou a zapisují záznamy v jednom datovém souboru. Budete zřejmě potřebovat přísně omezit přístup k souboru. Můžete použít semafor s počáteční hodnotou jedna a kolem kódu pro operaci se souborem umístit dvě semaforové operace: první bude testovat a dekrementovat hodnotu semaforu, druhá ji bude testovat a inkrementovat. První proces, který se pokusí o přístup k souboru, bude dekrementovat semafor a zdaří-li se mu to, bude mít semafor nově hodnotu 0. Tento proces nyní může pokračovat dále a používat datový soubor, pokud se však nyní pokusí o dekrementaci semaforu jiný proces, operace se nezdaří protože nová hodnota semaforu by byla -1. Druhý proces bude pozastaven do doby, než první proces inkrementuje hodnotu semaforu zpět na 1. Nyní je možno pozastavený proces probudit a tentokrát pokus o dekrementaci semaforu uspěje.

    Každý objekt semaforu je popsán polem, Linux k tomuto účelu používá datovou strukturu semid_ds. Na všechny datové struktury semid_ds v systému se odkazuje pole semary, vektor ukazatelů. Všechny procesy, které mohou manipulovat s polem určitého objektu semafor, s polem manipulují pomocí systémových volání. Systémové volání může specifikovat mnoho operací, přičemž každá je popsána třemi vstupními hodnotami: indexem semaforu, operační hodnotou a množinou příznaků. Index semaforu je index do pole semaforů, operační hodnota je číslo, které se přičte k aktuální hodnotě semaforu. Nejprve Linux testuje, zda operace může proběhnout úspěšně. Operace proběhne úspěšně v případě, že po přičtení operační hodnoty k aktuální hodnotě semaforu bude výsledek větší než nula, nebo pokud jsou nulové jak operační hodnota, tak aktuální hodnota. Pokud by operace proběhla neúspěšně, Linux pozastaví proces, ovšem pouze v případě, že nebyl aktivní příznak neblokující operace.

    Pokud proces bude pozastaven, Linux musí uložit stav požadované operace a poté proces přemístit do čekací fronty. Dosáhne toho vytvořením datové struktury sem_queue na zásobníku a jejím naplněním hodnotami předávanými při volání operace. Nová datová struktura sem_queue se umístí na konec čekací fronty semaforu (pomocí ukazatelů sem_pending a sem_pending_last). Aktuální proces se umístí do čekací fronty v datové struktuře sem_queue (položka sleeper) a volá se plánovač, který spustí další proces.

    Pokud by všechny požadované semaforové operace uspěly a proces není nutno pozastavit, Linux pokračuje a provede požadované operace nad požadovanými položkami pole semaforů.

    Dále musí Linux otestovat, zda některý z čekajících procesů může nyní uspět ve své požadované operaci. Projde všechny položky fronty čekajících procesů (sem_turn) a testuje, zda jejich operace tentokrát uspěje. Pokud ano, odstraní datovou strukturu sem_queue z fronty čekajících procesů a provede nad polem semaforů požadovanou operaci. Probudí čekající proces, takže jej bude možno spustit při příýtím chodu plánovače. Linux zopakuje průchod frontou čekajících procesů znovu od začátku a hledá, zda není možno obsloužit další požadavek až do chvíle, kdy už není možno žádnou operaci provést a není možno probudit žádný proces.

    U semaforu se objevuje nebezpečí zablokování - deadlock. Dojde k tomu, pokud by nějaký proces při vstupu do kritické sekce změnil stav semaforu, avšak poté by kritickou sekci korektně neopustil například protože havaruje nebo je explicitně zrušen. Linux se proti zablokování chrání udržováním seznamu změn pole semaforů. Smyslem je, aby se podle seznamu změn dal semafor uvést do stejného stavu, v jakém byl před provedením operací požadovaných zaniklým procesem. Změny se ukládají v datových strukturách sem_undo ukládaných ve frontě ve strukturách semid_ds i task_struct těch procesů, které používají semafory.

    Každá jednotlivá semaforová operace může žádat o zaznamenání změny. Linux pro každý proces a každé semaforové pole spravuje minimálně jednu strukturu sem_undo. Pokud proces žádnou nemá, Linux ji v okamžiku potřeby vytvoří. Nová struktura sem_undo se zařadí do front jak ve struktuře task_struct procesu, tak ve struktuře semid_ds semaforu.

    Operace prováděné nad polem semaforů se negují a zapisují do příslušných položek pole změn v této struktuře sem_undo. Pokud je tedy operační hodnota požadavku 2, do pole změn se přičte hodnota -2.

    Když dojde k zániku procesu, Linux při jeho ukončení prochází datové struktury sem_undo a provádí změny na příslušných semaforových polích. Pokud daný semafor neexistuje, zůstává struktura sem_undo zařazena ve frontě struktury task_struct, identifikátor semaforu však není platný. V takovém případě úklidový kód semaforu datovou strukturu sem_undo jednoduše zruší.

5.3.4 Sdílená paměť

    Sdílená paměť umožňuje jednomu nebo více procesům komunikovat prostřednictvím oblasti paměti, která se objevuje ve virtuálním adresovém prostoru každého z nich. Na stránky virtuální paměti je uveden odkaz prostřednictvím položek v tabulce stránek každého ze sdílejících procesů. Sdílená paměť se u jednotlivých procesů nemusí objevovat na stejném místě virtuální paměti. Stejně jako u všech IPC objektů Systemu V je i přístup k oblastem sdílené paměti řízen pomocí klíčů a kontroly přístupových práv. Jakmile je sdílení paměti povoleno, nijak už se nekontroluje způsob využití sdílené paměti jednotlivými sdílejícími procesy. Ty musí při synchronizaci přístupu do sdílené paměti používat jiné mechanismy, například semafory.

    Každá nově vytvořená oblast sdílené paměti je reprezentována datovou strukturou shmid_ds. Tyto struktury se ukládají ve vektoru shm_segs. Datová struktura shmid_ds popisuje jak velká je oblast sdílené paměti, kolik procesů ji používá a jak se sdílená paměť mapuje do jejich adresových prostorů. Přístupová práva k paměti a typ klíče je určen tím, kdo sdílenou paměť vytvořil. Pokud má tvůrce sdílené oblasti paměti dostatečná práva, může dokonce nařídit trvalé uzamčení sdílené oblasti ve fyzické paměti.

    Každý proces, který si přeje paměť sdílet, se k ní musí připojit pomocí systémového volání. To vytvoří novou datovou strukturu vm_area_struct popisující sdílenou paměť v tomto procesu. Proces může sám rozhodnout, kam v jeho adresovém prostoru se má sdílená paměť mapovat, nebo může toto rozhodnutí ponechat na operačním systému. Nová struktura vm_area_struct se připojí do seznamu těchto struktur, na něž ukazuje struktura shmid_ds. K propojení struktur týkajících se oblastí sdílené paměti slouží ukazatele vm_next_shared a vm_prev_shared. V průběhu připojení se virtuální paměť fakticky nevytváří, k tomu dojde až v okamžiku prvního přístupu ke sdílené oblasti.

    Když se proces poprvé pokusí o přístup do některé ze stránek sdílené paměti, dojde k výpadku stránky. Při obsluze výpadku Linux nejprve hledá strukturu vm_area_struct, která popisuje vypadnuvší stránku. Ta obsahuje ukazatele na obslužné rutiny příslušného typu virtuální paměti. Obslužný kód výpadku sdílené stránky hledá v položkách tabulky stránek strukturu shmid_ds a zjišťuje, zda pro danou stránku struktura existuje. Pokud neexistuje, provede alokaci fyzické stránky a vytvoří pro ni položku v tabulce stránek. Kromě tabulky stránek aktuálního procesu se položka umístí i ve struktuře shmid_ds. Znamená to, že když se o přístup ke stejné oblasti sdílené paměti pokusí další proces a dojde k výpadku stránky, použije obslužný kód výpadku pro tento proces stejnou již vytvořenou fyzickou stránku. Tedy první proces přistupující ke sdílené oblasti paměti způsobí vytvoření sdílené paměti a u všech dalších procesů už dochází pouze k namapování fyzicky existující sdílené paměti do jejich virtuálního adresového prostoru.

    Jakmile proces nebude sdílenou paměť dále potřebovat, odpojí se od ní. Dokud existují jiné procesy, které stejnou sdílenou oblast ještě využívají, je odpojení záležitostí pouze aktuálního procesu. Z datové struktury shmid_ds se odstraní jeho položka vm_area_struct a zruší se. Aktualizuje se tabulka stránek aktuálního procesu a oblast paměti používaná pro sdílení se označí za neplatnou. Když se od sdílené paměti odpojí poslední proces, dojde k uvolnění stránek sdílené paměti z fyzické paměti a zruší se také struktura shmid_ds dané oblasti sdílené paměti.

    Další komplikace při práci se sdílenou pamětí se objevují v okamžiku, pokud stránky sdílené paměti nejsou uzamčeny ve fyzické paměti. V takovém případě může dojít k odložení sdílené paměti na disk v době, kdy je nedostatek fyzické paměti. Mechanismus odkládání sdílených paměťových stránek je popsán v kapitole Správa paměti.

Previous Content Next Up