-
Notifications
You must be signed in to change notification settings - Fork 0
VFS a montovani
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 - tady jde o vrstvu nad ním. Předpokládá to systémová volání a základní představu o procesech.
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.
| 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, 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.
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é.
sudo slabtop -o | head -5 # kolik paměti drží mezipaměť dentry a i-uzlů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. Nový program o přesměrování neví, píše na jedničku.
ls -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ý nginx
ulimit -n # měkký strop, na Debianu často 1024Chybu „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ř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.
sudo mount /dev/nvme0n1p2 /mnt/data # ručně, restart to nepřežije
findmnt /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á.
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é.
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.
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ř.
fuser -vm /mnt/data # kdo tam sahá a čím: otevřený soubor, cwd, běžící binárkaumount -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é.
| 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. 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.
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. 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í 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.
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.
sudo 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. Praktický dopad je jeden: když přípojný bod vidí jeden proces a druhý ne, neopravuj mount, ale porovnej jejich /proc/PID/mountinfo.
findmnt # strom přípojných bodů, čitelnější než holé mount
lsblk -f # disky, oddíly, typy, UUID a kam jsou připojené
df -hT # volné místo včetně typu souborového systému
mount | column -t # rychlý pohled na volby připojení
lsof /mnt/data # kdo tam má otevřeno
cat /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
|
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.
- Souborový systém - co je pod VFS: i-uzly, adresáře, alokace bloků
-
Žurnálování a konzistence - proč
umountnení formalita - Vstup a výstup - zařízení a mezipaměť stránek pod tím vším
- Virtualizace a kontejnery - jmenné prostory a overlayfs v plné šíři
-
Nástroje a diagnostika -
lsof,stracea/procjako každodenní nářadí
Našel jsi chybu nebo něco chybí? Založ issue nebo pošli pull request.
Základy
- Co dělá operační systém
- Režim jádra a uživatelský režim
- Systémová volání
- Přerušení a výjimky
- Architektury jádra
Procesy
Souběh
Paměť
Soubory a zařízení
Systém v provozu
Praxe