Nos computadores, para que um processo seja executável, ele precisa ser colocado na memória. Para isso, um campo deve ser atribuído a um processo na memória. A alocação de memória é uma questão importante a ser observada, especialmente em arquiteturas de kernel e de sistema.

Vamos dar uma olhada na alocação de memória do Linux em detalhes e entender o que acontece nos bastidores.

Como é feita a alocação de memória?

A maioria dos engenheiros de software não conhece os detalhes desse processo. Mas se você é um candidato a programador de sistemas, deve saber mais sobre isso. Ao olhar para o processo de alocação, é necessário entrar em um pequeno detalhe sobre o Linux e o glibc biblioteca.

Quando os aplicativos precisam de memória, eles precisam solicitá-la ao sistema operacional. Essa solicitação do kernel naturalmente exigirá uma chamada de sistema. Você não pode alocar memória no modo de usuário.

o malloc() família de funções é responsável pela alocação de memória na linguagem C. A questão a ser feita aqui é se malloc(), como uma função glibc, faz uma chamada direta ao sistema.

instagram viewer

Não há nenhuma chamada de sistema chamada malloc no kernel do Linux. No entanto, existem duas chamadas de sistema para demandas de memória de aplicativos, que são brk e mmap.

Como você solicitará memória em seu aplicativo por meio de funções glibc, você pode estar se perguntando qual dessas chamadas de sistema a glibc está usando neste momento. A resposta é ambos.

A primeira chamada do sistema: brk

Cada processo tem um campo de dados contíguo. Com a chamada do sistema brk, o valor de quebra do programa, que determina o limite do campo de dados, é aumentado e o processo de alocação é realizado.

Embora a alocação de memória com esse método seja muito rápida, nem sempre é possível devolver o espaço não utilizado ao sistema.

Por exemplo, considere que você aloca cinco campos, cada um com 16 KB de tamanho, com a chamada de sistema brk por meio da função malloc(). Ao terminar o número dois desses campos, não é possível devolver o recurso relevante (desalocação) para que o sistema possa utilizá-lo. Porque se você reduzir o valor do endereço para mostrar o local onde começa o seu campo número dois, com uma chamada para brk, você terá feito a desalocação para os campos números três, quatro e cinco.

Para evitar perda de memória neste cenário, a implementação do malloc na glibc monitora os locais alocados no campo de dados do processo e então especifica para devolvê-lo ao sistema com a função free(), para que o sistema possa usar o espaço livre para mais memória alocações.

Em outras palavras, após cinco áreas de 16 KB serem alocadas, se a segunda área for retornada com a função free() e outra área de 16 KB é solicitado novamente após algum tempo, ao invés de ampliar a área de dados através da chamada do sistema brk, o endereço anterior é retornado.

No entanto, se a área recentemente solicitada for maior que 16 KB, a área de dados será ampliada alocando uma nova área com a chamada de sistema brk, pois a área dois não pode ser usada. Embora a área número dois não esteja em uso, o aplicativo não pode usá-la devido à diferença de tamanho. Por causa de cenários como esse, há uma situação chamada fragmentação interna e, de fato, raramente você pode usar todas as partes da memória ao máximo.

Para entender melhor, tente compilar e executar o seguinte aplicativo de exemplo:

#incluir <stdio.h>
#incluir <stdlib.h>
#incluir <unistd.h>
inta Principal(int argc, Caracteres* argv[])
{
Caracteres *ptr[7];
int n;
printf("Pid de %s: %d", argv[0], getpid());
printf("Interrupção inicial do programa: %p", sbrk (0));
para (n=0; n<5; n++) ptr[n] = malloc (16 * 1024);
printf("Após 5 x 16kB malloc: %p", sbrk (0));
gratuitamente(ptr[1]);
printf("Depois de liberar os segundos 16kB: %p", sbrk (0));
ptr[5] = malloc (16 * 1024);
printf("Após alocar 6º de 16kB: %p", sbrk (0));
gratuitamente(ptr[5]);
printf("Após liberar o último bloco: %p", sbrk (0));
ptr[6] = malloc (18 * 1024);
printf("Após alocar um novo 18kB: %p", sbrk (0));
getchar();
Retorna0;
}

Ao executar o aplicativo, você obterá um resultado semelhante à seguinte saída:

Pid de ./a.out: 31990
Programa inicial pausa: 0x55ebcadf4000
Após 5 x 16kB malloc: 0x55ebcadf4000
Depois de liberar os segundos 16kB: 0x55ebcadf4000
Após alocar 6º de 16kB: 0x55ebcadf4000
Após liberar o último bloco: 0x55ebcadf4000
Após alocar um novo18KB: 0x55ebcadf4000

A saída para brk com strace será a seguinte:

brk(NULO) = 0x5608595b6000
brk (0x5608595d7000) = 0x5608595d7000

Como você pode ver, 0x21000 foi adicionado ao endereço final do campo de dados. Você pode entender isso pelo valor 0x5608595d7000. Então aproximadamente 0x21000, ou 132 KB de memória foram alocados.

Há dois pontos importantes a serem considerados aqui. A primeira é a alocação de mais do que o valor especificado no código de amostra. Outra é qual linha de código causou a chamada brk que forneceu a alocação.

Randomização de layout de espaço de endereço: ASLR

Ao executar o aplicativo de exemplo acima um após o outro, você verá valores de endereço diferentes a cada vez. Fazer com que o espaço de endereço mude aleatoriamente dessa maneira complica significativamente o trabalho de ataques de segurança e aumenta a segurança do software.

No entanto, em arquiteturas de 32 bits, geralmente são usados ​​oito bits para randomizar o espaço de endereço. Aumentar o número de bits não será apropriado, pois a área endereçável sobre os bits restantes será muito baixa. Além disso, o uso de apenas combinações de 8 bits não torna as coisas difíceis o suficiente para o invasor.

Em arquiteturas de 64 bits, por outro lado, como há muitos bits que podem ser alocados para operação ASLR, uma aleatoriedade muito maior é fornecida e o grau de segurança aumenta.

O kernel Linux também alimenta Dispositivos baseados em Android e o recurso ASLR está totalmente ativado no Android 4.0.3 e posterior. Mesmo só por esse motivo, não seria errado dizer que um smartphone de 64 bits oferece uma vantagem de segurança significativa em relação às versões de 32 bits.

Ao desabilitar temporariamente o recurso ASLR com o seguinte comando, parecerá que o aplicativo de teste anterior retornará os mesmos valores de endereço toda vez que for executado:

eco0 | sudo tee /proc/sys/kernel/randomize_va_space

Para restaurá-lo ao seu estado anterior, bastará escrever 2 em vez de 0 no mesmo arquivo.

A segunda chamada do sistema: mmap

mmap é a segunda chamada de sistema usada para alocação de memória no Linux. Com a chamada mmap, o espaço livre em qualquer área da memória é mapeado para o espaço de endereço do processo de chamada.

Em uma alocação de memória feita desta forma, quando você deseja retornar a segunda partição de 16KB com a função free() no exemplo brk anterior, não há mecanismo que impeça esta operação. O segmento de memória relevante é removido do espaço de endereçamento do processo. Ele é marcado como não mais usado e retornado ao sistema.

Como as alocações de memória com mmap são muito lentas em comparação com aquelas com brk, a alocação de brk é necessária.

Com mmap, qualquer área livre de memória é mapeada para o espaço de endereço do processo, portanto, o conteúdo do espaço alocado é redefinido antes que esse processo seja concluído. Se a redefinição não for feita dessa forma, os dados pertencentes ao processo que anteriormente usava a área de memória relevante também poderão ser acessados ​​pelo próximo processo não relacionado. Isso tornaria impossível falar sobre segurança em sistemas.

Importância da alocação de memória no Linux

A alocação de memória é muito importante, especialmente em questões de otimização e segurança. Como visto nos exemplos acima, não entender completamente esse problema pode significar destruir a segurança do seu sistema.

Mesmo conceitos semelhantes a push e pop que existem em muitas linguagens de programação são baseados em operações de alocação de memória. Ser capaz de usar e dominar bem a memória do sistema é vital tanto na programação de sistemas embarcados quanto no desenvolvimento de uma arquitetura de sistema segura e otimizada.

Se você também quiser mergulhar no desenvolvimento do kernel do Linux, considere dominar a linguagem de programação C primeiro.

Uma breve introdução à linguagem de programação C

Leia a seguir

CompartilharTweetCompartilharE-mail

Tópicos relacionados

  • Linux
  • Memória do computador
  • Kernel Linux

Sobre o autor

Fatih Küçükkarakurt (7 Artigos Publicados)

Um engenheiro e desenvolvedor de software que é fã de matemática e tecnologia. Ele sempre gostou de computadores, matemática e física. Ele desenvolveu projetos de mecanismos de jogos, bem como aprendizado de máquina, redes neurais artificiais e bibliotecas de álgebra linear. Além disso continua a trabalhar em aprendizado de máquina e matrizes lineares.

Mais de Fatih Küçükkarakurt

Assine a nossa newsletter

Junte-se à nossa newsletter para dicas de tecnologia, análises, e-books gratuitos e ofertas exclusivas!

Clique aqui para assinar