GNU/Linux >> Znalost Linux >  >> Linux

Linux mremap bez uvolnění starého mapování?

Je to možné, i když existují problémy s konzistencí mezipaměti specifické pro architekturu, které možná budete muset zvážit. Některé architektury jednoduše neumožňují přístup na stejnou stránku z více virtuálních adres současně bez ztráty koherence. Takže některé architektury to zvládnou, jiné ne.

Upraveno přidáním:AMD64 Architecture Programmer's Manual sv. 2, Programování systému, část 7.8.7 Změna typu paměti, uvádí:

Fyzická stránka by neměla mít různé typy cacheability přiřazené k ní prostřednictvím různých virtuálních mapování; měly by být buď všechny typu cacheable (WB, WT, WP), nebo všechny typu non-cacheable (UC, WC, CD). V opačném případě to může vést ke ztrátě koherence mezipaměti, což vede k zastaralým datům a nepředvídatelnému chování.

Na AMD64 by tedy mělo být bezpečné mmap() znovu stejný soubor nebo oblast sdílené paměti, pokud je stejný prot a flags Jsou používány; mělo by to způsobit, že jádro použije stejný typ cacheable pro každé z mapování.

Prvním krokem je vždy použít pro paměťové mapy zálohu souboru. Použijte mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0) aby mapování nerezervovala swap. (Pokud na to zapomenete, narazíte na limity pro swap mnohem dříve, než narazíte na skutečné limity pro mnoho úloh.) Dodatečná režie způsobená zálohováním souborů je naprosto zanedbatelná.

Upraveno pro přidání:Uživatel strcmp poukázal na to, že současná jádra neaplikují na adresy randomizaci adresního prostoru. Naštěstí to lze snadno opravit, stačí zadat náhodně vygenerované adresy do mmap() místo NULL . Na x86-64 je uživatelský adresní prostor 47bitový a adresa by měla být zarovnána podle stránky; můžete použít např. Xorshift* pro vygenerování adres a poté maskování nežádoucích bitů:& 0x00007FFFFE00000 by například poskytlo 2097152 bajtů zarovnaných 47bitových adres.

Protože záloha je do souboru, můžete vytvořit druhé mapování na stejný soubor po zvětšení záložního souboru pomocí ftruncate() . Teprve po vhodné dodatečné lhůtě – když víte, že žádné vlákno již mapování nepoužívá (možná použít atomové počítadlo, abyste to sledovali?) – původní mapování zrušíte.

V praxi, když je potřeba zvětšit mapování, nejprve zvětšíte podkladový soubor a poté zkuste mremap(mapping, oldsize, newsize, 0) abyste zjistili, zda lze mapování zvětšit, aniž byste mapování posunuli. Pouze pokud přemapování na místě selže, musíte přejít na nové mapování.

Upraveno pro přidání:Určitě chcete použít mremap() místo pouhého použití mmap() a MAP_FIXED vytvořit větší mapování, protože mmap() odmapuje (atomicky) všechna existující mapování, včetně těch, která patří do jiných souborů nebo oblastí sdílené paměti. S mremap() , zobrazí se chyba, pokud by se zvětšené mapování překrývalo s existujícím mapováním; s mmap() a MAP_FIXED , všechna existující mapování, která nové mapování překrývá, jsou ignorována (nemapována).

Bohužel musím přiznat, že jsem si neověřil, jestli jádro detekuje kolize mezi existujícími mapováními, nebo jestli jen předpokládá, že programátor o takových kolizích ví -- programátor přece musí znát adresu a délku každého mapování, a proto by měl vědět, zda by mapování kolidovalo s jiným existujícím mapováním. Upraveno pro přidání:Jádra řady 3.8 ano, vrací MAP_FAILED s errno==ENOMEM pokud by zvětšené mapování kolidovalo s existujícími mapami. Očekávám, že se všechna linuxová jádra budou chovat stejně, ale nemám žádný důkaz, kromě testování na 3.8.0-30-generic na x86_64.

Všimněte si také, že v Linuxu je sdílená paměť POSIX implementována pomocí speciálního souborového systému, typicky tmpfs připojeného na /dev/shm (nebo /run/shm s /dev/shm jako symbolický odkaz). shm_open() et. al jsou implementovány knihovnou C. Místo toho, abych měl velkou kapacitu sdílené paměti POSIX, osobně bych použil speciálně namontovaný tmpfs pro použití ve vlastní aplikaci. Pokud ne kvůli ničemu jinému, bezpečnostní kontroly (uživatelé a skupiny tam mohou vytvářet nové "soubory") jsou mnohem jednodušší a přehlednější.

Pokud mapování je a musí být anonymní, stále můžete použít mremap(mapping, oldsize, newsize, 0) vyzkoušet a změnit jeho velikost; může to prostě selhat.

I se stovkami tisíc mapování je 64bitový adresní prostor obrovský a případ selhání je vzácný. I když tedy musíte řešit i případ selhání, nemusí to být nutně rychlé . Upraveno za účelem úpravy:Na x86-64 je adresní prostor 47bitový a mapování musí začínat na hranici stránky (12 bitů pro normální stránky, 21 bitů pro 2 miliony velkých stránek a 30 bitů pro velké stránky 1G), takže existuje pouze 35, 26 nebo 17 bitů dostupných v adresovém prostoru pro mapování. Kolize jsou tedy častější, i když jsou navrženy náhodné adresy. (U 2M mapování se občas vyskytla kolize 1024 map, ale u 65536 map byla pravděpodobnost kolize (selhání změny velikosti) asi 2,3 %.)

Upraveno k přidání:Uživatel strcmp v komentáři poukázal na to, že ve výchozím nastavení Linux mmap() vrátí po sobě jdoucí adresy, v takovém případě se rozšiřování mapování vždy nezdaří, pokud nejde o poslední nebo pokud mapa nebyla právě zde zmapována.

Přístup, o kterém vím, že funguje v Linuxu, je komplikovaný a velmi specifický pro architekturu. Můžete přemapovat původní mapování pouze pro čtení, vytvořit novou anonymní mapu a zkopírovat tam starý obsah. Potřebujete SIGSEGV handler (SIGSEGV je aktivován signál pro konkrétní vlákno, které se pokouší zapisovat do mapování nyní pouze pro čtení, toto je jedno z mála obnovitelných SIGSEGV situace v Linuxu, i když POSIX nesouhlasí), který prozkoumá instrukci, která problém způsobila, simuluje ji (místo toho upraví obsah nového mapování) a poté problematickou instrukci přeskočí. Po uplynutí dodatečné lhůty, kdy již nebudou mít žádná vlákna přístup ke starému mapování, které je nyní pouze pro čtení, můžete mapování zrušit.

Všechny ohavnosti jsou v SIGSEGV manipulátor, samozřejmě. Nejen, že musí umět dekódovat všechny strojové instrukce a simulovat je (nebo alespoň ty, které zapisují do paměti), ale musí také zaneprázdnit-čekat, pokud nové mapování ještě nebylo kompletně zkopírováno. Je to komplikované, absolutně nepřenosné a velmi specifické pro architekturu... ale možné.


Toto bylo přidáno do jádra 5.7 jako nový příznak do mremap(2) nazvaný MREMAP_DONTUNMAP. Po přesunutí položek tabulky stránek tak zůstane stávající mapování na místě.

Viz https://github.com/torvalds/linux/commit/e346b3813067d4b17383f975f197a9aa28a3b077#diff-14bbdb979be70309bb5e7818efccacc8


Ano, můžete to udělat.

mremap(old_address, old_size, new_size, flags) smaže staré mapování pouze velikosti "stará_velikost". Pokud tedy předáte 0 jako "stará_velikost", nezruší se vůbec nic.

Upozornění:toto funguje podle očekávání pouze u sdílených mapování, takže taková mremap() by měla být použita v oblasti dříve namapované pomocí MAP_SHARED. To je vlastně vše, to znamená, že ani nepotřebujete mapování zálohované souborem, můžete jej úspěšně použít "MAP_SHARED | MAP_ANONYMOUS" kombinace pro příznaky mmap(). Některé velmi staré operační systémy nemusí podporovat "MAP_SHARED | MAP_ANONYMOUS", ale na linuxu jste v bezpečí.

Pokud to zkusíte v oblasti MAP_PRIVATE, výsledek by byl zhruba podobný memcpy(), tj. nevytvoří se žádný alias paměti. Ale stále bude používat stroj CoW. Z vaší úvodní otázky není jasné, zda potřebujete alias, nebo je kopie CoW v pořádku.

AKTUALIZACE:Aby to fungovalo, musíte také samozřejmě zadat příznak MREMAP_MAYMOVE.


Linux
  1. Jak změnit název hostitele vašeho počítače se systémem Linux bez nutnosti restartování

  2. Vyhledejte příkaz v Linuxu

  3. Je možné zavést linuxové jádro bez vytvoření obrazu initrd?

  1. Zvládněte příkaz Linux ls

  2. Extrahujte sériové číslo Linuxu bez sudo

  3. Mohu spustit binární soubor Linuxu, aniž by byl nastaven bit oprávnění ke spuštění?

  1. Číst a zapisovat do stejného souboru v Linuxu bez přepisování?

  2. Linux – Proč je nový PID menší než starý?

  3. Linux – Lze schopnosti používat ve skriptech, aniž by bylo nutné nastavit caping the Interpreter Binary?