GNU/Linux >> Znalost Linux >  >> Linux

Segmentace paměti Linuxu

Ano, Linux používá stránkování, takže všechny adresy jsou vždy virtuální. (Pro přístup k paměti na známé fyzické adrese udržuje Linux veškerou fyzickou paměť 1:1 mapovanou na rozsah virtuálního adresového prostoru jádra, takže může jednoduše indexovat do tohoto „pole“ pomocí fyzické adresy jako offsetu. Komplikace modulu pro 32 -bitová jádra na systémech s více fyzickou RAM než adresovým prostorem jádra.)

Tento lineární adresní prostor tvořený stránkami je rozdělen do čtyř segmentů

Ne, Linux používá model ploché paměti. Základ a limit pro všechny 4 tyto segmentové deskriptory jsou 0 a -1 (bez omezení). tj. všechny se plně překrývají a pokrývají celý 32bitový virtuální lineární adresní prostor.

Červená část se tedy skládá ze dvou segmentů __KERNEL_CS a __KERNEL_DS

Ne, tady jsi udělal chybu. Registry segmentů x86 nejsou používá se pro segmentaci; jsou to starší zavazadla x86, která se na x86-64 používá pouze pro režim CPU a výběr úrovně oprávnění . Namísto přidání nových mechanismů a úplného vypuštění segmentů pro dlouhý režim, AMD jen kastrovala segmentaci v dlouhém režimu (základ pevně nastavená na 0, jako všichni používali v 32bitovém režimu stejně) a nadále používala segmenty pouze pro účely konfigurace stroje, které nejsou zvláště zajímavé, pokud ve skutečnosti nepíšete kód, který se přepne do 32bitového režimu nebo cokoli jiného.

(Kromě toho, že můžete nastavit nenulový základ pro FS a/nebo GS, a Linux to dělá pro úložiště s vlákny. Ale to nemá nic společného s tím, jak copy_from_user() je implementováno nebo cokoli jiného. Musí pouze zkontrolovat hodnotu ukazatele, nikoli s odkazem na jakýkoli segment nebo CPL / RPL deskriptoru segmentu.)

V 32bitovém legacy režimu je možné napsat jádro, které používá model segmentované paměti, ale žádný z běžných operačních systémů to ve skutečnosti neudělal. Někteří lidé si přejí, aby se z toho stala věc, např. podívejte se na tuto odpověď, která naříká, že x86-64 znemožňuje OS ve stylu Multics. Ale to není jak Linux funguje.

Linux je https://wiki.osdev.org/Higher_Half_Kernel, kde ukazatele jádra mají jeden rozsah hodnot (červená část) a adresy v uživatelském prostoru jsou v zelené části. Jádro může jednoduše dereferencovat adresy v uživatelském prostoru, pokud jsou namapovány správné tabulky stránek v uživatelském prostoru, nemusí je překládat ani nic dělat se segmenty; toto znamená mít model s plochou pamětí . (Jádro může používat položky tabulky stránek "uživatel", ale ne naopak). Konkrétně pro x86-64 naleznete aktuální mapu paměti na https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt.

Jediným důvodem, proč musí být všechny tyto 4 položky GDT oddělené, jsou důvody na úrovni oprávnění a že deskriptory datových segmentů a segmentů kódu mají různé formáty. (Položka GDT obsahuje více než jen základ/limit; to jsou části, které se musí lišit. Viz https://wiki.osdev.org/Global_Descriptor_Table)

A zejména https://wiki.osdev.org/Segmentation#Notes_Regarding_C, která popisuje, jak a proč je GDT obvykle používán „normálním“ operačním systémem k vytvoření plochého paměťového modelu s dvojicí kódových a datových deskriptorů pro každou úroveň oprávnění. .

Pro 32bitové jádro Linuxu pouze gs získává nenulový základ pro místní úložiště vláken (takže režimy adresování jako [gs: 0x10] bude přistupovat k lineární adrese, která závisí na vláknu, které ji provádí). Nebo v 64bitovém jádře (a 64bitovém uživatelském prostoru) Linux používá fs . (Protože x86-64 udělal GS speciální s swapgs instrukce, určená pro použití s ​​syscall aby jádro našlo zásobník jádra.)

Ale každopádně nenulový základ pro FS nebo GS není z položky GDT, je nastaven pomocí wrgsbase návod. (Nebo na CPU, které to nepodporují, se zápisem na MSR).

ale jaké jsou tyto příznaky, konkrétně 0xc09b , 0xa09b a tak dále ? Mám sklon věřit, že jsou to selektoři segmentů

Ne, selektory segmentů jsou indexy do GDT. Jádro definuje GDT jako pole C pomocí syntaxe určeného inicializátoru jako [GDT_ENTRY_KERNEL32_CS] = initializer_for_that_selector .

(Nízké 2 bity selektoru, tj. hodnota segmentového registru, jsou aktuální úrovní oprávnění. Takže GDT_ENTRY_DEFAULT_USER_CS by mělo být `__USER_CS>> 2.)

mov ds, eax spouští hardware indexování GDT, nikoli lineární hledání odpovídajících dat v paměti!

Formát dat GDT:

Díváte se na zdrojový kód x86-64 Linuxu, takže jádro bude v dlouhém režimu, nikoli v chráněném režimu. Můžeme to říct, protože pro USER_CS existují samostatné položky a USER32_CS . Deskriptor segmentu 32bitového kódu bude mít svůj L trochu vyčištěno. Aktuální popis segmentu CS je to, co uvádí CPU x86-64 do 32bitového kompatibilního režimu vs. 64bitového dlouhého režimu. Chcete-li zadat 32bitový uživatelský prostor, iret nebo sysret nastaví CS:RIP na 32bitový segmentový volič uživatelského režimu.

Myslím můžete také mít CPU v 16bitovém režimu compat (jako režim compat, nikoli v reálném režimu, ale výchozí velikost operandu a velikost adresy jsou 16). Linux to však nedělá.

Každopádně, jak je vysvětleno v https://wiki.osdev.org/Global_Descriptor_Table and Segmentation,

Každý deskriptor segmentu obsahuje následující informace:

  • Základní adresa segmentu
  • Výchozí velikost operace v segmentu (16-bit/32-bit)
  • Úroveň oprávnění deskriptoru (Ring 0 -> Ring 3)
  • Zrnitost (limit segmentu je v jednotkách byte/4 kB)
  • Limit segmentu (maximální zákonný posun v rámci segmentu)
  • Přítomnost segmentu (je přítomna nebo ne)
  • Typ deskriptoru (0 =systém; 1 =kód/data)
  • Typ segmentu (Kód/Data/Čtení/Zápis/Přístup/Vyhovující/Nevyhovující/Rozbalit nahoru/Rozbalit dolů)

To jsou ty kousky navíc. Nijak zvlášť mě nezajímá, které bity jsou které, protože (myslím, že rozumím) rozumím na vysoké úrovni tomu, k čemu různé položky GDT jsou a k čemu slouží, aniž bych zacházel do podrobností o tom, jak je to ve skutečnosti kódováno.

Pokud si ale prohlédnete příručky x86 nebo wiki osdev a definice těchto maker init, měli byste zjistit, že výsledkem je záznam GDT s L bit nastavený pro 64bitové kódové segmenty, vymazán pro 32bitové kódové segmenty. A samozřejmě se liší typ (kód vs. data) a úroveň oprávnění.


Odmítnutí odpovědnosti

Zveřejňuji tuto odpověď, abych toto téma vyčistil od jakýchkoli mylných představ (jak poukázal @PeterCordes).

Paging

Správa paměti v Linuxu (x86 chráněný režim) používá stránkování pro mapování fyzických adres na virtualizovaný plochý lineární adresní prostor, od 0x00000000 na 0xFFFFFFFF (na 32bitové verzi), známý jako model s plochou pamětí . Linux spolu s CPU's MMU (Memory Management Unit) bude udržovat každou virtuální a logickou adresu mapovanou 1:1 na odpovídající fyzickou adresu. Fyzická paměť je obvykle rozdělena na 4KiB stránky, aby bylo možné snáze spravovat paměť.

virtuální adresy jádra může být souvislé jádro logické adresy přímo mapované do souvislých fyzických stránek; ostatní virtuální adresy jádra jsou plně virtuální adresy mapované v nesouvislých fyzických stránkách používaných pro velké alokace vyrovnávací paměti (přesahující souvislou oblast na systémech s malou pamětí) a/nebo paměti PAE (pouze 32bitové). MMIO porty (Memory-Mapped I/O) jsou také mapovány pomocí virtuálních adres jádra.

Každá dereferencovaná adresa musí být virtuální adresou. Buď se jedná o logickou nebo plně virtuální adresu, fyzické porty RAM a MMIO jsou před použitím mapovány ve virtuálním adresním prostoru.

Jádro získává část virtuální paměti pomocí kmalloc() , ukazuje virtuální adresa, ale co je důležitější, to je také logická adresa jádra, což znamená, že má přímé mapování na souvislé fyzické stránky (tedy vhodné pro DMA). Na druhé straně vmalloc() rutina vrátí část úplně virtuální paměť označená virtuální adresou, ale pouze souvislá ve virtuálním adresovém prostoru a mapovaná na nesouvislé fyzické stránky.

Logické adresy jádra používají pevné mapování mezi fyzickým a virtuálním adresním prostorem. To znamená, že prakticky sousedící oblasti jsou přirozeně také fyzicky sousedící. To není případ plně virtuálních adres, které ukazují na nesouvislé fyzické stránky.

Virtuální adresy uživatele - na rozdíl od logických adres jádra - nepoužívejte pevné mapování mezi virtuálními a fyzickými adresami, uživatelské procesy plně využívají MMU:

  • Mapují se pouze použité části fyzické paměti;
  • Paměť není spojitá;
  • Paměť může být vyměněna;
  • Paměť lze přesouvat;

Podrobněji, stránky fyzické paměti o velikosti 4KiB jsou mapovány na virtuální adresy v tabulce stránek OS, přičemž každé mapování je známé jako PTE (Page Table Entry). MMU CPU pak uchová mezipaměť každého nedávno použitého PTE z tabulky stránek OS. Tato oblast mezipaměti je známá jako TLB (Translation Lookaside Buffer). cr3 registr se používá k vyhledání tabulky stránek OS.

Kdykoli je potřeba přeložit virtuální adresu na fyzickou, prohledá se TLB. Pokud je nalezena shoda (TLB hit ), fyzická adresa je vrácena a zpřístupněna. Pokud však nedojde k žádné shodě (TLB miss ), obslužný program chyb TLB vyhledá tabulku stránek, aby zjistil, zda existuje mapování (stránka procházet ). Pokud nějaká existuje, je zapsána zpět do TLB a chybující instrukce je restartována, tento následný překlad pak najde zásah TLB a přístup do paměti bude pokračovat. Toto je známé jako nezletilý chyba stránky.

Někdy může operační systém potřebovat zvětšit velikost fyzické paměti RAM přesunutím stránek na pevný disk. Pokud se virtuální adresa přeloží na stránku namapovanou na pevném disku, musí se stránka načíst do fyzické paměti RAM, než k ní přistoupíte. Toto je známé jako hlavní chyba stránky. Obslužný program chyb stránky OS pak bude muset najít volnou stránku v paměti.

Proces překladu může selhat, pokud pro virtuální adresu není k dispozici žádné mapování, což znamená, že virtuální adresa je neplatná. Toto je známé jako neplatné výjimka chyby stránky a segfault bude procesu vydána obslužnou rutinou chyb stránky OS.

Segmentace paměti

Reálný režim

Reálný režim stále používá 20bitový segmentovaný adresní prostor paměti s 1MiB adresovatelné paměti (0x00000 - 0xFFFFF ) a neomezený přímý softwarový přístup ke veškeré adresovatelné paměti, adresám sběrnice, portům PMIO (Port-Mapped I/O) a perifernímu hardwaru. Skutečný režim neposkytuje žádnou ochranu paměti , žádné úrovně oprávnění a žádné virtualizované adresy. Registr segmentu obvykle obsahuje hodnotu selektoru segmentu a operand paměti je hodnota offsetu vzhledem k bázi segmentu.

Aby se obešly segmentace (překladače jazyka C obvykle podporují pouze model ploché paměti), kompilátory jazyka C používaly neoficiální far typ ukazatele reprezentující fyzickou adresu s segment:offset logický zápis adresy. Například logická adresa 0x5555:0x0005 , po výpočtu 0x5555 * 16 + 0x0005 poskytuje 20bitovou fyzickou adresu 0x55555 , použitelný ve vzdáleném ukazateli, jak je znázorněno níže:

char far    *ptr;           /* declare a far pointer */
ptr = (char far *)0x55555;  /* initialize a far pointer */

K dnešnímu dni se většina moderních x86 CPU stále spouští v reálném režimu pro zpětnou kompatibilitu a poté se přepne do chráněného režimu.

Chráněný režim

V chráněném režimu s modelem s plochou pamětí , segmentace je nepoužitá . Čtyři segmenty, konkrétně __KERNEL_CS , __KERNEL_DS , __USER_CS , __USER_DS všechny mají své základní adresy nastaveny na 0. Tyto segmenty jsou jen starším zavazadlem z dřívějšího modelu x86, kde se používala segmentovaná správa paměti. V chráněném režimu, protože všechny základní adresy segmentů jsou nastaveny na 0, jsou logické adresy ekvivalentní na lineární adresy.

Chráněný režim s modelem ploché paměti znamená žádnou segmentaci. Jedinou výjimkou, kdy má segment svou základní adresu nastavenou na hodnotu jinou než 0, je případ, kdy se jedná o místní úložiště podprocesu. FS (a GS na 64bitové) segmentové registry se používají pro tento účel.

Registry segmentů, například SS (registr segmentů zásobníku), DS (registr datových segmentů) nebo CS (registr segmentu kódu) jsou stále přítomny a slouží k uložení selektorů 16bitových segmentů , které obsahují indexy deskriptorů segmentů v LDT a GDT (Local &Global Descriptor Table).

Každá instrukce, která se dotýká paměti implicitně používá segmentový registr. V závislosti na kontextu se používá konkrétní segmentový registr. Například JMP instrukce používá CS zatímco PUSH používá SS . Selektory lze načíst do registrů pomocí instrukcí jako MOV , jedinou výjimkou je CS registru, který je upravován pouze pokyny ovlivňujícími tok provádění , například CALL nebo JMP .

CS Registr je zvláště užitečný, protože sleduje CPL (Current Privilege Level) ve svém segmentovém selektoru, čímž zachovává úroveň oprávnění pro aktuální segment. Tato 2bitová hodnota CPL je vždy ekvivalentní aktuální úrovni oprávnění CPU.

Ochrana paměti

Paging

Úroveň oprávnění CPU, známá také jako bit režimu nebo ochranný kroužek , od 0 do 3, omezuje některé instrukce, které mohou rozvrátit ochranný mechanismus nebo způsobit chaos, pokud jsou povoleny v uživatelském režimu, takže jsou vyhrazeny pro jádro. Pokus o jejich spuštění mimo kruh 0 způsobí obecnou ochranu výjimka chyby, stejný scénář, když dojde k chybě přístupu k neplatnému segmentu (privilegium, typ, limit, práva pro čtení/zápis). Podobně je jakýkoli přístup do paměti a MMIO zařízení omezen na základě úrovně oprávnění a každý pokus o přístup na chráněnou stránku bez požadované úrovně oprávnění způsobí výjimku chyby stránky.

Bit režimu se automaticky přepne z uživatelského režimu do režimu správce při každém žádosti o přerušení (IRQ), buď software (např. syscall ) nebo hardwaru.

Na 32bitovém systému lze efektivně adresovat pouze 4GiB paměti a paměť je rozdělena ve formě 3GiB/1GiB. Linux (s povoleným stránkováním) používá schéma ochrany známé jako jádro vyšší poloviny kde je plochý adresní prostor rozdělen do dvou rozsahů virtuálních adres:

  • Adresy v rozsahu 0xC0000000 - 0xFFFFFFFF jsou virtuální adresy jádra (červená oblast). Rozsah 896 MiB 0xC0000000 - 0xF7FFFFFF přímo mapuje logické adresy jádra 1:1 s fyzickými adresami jádra do souvislé nízkopaměti stránky (pomocí __pa() a __va() makra). Zbývajících 128MiB rozsah 0xF8000000 - 0xFFFFFFFF se pak používá k mapování virtuálních adres pro velké alokace vyrovnávací paměti, MMIO portů (Memory-Mapped I/O) a/nebo paměti PAE na nesouvislou vysokou paměť stránky (pomocí ioremap() a iounmap() ).

  • Adresy v rozsahu 0x00000000 - 0xBFFFFFFF jsou virtuální adresy uživatele (zelená plocha), kde se nachází uživatelský kód, data a knihovny. Mapování může být v nesouvislých stránkách s nízkou a velkou pamětí.

Vysoká paměť je přítomna pouze na 32bitových systémech. Veškerá paměť přidělená kmalloc()logiku virtuální adresa (s přímým fyzickým mapováním); paměť přidělená vmalloc()plné virtuální adresa (ale žádné přímé fyzické mapování). 64bitové systémy mají obrovskou schopnost adresování, a proto nepotřebují velkou paměť, protože každá stránka fyzické paměti RAM může být efektivně adresována.

hranice adresa mezi horní polovinou supervizora a dolní polovinou uživatele je známá jako TASK_SIZE_MAX v linuxovém jádře. Jádro zkontroluje, zda každá virtuální adresa, ke které se přistupuje z jakéhokoli uživatelského procesu, se nachází pod touto hranicí, jak je vidět v kódu níže:

static int fault_in_kernel_space(unsigned long address)
{
    /*
     * On 64-bit systems, the vsyscall page is at an address above
     * TASK_SIZE_MAX, but is not considered part of the kernel
     * address space.
     */
    if (IS_ENABLED(CONFIG_X86_64) && is_vsyscall_vaddr(address))
        return false;

    return address >= TASK_SIZE_MAX;
}

Pokud se uživatelský proces pokusí o přístup k adrese paměti vyšší než TASK_SIZE_MAX , do_kern_addr_fault() rutina zavolá __bad_area_nosemaphore() rutina, případně signalizující chybnou úlohu pomocí SIGSEGV (pomocí get_current() získat task_struct ):

/*
 * To avoid leaking information about the kernel page table
 * layout, pretend that user-mode accesses to kernel addresses
 * are always protection faults.
 */
if (address >= TASK_SIZE_MAX)
    error_code |= X86_PF_PROT;

force_sig_fault(SIGSEGV, si_code, (void __user *)address, tsk); /* Kill the process */

Stránky mají také privilegovaný kousek, známý jako U příznak ser/supervisor, který se kromě R používá pro SMAP (prevence přístupu v režimu supervizora) Příznak ead/Write, který používá SMEP (Supervisor Mode Execution Prevention).

Segmentace

Starší architektury využívající segmentaci obvykle provádějí ověření přístupu k segmentu pomocí bitu oprávnění GDT pro každý požadovaný segment. Bit oprávnění požadovaného segmentu, známý jako DPL (Descriptor Privilege Level), je porovnán s CPL aktuálního segmentu, což zajišťuje, že CPL <= DPL . Pokud je true, přístup do paměti je pak povolen k požadovanému segmentu.


Linux
  1. Linux – režim Raw klávesnice?

  2. Linux – Kernel IP Forwarding?

  3. Linux – Co je velká a nízká paměť v Linuxu?

  1. Využití paměti Linuxu

  2. Linux – poskvrněné jádro v Linuxu?

  3. Linuxová neaktivní paměť

  1. Linuxové jádro:5 nejlepších inovací

  2. Životní cyklus testování linuxového jádra

  3. Režim jednoho uživatele v Linux/CentOS/Redhat Enterprise Linux | Linux pro jednoho uživatele