Acredito que a resposta realmente curta é que os compiladores do Linux organizam o código em partes, pelo menos, um deles é apenas código puro e, portanto, pode ser mapeado na memória em mais de um espaço de endereço do processo. Todos os globals são mapeados de tal forma que cada processo recebe sua própria cópia.
Você pode ver isso usando readelf
ou objdump
, mas readelf
dá uma imagem mais clara, eu acho.
Aqui está uma saída de readelf -e /usr/lib/libc.so.6
. Essa é a biblioteca C, provavelmente mapeada em quase todos os processos. A parte relevante de readelf
output (embora tudo seja interessante) é o cabeçalho do programa:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x00000034 0x00000034 0x00140 0x00140 R E 0x4
INTERP 0x164668 0x00164668 0x00164668 0x00017 0x00017 R 0x4
[Requesting program interpreter: /usr/lib/ld-linux.so.2]
LOAD 0x000000 0x00000000 0x00000000 0x1adfc4 0x1adfc4 R E 0x1000
LOAD 0x1ae220 0x001af220 0x001af220 0x02c94 0x057c4 RW 0x1000
DYNAMIC 0x1afd90 0x001b0d90 0x001b0d90 0x000f8 0x000f8 RW 0x4
NOTE 0x000174 0x00000174 0x00000174 0x00044 0x00044 R 0x4
TLS 0x1ae220 0x001af220 0x001af220 0x00008 0x00048 R 0x4
GNU_EH_FRAME 0x164680 0x00164680 0x00164680 0x06124 0x06124 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
GNU_RELRO 0x1ae220 0x001af220 0x001af220 0x01de0 0x01de0 R 0x1
As duas linhas LOAD são as únicas partes do arquivo que são mapeadas diretamente na memória. O primeiro cabeçalho LOAD mapeia uma parte de /usr/lib/libc.so.6
na memória com permissões R e E: leia e execute. Esse é o código. Os recursos de hardware impedem que um programa grave nessa parte da memória, de modo que todos os programas podem compartilhar as mesmas páginas de memória física real. O kernel pode configurar o hardware para mapear a mesma memória física em todos os processos.
O segundo cabeçalho LOAD está marcado como RW - ler e gravar. Esta é a parte com variáveis globais que a biblioteca C usa. Cada processo obtém sua própria cópia na memória física, mapeada no espaço de endereço desse processo com as permissões de hardware configuradas para permitir leitura e gravação. Essa seção não é compartilhada.
Você pode ver esses mapeamentos de memória em um processo em execução usando o sistema de arquivos /proc
. Um bom comando para ilustrar: cat /proc/self/maps
. Isso lista todos os mapeamentos de memória que o processo cat
possui e de quais arquivos o kernel os obteve.
No que diz respeito a quanto você precisa fazer para garantir que sua função seja alocada para a memória que é mapeada em diferentes processos, é praticamente tudo para as sinalizações que você dá ao compilador. Código destinado a bibliotecas compartilhadas ".so" é compilado "independente de posição". O código independente de posição faz coisas como referir-se a localizações de memória de variáveis com desvios em relação à instrução atual e salta ou ramifica para localizações relativas à instrução atual, em vez de carregar de ou escrevendo para endereços absolutos e saltar para endereços absolutos. Isso significa que a parte "RE" LOAD de /usr/lib/libc.so
e a peça "RW" só precisam ser carregadas em endereços que estejam à mesma distância em cada processo. Em seu código de exemplo, a variável static
sempre será pelo menos um múltiplo de um tamanho de página à parte do código que a referencia, e sempre será carregada a distância no espaço de endereço de um processo devido à maneira como a LOAD Cabeçalhos ELF são dados.
Apenas uma nota sobre o termo "memória compartilhada": Há um sistema de memória compartilhada no nível do usuário, associado ao "Sistema de comunicação entre processos do System V". Essa é uma forma de mais de um processo compartilhar explicitamente uma parte da memória. É bastante complicado e obscuro para configurar e ficar correto. A memória compartilhada da qual estamos falando aqui é mais ou menos completamente invisível para qualquer processo do usuário. Seu código de exemplo não saberá a diferença se estiver sendo executado como código independente de posição compartilhado entre vários processos ou se for a única cópia de sempre.