Em sistemas Linux moderons de 64 bits, o kernel gearlmente utiliza 48 bits para o endereçamento virtual e cerca de 45 bits para o endereçamento físico. Essas informações podem ser validadas inspecionando o arquivo /proc/cpuinfo, que detalha as capacidades de hardware e software do processador.
Organização da Memória no Kernel x86
O gerenciamento de memória é exposto ao usuário através do sistema de arquivos /proc/meminfo. Abaixo, categorizamos os principais indicadores de estado da memória:
- Memória Física:
MemTotal: Soma total da RAM física disponível.MemFree: Memória que não está sendo utilizada para nada no momento.MemAvailable: Estimativa de quanta memória está disponível para iniciar novos aplicativos sem causar swapping.
- Cache e Buffers:
Buffers: Armazenamento temporário para blocos de disco brutos.Cached: Cache de páginas de arquivos lidos do disco.
- Segmentação de Atividade:
Active/Inactive: Memória que foi usada recentemente versus memória que é candidata à remoção ou swap.AnonPages: Páginas mapeadas em espaço de usuário que não possuem suporte em arquivo (como pilhas e heaps).
- Slab Allocator:
Slab: Cache do kernel para estruturas de dados internas.SReclaimable: Parte do Slab que pode ser recuperada (como caches de inodes).
Alocação de Memória Dinâmica no Kernel
O kernel oferece diferentes interfaces para alocação, dependendo da necessidade de contiguidade física ou virtual:
alloc_pages: Aloca memória em unidades de páginas.vmalloc: Aloca memória virtualmente contígua, mas fisicamente dispersa. Útil para grandes buffers onde a contiguidade física não é crítica.kmalloc: Aloca memória fisicamente contígua. É a interface principal para pequenos objetos, operando sobre o sistema Slab.
Na arquitetura ARM64, o espaço de endereçamento é dividido de forma eqiulibrada:
- User Space:
0x0000_0000_0000_0000até0x0000_FFFF_FFFF_FFFF. - Kernel Space:
0xFFFF_0000_0000_0000até0xFFFF_FFFF_FFFF_FFFF.
Dentro do espaço do kernel, encontramos regiões específicas como KASAN (detecção de erros de memória), modules (código de drivers dinâmicos), .text (instruções do kernel) e a área de vmemmap (utilizada para gerenciar o array de estruturas page em sistemas com memória esparsa).
Gerenciamento do Heap em Espaço de Usuário
O heap é a região onde os processos alocam dados dinamicamente. No Linux, o gerenciamento dessa área ocorre principalmente através de duas chamadas de sistema que expandem o limite superior do segmento de dados (brk) ou criam novos mapeamentos (mmap).
1. Manipulação via brk/sbrk
A função brk() altera o program break, que define o fim do segmento de dados do processo.
#include <stdio.h>
#include <unistd.h>
int main() {
// Obtém o endereço atual do break
void *ptr_inicial = sbrk(0);
if (ptr_inicial == (void *)-1) return 1;
// Incrementa o heap em 2048 bytes
void *bloco_novo = sbrk(2048);
if (bloco_novo == (void *)-1) return 1;
printf("Início do bloco alocado: %p\n", bloco_novo);
// Reduz o heap de volta ao estado original
if (brk(ptr_inicial) == -1) {
perror("Erro ao ajustar brk");
return 1;
}
return 0;
}
2. Alocação via mmap
Para alocações maiores, o mmap() é preferido, criando um mapeamento anônimo que não está vinculado a um arquivo.
#include <stdio.h>
#include <sys/mman.h>
int main() {
size_t tamanho = 4096; // 1 página típica
// Alocação de memória anônima e privada
void *area_mem = mmap(
NULL,
tamanho,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0
);
if (area_mem == MAP_FAILED) {
perror("mmap falhou");
return 1;
}
printf("Memória mapeada em: %p\n", area_mem);
// Liberação da região
if (munmap(area_mem, tamanho) == -1) {
return 1;
}
return 0;
}
Contadores Per-CPU e Escalabilidade
Em sistemas SMP (Symmetric Multi-Processing), a contenção de locks pode degradar o desempenho se vários núcleos tentarem atualizar o mesmo contador global. O Linux resolve isso com per-CPU counters.
A ideia fundamental é manter uma cópia do contador para cada CPU. Cada núcleo incrementa apenas sua variável local, eliminando a necessidade de travas (locks) durante a escrita. A soma total só é calculada quando uma leitura do valor global é solicitada.
#include <linux/percpu_counter.h>
struct percpu_counter estatistica_global;
// Inicialização (geralmente no init do módulo)
void iniciar_contagem(void) {
percpu_counter_init(&estatistica_global, 0, GFP_KERNEL);
}
// Incremento rápido sem contenção de lock
void registrar_evento(void) {
percpu_counter_inc(&estatistica_global);
}
// Leitura agregada dos valores de todas as CPUs
void mostrar_total(void) {
s64 total = percpu_counter_read_positive(&estatistica_global);
pr_info("Total acumulado: %lld\n", total);
}
// Limpeza
void finalizar_contagem(void) {
percpu_counter_destroy(&estatistica_global);
}