# Mapování do paměti, neblokující zápis V této kapitole se podíváme na dvě pokročilejší techniky vstupu a výstupu. První z nich se týká především obyčejných souborů, kterými jsme se zabývali v první kapitole: ukážeme si alternativu ke klasickým voláním ‹read› a ‹write› v podobě mapování souborů do paměti. Druhá část kapitoly pak uzavírá téma komunikace pomocí rour a proudových socketů z předchozí kapitoly. Budeme se zejména zabývat otázkou krátkého zápisu – situací analogickou ke krátkému čtení z předchozí kapitoly. Ukázky: 1. ‹mode› – výpočet modu (nejčastější hodnoty), 2. ‹bwconv› – konverze bitmapového obrázku, 3. ‹iota› – neblokující zápis. Přípravy: 1. ‹sum› – sumarizace tabulky s pevně velkými záznamy 2. ‹list› – kontrola struktury záznamů 3. ‹flood› – † in-situ flood fill v bitmapovém souboru 4. ‹write› – zápis bloku dat do neblokujícího popisovače 5. ‹hose› – souběžný zápis do několika popisovačů 6. ‹tee› – ‡ souběžný zápis s vyrovnávací pamětí Řešené: 1. ‹sort› – in-situ řazení záznamů v souboru, 2. ‹select› – výběr záznamů podle klíče, 3. ‹avg› – výpočet průměrné hodnoty dlaždic. ## Mapování souborů do paměti Abychom mohli s daty uloženými v souboru pracovat, musíme je načíst do operační paměti – k tomu nám doposud sloužilo systémové volání ‹read›. Protože operační paměť má řádově menší kapacitu (a je to obecně mnohem vzácnější zdroj, než perzistentní úložiště), jsme často nuceni pracovat se soubory po částech.¹ Správa paměti spojená se vstupem a výstupem je díky tomu relativně nepohodlná. Mapování souborů do paměti nám umožňuje předstírat, že máme celý soubor načtený v operační paměti a tím řadu úkolů zjednodušit. Zároveň dává operačnímu systému kontrolu nad tím, která data budou skutečně v operační paměti uložena, a která se načtou až ve chvíli, kdy je program skutečně použije.² Pro interakci s tímto mechanismem nabízí POSIX tři systémová volání: • ‹mmap› vytvoří mapování – vstupem je popisovač obyčejného souboru, vlastnosti mapování (přístupová práva, sdílený vs soukromý přístup) a jeho rozsah, • ‹msync› vyžádá zápis změn, které program provedl v mapované paměti, zpátky do souboru na disku, a konečně • ‹munmap› již nepotřebné mapování uvolní a zároveň provede případné nedokončené zápisy do souboru. Mapování může být dvou základních typů: • «sdílené» (‹MAP_SHARED›) – změny provedené skrz toto mapování se projeví v souboru, do kterého takto mapované adresy odkazují, • «soukromé» (‹MAP_PRIVATE›) – změny provedené v mapované paměti zůstávají viditelné pouze v procesu, který mapování vlastní. «Pozor!» Není zaručeno, že v «soukromém» mapování se nebudou projevovat vnější změny souboru – přesné chování závisí na konkrétním operačním systému. Zároveň není zaručeno, že změny provedené ve «sdíleném» mapování budou viditelné pro ostatní vstupně-výstupní operace ihned, a to ani ze stejného procesu – operace ‹read› «může» vrátit verzi dat, kde se zápis skrze mapování ještě neprojevil. Přenos upraveného obsahu paměti do souboru je nutné vyžádat voláním ‹msync› nebo ‹munmap›. ¹ Hlavní problém představují velmi velké soubory. Ne každý program musí být schopen s velkými soubory pracovat, ale u řady programů očekáváme, že budou smysluplně fungovat i se soubory, které jsou mnohem větší než je dostupná operační paměť. A i v situaci, kdy je operační paměti dostatek, obvykle pro ni existuje lepší využití. ² Více informací o tom, jak tento mechanismus funguje na úrovni operačního systému, naleznete v sekcích 1.4 a 3.2 skript. ## Správa zdrojů V této kapitole se poprvé setkáváte se situací, kdy je Vámi vytvořený podprogram odpovědný za nějaký zdroj (konkrétně mapování vytvořené službou ‹mmap›). Platí zde několik jednoduchých, ale velmi důležitých pravidel: 1. každý zdroj, který byl vyžádán (alokován), musí být také vrácen (uvolněn), 2. odpovědnost za vrácení zdroje musí být jasně určena, 3. obě pravidla platí i v situaci, kdy nějaká operace selže. Nejjednodušší forma správy zdroje je spojena s «lexikálním rozsahem platnosti». Je zde zřejmá analogie s «lokální proměnnou», pro kterou je při vstupu do funkce alokována na zásobníku paměť a při opuštění funkce je takto vyhrazená paměť automaticky uvolněna. Mluvíme o «lokálním» (někdy také dočasném) zdroji. Odpovědnost za jeho uvolnění nese «vždy» podprogram (např. ‹subr›), který jej alokoval. Takto alokovaný zdroj může být předán dalšímu podprogramu, odpovědnost za uvolnění zdroje ale i tak zůstává na ‹subr›. Podprogramy, kterým je předán (propůjčen) zdroj spravovaný nějakou vnější entitou, a které tento zdroj pouze využívají, již dobře znáte. Složitější jsou podprogramy, které alokovaný zdroj předávají volajícímu (v návratové hodnotě, výstupním parametru, nebo jako součást nějaké složitější struktury). Těmito se zatím zabývat nebudeme – v této kapitole si vystačíte s lokálními zdroji. ## Neblokující zápis Podrobněji o neblokujícím zápisu pojednává třetí ukázka. Prozatím nebudeme potřebovat režim popisovačů měnit (tzn. zapínat nebo vypínat jejich příznak ‹O_NONBLOCK›) – budou vždy připraveny v potřebném režimu. Neblokující zápis se chová podobně, jako čtení ze socketu nebo roury – počet skutečně zapsaných bajtů může být menší, než bylo vyžádáno. Navíc se nemusí provést zápis vůbec žádný – v takovém případě vrátí hodnotu -1, i přesto, že se nejedná o chybu v obvyklém smyslu. Kód chyby (‹errno›) bude v takovém případě ‹EAGAIN›.