2013-04-22 38 views
5

Piszę jądro i potrzebuję (i chcę) umieścić wiele stosów i stert w pamięci wirtualnej, ale nie mogę wymyślić, jak je efektywnie umieścić. Jak robią to zwykłe programy?Gdzie umieszcza się wiele stosów i stert w pamięci wirtualnej?

W jaki sposób (lub gdzie) są stosy i sterty umieszczane w ograniczonej pamięci wirtualnej dostarczanej przez system 32-bitowy, tak aby miały jak najwięcej przestrzeni rosnącej?

Na przykład, gdy trywialny program jest ładowany do pamięci, układ swojej przestrzeni adresowej może wyglądać następująco:

[ Code Data BSS Heap-> ... <-Stack ] 

w tym przypadku kupa może rosnąć tak duża, jak pozwala pamięci wirtualnej (np górę do stosu), i uważam, że tak działa stertę dla większości programów. Nie ma zdefiniowanej górnej granicy.

Wiele programów ma wspólne biblioteki umieszczone w wirtualnej przestrzeni adresowej. Następnie są wielowątkowe programy, które mają wiele stosów, po jednym dla każdego wątku. A programy .NET mają multiple heaps, z których wszystkie muszą rosnąć w taki czy inny sposób.

Po prostu nie rozumiem, w jaki sposób jest to wykonywane w rozsądny sposób, bez wprowadzania wstępnie zdefiniowanego limitu rozmiaru wszystkich stosów i stosów.

Odpowiedz

0

Po prostu, ponieważ zasoby systemowe są zawsze ograniczone, nie można przejść bez ograniczeń.

Zarządzanie pamięcią zawsze składa się z kilku warstw, z których każda ma ściśle określoną odpowiedzialność. Z punktu widzenia programu widoczny jest menedżer na poziomie aplikacji, który zwykle dotyczy tylko własnej przydzielonej sterty. Poziom wyżej może zajmować się tworzeniem wielu stert, jeśli jest to potrzebne, z jednej (jednej) sterty globalnej i przypisywania jej do podprogramów (każdy z własnym menedżerem pamięci). Powyżej tego może być standardem, który wykorzystuje i powyżej systemu operacyjnego zajmującego się stronami i rzeczywistą alokacją pamięci na proces (w zasadzie nie dotyczy to tylko wielu stert, ale nawet hałdy na poziomie użytkownika w ogóle).

Zarządzanie pamięcią jest kosztowne, podobnie jak przechwytywanie jądra. Połączenie tych dwóch może spowodować poważne pogorszenie wydajności, więc to, co wydaje się być rzeczywistym zarządzaniem stosem z punktu widzenia aplikacji, jest faktycznie zaimplementowane w przestrzeni użytkownika (biblioteka środowiska wykonawczego C) ze względu na wydajność (i inne przyczyny poza zakresem na teraz).).

Podczas ładowania biblioteki współużytkowanej (DLL), jeśli jest ona załadowana podczas uruchamiania programu, zostanie oczywiście najprawdopodobniej załadowana do KODU/DANYCH/itd., Więc nie wystąpi fragmentacja sterty. Z drugiej strony, jeśli jest załadowany w czasie wykonywania, nie ma praktycznie żadnej innej szansy niż wykorzystanie miejsca na sterty. Biblioteki statyczne są oczywiście po prostu połączone z sekcjami CODE/DATA/BSS/etc.

Pod koniec dnia musisz nałożyć limity na stosy i stosy, aby nie mogły one ulec przepełnieniu, ale możesz przydzielić inne. Jeśli ktoś potrzebuje się rozwijać poza tę granicę, można

  • Zakończ aplikację z błędem
  • mieć menedżer pamięci przydzielić/Resize/przesunąć blok pamięci do tego stosu/sterty i najprawdopodobniej defragmentacji sterty (własny poziom) po; dlatego free() zwykle działa słabo.

Biorąc pod uwagę dość duży, 1KB stosu ramki na każdym call jako średnia arytmetyczna (może się zdarzyć, jeśli deweloper aplikacji jest niedoświadczony) stos 10MB byłaby wystarczająca do 10240 zagnieżdżonych call -s. Przy okazji BTW nie ma potrzeby więcej niż jednego stosu i sterty na wątek.

+0

Ale przełączanie między wątkami nie powinno przełączać całej przestrzeni adresowej (i powodować, aby TLB była opróżniana), więc dla każdego wątku używanego przez proces, jego stos _must_ jest obecny w przestrzeni adresowej procesu. I link w moim poście pokazuje obraz, jak proces CLR ma bardzo wiele stosów. Tak więc istnieje zapotrzebowanie na więcej niż jeden stos i stertę w jednej przestrzeni adresowej. – Virtlink

+0

Przestrzeń adresowa to zupełnie inny poziom abstrakcji. Właściwie w tym przypadku masz te wiele stosów i stert w tej samej przestrzeni adresowej. Sama przestrzeń adresowa jest zarządzana przez system operacyjny, podczas gdy sterta nie jest; jest zarządzany przez kod biblioteki na poziomie użytkownika. – Powerslave

2

Założę, że masz gotowe podstawy jądra, program do obsługi pułapek dla błędów stron, które mogą zamapować stronę pamięci wirtualnej na pamięć RAM. Na kolejnym poziomie potrzebujesz menedżera przestrzeni adresowej pamięci wirtualnej, z którego kod trybu użytkownika może zażądać przestrzeni adresowej. Wybierz ziarnistość segmentu, która zapobiega nadmiernej fragmentacji, 64 KB (16 stron) to dobra liczba. Zezwalaj kodowi kodu użytkownika na rezerwowanie przestrzeni i zatwierdzanie miejsca. Prosta mapa bitowa o rozmiarze 4GB/64KB = 64K x 2 bitów do śledzenia stanu segmentu wykonuje zadanie. Program obsługi pułapek błędów strony musi również sprawdzić tę bitmapę, aby sprawdzić, czy żądanie strony jest poprawne, czy też nie.

Stos to alokacja VM o ustalonym rozmiarze, zwykle 1 megabajt. Wątek zwykle potrzebuje tylko garstki stron, w zależności od poziomu zagnieżdżenia funkcji, więc zachowaj 1 MB i zatwierdz tylko kilka górnych stron. Gdy wątek zagłębi się głębiej, wywoła błąd strony, a jądro może po prostu zamapować dodatkową stronę na pamięć RAM, aby umożliwić kontynuowanie wątku. Będziesz chciał zaznaczyć kilka ostatnich stron jako specjalne, gdy błędy strony wątku na nich, deklarujesz nazwę tej strony.

Najważniejszym zadaniem menedżera sterty jest zapobieganie fragmentacji. Najlepszym sposobem na to jest utworzenie listy odsiewającej, która partycjonuje żądania sterty według rozmiaru. Wszystko poniżej 8 bajtów pochodzi z pierwszej listy segmentów. 8 do 16 z drugiego, 16 do 32 z trzeciego itd. Zwiększanie rozmiaru wiadra, gdy idziesz w górę. Będziesz musiał grać z rozmiarami wiadra, aby uzyskać najlepszą równowagę. Bardzo duże alokacje pochodzą bezpośrednio od menedżera adresów VM.

Po pierwszym trafieniu pozycji na liście odsłon, przydzielany jest nowy segment maszyny wirtualnej. Dziel segment na mniejsze bloki z połączoną listą. Po zwolnieniu takiej alokacji dodajesz blok do listy wolnych bloków. Wszystkie bloki mają ten sam rozmiar niezależnie od żądania programu, więc nie będzie żadnej fragmentacji. Gdy segment jest w pełni wykorzystany i nie ma wolnych bloków, przydzielany jest nowy segment. Gdy segment zawiera wyłącznie puste bloki, możesz go zwrócić do menedżera VM.

Ten schemat pozwala utworzyć dowolną liczbę stosów i stert.

+0

Ładnie opisujesz, jak działa kupa, ale nie widzę, jak ten "schemat" pozwala mi tworzyć dowolną liczbę kuponów. Jeśli zarezerwuję 16 MB dla pierwszej sterty zaraz po kodzie i danych użytkownika, to gdzie umieścić drugą stertę? Zaraz po pierwszym? Wtedy pierwsza kupa nie może przekroczyć początkowej 16 MB. Możesz też podzielić tę stertę podczas ekspansji (podzielić stertę), ale jest to niekorzystne dla lokalizacji pamięci podręcznej (na przykład sterty wysokiej częstotliwości), maksymalnego rozmiaru obiektu (np. Dużego stosu obiektów), czyszczenia pamięci lub z jakiegokolwiek powodu używanych wielu stert. Na przykład. .NET ma wiele stosów, jak to robią? – Virtlink

+0

Brakuje części, w której alokacje sterty to segmenty, a nie stały rozmiar. Sterty będą miały wiele segmentów, gdy będą rosły, mogą być rozproszone w przestrzeni adresowej. Miejsce w pamięci podręcznej jest ogólnie bardzo dobre, ponieważ tablice mają elementy o stałej wielkości, które pochodzą z tego samego łańcucha listy. –