Zgodnie z instrukcją dla programistów Linuksa:
brk () i sbrk () zmieniają lokalizację przerwania programu, która określa koniec segmentu danych procesu.
Co oznacza tutaj segment danych? Czy to tylko segment danych lub dane, BSS i sterta łącznie?
Według wiki:
Czasami obszary danych, BSS i hałdy są łącznie nazywane „segmentem danych”.
Nie widzę powodu, aby zmieniać rozmiar tylko segmentu danych. Jeśli są to dane, BSS i sterty zbiorczo, wówczas ma to sens, ponieważ sterty zyskają więcej miejsca.
Co prowadzi mnie do drugiego pytania. We wszystkich artykułach, które do tej pory czytałem, autor mówi, że stos rośnie w górę, a stos rośnie w dół. Ale nie wyjaśniają, co się dzieje, gdy kupa zajmuje całą przestrzeń między stertą a stosem?
brk()
wywołanie systemowe jest bardziej przydatne w języku asemblera niż w C. W Cmalloc()
powinno się go używać zamiastbrk()
do celów alokacji danych - ale to w żaden sposób nie unieważnia proponowanego pytania.brk()
asbrk()
? Stosami zarządza alokator stron na znacznie niższym poziomie.Odpowiedzi:
Na opublikowanym diagramie „przerwa” - adres obsługiwany przez
brk
isbrk
- jest linią przerywaną na górze stosu.Dokumentacja, którą przeczytałeś, opisuje to jako koniec „segmentu danych”, ponieważ w tradycyjnym (pre-shared-library, pre-
mmap
) Unixie segment danych był ciągły ze stertą; przed uruchomieniem programu jądro ładuje bloki „tekst” i „dane” do pamięci RAM, zaczynając od adresu zero (w rzeczywistości nieco powyżej adresu zero, aby wskaźnik NULL rzeczywiście nie wskazywał na nic) i ustawił adres przerwania na koniec segmentu danych. Pierwsze wywołanie domalloc
użyłoby następniesbrk
do przeniesienia podziału i utworzenia stosu pomiędzy górą segmentu danych a nowym, wyższym adresem przerwania, jak pokazano na schemacie, a kolejne użyciemalloc
spowodowałoby zwiększenie stosu jako niezbędne.W międzyczasie stos zaczyna się na górze pamięci i rośnie. Stos nie potrzebuje jawnych wywołań systemowych, aby był większy; albo zaczyna się od przydzielonej mu tyle pamięci RAM, ile może mieć (było to tradycyjne podejście), albo pod stosem znajduje się region zarezerwowanych adresów, do którego jądro automatycznie przydziela pamięć RAM, gdy zauważy tam próbę zapisu (jest to nowoczesne podejście). Tak czy inaczej, na dole przestrzeni adresowej może być, ale nie musi, istnieć obszar „ochronny”, który może być użyty do stosu. Jeśli ten region istnieje (wszystkie nowoczesne systemy to robią), jest nieodmapowany; jeśli albostos lub sterta próbuje w nie wyrosnąć, pojawia się błąd segmentacji. Jednak tradycyjnie jądro nie próbowało egzekwować granicy; stos może urosnąć w stertę lub sterty mogą urosnąć w stertę, i tak czy inaczej skrobią się nawzajem po danych, a program się zawiesi. Gdybyś miał szczęście, natychmiast by się zawiesił.
Nie jestem pewien, skąd pochodzi liczba 512 GB na tym schemacie. Oznacza 64-bitową wirtualną przestrzeń adresową, co jest niezgodne z bardzo prostą mapą pamięci, którą tam masz. Rzeczywista 64-bitowa przestrzeń adresowa wygląda mniej więcej tak:
Nie jest to zdalnie skalowane i nie powinno być interpretowane jako dokładnie tak, jak robi to dany system operacyjny (po jego narysowaniu odkryłem, że Linux faktycznie przybliża plik wykonywalny do adresu zero, niż myślałem, i że współużytkowane biblioteki pod zaskakująco wysokimi adresami). Czarne obszary tego diagramu nie są odwzorowane - każdy dostęp powoduje natychmiastową awarię - i są gigantyczne w stosunku do szarych obszarów. Jasnoszare regiony to program i jego wspólne biblioteki (mogą istnieć dziesiątki wspólnych bibliotek); każdy ma niezależnysegment tekstowy i danych (oraz segment „bss”, który również zawiera dane globalne, ale jest inicjowany do zera wszystkich bitów, zamiast zajmować miejsce w pliku wykonywalnym lub bibliotece na dysku). Sterta niekoniecznie jest już ciągła z segmentem danych pliku wykonywalnego - narysowałem to w ten sposób, ale wygląda na to, że Linux przynajmniej tego nie robi. Stos nie jest już związany z górną krawędzią wirtualnej przestrzeni adresowej, a odległość między stertą a stosem jest tak ogromna, że nie musisz się martwić o jej przekroczenie.
Przerwa jest nadal górną granicą stosu. Nie pokazałem jednak, że mogą istnieć dziesiątki niezależnych przydziałów pamięci gdzieś w czerni, wykonane
mmap
zamiastbrk
. (System operacyjny będzie próbował trzymać je z dala od tegobrk
obszaru, aby się nie zderzyły).źródło
malloc
nadal polega na tym,brk
czy używammap
możliwości „oddania” oddzielnych bloków pamięci?malloc
używabrk
obszaru dla małych alokacji i indywidualnychmmap
dla dużych (powiedzmy,> 128K) alokacji. Zobacz na przykład dyskusję na temat MMAP_THRESHOLD na stronie Linux-amalloc(3)
.mmap
; jest bardzo zależny od systemu operacyjnego.Minimalny możliwy do uruchomienia przykład
Prosi jądro, abyś mógł czytać i pisać w ciągłym fragmencie pamięci zwanym stertą.
Jeśli nie zapytasz, może cię to zepsuć.
Bez
brk
:Z
brk
:GitHub w górę .
Powyższe może nie trafić na nową stronę i nie uszkodzić nawet bez
brk
, więc tutaj jest bardziej agresywna wersja, która alokuje 16 MB i jest bardzo prawdopodobne, że ulegnie awarii bezbrk
:Testowane na Ubuntu 18.04.
Wizualizacja wirtualnej przestrzeni adresowej
Przed
brk
:Po
brk(p + 2)
:Po
brk(b)
:Aby lepiej zrozumieć przestrzenie adresowe, powinieneś zapoznać się ze stronicowaniem: Jak działa stronicowanie x86?.
Dlaczego potrzebujemy zarówno
brk
isbrk
?brk
można oczywiście zaimplementować za pomocąsbrk
+ obliczeń przesunięcia, oba istnieją tylko dla wygody.W backendu jądro Linux v5.0 ma pojedyncze wywołanie systemowe,
brk
które służy do implementacji obu: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23Czy
brk
POSIX?brk
kiedyś był POSIX, ale został usunięty w POSIX 2001, dlatego potrzeba_GNU_SOURCE
dostępu do opakowania glibc.Usunięcie jest prawdopodobnie spowodowane wprowadzeniem
mmap
, które jest nadzbiorem, który umożliwia przydzielenie wielu zakresów i więcej opcji alokacji.Myślę, że nie ma uzasadnionego przypadku, w którym powinieneś używać
brk
zamiastmalloc
lubmmap
obecnie.brk
vsmalloc
brk
to jedna stara możliwość wdrożeniamalloc
.mmap
jest nowszym, znacznie silniejszym mechanizmem, który prawdopodobnie wszystkie systemy POSIX obecnie używają do implementacjimalloc
. Oto minimalne runnablemmap
przykład alokacji pamięci .Czy mogę mieszać
brk
i Malloc?Jeśli twoja
malloc
implementacja jest zaimplementowanabrk
, nie mam pojęcia, jak to może nie wysadzić rzeczy w powietrze, ponieważbrk
zarządza tylko jednym zakresem pamięci.Nie znalazłem jednak nic na ten temat w dokumentacji glibc, np .:
Pewnie coś tam po prostu zadziała, jak przypuszczam, ponieważ
mmap
prawdopodobnie jest używanemalloc
.Zobacz też:
Więcej informacji
Jądro wewnętrznie decyduje, czy proces może mieć tyle pamięci, i wyznacza strony pamięci na takie użycie.
To wyjaśnia, w jaki sposób stos różni się od stosu: jaka jest funkcja instrukcji push / pop używanych w rejestrach w zestawie x86?
źródło
p
jest wskaźnikiem do pisaniaint
, czy nie powinno tak byćbrk(p + 2);
?*(p + i) = 1;
brk(p + 2)
zamiast po prostu go zwiększyćsbrk(2)
? Czy BRK jest naprawdę potrzebny?brk
syscall).brk
jest nieco wygodniej przywrócić wcześniej przydzielony stos.Możesz użyć siebie
brk
isbrk
samego, aby uniknąć „kosztów ogólnych”, na które wszyscy zawsze narzekają. Ale nie możesz łatwo użyć tej metody w połączeniu z,malloc
więc jest ona odpowiednia tylko wtedy, gdy nie musiszfree
nic. Ponieważ nie możesz. Ponadto należy unikać wywołań biblioteki, które mogą być używanemalloc
wewnętrznie. To znaczy.strlen
jest prawdopodobnie bezpieczny, alefopen
prawdopodobnie nie jest.Zadzwoń
sbrk
tak, jak byś zadzwoniłmalloc
. Zwraca wskaźnik do bieżącego przerwania i zwiększa przerwę o tę kwotę.Chociaż nie można darmowe indywidualne przydziały (bo nie ma malloc-napowietrznych , pamiętam), to może uwolnić całą przestrzeń wywołując
brk
o wartości zwracanej przez pierwsze wywołaniesbrk
, więc przewijanie BRK .Możesz nawet ułożyć te regiony w stos, odrzucając najnowszy region, przewijając przerwę do początku regionu.
Jeszcze jedna rzecz ...
sbrk
jest również przydatny w golfie kodowym, ponieważ jest o 2 znaki krótszy niżmalloc
.źródło
malloc
/ zfree
pewnością może (i zrobić) przywrócić pamięć do systemu operacyjnego. Nie zawsze mogą to zrobić, kiedy chcesz, ale to jest kwestia niedostosowania heurystyki do twojego przypadku użycia. Co ważniejsze, niebezpieczne jest wywoływaniesbrk
z niezerowym argumentem w dowolnym programie, który mógłby kiedykolwiek wywołaćmalloc
- i prawie wszystkie funkcje biblioteki C mogą wywoływaćmalloc
wewnętrznie. Jedynymi, które na pewno nie będą, są funkcje bezpieczne dla sygnału asynchronicznego .malloc
.sbrk
do tego jest przydatne tylko dla golfa, ponieważ ręczne użyciemmap(MAP_ANONYMOUS)
jest lepsze pod każdym względem, z wyjątkiem rozmiaru kodu źródłowego.Istnieje specjalnie wyznaczone anonimowe mapowanie pamięci prywatnej (tradycyjnie zlokalizowane tuż poza danymi / bss, ale współczesny Linux faktycznie dostosuje lokalizację za pomocą ASLR). Zasadniczo nie jest to lepsze niż jakiekolwiek inne mapowanie, które można utworzyć
mmap
, ale Linux ma pewne optymalizacje, które pozwalają rozszerzyć koniec tego mapowania (przy użyciubrk
syscall) w górę przy zmniejszonym koszcie blokowania w stosunku do tego, commap
lub comremap
byśmy ponieśli. Dzięki temumalloc
implementacje są atrakcyjne w przypadku implementacji głównej sterty.źródło
Mogę odpowiedzieć na twoje drugie pytanie. Malloc zawiedzie i zwróci wskaźnik zerowy. Dlatego zawsze sprawdzasz, czy wskaźnik jest zerowy podczas dynamicznego przydzielania pamięci.
źródło
malloc()
użyjebrk()
i / lubsbrk()
pod maską - i Ty też możesz, jeśli chcesz wdrożyć własną wersjęmalloc()
.Sterta jest umieszczana na końcu w segmencie danych programu.
brk()
służy do zmiany (rozszerzenia) wielkości stosu. Gdy sterty nie będzie już rosnąć, każdemalloc
połączenie zakończy się niepowodzeniem.źródło
Segment danych to część pamięci, w której przechowywane są wszystkie dane statyczne, odczytywane z pliku wykonywalnego przy uruchomieniu i zwykle wypełnione zerami.
źródło
.bss
) są inicjowane przez system operacyjny na zero-bit przed uruchomieniem programu; jest to faktycznie zagwarantowane przez standard C. Przypuszczam, że niektóre systemy wbudowane mogą nie zawracać sobie głowy (nigdy takiego nie widziałem, ale nie działają tak wszystkie wbudowane)mmap
, ale zakładam,.bss
że nadal będą wyzerowane. Przestrzeń BSS jest prawdopodobnie najbardziej kompaktowym sposobem wyrażenia faktu, że program chce mieć tablice zerowe..bss
i nie zeruje,.bss
byłaby zatem niezgodna. Ale nic nie zmusza implementacji C do użycia,.bss
a nawet posiadania czegoś takiego.main
; kod ten mógłby wyzerować.bss
obszar, a nie zmusić jądro do zrobienia tego, i to nadal byłoby zgodne.malloc używa wywołania systemowego brk do alokacji pamięci.
zawierać
uruchom ten prosty program ze strace, wywoła on system brk.
źródło