Organização do espaço de endereço lógico do Kernel Linux

8

De acordo com "Write Great Code", em quase todo o SO, a memória de tempo de execução é organizada nas seguintes regiões:

OS Pilha | Montão | Texto Estático Armazenamento / BSS

[De forma crescente ao endereço]

O processo de espaço do usuário usa uma região de memória superior para seus diferentes tipos de objetos de dados.

O processo de espaço do kernel também possui diferentes tipos de objetos de dados. Esses objetos compartilham as regiões de memória do espaço do usuário (pilha, pilha etc.) ou possuem subseções separadas (pilha, pilha etc.) localizadas na região do sistema operacional. . Obrigado,

gkt
fonte

Respostas:

5

Está errado sobre o pedido. O sistema operacional está localizado na parte superior da memória, geralmente acima da marca de 3 GB (0xC0000000) no kernel de 32 bits, e no kernel de 64 bits é o ponto intermediário de 0x8000000000000000 IIRC.

A localização da pilha e da pilha é aleatória. Não há uma regra real sobre a ordenação dos segmentos de texto / dados / bss no programa principal, e toda biblioteca dinâmica possui seu próprio conjunto, portanto há muitas espalhadas por toda a memória.

Quando os dinossauros governavam a Terra (há mais de 20 anos), o espaço de endereço do programa era linear (sem buracos) e a ordem era texto, dados, bss, pilha e pilha. Também não havia bibliotecas dinâmicas ou threading na época. Tudo isso mudou com a memória virtual.

Os processos do kernel estão totalmente contidos na parte do kernel do espaço de endereço; a parte do usuário é ignorada. Isso permite que o kernel acelere a alternância de contexto entre os threads do kernel, pois não precisa atualizar as tabelas de páginas, pois todos os processos compartilham a mesma porção de kernel das tabelas de páginas.

psusi
fonte
4

Isso não é verdade para "quase todo o SO". Os tipos de áreas de memória representadas são bastante típicos, mas não há motivo para que eles estejam em uma ordem específica e pode haver mais de uma parte de um determinado tipo.

No Linux, você pode examinar o espaço de endereço de um processo com cat /proc/$pid/mapsonde $pidestá o ID do processo, por exemplo, cat /proc/$$/mapspara ver o shell do qual você está executando catou cat /proc/self/mapspara ver os catmapeamentos do próprio processo. O comando pmapproduz uma saída um pouco melhor.

08048000-08054000 r-xp 00000000 08:01 828061     /bin/cat
08054000-08055000 r--p 0000b000 08:01 828061     /bin/cat
08055000-08056000 rw-p 0000c000 08:01 828061     /bin/cat
08c7f000-08ca0000 rw-p 00000000 00:00 0          [heap]
b755a000-b7599000 r--p 00000000 08:01 273200     /usr/lib/locale/en_US.utf8/LC_CTYPE
b7599000-b759a000 rw-p 00000000 00:00 0 
b759a000-b76ed000 r-xp 00000000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76ed000-b76ee000 ---p 00153000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76ee000-b76f0000 r--p 00153000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76f0000-b76f1000 rw-p 00155000 08:01 269273     /lib/tls/i686/cmov/libc-2.11.1.so
b76f1000-b76f4000 rw-p 00000000 00:00 0 
b770b000-b7712000 r--s 00000000 08:01 271618     /usr/lib/gconv/gconv-modules.cache
b7712000-b7714000 rw-p 00000000 00:00 0 
b7714000-b7715000 r-xp 00000000 00:00 0          [vdso]
b7715000-b7730000 r-xp 00000000 08:01 263049     /lib/ld-2.11.1.so
b7730000-b7731000 r--p 0001a000 08:01 263049     /lib/ld-2.11.1.so
b7731000-b7732000 rw-p 0001b000 08:01 263049     /lib/ld-2.11.1.so
bfbec000-bfc01000 rw-p 00000000 00:00 0          [stack]

Você pode ver o código e os dados de leitura e gravação (texto e BSS) do executável, o heap, o arquivo mapeado na memória, um pouco mais de dados de leitura e gravação, código, dados somente leitura e leitura. grave dados de uma biblioteca compartilhada (texto e BSS novamente), mais dados de leitura e gravação, outra biblioteca compartilhada (mais precisamente, o vinculador dinâmico) e, finalmente, a única pilha do encadeamento.

O código do kernel usa seus próprios intervalos de endereços. Em muitas plataformas, o Linux usa a parte superior do espaço de endereço para o kernel, geralmente o superior de 1 GB. Idealmente, esse espaço seria suficiente para mapear o código do kernel, os dados do kernel e a memória do sistema (RAM) e todos os dispositivos mapeados na memória. Nos PCs típicos de 32 bits atuais, isso não é possível, o que requer contorções que são de interesse apenas dos hackers do kernel.

Enquanto o código do kernel está lidando com uma chamada do sistema, idealmente (quando as contorções acima mencionadas não estão no lugar), a memória do processo é mapeada nos mesmos endereços. Isso permite que os processos passem dados para o kernel, e o kernel pode ler diretamente do ponteiro. Porém, não é um grande ganho, já que os ponteiros precisam ser validados de qualquer maneira (para que o processo não consiga induzir o kernel a ler na memória que o processo não deveria ter acesso).

As zonas de memória dentro do espaço do kernel do Linux são bastante complexas. Existem vários conjuntos de memórias diferentes, e as principais distinções não são de onde vem a memória, mas com quem é compartilhada. Se você estiver curioso sobre eles, comece com LDD3 .

Gilles 'SO- parar de ser mau'
fonte
1

Não é uma resposta, mas uma FYI que precisa de mais espaço.

Não acho que sua concepção de layout de endereço lógico esteja correta.

Você pode compilar e executar este programa para ver o que um processo de terra do usuário possui para endereços:

#include <stdio.h>
long global_initialized = 119234;
long global_uninitialized;
extern int _end, _edata, _etext;
int
main(int ac, char **av)
{
        long local;

        printf("main at 0x%lx\n", main);
        printf("ac at   0x%lx\n", &ac);
        printf("av at   0x%lx\n", &av);
        printf("av has  0x%lx\n", av);
        printf("initialized global at 0x%lx\n", &global_initialized);
        printf("global at             0x%lx\n", &global_uninitialized);
        printf("local at              0x%lx\n", &local);
        printf("_end at               0x%lx\n", &_end);
        printf("_edata at             0x%lx\n", &_edata);
        printf("_etext at             0x%lx\n", &_etext);
        return 0;
}

O Red Hat Enterprise Server que eu estou executando, possui readelf, que pode ser usado para dizer onde o kernel carregaria (logicamente) um arquivo executável:

readelf -S where

Mostra-me muitas das mesmas informações de endereçamento que a saída de wheregive.

Eu não acho readelfque funcione facilmente em um kernel Linux (/ boot / vmlinuz ou algo parecido), e acho que o kernel, por padrão, começa em 0x80000000 em seu próprio espaço de endereço: ele não é mapeado em um processo da terra do usuário, apesar de usar um endereço acima da pilha da terra do usuário em 0x7fffffff (x86, endereçamento de 32 bits).

Bruce Ediger
fonte
Obrigado pelo exemplo! Apenas minha nota sobre a parte Linux - eu apenas tentei este exemplo como where.c, no Ubuntu 11.04 usando gcc where.c -o where; relatórios "principal em 0x80483c4". Tentei readelf -S where, e relata, dizer "[13] .text PROGBITS 08048310 ...", o que parece certo? Embora eu também receba "ac em 0xbfb035a0" e "local em 0xbfb0358c", e esse intervalo de endereços (0xbf ...) parece não ser relatado por readelf -S.
Sdaau
@sdaau - Os argumentos ace ava variável automática localprovavelmente terão endereços diferentes em cada chamada. A maioria dos kernels Linux modernos tem "Randomização do Layout do Espaço de Endereço" para dificultar a exploração dos estouros de buffer.
precisa saber é o seguinte