Czy Linux nie używa segmentacji, a jedynie stronicowania?


24

Linux Programming Interface pokazuje układ wirtualnej przestrzeni adresowej procesu. Czy każdy region na diagramie jest segmentem?

wprowadź opis zdjęcia tutaj

Z zrozumienia jądra systemu Linux ,

czy to prawda, że ​​poniższe oznacza, że ​​jednostka segmentacji w MMU mapuje segmenty i przesunięcia w segmentach na adres pamięci wirtualnej, a jednostka przywoławcza następnie mapuje adres pamięci wirtualnej na adres pamięci fizycznej?

Jednostka zarządzania pamięcią (MMU) przekształca adres logiczny w adres liniowy za pomocą obwodu sprzętowego zwanego jednostką segmentacji; następnie drugi obwód sprzętowy zwany jednostką przywoławczą przekształca adres liniowy w adres fizyczny (patrz rysunek 2-1).

wprowadź opis zdjęcia tutaj

Dlaczego więc mówi, że Linux nie używa segmentacji, a jedynie stronicowania?

W mikroprocesorach 80x86 zastosowano segmentację, aby zachęcić programistów do podzielenia aplikacji na logicznie powiązane podmioty, takie jak podprogramy lub globalne i lokalne obszary danych. Jednak Linux używa segmentacji w bardzo ograniczony sposób. W rzeczywistości segmentacja i stronicowanie są nieco redundantne, ponieważ obie można wykorzystać do oddzielenia fizycznych przestrzeni adresowych procesów: segmentacja może przypisać inną liniową przestrzeń adresową do każdego procesu, podczas gdy stronicowanie może odwzorować tę samą liniową przestrzeń adresową na różne fizyczne przestrzenie adresowe . Linux woli stronicowanie od segmentacji z następujących powodów:

• Zarządzanie pamięcią jest prostsze, gdy wszystkie procesy używają tych samych wartości rejestru segmentów - to znaczy, gdy dzielą ten sam zestaw adresów liniowych.

• Jednym z celów projektowych Linuxa jest możliwość przenoszenia go na wiele różnych architektur; W szczególności architektury RISC mają ograniczoną obsługę segmentacji.

Wersja 2.6 Linuksa wykorzystuje segmentację tylko wtedy, gdy wymaga tego architektura 80x86.


Czy możesz podać edycje? Pomocne może być również podanie nazwisk autorów. Wiem, że przynajmniej pierwsza pochodzi od wybitnej postaci. Jednak oba tytuły są trochę ogólne, na początku nie było dla mnie jasne, że mówisz o książkach :-).
sourcejedi

2
Re „Segmentacja została uwzględniona w mikroprocesorach 80x86 ...”: To po prostu nieprawda. Jest to spuścizna procesorów 808x, które miały 16-bitowe wskaźniki danych i segmenty pamięci 64 Kb. Wskaźniki segmentów umożliwiły przełączanie segmentów w celu adresowania większej ilości pamięci. Ta architektura została przeniesiona do formatu 80x86 (z powiększeniem wskaźnika do 33 bitów). Obecnie w modelu x86_64 masz 64-bitowe wskaźniki, które mogą (teoretycznie - myślę, że w rzeczywistości używa się tylko 48 bitów) adresować 16 eksabajtów, więc segmenty nie są konieczne.
jamesqf

2
@ jamesqf, cóż, 32-bitowy tryb chroniony w 386 obsługuje segmenty, które są całkiem różne od 16-bajtowych skalowanych wskaźników, które są w 8086, więc nie jest to zwykła spuścizna. Oczywiście nie oznacza to nic o ich przydatności.
ilkkachu

@jamesqf 80186 miał ten sam model pamięci co 8086, bez „33 bitów”
Jasen

Nie warta odpowiedzi, dlatego tylko komentarz: Segmenty i Strony są porównywalne tylko w kontekście zamiany (np. Zamiana stron vs zamiana segmentów) iw tym kontekście zamiana stron po prostu powoduje zamianę segmentów z wody. Jeśli zamienisz segmenty we / wy, będziesz musiał zamienić cały segment, który może wynosić 2-4 GB. To nigdy nie było prawdziwe do użycia na x86. Za pomocą stron zawsze możesz pracować na jednostkach 4KB. Jeśli chodzi o dostęp do pamięci, segmenty i strony są powiązane poprzez tabele stron, a porównanie byłoby jabłkiem do pomarańczy.
vhu

Odpowiedzi:


20

Architektura x86-64 nie wykorzystuje segmentacji w trybie długim (tryb 64-bitowy).

Cztery rejestry segmentów: CS, SS, DS i ES są wymuszone na 0, a limit na 2 ^ 64.

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

OS nie może już ograniczać dostępnych zakresów „adresów liniowych”. Dlatego nie może używać segmentacji do ochrony pamięci; musi polegać wyłącznie na stronicowaniu.

Nie martw się o szczegóły procesorów x86, które miałyby zastosowanie tylko w starszych wersjach 32-bitowych. Linux dla trybów 32-bitowych nie jest tak często używany. Można nawet uznać to za „stan łagodnego zaniedbania przez kilka lat”. Zobacz 32-bitową obsługę x86 w Fedorze [LWN.net, 2017].

(Zdarza się, że 32-bitowy Linux również nie korzysta z segmentacji. Ale nie musisz mi w tym ufać, możesz to po prostu zignorować :-).


To trochę przesada. base / limit są ustalone na 0 / -1 w długim trybie dla starszych segmentów oryginalnych-8086 (CS / DS / ES / SS), ale FS i GS nadal mają dowolną podstawę segmentu. A deskryptor segmentu załadowany do CS określa, czy procesor działa w trybie 32-bitowym czy 64-bitowym. Przestrzeń użytkownika w Linuksie x86-64 używa FS do lokalnego przechowywania wątków ( mov eax, [fs:rdi + 16]). Jądro używa GS (po swapgs), aby znaleźć stos jądra bieżącego procesu w syscallpunkcie wejścia. Ale tak, segmentacja nie jest używana jako część głównego mechanizmu zarządzania pamięcią systemu operacyjnego / ochrony pamięci.
Peter Cordes,

To jest w zasadzie to, co cytat w pytaniu oznacza „Wersja 2.6 Linuksa używa segmentacji tylko wtedy, gdy wymaga tego architektura 80x86”. Ale twój drugi akapit jest zasadniczo błędny. Linux używa segmentacji w zasadzie identycznie w trybach 32- i 64-bitowym. tzn. podstawa = 0 / limit = 2 ^ 32 lub 2 ^ 64 dla klasycznych segmentów (CS / DS / ES / SS), które są domyślnie używane przez normalne instrukcje. W 32-bitowym kodzie systemu Linux nie ma żadnych „dodatkowych” powodów do zmartwień; funkcjonalność HW jest dostępna, ale nie jest używana.
Peter Cordes,

@PeterCordes zasadniczo źle interpretujesz odpowiedź :-). Zredagowałem to, aby mój argument był mniej dwuznaczny.
sourcejedi

Dobra poprawa, teraz nie wprowadza w błąd. Całkowicie zgadzam się z twoim prawdziwym punktem, że możesz i powinieneś całkowicie zignorować segmentację x86, ponieważ jest ona używana tylko do zarządzania systemem OSDev i do TLS. Jeśli chcesz w końcu się o tym dowiedzieć, o wiele łatwiej to zrozumieć, gdy już rozumiesz asm x86 z płaskim modelem pamięci.
Peter Cordes,

8

Ponieważ x86 ma segmenty, nie można ich nie używać. Jednak adresy podstawowe cs(segment kodu) i ds(segment danych) są ustawione na zero, więc segmentacja nie jest tak naprawdę używana. Wyjątkiem są lokalne dane wątków, jeden z normalnie nieużywanych rejestrów segmentów wskazuje na lokalne dane wątków. Ma to jednak przede wszystkim na celu uniknięcie rezerwowania jednego z rejestrów ogólnego przeznaczenia do tego zadania.

Nie mówi, że Linux nie używa segmentacji na x86, ponieważ nie byłoby to możliwe. Podkreśliłeś już jedną część, Linux używa segmentacji w bardzo ograniczony sposób . Druga część to Linux korzysta z segmentacji tylko wtedy, gdy wymaga tego architektura 80x86

Podałeś już powody, stronicowanie jest łatwiejsze i bardziej przenośne.


7

Czy każdy region na diagramie jest segmentem?

Nie.

Podczas gdy system segmentacji (w 32-bitowym trybie chronionym na x86) jest zaprojektowany do obsługi oddzielnych segmentów kodu, danych i stosu, w praktyce wszystkie segmenty są ustawione w tym samym obszarze pamięci. Oznacza to, że zaczynają się od 0, a kończą na końcu pamięci (*) . Dzięki temu adresy logiczne i adresy liniowe są równe.

Nazywa się to „płaskim” modelem pamięci i jest nieco prostsze niż model, w którym masz wyraźne segmenty, a następnie wskaźniki. W szczególności model segmentowy wymaga dłuższych wskaźników, ponieważ selektor segmentu musi być dołączony oprócz wskaźnika przesunięcia. (16-bitowy selektor segmentów + 32-bitowe przesunięcie, co daje 48-bitowy wskaźnik; w porównaniu z 32-bitowym płaskim wskaźnikiem).

Tryb 64-bitowy tak naprawdę nie obsługuje segmentacji innej niż płaski model pamięci.

Jeśli miałbyś programować w 16-bitowym trybie chronionym na 286, będziesz potrzebował więcej segmentów, ponieważ przestrzeń adresowa ma 24 bity, ale wskaźniki to tylko 16 bitów.

(* Pamiętaj, że nie pamiętam, jak 32-bitowy system Linux obsługuje separację jądra / przestrzeni użytkownika. Segmentacja umożliwiłaby to poprzez ustawienie limitów segmentów przestrzeni użytkownika, aby nie obejmowały przestrzeni jądra. Stronicowanie pozwala na to, ponieważ zapewnia poziom ochrony na stronę).

Dlaczego więc mówi, że Linux nie używa segmentacji, a jedynie stronicowania?

X86 nadal ma segmenty i nie można ich wyłączyć. Są one używane tak mało, jak to możliwe. W 32-bitowym trybie chronionym segmenty muszą być skonfigurowane dla modelu płaskiego, a nawet w trybie 64-bitowym nadal istnieją.


Huh, chyba 32-bitowe jądro może złagodzić Meltdown taniej niż zmiana tabel stron poprzez ustawienie limitów segmentów w CS / DS / ES / SS, które uniemożliwiają dostęp użytkownika do przestrzeni powyżej 2G lub 3G. (The Meltdown vuln to obejście dla bitu jądra / użytkownika we wpisach tablicy strony, pozwalające przestrzeni użytkownika na odczyt ze stron, które są tylko mapowane dla jądra). Strony VDSO mogą być mapowane na górze 4G, chociaż: / wrfsbasejest nielegalny w trybie chronionym / kompatybilnym, tylko w trybie długim, więc w 32-bitowej przestrzeni użytkownika jądra nie można ustawić wysokiej bazy FS.
Peter Cordes,

W 64-bitowym jądrze 32-bitowa przestrzeń użytkownika może potencjalnie znacznie przeskoczyć do 64-bitowego segmentu kodu, więc nie można polegać na limitach segmentów dla ochrony przed topieniem, tylko może w czystym 32-bitowym jądrze. (Co ma duże wady na komputerach z dużą ilością fizycznej pamięci RAM, np. Brak pamięci dla stosów wątków.) W każdym razie, tak, Linux chroni pamięć jądra przy pomocy stronicowania, pozostawiając base / limit = 0 / -1 w przestrzeni użytkownika na normalne segmenty (nie FS / GS, które są używane do lokalnego przechowywania wątków).
Peter Cordes,

Zanim bit NX był obsługiwany w sprzętowych tablicach stron (PAE), niektóre wczesne łaty bezpieczeństwa wykorzystywały segmentację do tworzenia niewykonywalnych stosów dla kodu przestrzeni użytkownika. np. linux.com/news/exec-shield-new-linux-security-feature (post Ingo Molnar wspomina o „doskonałej łatce stosu niewykonawczego Solar Designera”.)
Peter Cordes

3

Linux x86 / 32 nie używa segmentacji w tym sensie, że inicjuje wszystkie segmenty do tego samego adresu liniowego i limitu. Architektura x86 wymaga, aby program miał segmenty: kod może być wykonywany tylko z segmentu kodu, stos może znajdować się tylko w segmencie stosu, dane mogą być przetwarzane tylko w jednym z segmentów danych. Linux omija ten mechanizm, ustawiając wszystkie segmenty w ten sam sposób (z wyjątkami, o których i tak nie wspomina książka), aby ten sam adres logiczny był poprawny w dowolnym segmencie. W rzeczywistości jest to równoważne z brakiem segmentów.


2

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:

  1. segment: przesunięcie (baza segmentu sugerowana przez rejestr przechowujący przesunięcie lub zastąpiona prefiksem instrukcji)
  2. 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.)
  3. 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 repinstrukcje-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 iretlub sysretzamiast normalnego instrukcja skoku lub ret.

  • W x86-64 syscallpunkt wejścia używa swapgsdo 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). syscallInstrukcja 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 syscalltak, aby był użyteczny. Szczegółowe informacje znajdują się w linkach w tej odpowiedzi .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.