Exemplo mínimo executável
O que a chamada do sistema brk () faz?
Solicita que o kernel permita que você leia e grave em um pedaço de memória contíguo chamado heap.
Se você não perguntar, isso pode ser um defeito.
Sem 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;
}
Com 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 .
O exemplo acima pode não chegar a uma nova página e não segfault, mesmo sem o brk
, então aqui está uma versão mais agressiva que aloca 16MiB e é muito provável que segfault sem o 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;
}
Testado no Ubuntu 18.04.
Visualização do espaço de endereço virtual
Antes brk
:
+------+ <-- Heap Start == Heap End
Depois brk(p + 2)
:
+------+ <-- Heap Start + 2 * sizof(int) == Heap End
| |
| You can now write your ints
| in this memory area.
| |
+------+ <-- Heap Start
Depois brk(b)
:
+------+ <-- Heap Start == Heap End
Para entender melhor os espaços de endereço, familiarize-se com a paginação: Como funciona a paginação x86? .
Por que precisamos de ambos brk
e sbrk
?
brk
claro que poderia ser implementado com sbrk
cálculos de deslocamento, ambos existem apenas por conveniência.
No back-end, o kernel do Linux v5.0 possui uma única chamada do sistema brk
usada para implementar os dois: https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64. tbl # L23
12 common brk __x64_sys_brk
É brk
POSIX?
brk
costumava ser POSIX, mas foi removido no POSIX 2001, portanto, a necessidade de _GNU_SOURCE
acessar o wrapper glibc.
A remoção provavelmente ocorre devido à introdução mmap
, que é um superconjunto que permite que vários intervalos sejam alocados e mais opções de alocação.
Eu acho que não há um caso válido em que você deva usar em brk
vez de malloc
ou mmap
hoje em dia.
brk
vs malloc
brk
é uma antiga possibilidade de implementação malloc
.
mmap
é o novo mecanismo estritamente mais poderoso que provavelmente todos os sistemas POSIX usam atualmente para implementar malloc
. Aqui está um exemplo mínimo de mmap
alocação de memória executável .
Posso misturar brk
e malloc?
Se o seu malloc
for implementado brk
, não tenho idéia de como isso não pode explodir as coisas, pois brk
gerencia apenas um único intervalo de memória.
No entanto, não consegui encontrar nada sobre isso nos documentos da glibc, por exemplo:
As coisas provavelmente funcionarão lá, suponho, uma vez que provavelmente serão mmap
usadas malloc
.
Veja também:
Mais informações
Internamente, o kernel decide se o processo pode ter tanta memória e marca as páginas de memória para esse uso.
Isso explica como a pilha se compara à pilha: Qual é a função das instruções push / pop usadas nos registradores no assembly x86?