Studiuję systemy operacyjne i architekturę x86, a kiedy czytałem o segmentacji i stronicowaniu, naturalnie byłem ciekawy, jak nowoczesne systemy operacyjne obsługują zarządzanie pamięcią. Z tego, co znalazłem, Linux i większość innych systemów operacyjnych zasadniczo unika segmentacji na rzecz stronicowania. Kilka powodów, dla których znalazłem to prostota i przenośność.
Jakie praktyczne zastosowania ma segmentacja (x86 lub inna) i czy kiedykolwiek zobaczymy, że używają jej solidne systemy operacyjne, czy też nadal będą faworyzować system oparty na stronicowaniu.
Teraz wiem, że jest to obciążone pytanie, ale jestem ciekawy, jak poradzić sobie z segmentacją w nowo opracowanych systemach operacyjnych. Czy faworyzowanie stronicowania ma tyle sensu, że nikt nie rozważy bardziej „segmentowego” podejścia? Jeśli tak, dlaczego?
A kiedy mówię o „unikaniu” segmentacji, sugeruję, że Linux używa go tylko w takim stopniu, w jakim jest to konieczne. Tylko 4 segmenty dla segmentów kodu / danych użytkownika i jądra. Czytając dokumentację Intela, miałem wrażenie, że segmentacja została zaprojektowana z myślą o bardziej niezawodnych rozwiązaniach. Z drugiej strony wielokrotnie mówiono mi, jak skomplikowane może być x86.
Tę ciekawą anegdotę znalazłem po powiązaniu z oryginalnym „ogłoszeniem” Torvalda dla Linuksa. Powiedział to kilka postów później:
Po prostu powiedziałbym, że przeniesienie jest niemożliwe. Przeważnie jest w C, ale większość ludzi nie nazwałaby tego, co piszę. Wykorzystuje każdą możliwą funkcję 386, jaką mogłem znaleźć, ponieważ był to również projekt, aby nauczyć mnie o 386. Jak już wspomniano, używa MMU , zarówno dla stronicowania (jeszcze nie na dysk), jak i segmentacji. To segmentacja sprawia, że NAPRAWDĘ zależy od niej 386 (każde zadanie ma segment 64 MB na kod i dane - maksymalnie 64 zadania w 4 Gb. Każdy, kto potrzebuje więcej niż 64 Mb / zadanie - twarde pliki cookie).
Wydaje mi się, że moje własne eksperymenty z x86 skłoniły mnie do zadania tego pytania. Linus nie miał StackOverflow, więc po prostu zaimplementował go, aby go wypróbować.
źródło
Odpowiedzi:
Dzięki segmentacji możliwe byłoby na przykład umieszczenie każdego dynamicznie przydzielanego obiektu (malloc) we własnym segmencie pamięci. Sprzęt automatycznie sprawdza limity segmentów, a cała klasa błędów bezpieczeństwa (przepełnienia bufora) zostanie wyeliminowana.
Ponadto, ponieważ wszystkie przesunięcia segmentów zaczynają się od zera, cały skompilowany kod byłby automatycznie niezależny od pozycji. Wywołanie do innej biblioteki DLL sprowadzałoby się do dalekiego wywołania ze stałym przesunięciem (w zależności od wywoływanej funkcji). To znacznie uprościłoby konsolidatory i programy ładujące.
Dzięki 4 pierścieniom ochronnym można opracować bardziej szczegółową kontrolę dostępu (z przywoływaniem masz tylko 2 poziomy ochrony: użytkownik i administrator) oraz bardziej niezawodne jądra systemu operacyjnego. Na przykład tylko pierścień 0 ma pełny dostęp do sprzętu. Dzieląc jądro systemu operacyjnego i sterowniki urządzeń na pierścienie 0 i 1, można stworzyć bardziej niezawodny i bardzo szybki system operacyjny mikrojądra, w którym większość odpowiednich kontroli dostępu przeprowadzałaby HW. (Sterowniki urządzeń mogą uzyskać dostęp do sprzętu poprzez bitmapę dostępu we / wy w TSS.)
Jednak .. x86 jest nieco ograniczony. Ma tylko 4 „wolne” rejestry segmentów danych; ich ponowne załadowanie jest dość drogie i można jednocześnie uzyskać dostęp tylko do 8192 segmentów. (Zakładając, że chcesz zmaksymalizować liczbę dostępnych obiektów, więc GDT przechowuje tylko deskryptory systemowe i deskryptory LDT).
Teraz w trybie 64-bitowym segmentacja jest opisywana jako „starsza”, a sprawdzanie limitów sprzętowych odbywa się tylko w ograniczonych okolicznościach. IMHO, DUŻY błąd. Właściwie nie obwiniam Intela, głównie obwiniam programistów, z których większość uważała, że segmentacja była „zbyt skomplikowana” i tęskniła za płaską przestrzenią adresową. Obwiniam także autorów systemu operacyjnego, którym brakowało wyobraźni, aby dobrze wykorzystać segmentację. (AFAIK, OS / 2 był jedynym systemem operacyjnym, który w pełni wykorzystywał funkcje segmentacji.)
źródło
Krótka odpowiedź jest taka, że segmentacja to włamanie, które powoduje, że procesor z ograniczoną zdolnością adresowania pamięci przekracza te limity.
W przypadku 8086 na chipie było 20 linii adresowych, co oznacza, że mógł on fizycznie uzyskać dostęp do 1 MB pamięci. Jednak wewnętrzna architektura opierała się na adresowaniu 16-bitowym, prawdopodobnie ze względu na chęć zachowania spójności z 8080. Zestaw instrukcji zawierał rejestry segmentów, które byłyby połączone z indeksami 16-bitowymi, aby umożliwić adresowanie pełnego 1 MB pamięci . 80286 rozszerzył ten model o prawdziwą MMU, aby obsługiwać ochronę opartą na segmentach i adresowanie większej ilości pamięci (iirc, 16 Mb).
W przypadku PDP-11 późniejsze modele procesora zapewniły segmentację na przestrzenie instrukcji i danych, ponownie w celu obsługi ograniczeń 16-bitowej przestrzeni adresowej.
Problem z segmentacją jest prosty: Twój program musi jawnie obejść ograniczenia architektury. W przypadku 8086 oznaczało to, że największy ciągły blok pamięci, do którego można było uzyskać dostęp, wynosił 64k. jeśli chcesz uzyskać dostęp do większej ilości danych, musisz zmienić rejestry segmentów. Co dla programisty C oznaczało, że musisz powiedzieć kompilatorowi C, jakie wskaźniki powinien wygenerować.
O wiele łatwiej było zaprogramować MC68k, który miał 32-bitową architekturę wewnętrzną i 24-bitową fizyczną przestrzeń adresową.
źródło
Dla 80x86 dostępne są 4 opcje - „nic”, tylko segmentacja, tylko stronicowanie oraz zarówno segmentacja, jak i stronicowanie.
W przypadku „nic” (bez segmentacji lub stronicowania) nie ma łatwego sposobu na ochronę procesu przed samym sobą, żadnego łatwego sposobu na ochronę procesów przed sobą, żadnego sposobu radzenia sobie z takimi rzeczami, jak fragmentacja fizycznej przestrzeni adresowej, żaden sposób uniknięcia pozycji niezależny kod itp. Mimo tych wszystkich problemów może (teoretycznie) być użyteczny w niektórych sytuacjach (np. urządzenie osadzone, które uruchamia tylko jedną aplikację; lub może coś, co korzysta z JIT i i tak wszystko wirtualizuje).
Tylko do segmentacji; prawie rozwiązuje problem „chroń proces przed samym sobą”, ale wymaga wielu obejść, aby był użyteczny, gdy proces chce użyć więcej niż 8192 segmentów (zakładając jeden LDT na proces), co powoduje, że jest on w większości zepsuty. Prawie rozwiązujesz problem „ochrony procesów przed sobą”; ale różne programy działające na tym samym poziomie uprawnień mogą ładować / wykorzystywać swoje segmenty (istnieją sposoby na obejście tego - modyfikowanie wpisów GDT podczas transferów kontrolnych i / lub używanie LDT). W większości rozwiązuje również problem „kodu niezależnego od pozycji” (może powodować problem z „kodem zależnym od segmentu”, ale jest to znacznie mniej znaczące). Nie robi nic w przypadku problemu „fragmentacji fizycznej przestrzeni adresowej”.
Tylko do stronicowania; nie rozwiązuje problemu „ochrony procesu przed samym sobą” (ale bądźmy szczerzy, jest to tak naprawdę tylko problem do debugowania / testowania kodu napisanego w niebezpiecznych językach, i jest o wiele potężniejsze narzędzia, takie jak valgrind). Całkowicie rozwiązuje problem „ochrony procesów przed sobą”, całkowicie rozwiązuje problem „kodu niezależnego od pozycji” i całkowicie rozwiązuje problem „fragmentacji fizycznej przestrzeni adresowej”. Jako dodatkowy bonus otwiera kilka bardzo potężnych technik, które nie są tak praktyczne bez stronicowania; w tym rzeczy takie jak „kopiuj przy zapisie”, pliki mapowane w pamięci, wydajna obsługa przestrzeni wymiany itd.
Można by pomyśleć, że zastosowanie zarówno segmentacji, jak i stronicowania zapewniłoby korzyści obu; i teoretycznie może, z tą różnicą, że jedyną korzyścią wynikającą z segmentacji (której nie da się lepiej przywoływać) jest rozwiązanie problemu „ochrony procesu przed samym sobą”, na którym tak naprawdę nikogo nie obchodzi. W praktyce dostajesz złożoność obu i koszty ogólne obu, dla bardzo małej korzyści.
To dlatego prawie wszystkie systemy operacyjne zaprojektowane dla 80x86 nie używają segmentacji do zarządzania pamięcią (używają jej do takich rzeczy jak na procesor i pamięć na zadania, ale to głównie dla wygody, aby uniknąć konsumowania bardziej przydatnego rejestru ogólnego przeznaczenia dla tych rzeczy).
Oczywiście producenci procesorów nie są głupi - nie zamierzają poświęcać czasu i pieniędzy na optymalizację czegoś, o czym wiedzą, że nikt ich nie używa (zamiast tego zoptymalizują coś, z czego prawie wszyscy korzystają). Z tego powodu producenci procesorów nie optymalizują segmentacji, co powoduje, że segmentacja jest wolniejsza niż mogłaby, co sprawia, że twórcy systemów operacyjnych chcą tego jeszcze bardziej uniknąć. Przeważnie utrzymywali segmentację tylko dla kompatybilności wstecznej (co jest ważne).
W końcu AMD zaprojektowało długi tryb. 64-bitowy kod nie miał się czym martwić, więc (w przypadku kodu 64-bitowego) AMD pozbyło się jak największej segmentacji. Dało to twórcom systemów operacyjnych jeszcze jeden powód (brak łatwego sposobu przeniesienia kodu przeznaczonego do segmentacji do wersji 64-bitowej), aby nadal unikać segmentacji.
źródło
Jestem raczej zaskoczony, że przez cały czas, odkąd zadano to pytanie, nikt nie wspominał o pochodzeniu segmentowanych architektur pamięci i prawdziwej mocy, na którą mogą sobie pozwolić.
Pierwotnym systemem, który albo wymyślił, albo przerobił na użyteczną formę, wszystkie funkcje związane z projektowaniem i wykorzystaniem segmentowanych systemów stronicowanej pamięci wirtualnej (wraz z symetrycznym wieloprocesowym przetwarzaniem i hierarchicznymi systemami plików) był Multics (i patrz także strona Multicians ). Segmentowana pamięć pozwala Multicsowi zaoferować użytkownikowi widok, że wszystko jest w (wirtualnej) pamięci, i pozwala na najwyższy poziom współdzielenia wszystkiegow formie bezpośredniej (tj. bezpośrednio adresowalnej w pamięci). System plików staje się po prostu mapą do wszystkich segmentów w pamięci. Przy prawidłowym stosowaniu w sposób systematyczny (jak w Multics) pamięć segmentowa uwalnia użytkownika od wielu obciążeń związanych z zarządzaniem pamięcią wtórną, udostępnianiem danych i komunikacją między procesami. Inne odpowiedzi dały do zrozumienia, że segmentacja pamięci jest trudniejsza w użyciu, ale to po prostu nieprawda, a firma Multics udowodniła to z wielkim powodzeniem dekady temu.
Intel stworzył spulchnioną wersję pamięci segmentowej 80286, która choć jest dość potężna, jej ograniczenia uniemożliwiły wykorzystanie jej do czegokolwiek naprawdę przydatnego. 80386 poprawił te ograniczenia, ale siły rynkowe w tamtym czasie prawie uniemożliwiły sukces jakiegokolwiek systemu, który mógłby naprawdę skorzystać z tych ulepszeń. Od lat wydaje się, że zbyt wiele osób nauczyło się ignorować lekcje z przeszłości.
Intel również próbował wcześnie zbudować bardziej wydajną super-mikro zwaną iAPX 432 , która w tym czasie znacznie przewyższałaby wszystko inne, a także posiadała segmentową architekturę pamięci i inne funkcje silnie ukierunkowane na programowanie obiektowe. Oryginalna implementacja była jednak po prostu zbyt wolna i nie podjęto żadnych dalszych prób jej naprawy.
Bardziej szczegółowe omówienie wykorzystania segmentacji i stronicowania przez Multics można znaleźć w artykule Paula Greena Pamięć wirtualna Multics - samouczek i refleksje
źródło
Segmentacja polegała na włamaniu / obejściu pozwalającym na rozwiązanie do 1 MB pamięci przez 16-bitowy procesor - zwykle tylko 64K pamięci byłoby dostępne.
Gdy pojawiły się procesory 32-bitowe, można było zaadresować do 4 GB pamięci za pomocą płaskiego modelu pamięci i nie było już potrzeby segmentacji - Rejestry segmentów zostały ponownie przeznaczone jako selektory GDT / stronicowania w trybie chronionym (chociaż można to zrobić mają tryb chroniony 16-bitowy).
Również tryb płaskiej pamięci jest znacznie wygodniejszy dla kompilatorów - możesz pisać 16-bitowe programy segmentowane w C , ale jest to trochę uciążliwe. Płaski model pamięci ułatwia wszystko.
źródło
Niektóre architektury (jak ARM) w ogóle nie obsługują segmentów pamięci. Gdyby Linux był zależny od źródła od segmentów, nie mógłby być łatwo przeniesiony do tych architektur.
Patrząc na szerszy obraz, awaria segmentów pamięci ma związek z ciągłą popularnością C i arytmetyki wskaźników. Rozwój C jest bardziej praktyczny w przypadku architektury z płaską pamięcią; a jeśli chcesz mieć pamięć płaską, wybierasz stronicowanie pamięci.
Był czas na przełomie lat 80., kiedy Intel, jako organizacja, oczekiwał przyszłej popularności Ady i innych języków programowania wyższego poziomu. Z tego właśnie powodu pochodziły niektóre z ich bardziej spektakularnych awarii, takie jak okropna segmentacja pamięci APX432 i 286. Z 386 skapitulowali do programistów z płaską pamięcią; dodano stronicowanie i TLB, a rozmiary segmentów zmieniono na 4 GB. A następnie AMD w zasadzie usunął segmenty za pomocą x86_64, czyniąc base reg dont-care / implied-0 (z wyjątkiem fs? Dla TLS, myślę?)
Powiedziawszy to, zalety segmentów pamięci są oczywiste - przełączanie przestrzeni adresowej bez konieczności ponownego wypełniania TLB. Może kiedyś ktoś stworzy konkurencyjny pod względem wydajności procesor, który obsługuje segmentację, możemy zaprogramować dla niego system operacyjny zorientowany na segmentację, a programiści mogą zrobić Ada / Pascal / D / Rust / another-langage-that-not-need-flat- -programy pamięci dla tego.
źródło
Segmentacja stanowi ogromne obciążenie dla twórców aplikacji. Stąd duży nacisk na wyeliminowanie segmentacji.
Co ciekawe, często zastanawiam się, o ile lepszy byłby i86, gdyby Intel rozłożył całą starszą obsługę tych starych trybów. Tutaj lepiej oznaczałoby to niższą moc i być może szybszą pracę.
Wydaje mi się, że można argumentować, że Intel zalał mleko 16-bitowymi segmentami, co doprowadziło do buntu deweloperów. Ale spójrzmy prawdzie w oczy, przestrzeń adresowa na 64k to nic szczególnego, gdy spojrzysz na nowoczesną aplikację. W końcu musieli coś zrobić, ponieważ konkurencja mogła i skutecznie działała na rynku w związku z problemami przestrzeni adresowej i86.
źródło
Segmentacja prowadzi do wolniejszego tłumaczenia stron i wymiany
Z tych powodów segmentacja została znacznie zmniejszona na x86-64.
Główna różnica między nimi polega na tym, że:
Chociaż może wydawać się mądrzejsze, aby mieć konfigurowalne szerokości segmentów, ponieważ zwiększasz rozmiar pamięci dla procesu, fragmentacja jest nieunikniona, np .:
ostatecznie stanie się, gdy proces 1 rośnie:
dopóki podział jest nieunikniony:
W tym momencie:
Jednak w przypadku stron o stałym rozmiarze:
Kawałki pamięci o stałej wielkości są po prostu łatwiejsze do zarządzania i zdominowały obecny projekt systemu operacyjnego.
Zobacz także: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
źródło