Používá se prostor jádra, když se jádro spouští jménem uživatelského programu, tj. systémové volání? Nebo je to adresový prostor pro všechna vlákna jádra (například plánovač)?
Ano a ano.
Než půjdeme dále, měli bychom říci toto o paměti.
Získání paměti je rozděleno do dvou odlišných oblastí:
- Uživatelský prostor , což je sada míst, kde běží běžné uživatelské procesy (tedy vše kromě jádra). Úlohou jádra je řídit aplikace běžící v tomto prostoru tak, aby si nepletly mezi sebou a se strojem.
- Prostor jádra , což je umístění, kde je uložen kód jádra a spouští se pod.
Procesy běžící v uživatelském prostoru mají přístup pouze k omezené části paměti, zatímco jádro má přístup k celé paměti. Procesy běžící v uživatelském prostoru také nedělají mít přístup do prostoru jádra. Procesy v uživatelském prostoru mohou přistupovat pouze k malé části jádra přes rozhraní vystavené jádrem - systémová volání . Pokud proces provede systémové volání, softwarové přerušení je odesláno do jádra, které poté odešle příslušný obslužný program přerušení a pokračuje ve své práci poté, co obsluha skončí.
Kód prostoru jádra má vlastnost spouštět se v "režimu jádra", což (ve vašem typickém stolním počítači -x86-) je to, co nazýváte kód, který se spouští pod kruhem 0 . V architektuře x86 jsou obvykle 4 ochranné kruhy . Ring 0 (režim jádra), Ring 1 (mohou být používány hypervizory virtuálních strojů nebo ovladače), Ring 2 (mohou být používány ovladači, i když si tím nejsem tak jistý). Ring 3 je to, pod čím běží typické aplikace. Je to nejméně privilegovaný kruh a aplikace na něm běžící mají přístup k podmnožině instrukcí procesoru. Ring 0 (prostor jádra) je nejprivilegovanější kruh a má přístup ke všem instrukcím stroje. Například "prostá" aplikace (jako prohlížeč) nemůže používat x86 pokyny pro sestavení lgdt
pro načtení tabulky globálních deskriptorů nebo hlt
k zastavení procesoru.
Pokud je to první, znamená to, že běžný uživatelský program nemůže mít více než 3GB paměti (pokud je dělení 3GB + 1GB)? Také, jak v takovém případě může jádro používat vysokou paměť, protože na jakou adresu virtuální paměti budou mapovány stránky z velké paměti, protože bude logicky mapováno 1 GB prostoru jádra?
Odpověď na to najdete ve skvělé odpovědi od wag zde
CPU rings jsou nejjasnějším rozdílem
V chráněném režimu x86 je CPU vždy v jednom ze 4 kruhů. Linuxové jádro používá pouze 0 a 3:
- 0 pro jádro
- 3 pro uživatele
Toto je nejtvrdší a nejrychlejší definice kernel vs userland.
Proč Linux nepoužívá kruhy 1 a 2:https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used
Jak se určuje aktuální vyzvánění?
Aktuální vyzvánění je vybráno kombinací:
-
globální tabulka deskriptorů:tabulka položek GDT uložená v paměti a každá položka má pole
Privl
který zakóduje prsten.Instrukce LGDT nastaví adresu podle aktuální tabulky deskriptorů.
Viz také:http://wiki.osdev.org/Global_Descriptor_Table
-
segment registruje CS, DS atd., které ukazují na index položky v GDT.
Například
CS = 0
znamená, že první záznam GDT je aktuálně aktivní pro spouštěcí kód.
Co každý prsten umí?
Čip CPU je fyzicky postaven tak, že:
-
prsten 0 může dělat cokoliv
-
ring 3 nemůže spustit několik instrukcí a zapisovat do několika registrů, zejména:
-
nemůže změnit svůj vlastní prsten! V opačném případě by se mohl nastavit na vyzvánění 0 a vyzvánění by bylo zbytečné.
Jinými slovy, nemůže upravit aktuální deskriptor segmentu, který určuje aktuální kruh.
-
nelze upravit tabulky stránek:https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
Jinými slovy, nemůže upravit registr CR3 a samotné stránkování brání úpravě tabulek stránek.
To zabraňuje jednomu procesu vidět paměť ostatních procesů z důvodu bezpečnosti / snadnosti programování.
-
nelze zaregistrovat obsluhu přerušení. Ty se konfigurují zápisem do paměťových míst, čemuž také brání stránkování.
Obslužné rutiny běží v kruhu 0 a narušily by model zabezpečení.
Jinými slovy, nelze použít instrukce LGDT a LIDT.
-
neumí provádět IO instrukce jako
in
aout
, a tedy mít libovolný přístup k hardwaru.Jinak by byla například oprávnění k souborům k ničemu, pokud by jakýkoli program mohl přímo číst z disku.
Přesněji díky Michaelu Petchovi:ve skutečnosti je možné, aby OS povolil IO instrukce na kruhu 3, to je ve skutečnosti řízeno segmentem stavu úloh.
Není možné, aby si prsten 3 dal svolení, aby tak učinil, pokud jej původně neměl.
Linux to vždy zakazuje. Viz také:https://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss
-
Jak programy a operační systémy přecházejí mezi kruhy?
-
když je CPU zapnuté, spustí se počáteční program v kruhu 0 (dobře, ale je to dobrá aproximace). Tento počáteční program můžete považovat za jádro (ale normálně je to zavaděč, který pak volá jádro stále v kruhu 0).
-
když uživatelský proces chce, aby pro něj jádro udělalo něco, jako je zápis do souboru, použije instrukci, která vygeneruje přerušení, jako je
int 0x80
nebosyscall
signalizovat jádru. Příklad x86-64 Linux syscall hello world:.data hello_world: .ascii "hello world\n" hello_world_len = . - hello_world .text .global _start _start: /* write */ mov $1, %rax mov $1, %rdi mov $hello_world, %rsi mov $hello_world_len, %rdx syscall /* exit */ mov $60, %rax mov $0, %rdi syscall
zkompilujte a spusťte:
as -o hello_world.o hello_world.S ld -o hello_world.out hello_world.o ./hello_world.out
GitHub upstream.
Když k tomu dojde, CPU zavolá obsluhu zpětného volání přerušení, kterou jádro zaregistrovalo při spouštění. Zde je konkrétní příklad baremetalu, který registruje handler a používá jej.
Tento obslužný program běží v kruhu 0, který rozhoduje, zda jádro tuto akci povolí, provede akci a restartuje program userland v kruhu 3. x86_64
-
když
exec
používá se systémové volání (nebo když se jádro spustí/init
), jádro připraví registry a paměť nového uživatelského procesu, poté skočí na vstupní bod a přepne CPU na kruh 3 -
Pokud se program pokusí udělat něco nezbedného, jako je zápis do zakázaného registru nebo adresy paměti (kvůli stránkování), CPU také zavolá nějakou obsluhu zpětného volání jádra v kruhu 0.
Ale protože uživatelská země byla nezbedná, jádro by tentokrát mohlo proces zabít nebo ho varovat signálem.
-
Když se jádro zavede, nastaví hardwarové hodiny s určitou pevnou frekvencí, která periodicky generuje přerušení.
Tyto hardwarové hodiny generují přerušení, která spouští kruh 0, a umožňují mu naplánovat, které uživatelské procesy se mají probudit.
Tímto způsobem může k plánování dojít, i když procesy neprovádějí žádná systémová volání.
Jaký smysl má mít více zazvonění?
Oddělení jádra a uživatelské oblasti má dvě hlavní výhody:
- je jednodušší vytvářet programy, protože máte větší jistotu, že jeden nebude rušit druhý. Jeden uživatelský proces se například nemusí starat o přepsání paměti jiného programu kvůli stránkování ani o uvedení hardwaru do neplatného stavu pro jiný proces.
- je to bezpečnější. Např. oprávnění k souborům a oddělení paměti by mohlo zabránit hackerské aplikaci ve čtení vašich bankovních dat. To samozřejmě předpokládá, že důvěřujete jádru.
Jak si s tím pohrát?
Vytvořil jsem nastavení holého kovu, které by mělo být dobrým způsobem, jak přímo manipulovat s prsteny:https://github.com/cirosantilli/x86-bare-metal-examples
Bohužel jsem neměl trpělivost vytvořit příklad uživatelské země, ale šel jsem až k nastavení stránkování, takže uživatelská země by měla být proveditelná. Rád bych viděl žádost o stažení.
Alternativně moduly linuxového jádra běží v kruhu 0, takže je můžete použít k vyzkoušení privilegovaných operací, např. přečtěte si řídicí registry:https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers-cr0-cr2-cr3-from-a-program-getting-segmenta/7419306#7419306
Zde je pohodlné nastavení QEMU + Buildroot, abyste si to vyzkoušeli, aniž byste zabili svého hostitele.
Nevýhodou jaderných modulů je, že jsou spuštěna jiná kthreads a mohla by narušit vaše experimenty. Ale teoreticky můžete převzít všechny obsluhy přerušení se svým modulem jádra a vlastnit systém, to by byl vlastně zajímavý projekt.
Negativní kroužky
I když se v příručce Intel ve skutečnosti nezmiňují záporné prstence, ve skutečnosti existují režimy CPU, které mají další možnosti než samotný prstenec 0, a proto se dobře hodí pro název "negativní prstenec".
Jedním z příkladů je režim hypervizoru používaný ve virtualizaci.
Další podrobnosti viz:
- https://security.stackexchange.com/questions/129098/what-is-protection-ring-1
- https://security.stackexchange.com/questions/216527/ring-3-exploits-and-existence-of-other-rings
ARM
V ARM se prsteny místo toho nazývají úrovně výjimek, ale hlavní myšlenky zůstávají stejné.
V ARMv8 existují 4 úrovně výjimek, běžně používané jako:
-
EL0:uživatelská země
-
EL1:kernel ("supervisor" v terminologii ARM).
Zadáno pomocí
svc
instrukce (SuperVisor Call), dříve známá jakoswi
před sjednoceným sestavením, což je instrukce používaná k provádění systémových volání Linuxu. Dobrý den, světe příklad ARMv8:ahoj. S
.text .global _start _start: /* write */ mov x0, 1 ldr x1, =msg ldr x2, =len mov x8, 64 svc 0 /* exit */ mov x0, 0 mov x8, 93 svc 0 msg: .ascii "hello syscall v8\n" len = . - msg
GitHub upstream.
Vyzkoušejte to pomocí QEMU na Ubuntu 16.04:
sudo apt-get install qemu-user gcc-arm-linux-gnueabihf arm-linux-gnueabihf-as -o hello.o hello.S arm-linux-gnueabihf-ld -o hello hello.o qemu-arm hello
Zde je konkrétní příklad baremetalu, který zaregistruje obslužnou rutinu SVC a provede volání SVC.
-
EL2:hypervizory, například Xen.
Zadáno pomocí
hvc
instrukce (HyperVisor Call).Hypervizor je pro OS, co je OS pro uživatelskou zemi.
Xen vám například umožňuje provozovat více operačních systémů, jako je Linux nebo Windows, na stejném systému současně a izoluje od sebe operační systémy pro zabezpečení a snadné ladění, stejně jako Linux pro uživatelské programy.
Hypervizory jsou klíčovou součástí dnešní cloudové infrastruktury:umožňují provoz více serverů na jednom hardwaru, udržují využití hardwaru vždy blízko 100 % a šetří spoustu peněz.
AWS například používal Xen až do roku 2017, kdy jeho přechod na KVM přinesl novinky.
-
EL3:další úroveň. Příklad TODO.
Zadáno pomocí
smc
instrukce (volání v zabezpečeném režimu)
Referenční model architektury ARMv8 DDI 0487C.a – Kapitola D1 – Model programátorské úrovně systému AArch64 – Obrázek D1-1 to krásně ilustruje:
Situace ARM se trochu změnila s příchodem ARMv8.1 Virtualization Host Extensions (VHE). Toto rozšíření umožňuje jádru efektivně běžet v EL2:
VHE bylo vytvořeno, protože virtualizační řešení v jádře Linuxu, jako je KVM, získala půdu nad Xen (viz například výše zmíněný přechod AWS na KVM), protože většina klientů potřebuje pouze virtuální počítače s Linuxem, a jak si dokážete představit, vše v jednom KVM je jednodušší a potenciálně efektivnější než Xen. Nyní tedy hostitelské linuxové jádro v těchto případech funguje jako hypervizor.
Všimněte si, že ARM, možná díky výhodě zpětného pohledu, má lepší konvenci pojmenování pro úrovně oprávnění než x86, aniž by bylo nutné záporné úrovně:0 je nižší a 3 nejvyšší. Vyšší úrovně bývají vytvářeny častěji než nižší.
Aktuální EL lze dotazovat pomocí MRS
instrukce:https://stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etc
ARM nevyžaduje, aby byly přítomny všechny úrovně výjimek, aby umožnily implementace, které nepotřebují tuto funkci pro úsporu oblasti čipu. ARMv8 "Úrovně výjimek" říká:
Implementace nemusí zahrnovat všechny úrovně výjimek. Všechny implementace musí zahrnovat EL0 a EL1. EL2 a EL3 jsou volitelné.
QEMU je například výchozí na EL1, ale EL2 a EL3 lze povolit pomocí možností příkazového řádku:https://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulating-a53-power-up
Fragmenty kódu testované na Ubuntu 18.10.
Pokud je to první, znamená to, že běžný uživatelský program nemůže mít více než 3 GB paměti (pokud je dělení 3 GB + 1 GB)?
Ano, to je případ normálního linuxového systému. V jednom bodě se vznášela sada patchů "4G/4G", díky nimž byl uživatel a adresní prostory jádra zcela nezávislé (za cenu výkonu, protože jádru znesnadňovaly přístup k uživatelské paměti), ale nemyslím si, že byly vždy sloučeny proti proudu a zájem opadl s nárůstem x86-64
Také, jak v takovém případě může jádro používat vysokou paměť, protože na jakou adresu virtuální paměti budou mapovány stránky z velké paměti, protože bude logicky mapováno 1 GB prostoru jádra?
Způsob, jakým linux fungoval (a stále funguje na systémech, kde je paměť malá ve srovnání s adresním prostorem), byl takový, že celá fyzická paměť byla trvale mapována do části jádra adresního prostoru. To umožnilo jádru přístup k veškeré fyzické paměti bez přemapování, ale zjevně se neškáluje na 32bitové stroje se spoustou fyzické paměti.
Tak se zrodil koncept nízké a vysoké paměti. "nízká" paměť je trvale mapována do adresového prostoru jádra. "vysoká" paměť není.
Když procesor spouští systémové volání, běží v režimu jádra, ale stále v kontextu aktuálního procesu. Může tedy přímo přistupovat jak k adresnímu prostoru jádra, tak k uživatelskému adresnímu prostoru aktuálního procesu (za předpokladu, že nepoužíváte výše uvedené záplaty 4G/4G). To znamená, že není problém, aby byla "vysoká" paměť přidělena uživatelskému procesu.
Použití "vysoké" paměti pro účely jádra je spíše problém. Pro přístup k velké paměti, která není namapována na aktuální proces, musí být dočasně namapována do adresového prostoru jádra. To znamená extra kód a penalizaci za výkon.