Existuje speciálně určené anonymní mapování soukromé paměti (tradičně umístěné hned za data/bss, ale moderní Linux ve skutečnosti polohu upraví pomocí ASLR). V zásadě to není o nic lepší než jakékoli jiné mapování, které byste mohli vytvořit pomocí mmap
, ale Linux má některé optimalizace, které umožňují rozšířit konec tohoto mapování (pomocí brk
syscall) nahoru se sníženými náklady na zamykání ve srovnání s mmap
nebo mremap
by vznikly. Díky tomu je atraktivní pro malloc
implementace, které se mají použít při implementaci hlavní haldy.
Můžete použít brk
a sbrk
abyste se vyhnuli „mallocké režii“, na kterou si všichni neustále stěžují. Ale nemůžete snadno použít tuto metodu ve spojení s malloc
takže je to vhodné pouze tehdy, když nemusíte free
cokoliv. Protože nemůžeš. Také byste se měli vyvarovat volání knihoven, která mohou používat malloc
vnitřně. Tj. strlen
je pravděpodobně bezpečný, ale fopen
pravděpodobně není.
Zavolejte na číslo sbrk
stejně jako byste zavolali malloc
. Vrátí ukazatel na aktuální přerušení a zvýší přerušení o tuto hodnotu.
void *myallocate(int n){
return sbrk(n);
}
I když nemůžete uvolnit jednotlivé příděly (protože neexistuje malloc-overhead , pamatujte), můžete uvolnit celý prostor voláním brk
s hodnotou vrácenou prvním voláním sbrk
, čímž se přetočí brk .
void *memorypool;
void initmemorypool(void){
memorypool = sbrk(0);
}
void resetmemorypool(void){
brk(memorypool);
}
Tyto oblasti můžete dokonce naskládat na sebe a poslední oblast zahodit přetočením konce na začátek oblasti.
Ještě jedna věc...
sbrk
je také užitečné v kódovém golfu, protože je o 2 znaky kratší než malloc
.
Minimální spustitelný příklad
Co dělá systémové volání brk()?
Požádá jádro, aby vám umožnilo číst a zapisovat do souvislého bloku paměti zvaného halda.
Pokud se nezeptáte, může vás to napadnout.
Bez brk
:
#define _GNU_SOURCE
#include <unistd.h>
int main(void) {
/* Get the first address beyond the end of the heap. */
void *b = sbrk(0);
int *p = (int *)b;
/* May segfault because it is outside of the heap. */
*p = 1;
return 0;
}
S brk
:
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
int main(void) {
void *b = sbrk(0);
int *p = (int *)b;
/* Move it 2 ints forward */
brk(p + 2);
/* Use the ints. */
*p = 1;
*(p + 1) = 2;
assert(*p == 1);
assert(*(p + 1) == 2);
/* Deallocate back. */
brk(b);
return 0;
}
GitHub upstream.
Výše uvedené nemusí narazit na novou stránku a nemusí dojít k chybě ani bez brk
, takže zde je agresivnější verze, která alokuje 16 MiB a je velmi pravděpodobné, že dojde k chybě bez brk
:
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
int main(void) {
void *b;
char *p, *end;
b = sbrk(0);
p = (char *)b;
end = p + 0x1000000;
brk(end);
while (p < end) {
*(p++) = 1;
}
brk(b);
return 0;
}
Testováno na Ubuntu 18.04.
Vizualizace virtuálního adresního prostoru
Před brk
:
+------+ <-- Heap Start == Heap End
Po brk(p + 2)
:
+------+ <-- Heap Start + 2 * sizof(int) == Heap End
| |
| You can now write your ints
| in this memory area.
| |
+------+ <-- Heap Start
Po brk(b)
:
+------+ <-- Heap Start == Heap End
Abyste lépe porozuměli adresním prostorům, měli byste se seznámit se stránkováním:Jak funguje stránkování x86?.
Proč potřebujeme obě brk
a sbrk
?
brk
lze samozřejmě implementovat pomocí sbrk
+ offsetové výpočty, oba existují jen pro pohodlí.
V backendu má linuxové jádro v5.0 jediné systémové volání brk
který se používá k implementaci obou:https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64.tbl#L23
12 common brk __x64_sys_brk
Je brk
POSIX?
brk
býval POSIX, ale byl odstraněn v POSIX 2001, takže je potřeba _GNU_SOURCE
pro přístup k obalu glibc.
Odstranění je pravděpodobně způsobeno zavedením mmap
, což je nadmnožina, která umožňuje alokaci více rozsahů a více možností alokace.
Myslím, že neexistuje žádný platný případ, kdy byste měli použít brk
místo malloc
nebo mmap
v dnešní době.
brk
vs malloc
brk
je jedna stará možnost implementace malloc
.
mmap
je novější mnohem výkonnější mechanismus, který pravděpodobně všechny systémy POSIX v současnosti používají k implementaci malloc
. Zde je minimální spustitelný mmap
příklad přidělení paměti.
Mohu kombinovat brk
a malloc?
Pokud je vaše malloc
je implementován pomocí brk
, nemám ponětí, jak to nemůže vyhodit věci do povětří, protože brk
spravuje pouze jeden rozsah paměti.
V glibc docs jsem však o tom nic nenašel, např.:
- https://www.gnu.org/software/libc/manual/html_mono/libc.html#Resizing-the-Data-Segment
Věci tam budou pravděpodobně fungovat, předpokládám, od mmap
se pravděpodobně používá pro malloc
.
Viz také:
- Co je na brk/sbrk nebezpečné/starší?
- Proč volání sbrk(0) dvakrát dává jinou hodnotu?
Další informace
Interně jádro rozhodne, zda proces může mít tolik paměti, a vyčlení stránky paměti pro toto použití.
To vysvětluje, jak se zásobník porovnává s haldou:Jaká je funkce instrukcí push / pop používaných v registrech v sestavě x86?
V diagramu, který jste zveřejnili, je „přerušení“ – adresa upravená pomocí brk
a sbrk
—je tečkovaná čára v horní části haldy.
Dokumentace, kterou jste si přečetli, to popisuje jako konec „datového segmentu“, protože v tradičních (pre-shared-libraries, pre-mmap
) Unix datový segment byl spojitý s haldou; před spuštěním programu jádro načte bloky "text" a "data" do paměti RAM počínaje adresou nula (ve skutečnosti trochu nad adresou nula, takže ukazatel NULL skutečně neukazuje na nic) a nastaví adresu přerušení na konec datového segmentu. První volání na malloc
by pak použil sbrk
přesunout rozdělení a vytvořit haldu mezi horní část datového segmentu a nová, vyšší adresa přerušení, jak je znázorněno na diagramu, a následné použití malloc
by ho použil k tomu, aby se halda podle potřeby zvětšila.
Mezitím zásobník začíná v horní části paměti a roste dolů. Zásobník nepotřebuje explicitní systémová volání, aby byl větší; buď začíná s přidělenou tolik RAM, kolik může mít (toto byl tradiční přístup), nebo je pod zásobníkem oblast vyhrazených adres, kterým jádro automaticky přidělí RAM, když zaznamená pokus o zápis. (to je moderní přístup). Ať tak či onak, ve spodní části adresního prostoru může nebo nemusí být „strážní“ oblast, kterou lze použít pro zásobník. Pokud tato oblast existuje (všechny moderní systémy to dělají), je trvale nezmapovaná; pokud buď zásobník nebo halda se do něj snaží vrůst, dostanete chybu segmentace. Tradičně se však jádro nepokusilo vynutit si hranici; zásobník by se mohl rozrůst do hromady, nebo by se hromada mohla rozrůst do hromady a v každém případě by si navzájem čmárali data a program by se zhroutil. Pokud byste měli velké štěstí, okamžitě by se zhroutil.
Nejsem si jistý, odkud pochází číslo 512 GB v tomto diagramu. Znamená to 64bitový virtuální adresní prostor, který je v rozporu s velmi jednoduchou mapou paměti, kterou tam máte. Skutečný 64bitový adresní prostor vypadá spíše takto:
Legend: t: text, d: data, b: BSS
Toto není ani vzdáleně škálovatelné a nemělo by to být interpretováno přesně tak, jak kterýkoli daný operační systém dělá věci (poté, co jsem to nakreslil, jsem zjistil, že Linux ve skutečnosti staví spustitelný soubor mnohem blíže k adrese nula, než jsem si myslel, že ano, a sdílené knihovny na překvapivě vysokých adresách). Černé oblasti tohoto diagramu nejsou zmapované – jakýkoli přístup způsobí okamžitou segfault – a jsou gigantické vzhledem k šedým oblastem. Světle šedé oblasti jsou program a jeho sdílené knihovny (sdílených knihoven mohou být desítky); každý má nezávislou textový a datový segment (a segment "bss", který také obsahuje globální data, ale je inicializován na všechny bity nula, místo aby zabíral místo ve spustitelném souboru nebo knihovně na disku). Hromada již není nutně spojitá s datovým segmentem spustitelného souboru – nakreslil jsem to tak, ale vypadá to, že alespoň Linux to nedělá. Zásobník již není navázán na vrchol virtuálního adresového prostoru a vzdálenost mezi haldou a zásobníkem je tak obrovská, že se nemusíte bát, že ji překročíte.
Přerušení je stále horní hranicí haldy. Co jsem však neukázal je, že by tam někde mohly být desítky nezávislých alokací paměti, vytvořené pomocí mmap
místo brk
. (OS se bude snažit udržet je daleko od brk
oblast, aby se nesrazily.)