UNIX 7. Deskriptory a proudy Obsah: deskriptor, funkce pro práci se souborem mazání otevřených souborů, důsledky standardní proudy – vznik a použití přesměrování, kolona, filtr © Milan Keršláger http://www.pslib.cz/ke/slajdy 2. 4. 2014 http://creativecommons.org/licenses/by-nc-nd/3.0/
Soubory a proudy již víme, co je soubor a adresář vzniká otázka, jak se s nimi pracuje programátor (uživatel) zná název (+cestu) jádro OS neumí dobře pracovat se jmény souborový deskriptor abstraktní klíč → definuje POSIX pro jazyk C → file handle malé celé číslo (0, 1, 2, 3, …) index do tabulky otevřených souborů
Práce se souborem systémová volání jádra operačního systému řekneme jméno a požadovanou operaci získáme souborový deskriptor dále již používáme jen deskriptor ukončíme práci s deskriptorem → uvolnění knihovní funkce (jazyk C) základní nízkoúrovňové operace fopen, fclose, fread, fwrite, ... interně používají systémové volání vracejí strukturu FILE → datový proud (stream)
open() systémové volání pro otevření souboru příznaky: int open (const char *cesta, int flag,...); příznaky: O_RDONLY → jen pro čtení O_WRONLY → jen pro zápis O_RDWR → pro čtení i zápis doplňující parametry: O_APPEND (jen přidávání), O_CREAT (vytvoření), O_TRUNC (vymazání dat), O_EXCL (chyba, jeli O_CREAT a existuje), O_NONBLOCK (volání selže místo zablokování, je-li bez dat)
Offset v souboru soubor není jako RAM nelze číst libovolný bajt čtení i zápis probíhají z určité pozice jádro si musí pamatovat pozici (offset) počítána v bajtech od začátku další zápis (čtení) automaticky na následující pozici změna pozice pouze pomocí volání lseek() problém s velkými soubory původně 32bitové číslo → 2 GiB moderní systémy implicitně 64bitový
Další systémová volání read() čtení z deskriptoru čte se do bufferu, je dán maximální počet bajtů write() zápis do deskriptoru zápis z bufferu, je dán maximální počet bajtů close() uzavření deskriptoru unlink() smazání zadaného názvu souboru z adresáře
Mazání otevřených souborů běžná praxe v unixových systémech název je jen položka v adresáři (+ odkaz na i- uzel) i-uzel s metadaty existuje nezávisle na jménu využíváno u dočasných souborů fd=open('/tmp/data', O_CREAT | O_EXCL); unlink('/tmp/data'); ... volání funkcí read(), write() close(fd); soubor je založen a vzápětí smazán protože je otevřen, lze zapisovat i číst data v souboru nikdo jiný ale soubor nevidí teprve po uzavření souboru jsou data z disku uvolněna
Důsledky odloženého mazání data jsou uvolněna až později na disku není volné místo ihned po smazání program, ve kterém je otevřen, musí být ukončen aktualizace, prelink v paměti je stále starší verze kompatibilita v MS Windows nelze částečně řešeno pomocí O_TEMPORARY tento speciální příznak obsluhuje knihovna způsobuje potíže při psaní přenositelných programů
Aktualizace (1) aktualizace programu program je spuštěn (běží) proběhne aktualizace souboru starý je smazán, nový je vytvořen v RAM stále běží stará verze programu → je nutné program RESTARTOVAT typicky démon poskytující nějakou službu nezbytné u bezpečnostních aktualizací bez restartu může program havarovat snaží se natáhnout komponentu z disku, ale ta je novější lze vyzkoušet u Firefoxu je chyba, že si Firefox o restart sám neřekne
Aktualizace (2) aktualizace knihoven stejný problém jako v předchozím případě bezpečnostní aktualizace knihovny může ohrozit všechny programy, které používají starší verzi na rozdíl od Windows jsou v Linuxu masivně sdíleny řešením je RESTART všech dotčených programů při startu programu je do paměti zavedena nová verze knihoven (podle jejich názvů)
prelink rozmístění sdílených knihoven v paměti každá knihovna svoje místo (adresy) je-li jiná knihovna na stejném místě → konflikt druhá musí být uložena na jinou adresu → duplikace ve 32bitovém systému je malý adresní prostor Microsoft optimalizuje DLL pro svoje aplikace prelink projde všechny (často používané) binárky (podle atime), zjistí jejich dynamické knihovny, spočítá co nejideálnější nekonfliktní rozložení knihoven v paměti → změna knihovny na disku běžící program musí být restartován (nebo reboot)
Práce s adresářem opendir() otevření adresáře a nastavení na první položku readdir() vrací přečtenou položku a posune na další položku closedir() uzavření adresáře rewinddir() nastavení na první položku v adresáři ...
Standardní proudy standardní chování unixových systémů proces má při startu otevřeny 3 deskriptory (proudy): 0 – stdin → klávesnice (standardní vstup) 1 – stdout → terminál (standardní výstup) 2 – stderr → terminál (standardní chybový výstup) přípravu deskriptorů zajišťuje VŽDY rodič (shell) DOS, Windows – každý program sám → nekompatibility chce-li program číst vstup, čte z deskriptoru 0 chce-li program zapsat výstup, použije 1 nebo 2 využívá se při přesměrování vstupu/výstupu
Původ 0, 1 a 2 nastavení pomocí specializovaného ioctl() při startu systému: první je spuštěn init pro konzoli spustí program getty sdělí programu identifikaci konzole (číslo) getty použije ioctl() pro připojení 0, 1 a 2 getty vypíše login: uživatel zapíše jméno a heslo, getty je zkontroluje getty se změní na shell → exec() dědí deskriptory uživatel zapíše příkaz → potomek dědí deskriptory po odhlášení se ukončí shell a init spustí nové getty
Vznik příkazového řádku getty() jméno + heslo Místo getty může čekat ssh démon (přihlášení ze sítě). exec('/bin/bash') shell ls fork() exec('ls') ls výpis adresáře wait()
Přesměrování vstupu/výstupu využívají se standardní proudy každý program používá deskriptory 0, 1 a 2 démon je zavírá → s uživatelem nekomunikuje rodič pozmění cíl těchto deskriptorů tj. shell je mění podle značek <, >, 2>, >> atd. potomek dědí nastavení deskriptorů tj. spuštěný program (např. ls, cat, grep, ...) potomek používá pro vstup/výstup stále 0, 1 a 2 místo na terminál pak zapisuje program do souboru místo z klávesnice čte z předchozího procesu (kolona) atd.
Přesměrování – BASH > >> < <<X 2>&1 Přesměrování výstupu neexistující soubor je vytvořen, existující je přepsán >> Přesměrování vstupu neexistující soubor je vytvořen, u existujícího přidání za konec < standardní vstup je čten z existujícího souboru <<X Přesměrování vstupu – tzv. „here document“ standardní vstup je čten ze stejného vstupu (nejčastěji ze skriptu) až ke značce X 2>&1 Sloučení stderr (2) se stdout (1) zapisuje se až za přesměrování (ale ev. před znak kolony) Příklady: ls > vystup.txt find /proc > vystup.txt 2>&1 unix2dos < vystup.txt > dos.txt
Realizace přesměrování shell ls > vypis.txt fork() fd = open('vypis.txt', O_CREAT); dup2(fd, stdout); close(fd); exec('ls') ls write(stdout,„ahoj“) výpis adresáře je do stdout, tj. do souboru vypis.txt wait()
Použití standardních proudů standardní deskriptory jsou výhoda programátor se nemusí starat (zajišťuje rodič – shell) využíváno v příkazovém řádku → filtry v GUI skryto na stdout zapisují aplikace ladící informace na stderr zapisují aplikace chybová hlášení typicky však zahazováno chceme-li výstup vidět, spustíme program z přík. řádku Windows 7 → předávání objektů skrze rouru nutno přizpůsobit všechny dotčené programy
Kolona (roura) specifické využití standardních deskriptorů programA | programB stdout z programA jde na stdin programB datový proud je bez struktury (chápou ho programy) umožňuje řetězit jednoduché programy umožňuje postupné zpracování vstupních dat využívá koncept jednoúčelových nástrojů a filtrů filtr čte vstup, pozmění ho a zapíše na výstup nedostane-li parametr, používá 0, 1 a 2 téměř všechny unixové nástroje se chovají jako filtry např.: cat, grep, sort, less, awk, sed, …
Realizace kolony (roury) shell grep … | sort Funkce pipe() vytváří dva nové deskriptory, které jsou propojené (zápis do jednoho se objevuje v druhém). Kód je jen naznačen. fork() pipe(); fork() exec('grep'); exec('sort'); grep ... roura sort wait()
Filtr je program, respektující standardní proudy při spuštění bez parametrů: čte z deskriptoru 0 (tj. klávesnice) zapisuje na 1 (stdout) a 2 (stderr) parametry mohou zařídit interní přesměrování: ze souboru nebo do souboru filtr lze použít v koloně kolona propojuje výstup prvního se vstupem druhého filtr „filtruje“ (modifikuje) data → odtud název Příklad: cut -d: -f1 /etc/passwd | less grep ro /etc/passwd | sort
využití konceptu filtrů ve vlastním programu