Operační systémy
Obsah Soubory
markdown

Souborovy-system.md

15 kB 219 řádků Změněno Zobrazit na GitHubu Stáhnout
markdown
# Souborový systém: i-uzly, adresáře a odkazy Smažeš `rm` dvougigabajtový log, `df -h` ukáže pořád stejně volného místa a `nginx`, který ho měl otevřený, do něj zapisuje dál. Nic nehlásí chybu. Nebo obráceně: jeden soubor je vidět ve dvou adresářích naráz, v každém pod jiným jménem. Jeden z nich smažeš a druhý funguje dál i s daty. Ani jedno není chyba a ani jedno není zvláštní režim. Obojí plyne z jedné věci, kterou většina lidí o souborech neví, protože v běžné práci ji nikdy neuvidí. Stránka je o tom, jak jsou soubory na disku doopravdy zařízené: co je i-uzel, proč je adresář jen tabulka, jak se z i-uzlu dostaneš k datům a proč se ext4 chová jinak než FAT. Neřeší, co přežije výpadek proudu uprostřed zápisu - to je [žurnálování a konzistence](Zurnalovani-a-konzistence) - ani jak se souborový systém zapojuje do stromu adresářů, což je [VFS a montování](VFS-a-montovani). Předpokládá, že víš, co je [systémové volání](Systemova-volani). ## Jméno souboru není soubor Tohle je nejdůležitější věta na celé stránce: **soubor je i-uzel, jméno je jen záznam v adresáři, který na něj ukazuje.** I-uzel zmizí teprve tehdy, když na něj neukazuje žádné jméno **a zároveň** žádný otevřený deskriptor. Zbytek stránky je rozvedení téhle věty. `rm` se ve skutečnosti jmenuje `unlink` a dělá přesně to: odpojí jméno. Kolik jmen na i-uzel ukazuje, si počítá sám i-uzel, otevřené deskriptory k tomu připočítává jádro. Nula a nula znamená uvolnit bloky. ### Co v i-uzlu je a co v něm není I-uzel (inode) je záznam pevné velikosti, u ext4 obvykle 256 bajtů. Drží typ souboru a práva, UID vlastníka a GID skupiny, velikost v bajtech, počet pevných odkazů, ukazatele na datové bloky a tři časy. Ty časy si lidi pletou nejčastěji: - **mtime** - kdy se změnil **obsah**. Tohle chceš skoro vždycky.- **ctime** - kdy se změnil **i-uzel**: práva, vlastník, počet odkazů. Změna obsahu ho mění taky. Nedá se nastavit dozadu, což je při vyšetřování průšvihu jediné, co ti zbyde.- **atime** - kdy se naposledy **četlo**. Od jádra 2.6.30 (2009) se montuje s `relatime` a atime se přepíše, jen když je starší než mtime nebo starší než den. Jinak by každé čtení znamenalo zápis. Co v i-uzlu **není**: jméno souboru. Pusť si `stat soubor.txt`: ```  Size: 10          Blocks: 8       IO Block: 4096   regular fileDevice: 259,2   Inode: 1837042      Links: 1Access: (0644/-rw-r--r--)  Uid: ( 1000/  martin)   Gid: ( 1000/  martin)Modify: 2026-08-06 21:03:55 +0200Change: 2026-08-07 08:59:11 +0200``` Jméno v tom výpisu chybí - `stat` ho tiskne jen proto, žes mu ho sám napsal na příkazovou řádku. Zajímavá jsou dvě čísla: `Inode: 1837042` je identita souboru v rámci oddílu a `Blocks: 8` je osm půlkilobajtových jednotek, čili jeden 4KiB blok. Deset bajtů zabírá 4 096. ### Adresář je taky jen soubor Adresář je soubor, jehož obsahem jsou dvojice **jméno → číslo i-uzlu**. Nic víc v něm není a zapisovat do něj smí jen jádro. Odtud plyne věc, která vypadá jako díra a není: **abys mohl smazat soubor, potřebuješ právo zápisu do adresáře, ne k souboru.** ```bashls -l cizi.txt        # -rw------- 1 root root 42 Aug  7 09:12 cizi.txtrm cizi.txt           # zeptá se, ale projde - adresář je tvůj``` Tohle je nejlepší možný důkaz té ústřední věty. A je to důvod, proč má `/tmp` sticky bit: zapisovat tam smí každý, takže bez něj by ti kdokoli mohl smazat cokoli. Detaily jsou na stránce [práva a uživatelé](Prava-a-uzivatele). ## `ln` a `ln -s` nejsou dvě varianty téhož ### Pevný odkaz je druhé jméno ```bashln soubor.txt kopie.txt        # nový záznam v adresáři na TENTÝŽ i-uzells -li soubor.txt kopie.txt    # -i vypíše číslo i-uzlu jako první sloupec``` Obě jména mají stejné číslo i-uzlu a počet odkazů `2`. Není mezi nimi originál a kopie, jsou rovnocenná. Smažeš jedno, počet klesne na jedna a data se nikam nehnou. ### Symbolický odkaz je soubor s cestou uvnitř `ln -s` vyrobí **nový i-uzel** vlastního typu, jehož obsahem je textová cesta k cíli. ext4 cestu do 59 bajtů uloží rovnou do i-uzlu, takže symbolický odkaz obvykle nezabere ani blok. | Vlastnost | Pevný (`ln`) | Symbolický (`ln -s`) ||---|---|---|| Vlastní i-uzel | ne, sdílí s cílem | ano || Přes hranici oddílu | **ne** | ano || Na adresář | **ne** | ano || Po smazání cíle | funguje dál | ukazuje do prázdna || Po přejmenování cíle | funguje dál | rozbije se || `ls -l` ukáže | obyčejný soubor | `l` a `-> cesta` | Přes hranici oddílu pevný odkaz neprojde proto, že v adresáři je uložené **číslo** i-uzlu a to platí jen uvnitř jednoho souborového systému. **Když nevíš, ber `ln -s`** - je vidět a dá se opravit, kdežto pevný odkaz vypadá jako obyčejný soubor a člověk, který ho najde za rok, netuší, že úpravou jednoho mění i druhý. ### Proč pevný odkaz na adresář nejde Vznikl by cyklus. Adresáře tvoří strom právě proto, že do každého vede jediná cesta shora. Smyčkou by `find`, `du` ani `rm -r` neprošly - nemají jak poznat, že už tam byly - a počítání odkazů by u cyklu nikdy nekleslo na nulu. Výjimkou jsou `.` a `..`, které si zakládá souborový systém sám. Odtud plyne účetnictví, které mate: **prázdný adresář má počet odkazů 2** - jednou jeho jméno v rodiči, podruhé jeho vlastní `.`. Každý podadresář přidá další, protože jeho `..` ukazuje zpátky. S počtem souborů uvnitř to nesouvisí. ## Jak se i-uzel dostane k datům ### Přímé a nepřímé bloky Klasické unixové schéma má v i-uzlu patnáct ukazatelů: dvanáct **přímých** na datové bloky, jeden **jednoúrovňový nepřímý** (ukazuje na blok plný ukazatelů), jeden **dvouúrovňový** a jeden **trojúrovňový**. Při bloku 4 KiB a čtyřbajtovém ukazateli se do jednoho bloku vejde 1 024 ukazatelů: | Úroveň | Bloků | Dat ||---|---|---|| 12 přímých | 12 | 48 KiB || jednoúrovňový | 1 024 | 4 MiB || dvouúrovňový | 1 048 576 | 4 GiB || trojúrovňový | 1 073 741 824 | 4 TiB | Malý soubor je dostupný okamžitě, na konec velkého se prokoušeš až po třech dalších čteních. To je záměr: většina souborů v systému je malá. ### Extenty, čili co s tím udělal ext4 Souvislý 1GiB soubor potřebuje ve starém schématu čtvrt milionu ukazatelů, přestože jde o jeden jediný souvislý rozsah. ext4 (výchozí od roku 2008) to nahradil **extenty**. Extent je trojice „logický blok, fyzický blok, délka" a pokryje až 32 768 bloků, čili 128 MiB. Do i-uzlu se vejdou čtyři, na víc se rozvine strom. Ten samý 1GiB soubor je pak popsaný osmi extenty. Kromě místa to šetří hlavně čtení metadat a dovoluje alokovat velké kusy naráz. ## Kam se bloky ukládají a proč se Linux „nedefragmentuje" Bloky se dají rozdat třemi způsoby. **Souvislá alokace** je nejrychlejší na čtení a nepoužitelná, protože soubor nemůže růst. **Seznamová** dá do každého bloku odkaz na další a tím zabije náhodný přístup - přesně to dělá FAT. **Indexová** drží seznam bloků stranou v i-uzlu, a to je Unix. Vnitřní fragmentaci neřeší žádná: blok je nejmenší jednotka, takže soubor o deseti bajtech zabere 4 KiB a 4 086 bajtů se zahodí. Na dvou stech tisících drobných souborech je to skoro gigabajt. **Linux se opravdu nedefragmentuje a není to mýtus.** ext4 používá **zpožděnou alokaci**: data drží v [page cache](Vstup-a-vystup) a fyzické bloky vybere až při zápisu na disk, kdy zná celkovou velikost. K tomu přidává víceblokovou alokaci a rezervaci místa za koncem rostoucího souboru. Běžný soubor tak vznikne jako jeden nebo dva extenty a nemá se co rozsekat. **Kdy to přestává platit: nad zhruba devadesát procent zaplnění.** Volné místo přestane být souvislé, alokátor bere, co zbylo, a soubory vznikají rozsekané na desítky kusů. Rady na defragmentaci, které v návodech pořád visí, řeší následek jiného problému: správná odpověď je uvolnit místo, ne pouštět `e4defrag`. ## FAT jako protiklad Na začátku oddílu je **tabulka**, ve které má každý cluster položku s číslem clusteru následujícího - spojový seznam. Adresářový záznam nese jméno, velikost, atributy a číslo prvního clusteru. Žádný i-uzel neexistuje: identita souboru a jeho jméno jsou jedna a ta samá věc. Z toho plyne, co FAT nemá: vlastníka, práva, pevné ani symbolické odkazy, žurnál. Systém na něm provozovat nejde už proto, že by nebyl rozdíl mezi rootem a kýmkoli jiným. Přesto ho používáš denně - USB klíčenka, kterou přečte i patnáct let staré rádio, a **EFI oddíl**, kde FAT vyžaduje přímo specifikace UEFI, protože firmware musí načíst zavaděč bez ovladačů (viz [start systému](Start-systemu)). | Systém | Největší soubor | Práva | Konzistence | Kdy ho chceš ||---|---|---|---|---|| FAT32 | 4 GiB - 1 B | ne | žádná | EFI oddíl, staré přístroje || exFAT | prakticky bez limitu | ne | žádná | velké SD karty sdílené s Windows || **ext4** | 16 TiB | ano | žurnál | **výchozí volba na systém i data** || btrfs | 16 EiB | ano | copy-on-write | snapshoty, kontrolní součty, více disků || XFS | 8 EiB | ano | žurnál | velké soubory, hodně paralelních zápisů | Když nevíš, ber ext4. btrfs si vezmi, když opravdu chceš snapshoty nebo detekci tichého poškození dat, XFS na server s videem. ## Pět procent, na která nesmíš ext4 si při vytvoření rezervuje **5 procent bloků pro roota**. Když se disk zaplní, systémové služby mají ještě kam zapsat a stroj se dá zachránit, a alokátor navíc drží rezervu, aby při plném disku nezačal hned rozsekávat soubory. Na kořenovém oddíle to nech být. Na dvouterabajtovém datovém disku je to sto gigabajtů, které nikdy nikdo nepoužije: ```bashtune2fs -l /dev/nvme0n1p2 | grep -i "reserved block"   # kolik je vyhrazeno teďtune2fs -m 1 /dev/nvme0n1p2                            # sniž na 1 %, jen u datového oddílu``` ## Když si `du` a `df` odporují `ls -l` čte velikost z i-uzlu, `du` sčítá **skutečně přidělené bloky**. Obě čísla bývají jiná a obě jsou správně. **Řídký soubor (sparse)** má díry, kterým nikdy nebyly přiděleny bloky. Čtení z díry vrátí nuly, na disku ale není nic: ```bashtruncate -s 1G prazdny.imgls -lh prazdny.img     # 1,0Gdu -h prazdny.img      # 0``` Takhle fungují obrazy disků virtuálních strojů. Pozor při přenosu: `rsync` bez `-S` a `tar` bez `-S` díry vyplní nulami a z jednogigabajtového souboru je rázem gigabajt na disku. Druhý případ je ten z úvodu. Smazaný, ale stále otevřený soubor už nemá jméno, takže ho `du` při procházení stromu nenajde. Jeho bloky pořád existují, takže je `df` počítá jako obsazené. Rozdíl mezi `du -sh /` a `df -h /` je přesně to, co drží běžící procesy. ```bashlsof +L1               # soubory s počtem odkazů 0, které někdo drží otevřené: > /proc/1234/fd/7    # uvolní místo bez restartu procesu``` Druhý příkaz zkrátí soubor přes deskriptor. Spolehlivě funguje u procesu, který zapisuje v režimu append - jinak bude psát dál na svůj starý offset a vyrobí ti řídký soubor. ## Toy Story 2, rok 1998 Během výroby filmu někdo pustil na sdíleném svazku Pixaru `rm -rf` na špatném místě. Soubory mizely za běhu a animátoři sledovali, jak se ztrácejí celé adresáře s postavami a scénami. Než stačili stroj odpojit, byla pryč většina filmu - podle pozdějších vyjádření technického ředitele zhruba devadesát procent. Záloha existovala. Jenže zálohovací systém tiše selhával a nikdo si toho nevšiml, protože obnovu nikdy nikdo nezkusil. Film zachránila kopie, kterou měla doma technická ředitelka Galyn Susmanová - pracovala z domova po narození dítěte a data si k sobě synchronizovala. **Poučení: záloha, kterou jsi nikdy neobnovil, není záloha.** Je to jen složka, o které si to myslíš. ## Diagnostika ```bashdf -h /var                 # volné bajtydf -i /var                 # volné i-uzly - jiný zdroj, dochází dřívlsof +L1                   # smazané a stále otevřené souboryls -li                     # čísla i-uzlů a počty odkazůstat soubor.txt            # všechno o jednom i-uzlufilefrag -v velky.img      # na kolik extentů je soubor rozsekanýdu -sh --apparent-size .   # velikost proti skutečně obsazenému místu``` | Příznak | Kde je problém ||---|---|| „No space left on device" a `df -h` ukazuje volno | došly i-uzly, ověř `df -i` || Smazal jsi logy, místo nepřibylo | proces drží soubor otevřený, `lsof +L1` || `du -sh /` a `df -h /` si odporují | totéž, smazané a otevřené soubory || `du` hlásí míň než `ls -l` | řídký soubor, díry nezabírají bloky || Soubor nejde smazat, i když je tvůj | chybí zápis do **adresáře**, sticky bit nebo `chattr +i` || Uživateli došlo místo, rootovi ne | 5 procent vyhrazených bloků || Kopírování 5GB souboru na klíčenku selže | FAT32, limit 4 GiB - 1 B | Počet i-uzlů se u ext4 stanoví při `mkfs` a **později se nedá zvětšit** - to je klasika serverů s poštou nebo cache. Jediné řešení je přeformátovat s `mkfs.ext4 -i 8192` a obnovit ze zálohy. Víc nástrojů je na stránce [nástroje a diagnostika](Nastroje-a-diagnostika). ## Co si odnést **Soubor je i-uzel, jméno je ukazatel na něj.** Všechno divné chování kolem mazání plyne z téhle věty. **Mazání je odebrání jména.** Data zmizí, až když nezbyde jméno ani otevřený deskriptor. **Právo mazat je právo zápisu do adresáře.** K samotnému souboru nepotřebuješ nic. **Pevný odkaz je rovnocenné jméno, symbolický je soubor s cestou uvnitř.** První neprojde přes oddíl, druhý se rozbije přejmenováním cíle. **Extenty popisují rozsahy, ne jednotlivé bloky.** Osm položek místo čtvrt milionu ukazatelů. **Zaplnění nad devadesát procent láme alokátor.** Do té doby se ext4 fragmentuje zanedbatelně. **Místo a i-uzly jsou dva nezávislé zdroje.** Každý dochází zvlášť a `df -h` o tom druhém mlčí. ## Kam dál - **[Žurnálování a konzistence](Zurnalovani-a-konzistence)** - co z těchhle struktur přežije výpadek proudu- **[VFS a montování](VFS-a-montovani)** - jak se ext4, tmpfs a FAT tváří pro programy stejně- **[Práva a uživatelé](Prava-a-uzivatele)** - rwx bity, setuid a sticky bit, které sedí v i-uzlu- **[Vstup a výstup](Vstup-a-vystup)** - page cache a proč se zápis nekoná hned- **[Nástroje a diagnostika](Nastroje-a-diagnostika)** - `lsof`, `strace` a `/proc` na živém systému