Hlavní navigace

Porovnání systémů Linux a FreeBSD (12)

12. 2. 2004
Doba čtení: 6 minut

Sdílet

Závěrečná část rozsáhlé kapitoly o virtuální paměti pojednává o chybách, které se v jejích implementacích vyskytují, a protože většinu z nich nelze uspokojivě vyřešit, povíme si alespoň, jak se vyhnout jejich následkům.

Chyby ve virtuální paměti

Elementární programátorské bugy se vyskytují ve všech druzích kódu. Většina bugů se dá snadno najít a odstranit, proto nepředstavují závažnější problém. Virtuální paměť je specifická tím, že se v ní vyskytují neodstranitelné bugy. Vývojáři o těchto chybách vědí, nicméně nedokáží s nimi nic udělat. K odstranění by bylo třeba zásadně přepsat nejen virtuální paměť, ale i filesystému a dokonce i síťování. V současných verzích systémů se proto vývojáři soustředí spíše na zmenšení pravděpodobnosti výskytu bugu než na jeho úplné odstranění.

Zásadním problémem virtuální paměti je tzv. low-memory deadlock. Princip bugu je následující: v systému došla volná paměť; je potřeba nějaké stránky uvolnit; stránky jsou špinavé, takže je potřeba je zapsat na disk; k uvolnění stránek je však potřeba další paměť. Systém zapisování stránek tedy čeká, než bude uvolněna nějaká paměť, systém správy paměti zase čeká, než budou nějaké stránky zapsány, aby je mohl uvolnit. Výsledek je ten, že operační systém vytuhne.

Low-memory deadlock se může vyskytovat v nejrůznějších případech:

  • Swapování stránek do swapové partition — zde se low-memory deadlock nevyskytuje, neboť kód pro swapování je napsán tak, že žádnou paměť nealokuje. I když v systému není žádná volná stránka, je možno stránky zapisovat do swapu. Některé specifické ovladače blokových zařízení však paměť potřebují k tomu, aby mohly provést operaci zápisu. Na Linuxu se například nedoporučuje swapovat na softwarovou RAID partition (v jádrech 2.6 by to již mělo fungovat, v 2.4 a nižších ne). Na FreeBSD paměť pro požadavek alokuje i samotný ovladač IDE disku a vývojáři prostě doufají, že k nedostatku systémové paměti nedojde.
  • Zapisování modifikovaných stránek do souboru — zde se low-memory deadlock vyskytuje. Filesystém totiž potřebuje k provedení zápisu do souboru nějakou paměť alokovat (na to, aby našel fyzický blok, který dané části souboru náleží, potřebuje přečíst nějaké sektory z disku). Low-memory deadlock se nevyskytuje při zapisování dat pomocí syscallu write  — syscall write se totiž může zablokovat a počkat, než bude paměť dostupná. Low-memory deadlock se vyskytuje při zapisování namapovaných stránek, neboť tam se až příliš pozdě z tabulky stránek zjistí, že stránka byla modifikovaná a je třeba ji zapsat.
  • Swapování stránek do swapového souboru — zde se low-memory deadlock vyskytuje — situace je analogická předchozímu případu.
  • Zapisování modifikovaných stránek přes NFS — zde se low-memory deadlock vyskytuje — k zapsání dat přes síť je třeba alokovat síťový buffer pro odchozí packet a je nutné alokovat buffer pro příchozí potvrzovací packet.
  • Swapování stránek přes NFS nebo NBD — analogické předchozímu případu.
  • Zapisování dat na loopback device — loopback device je speciální blokové zařízení, které je možno nastavit, aby obsahovalo data z nějakého souboru. Na loopback device je možno namountovat filesystém, který je ve skutečnosti souborem na jiném filesystému. Pokud bude systém zapisovat špinavé buffery nebo stránky na loopback device ve snaze se špinavých bufferů nebo stránek zbavit a uvolnit paměť, ve skutečnosti tím bude produkovat ještě větší množství špinavých stránek na hostitelském filesystému, který obsahuje soubor s daty pro loopback device. Tato snaha může vést až k totálnímu vyčerpání veškeré volné paměti a k zatuhnutí na předchozím případu „zapisování modifikovaných stránek do souboru”.
  • Zapisování dat nacházejících se nad hranicí přímo namapované paměti — tato data není možno zapsat rovnou, ale je k tomu potřeba „bounce buffer” (jak bylo popsáno v kapitole o mapování stránek). Pokud se nenacházejí žádné volné stránky v přímo namapované zóně, dojde k nekonečnému čekání na bounce buffer a k vytuhnutí. Tento problém se vyskytoval v raných jádrech 2.4. V sou­časných jádrech 2.4 a 2.6 se už nevyskytuje — ta mají pro bounce buffery předalokovanou část paměti.

Vzhledem k tomu, že dnes se v typických zátěžích systému používá většina paměti jako cache, k low-memory deadlocku nedochází, neboť čisté cachované stránky je možno vždy uvolnit. Nicméně low-memory deadlock se stále může vyskytnout například při zápisu velikého souboru nebo pokud nějaký program alokuje veškerou paměť a pak swapuje.

Linux 2.0 řešil low-memory deadlock na filesystému tak, že bufferová cache byla schopna znovupoužívat buffery. Pokud v systému nebyla žádná volná stránka a byl vznesen požadavek na čtení bufferu, tak se nějaký buffer uvolnil (v případě, že se žádný čistý buffer nevyskytoval, se nějaký špinavý buffer zapsal) a tento uvolněný buffer se okamžitě použil pro nová data. Žádná paměť se nevracela ani nealokovala ze systému správy paměti. Pokud filesystém k zápisu potřeboval jen buffery a nepotřeboval žádnou další paměť, nemohlo k low-memory deadlocku při zápisu souboru dojít. K low-memory deadlocku mohlo dojít, pokud v systému nebyly žádné buffery nebo pokud filesystém potřeboval více bufferů současně. Linux 2.2 a vyšší znovupoužívání bufferů nemá.

Nejjednodušším způsobem řešení low-memory deadlocku je rezervovat nějaké stránky, které smí použít jen proces odswapovávající nějaká data. Tohle řešení funguje ve většině případů, ale není ideální a k deadlocku může stále dojít. Má následující problémy:

  • Musíme znát horní mez pro množství paměti potřebné k zapsání stránky. To není vůbec triviální zjistit, mohou-li být použity různorodé ovladače filesystému a blokového zařízení.
  • Co když proces uvolňující paměť uvnitř filesystému čeká na zámek, který drží jiný proces. Onen jiný proces nemůže alokovat rezervované stránky a čeká, než bude nějaká paměť uvolněna.
  • Pokud více procesů současně uvolňuje paměť, může i tato rezervovaná paměť dojít. Tento problém se vyskytuje na Linuxu, kde jakýkoli proces nemající paměť může z  alloc_pages zavolat try_to_free_pages, což spustí výše uvedený uvolňovací algoritmus. Na FreeBSD uvolňování paměti provádí jen jeden kernel thread, proto se zde tento problém nevyskytuje.
  • V případě, že jsou stránky zapisovány přes síť, je potřeba nejen odeslat data, ale i přijmout potvrzení od serveru. Alokace paměti pro toto potvrzení je velmi problematická — v době alokace packetového bufferu nevíme, jaký packet do něj bude uložen. Není tedy možno rozhodnout, zda buffer alokovat z rezervované zóny, nebo ne. Proti deadlocku při zapisování dat přes síť nám rezervování paměti nepomůže.
  • Když proces alokuje z rezervované paměti a dělá zápis stránek, může tyto stránky zapisovat na loopback device — zápis tedy způsobí vyrobení spousty nových špinavých stránek a vyčerpání rezervované zóny.

Jediné, co se v současných systémech používá, je rezervace paměti pro swapující proces, ale i to má výše uvedené nevýhody. Nejlépe na tom byl Linux 2.0, neboť ten měl znovupoužívání bufferů, které low-memory deadlocku na lokálním filesystému zabraňovalo.

CS24_early

Problém loopback device se řeší tak, že limit pro špinavé buffery na loopback device je nižší než na ostatních zařízeních. Buffery pro loopback se tedy začnou zapisovat dříve a většinou nezaplní celou paměť. Toto řešení opět není stoprocentní a k vytuhnutí systému při použití loopback device občas opravdu dochází. Téměř určitě dojde k vytuhnutí, pokud přiřadíme loopback device soubor, který se nachází na filesystému na jiném loopback device.

Poučení z této kapitoly zní:

  • Nemapovat veliké soubory pro zápis (možná to rezervace paměti vyřeší, ale 100% jistota to není).
  • Už vůbec to nedělat, pokud jsou tyto soubory na NFS (tady nám opravdu nepomůže vůbec nic a systém vytuhne).
  • Neswapovat do lokálního souboru a už vůbec ne přes NFS. Swapovat na partition.
  • Nepoužívat loopback device pro zápis. V raném Linuxu 2.4 to skutečně často zatuhlo; v novějších jádrech byla pouze snížena pravděpodobnost vytuhnutí, ale problém vyřešen nebyl.

Byl pro vás článek přínosný?