Operační systémy
Obsah Soubory
markdown

VFS-a-montovani.md

13 kB 176 řádků Změněno Zobrazit na GitHubu Stáhnout
markdown
# VFS a připojování souborových systémů Pusť `cat` na pět různých věcí: na soubor na NVMe disku, na soubor na USB klíčence naformátované na FAT, na soubor na síťovém disku na druhém konci republiky, na `/proc/cpuinfo` a na `/dev/urandom`. Pokaždé to funguje a pokaždé je to tentýž program s tímtéž kódem. Pod tím přitom leží pět úplně různých mechanismů: extenty na ext4, tabulka alokačních jednotek na FAT, síťový protokol s časovými limity, funkce jádra, která odpověď sestaví až ve chvíli čtení, a generátor, který neukládá vůbec nic. `cat` o žádném z nich neví a nikdy vědět nebude. Zavolá `open`, `read` a `close`, víc v něm není. Mezi ním a těmi pěti věcmi stojí jedna vrstva jádra, která předstírá, že jsou všechny stejné. Říká se jí VFS (Virtual File System) a je to nejlepší příklad abstrakce v celém systému. Tahle stránka je o tom, jak ta vrstva vypadá zevnitř, jak se do stromu připojují další souborové systémy a co se na tom rozbíjí. Uspořádání dat na disku má vlastní stránku [Souborový systém](Souborovy-system) - tady jde o vrstvu nad ním. Předpokládá to [systémová volání](Systemova-volani) a základní představu o [procesech](Procesy). ## Jedna sada operací pro všechno Jádro definuje pevnou sadu operací nad souborem: otevři, čti, zapiš, posuň se, zavři a několik desítek dalších. Technicky je to struktura ukazatelů na funkce a pro celý systém je jedna jediná. Každý souborový systém a každý ovladač do ní dosadí své vlastní funkce. ext4 dosadí čtení, které umí extenty, FAT čtení přes alokační tabulku a `/dev/urandom` čtení, které nikam nesáhne a bajty vyrobí. Tohle je nejdůležitější věc na celé stránce: **v Linuxu je souborem všechno, protože jádro má jednu jedinou sadu operací a každý souborový systém i každé zařízení k ní jen dodá své funkce.** Program zavolá `read`, VFS se podívá, o jaký objekt jde, a zavolá to, co tam kdo zaregistroval. Zbytek stránky je jen rozvedení téhle věty. ## Čtyři objekty, na kterých VFS stojí | Objekt | Co představuje | Vznikne, když ||---|---|---|| superblok | připojený souborový systém jako celek | připojíš svazek || i-uzel | soubor sám o sobě - práva, velikost, umístění dat | sáhneš na soubor || dentry | jméno v adresáři a jeho vazba na i-uzel | přeloží se cesta || soubor | otevřená instance včetně pozice a příznaků | zavoláš `open` | I-uzel je tentýž pojem, který popisuje [Souborový systém](Souborovy-system), jen držený v paměti. Nemá v sobě jméno - jméno je až dentry, a proto může jeden i-uzel viset pod několika jmény. Objekt „soubor“ naopak vzniká na každé otevření zvlášť: dva procesy čtoucí týž soubor mají dva objekty s vlastní pozicí, ale jeden společný i-uzel. ### Proč existuje mezipaměť dentry Překlad cesty `/home/martin/data/soubor.txt` není jedna operace. Je to čtyři vyhledání za sebou: `home` v kořeni, `martin` v `home`, `data` v `martin` a teprve pak soubor. Bez mezipaměti by se ten řetěz i s načtením adresářových bloků odehrál znovu při každém otevření. Dentry cache si proto pamatuje provedené překlady, a to i **negativní výsledky** - že se pod tímhle jménem nic nenachází. Marné hledání knihovny v deseti adresářích je díky tomu levné. ```bashsudo slabtop -o | head -5    # kolik paměti drží mezipaměť dentry a i-uzlů``` ### Deskriptor souboru: číslo, ne soubor Deskriptor je malé nezáporné celé číslo a nic jiného v něm není: index do tabulky otevřených souborů daného procesu. Jádro přiděluje vždycky nejnižší volné. Čísla 0, 1 a 2 jsou standardní vstup, výstup a chybový výstup - není to pravidlo jádra, ale dohoda, kterou drží shell a libc. **Deskriptory přežijí `fork` a ve výchozím stavu i `exec`.** Potomek dostane kopie ukazující na tytéž otevřené soubory a při `exec` zůstávají otevřené, pokud u nich není příznak `FD_CLOEXEC`. Přesně na tom stojí přesměrování v shellu: shell udělá `fork`, v potomkovi otevře soubor a přehodí ho na deskriptor 1, a teprve pak zavolá `exec` - mechanika obojího je na stránce [Procesy](Procesy). Nový program o přesměrování neví, píše na jedničku. ```bashls -l /proc/self/fd                     # čím má otevřeno proces, který to voláls -l /proc/$(pgrep -n nginx)/fd | head # a čím poslední spuštěný nginxulimit -n                               # měkký strop, na Debianu často 1024``` Chybu „Too many open files“ dostaneš, když ten strop vyčerpáš. Většinou to není málo nastavený limit, ale program, který deskriptory otevírá a nezavírá. Serveru, který jich opravdu potřebuje víc, zvedni `LimitNOFILE=` v jednotce systemd, ne `ulimit` ve spouštěcím skriptu. ## Připojování a přípojné body Připojení napojí kořen jednoho souborového systému na existující adresář jiného. Od té chvíle vede cesta přes ten adresář jinam. **Obsah překrytého adresáře nezmizí, jen není vidět.** Po odpojení je zpátky. Odtud pochází častý úlek: napíšeš gigabajty do `/mnt/data`, pak tam připojíš disk a soubory jsou pryč. Nejsou, jsou pod přípojným bodem. ```bashsudo mount /dev/nvme0n1p2 /mnt/data   # ručně, restart to nepřežijefindmnt /mnt/data                     # co je tam doopravdy připojené``` Aby připojení přežilo restart, patří do `/etc/fstab`. Řádek má šest polí: zařízení, přípojný bod, typ, volby, příznak pro `dump` a pořadí kontroly při startu. ```UUID=8f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8  /mnt/data  ext4  defaults,noatime  0  2``` **Zařízení se píše jako `UUID=`, nikdy jako `/dev/sda1`.** Názvy zařízení přiděluje jádro v pořadí, ve kterém je najde, a to se mezi starty mění - stačí zapomenutá USB klíčenka a z `/dev/sdb1` je `/dev/sdc1`. UUID je zapsané v souborovém systému samotném a vypíše ho `lsblk -f`. Místo `mount` bez argumentů používej `findmnt`: vypíše strom místo hromady řádků a s `findmnt -T /mnt/data/projekt` řekne, pod jaký přípojný bod daná cesta spadá. ### Volby, které stojí za to nastavit **Na svazku, kde jsou jenom data, nastav `nosuid,nodev,noexec`.** `nosuid` zruší účinek bitu setuid, `nodev` zakáže soubory zařízení, `noexec` spouštění. Co setuid dělá a proč je citlivý, rozebírají [Práva a uživatelé](Prava-a-uzivatele). Kde to přestává platit: `noexec` není bezpečnostní hranice. Zabrání spuštění binárky, ale `bash /mnt/data/skript.sh` projde, protože interpret soubor jenom čte. Je to obrana proti nedopatření, ne proti útočníkovi. Připojení jen pro čtení (`ro`) použij vždycky, když z disku zachraňuješ data. ### Když se svazek nedá odpojit `umount: target is busy` znamená, že do toho stromu má někdo otevřenou cestu - obvykle proces s otevřeným deskriptorem nebo s pracovním adresářem uvnitř. ```bashfuser -vm /mnt/data    # kdo tam sahá a čím: otevřený soubor, cwd, běžící binárka``` **`umount -l` je poslední možnost, ne první.** Odpojí přípojný bod ze stromu okamžitě, ale souborový systém žije dál, dokud ho poslední proces nezavře. Procesy tedy dál zapisují do něčeho, co už nikdo nevidí. **Reálný případ, 31. ledna 2017.** Inženýr GitLabu při potížích s replikací smazal datový adresář databáze na produkčním stroji místo na záložním. Přišlo se zhruba o šest hodin dat a ukázalo se, že pět různých záložních postupů nefunguje tak, jak se čekalo. Poučení sem: **cesta vypadá na obou strojích stejně, identitu jí dává až to, co je pod ní připojené.** ## Souborové systémy, které nejsou na disku | Cesta | Typ | Co tam najdeš ||---|---|---|| `/proc` | procfs | adresář na každý proces, plus stav jádra (`/proc/meminfo`) || `/proc/PID/` | procfs | `fd`, `maps`, `status`, `cmdline`, `cwd`, `exe` || `/sys` | sysfs | zařízení, ovladače, sběrnice, přepínače jádra || `/dev` | devtmpfs | uzly zařízení, které jádro vytvoří samo || `/run` | tmpfs | běhové soubory služeb, PID soubory, sokety || `/dev/shm` | tmpfs | sdílená paměť mezi procesy || `/sys/fs/cgroup` | cgroup2 | limity a účtování zdrojů | Ani jeden z nich nemá na disku jediný bajt. Čtení z `/proc/cpuinfo` spustí funkci v jádře, která text vyrobí až v tu chvíli - proto `ls -l /proc/cpuinfo` hlásí nulovou velikost, i když z něj vypadne dvacet řádků. **`/proc` jsou procesy jako adresáře, `/sys` zařízení jako adresáře.** Sysfs navíc drží pravidlo jedna hodnota na soubor, takže se čte i nastavuje obyčejným `cat` a `echo`. **tmpfs žije v paměti a započítává se do ní.** Soubory v `/run` a `/dev/shm` zmizí při restartu a dokud existují, zabírají paměť. Odložit se dají, ale zahodit ne, protože nemají kam - aplikace, která zaplní `/dev/shm`, se tak umí dostat až k zásahu OOM killeru, jak popisují [Výměnné algoritmy](Vymenne-algoritmy). Výchozí strop `/dev/shm` je polovina paměti, tedy 8 GiB na stroji se 16 GB. Co se dnes už nedělá: statické `/dev` s tisícovkami předem vytvořených uzlů a `mknod` v návodech na instalaci. Uzly vytváří jádro samo přes devtmpfs, jména a práva jim doladí udev. ## Vrstvené a uživatelské souborové systémy **overlayfs skládá adresáře na sebe.** Spodních vrstev může být několik a jsou jen pro čtení, horní je zapisovatelná. Čtení propadává odshora dolů, první zápis do souboru ho celý zkopíruje nahoru. Na tomhle stojí kontejnery: vrstvy obrazu jsou spodek, zapisovatelná vrstva kontejneru vršek a se smazáním kontejneru zmizí, jak rozebírají [Virtualizace a kontejnery](Virtualizace-a-kontejnery). Důsledek: zápis jednoho bajtu do dvougigabajtového souboru zkopíruje dva gigabajty. **FUSE přesouvá souborový systém do uživatelského prostoru.** Každá operace jde z aplikace do jádra, z jádra do procesu s obsluhou, zpátky do jádra a teprve pak do aplikace. Dvě přepnutí navíc na jednu operaci, a při ceně přechodu popsané u [systémových volání](Systemova-volani) je to znát hlavně u mnoha malých souborů. Přesto ho používej. Pro sshfs, připojený cloud přes rclone nebo šifrované úložiště je to jediná rozumná cesta, protože alternativou je ovladač v jádře, kde chyba položí celý stroj. Jen po něm nechtěj propustnost lokálního disku. ## Každý proces může vidět jiný strom Přípojné body nejsou globální. Jmenný prostor přípojných bodů je kopie stromu, kterou proces mění, aniž by o tom zbytek systému věděl. ```bashsudo unshare -m bash    # nový shell s vlastním stromem přípojných bodů``` Co v takovém shellu připojíš, nikdo jiný neuvidí. Na tomhle stojí kontejnery a celý výklad je na stránce [Virtualizace a kontejnery](Virtualizace-a-kontejnery). Praktický dopad je jeden: když přípojný bod vidí jeden proces a druhý ne, neopravuj `mount`, ale porovnej jejich `/proc/PID/mountinfo`. ## Diagnostika ```bashfindmnt              # strom přípojných bodů, čitelnější než holé mountlsblk -f             # disky, oddíly, typy, UUID a kam jsou připojenédf -hT               # volné místo včetně typu souborového systémumount | column -t    # rychlý pohled na volby připojenílsof /mnt/data       # kdo tam má otevřenocat /proc/mounts     # totéž očima jádra``` | Příznak | Kde je problém ||---|---|| `umount: target is busy` | otevřený deskriptor nebo cwd uvnitř - `fuser -vm` || Po restartu svazek chybí | není v `/etc/fstab` nebo se změnil název zařízení || Zapisuju a soubory po připojení zmizí | psalo se do adresáře pod přípojným bodem || `df` hlásí plno, `du` ukazuje málo | smazaný soubor drží otevřený proces - `lsof +L1` || `/dev/shm` plné, systém začne odkládat | tmpfs se počítá do paměti || „Too many open files“ | strop `ulimit -n`, obvykle neuzavírané deskriptory || Přípojný bod vidí jen některé procesy | jiný jmenný prostor - `/proc/PID/mountinfo` | ## Co si odnést **VFS je jedna sada operací, do které si každý dodá své funkce.** Odtud plyne „souborem je všechno“, ne naopak. **Čtyři objekty: superblok, i-uzel, dentry, soubor.** Svazek, soubor, jméno, otevření. **Deskriptor je číslo do tabulky procesu**, přežije `fork` a bez `FD_CLOEXEC` i `exec`. **Překrytý adresář se neztratil, jen ho nevidíš.** Odpoj a je zpátky. **V `fstab` patří `UUID=`**, protože `/dev/sdX` se mezi starty mění. **`/proc`, `/sys` a `tmpfs` nemají na disku nic**, ale tmpfs ti ubírá paměť. **`umount -l` problém neřeší, jen ho schová.** Napřed `fuser -vm`. ## Kam dál - **[Souborový systém](Souborovy-system)** - co je pod VFS: i-uzly, adresáře, alokace bloků- **[Žurnálování a konzistence](Zurnalovani-a-konzistence)** - proč `umount` není formalita- **[Vstup a výstup](Vstup-a-vystup)** - zařízení a mezipaměť stránek pod tím vším- **[Virtualizace a kontejnery](Virtualizace-a-kontejnery)** - jmenné prostory a overlayfs v plné šíři- **[Nástroje a diagnostika](Nastroje-a-diagnostika)** - `lsof`, `strace` a `/proc` jako každodenní nářadí