De acordo com o manual do programador Linux:
brk () e sbrk () alteram o local da interrupção do programa, que define o final do segmento de dados do processo.
O que o segmento de dados significa aqui? É apenas o segmento de dados ou dados, BSS e heap combinados?
De acordo com o wiki:
Às vezes, os dados, o BSS e as áreas de heap são coletivamente chamados de "segmento de dados".
Não vejo razão para alterar o tamanho apenas do segmento de dados. Se forem dados, BSS e heap coletivamente, faz sentido, pois o heap terá mais espaço.
O que me leva à minha segunda pergunta. Em todos os artigos que li até agora, o autor diz que a pilha cresce para cima e a pilha para baixo. Mas o que eles não explicam é o que acontece quando o heap ocupa todo o espaço entre o heap e a pilha?
brk()
chamada do sistema é mais útil na linguagem assembly do que em C. Em C,malloc()
deve ser usada em vez debrk()
para fins de alocação de dados - mas isso não invalida a pergunta proposta de forma alguma.brk()
esbrk()
? As pilhas são gerenciadas pelo alocador de páginas, em um nível muito mais baixo.Respostas:
No diagrama que você postou, a "quebra" - o endereço manipulado por
brk
esbrk
- é a linha pontilhada na parte superior da pilha.A documentação que você leu descreve isso como o final do "segmento de dados" porque no
mmap
Unix tradicional (bibliotecas pré-compartilhadas, pré- ) o segmento de dados era contínuo com a pilha; antes do início do programa, o kernel carregava os blocos "texto" e "dados" na RAM, começando no endereço zero (na verdade, um pouco acima do endereço zero, de modo que o ponteiro NULL realmente não apontava para nada) e configurava o endereço de interrupção como o fim do segmento de dados. A primeira chamada paramalloc
então seria usadasbrk
para mover a divisão e criar o heap entre a parte superior do segmento de dados e o novo endereço de interrupção mais alto, como mostrado no diagrama, e o uso subsequente demalloc
usá-lo para aumentar o heap como necessário.Enquanto isso, a pilha começa no topo da memória e cresce. A pilha não precisa de chamadas explícitas do sistema para aumentá-la; ou ele começa com o máximo de RAM alocado possível (essa era a abordagem tradicional) ou há uma região de endereços reservados abaixo da pilha, na qual o kernel aloca automaticamente a RAM quando nota uma tentativa de escrever nele (esta é a abordagem moderna). De qualquer forma, pode ou não haver uma região de "guarda" na parte inferior do espaço de endereço que pode ser usada para a pilha. Se essa região existe (todos os sistemas modernos fazem isso), ela é permanentemente mapeada; se quera pilha ou a pilha tenta crescer nela, você recebe uma falha de segmentação. Tradicionalmente, porém, o kernel não fez nenhuma tentativa de impor um limite; a pilha pode crescer na pilha, ou a pilha pode crescer na pilha, e de qualquer maneira eles escreviam sobre os dados uns dos outros e o programa travava. Se você tivesse muita sorte, ele falharia imediatamente.
Não sei de onde vem o número de 512 GB neste diagrama. Isso implica em um espaço de endereço virtual de 64 bits, que é inconsistente com o mapa de memória muito simples que você possui lá. Um espaço de endereço real de 64 bits se parece mais com isso:
Isso não é remotamente escalável, e não deve ser interpretado como exatamente como qualquer sistema operacional faz as coisas (depois que o desenhei, descobri que o Linux realmente coloca o executável muito mais próximo do endereço zero do que eu pensava, e as bibliotecas compartilhadas em endereços surpreendentemente altos). As regiões negras deste diagrama não são mapeadas - qualquer acesso causa um segfault imediato - e são gigantescas em relação às áreas cinzentas. As regiões cinza claro são o programa e suas bibliotecas compartilhadas (pode haver dezenas de bibliotecas compartilhadas); cada um tem um independentesegmento de texto e dados (e segmento "bss", que também contém dados globais, mas é inicializado com todos os bits zero, em vez de ocupar espaço no executável ou na biblioteca em disco). A pilha não é mais necessariamente contínua com o segmento de dados do executável - eu desenhei dessa maneira, mas parece que o Linux, pelo menos, não faz isso. A pilha não está mais vinculada à parte superior do espaço de endereço virtual e a distância entre o heap e a pilha é tão grande que você não precisa se preocupar em atravessá-la.
A quebra ainda é o limite superior da pilha. No entanto, o que eu não mostrei é que poderia haver dezenas de alocações independentes de memória no escuro em algum lugar, feitas com e
mmap
nãobrk
. (O sistema operacional tentará mantê-los longe dabrk
área para que não colidam.)fonte
malloc
ainda dependebrk
ou se está usandommap
para poder "devolver" blocos de memória separados?malloc
s atuais usam abrk
área para pequenas alocações emmap
s individuais para grandes (digamos,> 128K) alocações. Veja, por exemplo, a discussão de MMAP_THRESHOLD na página demalloc(3)
manual do Linux .mmap
; é extremamente dependente do sistema operacional.Exemplo mínimo executável
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
:Com
brk
: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 obrk
:Testado no Ubuntu 18.04.
Visualização do espaço de endereço virtual
Antes
brk
:Depois
brk(p + 2)
:Depois
brk(b)
: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
esbrk
?brk
claro que poderia ser implementado comsbrk
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É
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 demalloc
oummap
hoje em dia.brk
vsmalloc
brk
é uma antiga possibilidade de implementaçãomalloc
.mmap
é o novo mecanismo estritamente mais poderoso que provavelmente todos os sistemas POSIX usam atualmente para implementarmalloc
. Aqui está um exemplo mínimo demmap
alocação de memória executável .Posso misturar
brk
e malloc?Se o seu
malloc
for implementadobrk
, não tenho idéia de como isso não pode explodir as coisas, poisbrk
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
usadasmalloc
.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?
fonte
p
é um ponteiro para digitarint
, não deveria ter sidobrk(p + 2);
?*(p + i) = 1;
brk(p + 2)
vez de simplesmente aumentá-losbrk(2)
? Brk é realmente necessário?brk
syscall).brk
é um pouco mais conveniente restaurar a pilha alocada anteriormente.Você pode usar
brk
esbrk
-se para evitar a "sobrecarga malloc" todo mundo está sempre reclamando. Mas você não pode usar esse método facilmente em conjunto com,malloc
por isso é apropriado apenas quando você não precisa fazerfree
nada. Porque você não pode. Além disso, você deve evitar chamadas de biblioteca que possam ser usadasmalloc
internamente. Ou seja.strlen
provavelmente é seguro, masfopen
provavelmente não é.Ligue
sbrk
como você chamariamalloc
. Ele retorna um ponteiro para a interrupção atual e incrementa a interrupção nesse valor.Embora não seja possível liberar alocações individuais (porque não há sobrecarga de malloc , lembre-se), você pode liberar todo o espaço chamando
brk
com o valor retornado pela primeira chamada parasbrk
, rebobinando assim o brk .Você pode até empilhar essas regiões, descartando a região mais recente, rebobinando a interrupção no início da região.
Mais uma coisa ...
sbrk
também é útil no código de golfe, pois é 2 caracteres menor quemalloc
.fonte
malloc
/free
certamente pode (e faz) devolver memória ao sistema operacional. Eles nem sempre o fazem quando você deseja, mas isso é uma questão de as heurísticas serem ajustadas imperfeitamente para o seu caso de uso. Mais importante, não é seguro chamarsbrk
com um argumento diferente de zero em qualquer programa que possa chamarmalloc
- e quase todas as funções da biblioteca C podem chamarmalloc
internamente. Os únicos que definitivamente não serão as funções de segurança de sinal assíncrono .malloc
.sbrk
para isso é útil apenas para golfe de código, porque o uso manualmmap(MAP_ANONYMOUS)
é melhor em todos os aspectos, exceto no tamanho do código-fonte.Há um mapeamento de memória privada anônima designado especial (tradicionalmente localizado logo além dos dados / bss, mas o Linux moderno realmente ajustará o local com o ASLR). Em princípio, não é melhor do que qualquer outro mapeamento você poderia criar com é
mmap
, mas o Linux tem algumas otimizações que tornam possível para expandir o final deste mapeamento (usando obrk
syscall) para cima com bloqueio custo reduzido em relação ao quemmap
oumremap
incorreria. Isso torna atraente omalloc
uso das implementações ao implementar o heap principal.fonte
Eu posso responder sua segunda pergunta. Malloc falhará e retornará um ponteiro nulo. É por isso que você sempre procura um ponteiro nulo ao alocar dinamicamente a memória.
fonte
malloc()
irá usarbrk()
e / ousbrk()
sob o capô - e você também pode, se quiser implementar sua própria versão personalizada domalloc()
.A pilha é colocada por último no segmento de dados do programa.
brk()
é usado para alterar (expandir) o tamanho do heap. Quando o heap não puder crescer mais, qualquermalloc
chamada falhará.fonte
O segmento de dados é a parte da memória que contém todos os seus dados estáticos, lidos no executável na inicialização e geralmente preenchidos com zero.
fonte
.bss
) são inicializados em todos os bits-zero pelo sistema operacional antes do início do programa; isso é realmente garantido pelo padrão C. Alguns sistemas embarcados podem não incomoda, acho que (eu nunca vi um, mas eu não trabalho tudo o que incorporado)mmap
, mas eu suponho.bss
que ainda seria zerado. O espaço do BSS é provavelmente a maneira mais compacta de expressar o fato de que um programa deseja algumas matrizes zerod..bss
e não zera.bss
seria, portanto, não conforme. Mas nada obriga uma implementação C a usar.bss
ou mesmo ter uma coisa dessas.main
; esse código poderia zerar a.bss
área em vez de ter o kernel, e isso ainda estaria em conformidade.O malloc usa a chamada do sistema brk para alocar memória.
incluir
executar este programa simples com strace, ele chamará o sistema brk.
fonte