Czy przestrzeń jądra jest używana, gdy jądro wykonuje się w imieniu programu użytkownika, tj. Wywołania systemowego? Czy jest to przestrzeń adresowa dla wszystkich wątków jądra (na przykład program planujący)?
Tak i tak.
Zanim przejdziemy dalej, powinniśmy powiedzieć o pamięci.
Get pamięci jest podzielony na dwa odrębne obszary:
- Przestrzeń użytkownika , która jest zbiorem lokalizacji, w których działają normalne procesy użytkownika (tj. Wszystko inne niż jądro). Rola jądra polega na zarządzaniu aplikacjami działającymi w tym obszarze przed niechlujnością między sobą i maszyną.
- Przestrzeń jądra , czyli miejsce , w którym przechowywany jest kod jądra, i wykonuje się pod nim.
Procesy działające w przestrzeni użytkownika mają dostęp tylko do ograniczonej części pamięci, podczas gdy jądro ma dostęp do całej pamięci. Procesy działające w przestrzeni użytkownika również nie mają dostępu do przestrzeni jądra. Procesy przestrzeni użytkownika mogą uzyskać dostęp tylko do niewielkiej części jądra za pośrednictwem interfejsu odsłoniętego przez jądro - wywołania systemowe . Jeśli proces wykonuje wywołanie systemowe, do jądra wysyłane jest przerwanie programowe, które następnie wysyła odpowiednią procedurę obsługi przerwań i kontynuuje pracę po zakończeniu procedury obsługi.
Kod przestrzeni jądra ma właściwość do uruchomienia w „trybie jądra”, który (na typowym komputerze stacjonarnym -x86-) jest tym, co nazywamy kodem, który wykonuje się pod pierścieniem 0 . Zazwyczaj w architekturze x86 istnieją 4 pierścienie ochronne . Pierścień 0 (tryb jądra), Pierścień 1 (może być używany przez hiperwizory lub sterowniki maszyny wirtualnej), Pierścień 2 (może być używany przez sterowniki, ale nie jestem tego pewien). Pierścień 3 to typowe aplikacje. Jest to najmniej uprzywilejowany pierścień, a działające na nim aplikacje mają dostęp do podzbioru instrukcji procesora. Pierścień 0 (przestrzeń jądra) jest najbardziej uprzywilejowanym pierścieniem i ma dostęp do wszystkich instrukcji maszyny. Na przykład „prosta” aplikacja (np. Przeglądarka) nie może korzystać z instrukcji montażu x86lgdt
aby załadować globalną tabelę deskryptorów lub hlt
zatrzymać procesor.
Jeśli jest to pierwszy, czy to nie oznacza, że normalny program użytkownika nie może mieć więcej niż 3 GB pamięci (jeśli podział wynosi 3 GB + 1 GB)? Również w takim przypadku, w jaki sposób jądro może korzystać z wysokiej pamięci, ponieważ na jaki adres pamięci wirtualnej zostaną zamapowane strony z dużej pamięci, ponieważ 1 GB miejsca w jądrze zostanie zmapowane logicznie?
Dla odpowiedzi na ten temat znajduje się w doskonałej odpowiedzi przez machać tutaj
-1
jest przeznaczony dla hiperwizorów? en.wikipedia.org/wiki/Protection_ringPierścienie procesora są najbardziej wyraźnym rozróżnieniem
W trybie chronionym x86 procesor jest zawsze w jednym z 4 pierścieni. Jądro Linux używa tylko 0 i 3:
Jest to najtrudniejsza i najszybsza definicja jądra w porównaniu do przestrzeni użytkownika.
Dlaczego Linux nie używa pierścieni 1 i 2: https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used
Jak określa się bieżący pierścień?
Bieżący pierścień jest wybierany przez kombinację:
globalna tabela deskryptorów: tablica wpisów GDT w pamięci, a każda pozycja ma pole,
Privl
które koduje pierścień.Instrukcja LGDT ustawia adres do bieżącej tabeli deskryptorów.
Zobacz także: http://wiki.osdev.org/Global_Descriptor_Table
segment rejestruje CS, DS itp., które wskazują na indeks wpisu w GDT.
Na przykład
CS = 0
oznacza, że pierwszy wpis GDT jest obecnie aktywny dla kodu wykonawczego.Co może zrobić każdy pierścień?
Układ procesora jest fizycznie zbudowany, dzięki czemu:
pierścień 0 może zrobić wszystko
pierścień 3 nie może uruchomić kilku instrukcji i zapisać w kilku rejestrach, w szczególności:
nie może zmienić własnego pierścienia! W przeciwnym razie mógłby ustawić się na pierścień 0 i pierścienie byłyby bezużyteczne.
Innymi słowy, nie można zmodyfikować bieżącego deskryptora segmentu , który określa bieżący pierścień.
nie może modyfikować tabel stron: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
Innymi słowy, nie można zmodyfikować rejestru CR3, a samo stronicowanie zapobiega modyfikacji tabel stron.
Uniemożliwia to procesowi odczytanie pamięci innych procesów ze względów bezpieczeństwa / łatwości programowania.
nie można zarejestrować programów obsługi przerwań. Są one konfigurowane przez zapis w lokalizacjach pamięci, co również zapobiega stronicowaniu.
Programy obsługi działają w pierścieniu 0 i psują model bezpieczeństwa.
Innymi słowy, nie można użyć instrukcji LGDT i LIDT.
nie może wykonywać instrukcji IO takich jak
in
iout
, a zatem ma dowolny dostęp do sprzętu.W przeciwnym razie, na przykład, uprawnienia do plików byłyby bezużyteczne, gdyby jakikolwiek program mógł bezpośrednio czytać z dysku.
Dokładniej dzięki Michaelowi Petchowi : w rzeczywistości system operacyjny może zezwolić na instrukcje IO na pierścieniu 3, jest to faktycznie kontrolowane przez segment stanu zadania .
Niemożliwe jest, aby pierścień 3 wyraził na to zgodę, jeśli go nie miał.
Linux zawsze go nie zezwala. Zobacz także: https://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss
W jaki sposób programy i systemy operacyjne przechodzą między pierścieniami?
gdy procesor jest włączony, zaczyna uruchamiać program początkowy w pierścieniu 0 (no cóż, ale jest to dobre przybliżenie). Możesz myśleć, że ten program początkowy jest jądrem (ale zwykle jest to program ładujący, który następnie wywołuje jądro wciąż w pierścieniu 0).
gdy proces użytkownika chce, aby jądro zrobiło coś takiego, jak zapis do pliku, używa instrukcji, która generuje przerwanie, takie jak
int 0x80
lubsyscall
sygnalizowanie jądra. x86-64 Linux syscall przykład hello world:skompiluj i uruchom:
GitHub w górę .
Kiedy tak się dzieje, CPU wywołuje procedurę obsługi wywołania zwrotnego przerwania, którą jądro zarejestrowało w czasie uruchamiania. Oto konkretny przykład z nagiego metalu, który rejestruje moduł obsługi i używa go .
Ten moduł obsługi działa w pierścieniu 0, który decyduje, czy jądro zezwoli na to działanie, wykonaj działanie i zrestartuj program użytkownika w pierścieniu 3. x86_64
kiedy
exec
używane jest wywołanie systemowe (lub gdy jądro się uruchomi/init
), jądro przygotowuje rejestry i pamięć nowego procesu użytkownika, następnie przeskakuje do punktu wejścia i przełącza CPU na pierścień 3Jeśli program próbuje zrobić coś niegrzecznego, np. Zapis do zabronionego rejestru lub adresu pamięci (z powodu stronicowania), CPU wywołuje również funkcję obsługi wywołania zwrotnego jądra w pierścieniu 0.
Ale ponieważ środowisko użytkownika było niegrzeczne, jądro może tym razem zabić proces lub ostrzec go sygnałem.
Kiedy jądro uruchamia się, ustawia zegar sprzętowy z pewną stałą częstotliwością, która okresowo generuje przerwania.
Ten zegar sprzętowy generuje przerwania, które uruchamiają pierścień 0, i pozwala mu zaplanować, które procesy użytkownika będą się budzić.
W ten sposób planowanie może się zdarzyć, nawet jeśli procesy nie wykonują żadnych wywołań systemowych.
Jaki jest sens posiadania wielu pierścieni?
Istnieją dwie główne zalety oddzielenia jądra i przestrzeni użytkownika:
Jak się z tym bawić?
Stworzyłem gołe metalowe ustawienie, które powinno być dobrym sposobem na bezpośrednie manipulowanie pierścieniami: https://github.com/cirosantilli/x86-bare-metal-examples
Niestety nie miałem cierpliwości, by podać przykład przestrzeni użytkownika, ale posunąłem się do konfiguracji stronicowania, więc droga użytkownika powinna być możliwa. Chciałbym zobaczyć prośbę o pociągnięcie.
Alternatywnie, moduły jądra Linux działają w pierścieniu 0, więc można ich użyć do wypróbowania operacji uprzywilejowanych, np. Odczytać rejestry kontrolne: https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers -cr0-cr2-cr3-from-a-program-getting-segmenta / 7419306 # 7419306
Oto wygodna konfiguracja QEMU + Buildroot, aby wypróbować ją bez zabijania hosta.
Minusem modułów jądra jest to, że działają inne kthreads i mogą zakłócać twoje eksperymenty. Ale teoretycznie możesz przejąć wszystkie moduły obsługi przerwań za pomocą modułu jądra i posiadać system, co w rzeczywistości byłoby ciekawym projektem.
Pierścienie ujemne
Chociaż w podręczniku Intela nie ma tak naprawdę odniesień do pierścieni ujemnych, w rzeczywistości istnieją tryby procesora, które mają więcej możliwości niż sam pierścień 0, a zatem dobrze pasują do nazwy „pierścień ujemny”.
Jednym z przykładów jest tryb hiperwizora używany w wirtualizacji.
Aby uzyskać więcej informacji, zobacz: https://security.stackexchange.com/questions/129098/what-is-protection-ring-1
RAMIĘ
W ARM pierścienie nazywane są zamiast tego Poziomami Wyjątków, ale główne idee pozostają takie same.
Istnieją 4 poziomy wyjątków w ARMv8, powszechnie używane jako:
EL0: obszar użytkownika
EL1: jądro („superwizor” w terminologii ARM).
Wprowadzony z
svc
instrukcją (SuperVisor Call), wcześniej znaną jakoswi
przed zunifikowanym asemblerem , która jest instrukcją używaną do wykonywania wywołań systemowych Linuksa. Przykład Witaj świecie ARMv8:GitHub w górę .
Przetestuj to za pomocą QEMU na Ubuntu 16.04:
Oto konkretny przykład typu baremetal, który rejestruje moduł obsługi SVC i wykonuje wywołanie SVC .
EL2: hiperwizory , na przykład Xen .
Wprowadzony z
hvc
instrukcją (połączenie HyperVisor).Hiperwizor dotyczy systemu operacyjnego, czym jest system operacyjny dla użytkownika.
Na przykład Xen pozwala na jednoczesne uruchamianie wielu systemów operacyjnych, takich jak Linux lub Windows, i izoluje systemy operacyjne dla bezpieczeństwa i łatwości debugowania, podobnie jak Linux dla programów użytkownika.
Hiperwizory są kluczową częścią dzisiejszej infrastruktury chmurowej: pozwalają wielu serwerom działać na jednym sprzęcie, dzięki czemu zużycie sprzętu jest zawsze bliskie 100% i pozwala zaoszczędzić dużo pieniędzy.
Na przykład AWS używał Xen do 2017 roku, kiedy wiadomość o jego przejściu na KVM była aktualna .
EL3: kolejny poziom. Przykład DO ZROBIENIA.
Wprowadzono z
smc
instrukcją (połączenie w trybie bezpiecznym)ARMv8 Architektura Model referencyjny DDI 0487C.a - Rozdział D1 - na poziomie systemu AArch64 programisty Model - Rysunek D1-1 ilustruje to pięknie:
Zwróć uwagę, że ARM, być może ze względu na korzyści z perspektywy czasu, ma lepszą konwencję nazewnictwa dla poziomów uprawnień niż x86, bez potrzeby stosowania poziomów ujemnych: 0 oznacza najniższą, a 3 najwyższą. Wyższe poziomy są zwykle tworzone częściej niż niższe.
Aktualny EL można uzyskać za pomocą
MRS
instrukcji: https://stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etcARM nie wymaga obecności wszystkich poziomów wyjątków, aby umożliwić implementacje, które nie potrzebują tej funkcji do oszczędzania obszaru chipa. ARMv8 „Poziomy wyjątków” mówi:
Na przykład QEMU domyślnie ma wartość EL1, ale EL2 i EL3 można włączyć za pomocą opcji wiersza poleceń: https://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulations-a53-power-up
Fragmenty kodu przetestowane na systemie Ubuntu 18.10.
źródło
Tak jest w przypadku normalnego systemu Linux. W jednym punkcie unosił się zestaw łatek „4G / 4G”, dzięki czemu przestrzenie adresowe użytkownika i jądra były całkowicie niezależne (kosztem wydajności, ponieważ utrudniało jądru dostęp do pamięci użytkownika), ale nie sądzę były one zawsze łączone w górę i zainteresowanie spadło wraz ze wzrostem x86-64
Sposób, w jaki Linux działał (i nadal działa w systemach, w których pamięć jest niewielka w porównaniu do przestrzeni adresowej), polegał na tym, że cała pamięć fizyczna była na stałe odwzorowana w części przestrzeni adresowej jądra. Dzięki temu jądro miało dostęp do całej pamięci fizycznej bez ponownego mapowania, ale wyraźnie nie skaluje się do 32-bitowych maszyn z dużą ilością pamięci fizycznej.
Tak narodziła się koncepcja niskiej i wysokiej pamięci. „niska” pamięć jest trwale mapowana do przestrzeni adresowej jądra. „wysoka” pamięć nie jest.
Gdy procesor wykonuje wywołanie systemowe, działa w trybie jądra, ale nadal w kontekście bieżącego procesu. Dzięki temu może bezpośrednio uzyskiwać dostęp zarówno do przestrzeni adresowej jądra, jak i przestrzeni adresowej użytkownika bieżącego procesu (zakładając, że nie używasz wspomnianych łat 4G / 4G). Oznacza to, że przydzielenie „dużej” pamięci procesowi przestrzeni użytkownika nie stanowi problemu.
Używanie „wysokiej” pamięci do celów jądra jest większym problemem. Aby uzyskać dostęp do dużej pamięci, która nie jest odwzorowana na bieżący proces, musi zostać tymczasowo odwzorowana w przestrzeni adresowej jądra. Oznacza to dodatkowy kod i obniżenie wydajności.
źródło