Czy każdy region na diagramie jest segmentem?
Są to 2 prawie całkowicie różne zastosowania słowa „segment”
- Segmentacja / rejestry segmentów x86 : nowoczesne systemy operacyjne x86 używają płaskiego modelu pamięci, w którym wszystkie segmenty mają tę samą bazę = 0 i limit = maks. w trybie 32-bitowym, tak samo jak sprzęt wymusza to w trybie 64-bitowym , dzięki czemu segmentacja jest trochę szczątkowa . (Z wyjątkiem FS lub GS, używane do lokalnego przechowywania wątków nawet w trybie 64-bitowym).
- Sekcje / segmenty linkera / programu ładującego program. ( Jaka jest różnica sekcji i segmentu w formacie pliku ELF )
Zwyczaje mają wspólne pochodzenie: jeśli były przy użyciu segmentową modelu pamięci (zwłaszcza bez stronicowanej pamięci wirtualnej), możesz mieć dane i adresy BSS być w stosunku do podstawy DS segmentu, stos względem podstawy SS i kod względem Adres bazowy CS.
Tak więc wiele różnych programów można załadować do różnych adresów liniowych, a nawet przenieść po uruchomieniu, bez zmiany 16 lub 32-bitowych przesunięć względem baz segmentów.
Ale wtedy musisz wiedzieć, do którego segmentu odnosi się wskaźnik, więc masz „dalekie wskaźniki” i tak dalej. (Rzeczywiste 16-bitowe programy x86 często nie musiały uzyskiwać dostępu do swojego kodu jako danych, więc mogłyby gdzieś użyć segmentu kodu 64k, a może innego bloku 64k z DS = SS, z rosnącym stosem z wysokich offsetów i danymi w na dole lub mały model kodu z równymi podstawami segmentów).
Jak segmentacja x86 współdziała ze stronicowaniem
Mapowanie adresów w trybie 32/64-bitowym to:
- segment: przesunięcie (baza segmentu sugerowana przez rejestr przechowujący przesunięcie lub zastąpiona prefiksem instrukcji)
- 32 lub 64-bitowy liniowy adres wirtualny = baza + przesunięcie. (W płaskim modelu pamięci, takim jak Linux, wskaźniki / przesunięcia = również adresy liniowe. Z wyjątkiem dostępu do TLS w stosunku do FS lub GS.)
tabele stron (buforowane przez TLB) mapują liniowo na 32 (starszy tryb), 36 (starszy PAE) lub 52-bitowy (x86-64) adres fizyczny. ( /programming/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the ).
Ten krok jest opcjonalny: stronicowanie musi być włączone podczas uruchamiania poprzez ustawienie bitu w rejestrze kontrolnym. Bez stronicowania adresy liniowe są adresami fizycznymi.
Zauważ, że segmentacja nie pozwala na użycie więcej niż 32 lub 64 bitów wirtualnej przestrzeni adresowej w jednym procesie (lub wątku) , ponieważ płaska (liniowa) przestrzeń adresowa, na którą wszystko jest mapowane, ma tylko taką samą liczbę bitów jak same przesunięcia. (Nie miało to miejsca w przypadku 16-bitowego x86, w którym segmentacja była faktycznie przydatna do użycia więcej niż 64k pamięci z przeważnie 16-bitowymi rejestrami i przesunięciami.)
Procesor buforuje deskryptory segmentów ładowane z GDT (lub LDT), w tym bazę segmentu. Po wyrejestrowaniu wskaźnika, w zależności od tego, w jakim rejestrze się znajduje, domyślnie jest to DS lub SS jako segment. Wartość rejestru (wskaźnik) jest traktowana jako przesunięcie względem podstawy segmentu.
Ponieważ podstawa segmentu wynosi zwykle zero, procesory robią to w szczególnych przypadkach. Albo z innej perspektywy, jeśli zrobić mają niezerową bazowy segmentu, ładunki mają dodatkowe opóźnienia, ponieważ „specjalne” (normalny) przypadek pominięciem dodając adres bazowy nie ma zastosowania.
Jak Linux konfiguruje rejestry segmentów x86:
Podstawa i limit CS / DS / ES / SS to 0 / -1 w trybie 32 i 64-bitowym. Nazywa się to płaskim modelem pamięci, ponieważ wszystkie wskaźniki wskazują na tę samą przestrzeń adresową.
(Architekci procesorów AMD zneutralizowali segmentację poprzez wymuszenie płaskiego modelu pamięci dla trybu 64-bitowego, ponieważ systemy operacyjne głównego nurtu i tak go nie używały, z wyjątkiem ochrony przed brakiem wykonania, która została zapewniona w znacznie lepszy sposób dzięki stronicowaniu z PAE lub x86- Format 64 stronicowania).
TLS (lokalna pamięć wątków): FS i GS nie są ustalone w bazie = 0 w trybie długim. (Były nowe z wersją 386 i nie były domyślnie używane przez żadne instrukcje, nawet rep
instrukcje-string zawierające ES). Linux x86-64 ustawia adres podstawowy FS dla każdego wątku na adres bloku TLS.
np. mov eax, [fs: 16]
ładuje 32-bitową wartość z 16 bajtów do bloku TLS dla tego wątku.
deskryptor segmentu CS wybiera tryb, w którym znajduje się CPU (tryb chroniony 16/32/64-bit / tryb długi). Linux używa pojedynczego wpisu GDT dla wszystkich 64-bitowych procesów w przestrzeni użytkownika i innego wpisu GDT dla wszystkich 32-bitowych procesów w przestrzeni użytkownika. (Aby procesor działał poprawnie, DS / ES również musi mieć ustawione prawidłowe wpisy, podobnie jak SS). Wybiera również poziom uprawnień (jądro (pierścień 0) vs. użytkownik (pierścień 3)), więc nawet po powrocie do 64-bitowej przestrzeni użytkownika, jądro nadal musi zorganizować zmianę CS, używając iret
lub sysret
zamiast normalnego instrukcja skoku lub ret.
W x86-64 syscall
punkt wejścia używa swapgs
do odwrócenia GS z GS przestrzeni użytkownika do jądra, którego używa do znalezienia stosu jądra dla tego wątku. (Specjalny przypadek lokalnego przechowywania wątków). syscall
Instrukcja nie zmienia wskaźnik stosu do punktu na stosie jądra; nadal wskazuje na stos użytkowników, gdy jądro osiąga punkt wejścia 1 .
DS / ES / SS również muszą być ustawione na prawidłowe deskryptory segmentów, aby procesor działał w trybie chronionym / trybie długim, nawet jeśli podstawa / limit tych deskryptorów jest ignorowany w trybie długim.
Zasadniczo do TLS stosowana jest segmentacja x86, a do obowiązkowych rzeczy w osdev x86, których wymaga sprzęt.
Przypis 1: Zabawna historia: istnieją archiwa list mailingowych wiadomości między deweloperami jądra a architektami AMD sprzed kilku lat przed wydaniem krzemu AMD64, co spowodowało poprawki w projekcie syscall
tak, aby był użyteczny. Szczegółowe informacje znajdują się w linkach w tej odpowiedzi .