Processo Linux (3) --- compreensão aprofundada do espaço de endereço do processo

Índice

Divisão e verificação do espaço de endereço

É a chamada memória de espaço de endereço?

Um fenômeno estranho (introdução de endereços virtuais)

O que é um espaço de endereço de processo?

A memória que costumamos acessar é a memória física?

Compreensão profunda da divisão regional

Vamos falar sobre fenômenos estranhos

Por que uma variável pode conter dois valores diferentes ao mesmo tempo em fork()

Expansão: Quando o programa não é compilado e carregado na memória, há um endereço dentro do programa?


Divisão e verificação do espaço de endereço

Quando escrevemos código antes, muitas vezes definimos algumas variáveis, incluindo variáveis ​​estáticas, variáveis ​​globais, variáveis ​​comuns ou variáveis ​​que se aplicam ao espaço, etc. Essas variáveis ​​também terão suas próprias posições correspondentes na memória . Acredito que você pode usar esta imagem mais ou menos visto.

Quer você tenha visto esta imagem ou não, você deve se lembrar com firmeza da localização de cada área no espaço de endereçamento.

Observe que o endereço da variável na área do heap cresce para cima, enquanto a área da pilha cresce para baixo. 

Escrevemos o seguinte código de código para verificar este gráfico.

  1 #include<stdio.h>  
  2 #include<stdlib.h>  
  3 //定义未初始化全局数据  
  4 int g_unInitVal;  
  5 //定义初始化全局数据  
  6 int g_InitVal=0;  
  7 int main()  
  8 {  
  9   printf("text:%p\n",main);  
 10   printf("init:%p\n",&g_InitVal);  
 11   printf("Uninit:%p\n",&g_unInitVal);                                                                                                                                                                        
 12     
 13   //定义堆区数据  
 14   char* p1 = (char*)malloc(sizeof(char));  
 15   char* p2 = (char*)malloc(sizeof(char));  
 16   char* p3 = (char*)malloc(sizeof(char));  
 17     
 18   printf("heap1:%p\n",p1);  
 19   printf("heap2:%p\n",p2);  
 20   printf("heap3:%p\n",p3);  
 21   
 22   //定义栈区数据  
 23   int a = 1,b = 2,c = 3;  
 24   
 25   printf("stack:%p\n",&a);  
 26   printf("stack:%p\n",&b);  
 27   printf("stack:%p\n",&c);  
 28   
 29   return 0;  
 30 }  

Saímos do vim, compilamos com make e executamos:

Você também pode ver que há uma grande parte da diferença de endereço entre o heap na área de heap e a pilha na área de pilha.Essa parte do espaço é chamada de espaço compartilhado , que será explicado mais adiante. 

Ao mesmo tempo, deixe-me explicar: as conclusões acima são válidas apenas no Linux, mas a maior parte do espaço de endereço do processo é baseada no Linux.

É a chamada memória de espaço de endereço?

Então há uma pergunta: o espaço desenhado está acima da memória?

Segundo nosso entendimento, sempre que executarmos o programa, diversas variáveis ​​ou códigos serão temporariamente salvos nessas áreas divididas da memória para uso posterior.

Mas a resposta é que a imagem não é uma memória real!

Então, o que é exatamente?O espaço de endereço é muito abstrato, o que será discutido mais adiante neste artigo. Explicar apenas um aspecto extrairá muito conhecimento relacionado; portanto, primeiro montamos uma prateleira e, gradualmente, introduzimos a explicação.

Observamos o seguinte fenômeno:

Um fenômeno estranho (introdução de endereços virtuais)

Sabemos que o processo pai fork() cria um processo filho, e o processo filho compartilhará as variáveis ​​globais do processo pai, então seus endereços são os mesmos.

 Vamos compilar:

Verificou-se que os endereços das duas variáveis ​​globais são de fato os mesmos.

Se fizermos uma pequena alteração no código neste momento, no processo filho, modifique o valor g_val para 20 e, em seguida, observe o valor do processo pai e filho novamente.

  Saímos e fazemos compilações:

 Neste momento, ocorre um fenômeno muito estranho, o valor da variável global g_val dos processos pai e filho é diferente.

Mas é estranho que seus endereços sejam os mesmos . Quando o mesmo endereço é lido ao mesmo tempo, aparecem valores diferentes, como isso é possível?

Mas podemos tirar uma conclusão importante disso:

O endereço dessas variáveis ​​aqui definitivamente não é o endereço da memória física, pois se for um endereço físico é absolutamente impossível que o valor da variável seja diferente quando lido ao mesmo tempo, deve ser o mesmo.

Na verdade, isso é chamado de endereço virtual (endereço linear).

E o que precisa ser dito é que quase todas as línguas, se tem o conceito de "endereço", então esse endereço não deve ser um endereço físico, mas sim um endereço virtual .

Então, o que é um endereço virtual? Como devemos entendê-lo? Por que é projetado assim? O que isso tem a ver com endereços físicos? E outras questões relacionadas, tentarei ajudá-lo a entender com o máximo de exemplos possível.

O que é um espaço de endereço de processo?

 Este é um exemplo para ajudar a entender:

Há um milionário americano que possui 1 bilhão de dólares americanos e tem 3 filhos ilegítimos, a saber, a, b e c.

Um dia, ele chamou A para si mesmo e disse: "Estude muito, e se você conseguir alguma coisa até lá, eu lhe darei todos os 1 bilhão de dólares americanos." Quando A estava feliz, ele estudou mais. Esforce-se para obter resultados de pesquisas científicas

Da mesma forma, no dia seguinte, ele chamou B para si mesmo novamente e disse: "Você é um empresário. Se você puder aumentar seu negócio no futuro, meu $ 1 bilhão será seu no futuro". meu próprio negócio com motivação e entusiasmo.

No terceiro dia, diga a mesma coisa sobre c.

Até agora, cada filho pensa que terá 1 bilhão de dólares, mas o dinheiro não chegou a eles agora, e eles não sabem da existência um do outro, e então todos trabalham mais. É equivalente a dar a cada filho um homem rico Tudo custou um grande bolo.

Nessa época, para conseguir 1 bilhão de dólares americanos, filho estudou muito, mas precisava de algum dinheiro para comprar mais livros e recursos, então um dia, ele foi até o homem rico e disse: "Pai, você pode me dar 100 Dólares americanos primeiro?" Preciso comprar alguns recursos." Quando o homem rico ouviu isso, ele poderia usá-lo para negócios, afinal. Assim é dado, assim como b e c.

Mas mesmo assim, todo mundo ainda acha que tem um bilhão

Da perspectiva de Deus, devemos saber que os três filhos não podem obter o 1 bilhão. Os três filhos só pedem ao homem rico esporadicamente todas as vezes. Devo também pensar que meu pai tem esse 1 bilhão. Mesmo que um certo filho queira um lote um dia, tipo 100 milhões, e meu pai fala que não tem tanto, e eu não posso dar, mesmo que falte o espaço do aplicativo, esses filhos vão achar que meu pai tem tal 100 milhões, 100 milhões , mas eles ainda não estão dispostos a me dar.

Se você entender esse significado, entenderá o espaço de endereçamento neste momento:

Aqui, o pai rico corresponde ao sistema operacional . Seus filhos (a, b, c) são 3 processos, e a torta de 1 bilhão que o pai desenhou para esses filhos é o espaço de endereço do processo.

As tortas na realidade , ou seja, o dinheiro que o rico realmente possui é a memória física. Ele desenhou não só uma torta para cada filho, mas muitos filhos terão muitas tortas. Portanto, essas tortas precisam ser administradas naquele momento . Como gerenciá-los? ? Descreva primeiro, organize depois!

O espaço de endereço no kernel é essencialmente uma estrutura de dados no futuro e também será associado a um processo específico no futuro. 

Como entendê-lo? Eu vou falar sobre isso mais tarde.

A memória que costumamos acessar é a memória física?

Precisamos saber: a própria memória física pode ser lida e escrita a qualquer momento, não existe algo ilegível, não gravável, etc.

O que acontece se você acessar a memória física diretamente?

Por exemplo, devido a operação incorreta do processo 1, é gerado um ponteiro selvagem, que aponta para o endereço do processo 2. Neste momento, modificar o ponteiro selvagem no processo 1 modificará diretamente os dados ou código do processo 2.

Ou, existem alguns dados de senha privados no processo 3, apontamos diretamente o ponteiro para este espaço no processo 1 e, em seguida, acessamos diretamente para obter os dados da senha, o que é absolutamente impossível.

Portanto, existe um problema fatal em acessar diretamente a memória física: é extremamente inseguro! E também pode causar alguns problemas de fragmentação de memória.

Portanto, é impossível para o nosso computador usar diretamente esse método de permitir diretamente que os usuários acessem endereços físicos.

Então, para resolver esse tipo de problema, os computadores modernos propõem o seguinte:

Sabemos que quando cada processo é iniciado, o sistema operacional criará uma estrutura task_struct para identificar todas as informações de atributo do processo e, em seguida, o sistema operacional também criará um espaço de endereço de processo para cada processo . Esse espaço de endereço é chamado de endereço virtual e então o sistema também Haverá um mecanismo de mapeamento (tabela de páginas) , não vamos falar sobre isso por enquanto, e então mapear a memória virtual para a memória física através do mecanismo de mapeamento.

 Haverá um atributo na estrutura task_struct para apontar para este espaço de endereço virtual.

Mas se o endereço virtual também for um endereço ilegal, após o mapeamento, ele ainda é ilegal no endereço físico.

De fato, quando um processo tenta acessar um endereço ilegal, como acessar uma área de memória não mapeada ou direitos de acesso ilegais, a MMU detectará esse erro e disparará uma exceção ou interrupção. será discutido mais tarde explicar.

Por exemplo, você ganha 500 mesadas durante o Ano Novo Chinês. É ótimo acessar diretamente os 500 yuans, mas quando você era jovem, e se você fosse enganado por outras pessoas, nessa época nossa mãe geralmente tirava nossa mesada. Aí você disse que eu te daria quando você precisasse, e aí você disse que queria comprar um livro para ler, sua mãe disse que sim, então ela deu para você, então depois de um tempo, você disse que eu queria comprar um brinquedo, sua mãe diz qual brinquedo comprar, gaste seu dinheiro no lugar certo! QAQ.

Aqui, a mãe desempenha um papel de gerenciamento e tem a capacidade de distinguir o certo do errado . Para distinguir se suas necessidades são legais ou não. Da mesma forma, se um endereço virtual ilegal for encontrado, o MMU nesse endereço virtual o proibirá de acessá-lo .

Isso equivale a proteger a memória física disfarçada!

Compreensão profunda da divisão regional

Então, como entendemos a divisão das regiões ?

A essência da divisão de área é definir o início e o fim de um intervalo, usado para identificar o início e o fim.

Por exemplo, quando estávamos no ensino fundamental, duas pessoas podem ter uma discussão e traçar a chamada "linha 38".

 O que precisa ser enfatizado aqui é: cada processo deve ter um espaço de endereço de processo!

O espaço de endereçamento é uma espécie de estrutura de dados do kernel, que deve ter no mínimo a divisão de cada área.

Abstraímos cada área em uma estrutura de dados.

O tamanho de cada área não é fixo, por exemplo, a pilha cresce para cima, mas na verdade o tamanho está mudando.

A chamada mudança de faixa é essencialmente para marcar o início ou o fim +/- uma faixa específica. 

No Linux, abstraímos a estrutura do espaço de endereço virtual do processo em uma estrutura chamada mm_struct.

Olhamos diretamente para o código-fonte do Linux, encontramos task_struct e encontramos mm_struct a partir dele, porque cada processo terá um espaço de endereço de processo, portanto, deve ser armazenado em task_struct como um atributo.

veja o que tem lá 

Você já viu, esta é a divisão de área, usando unsigned int para representar o início e o fim de cada tipo de dados de intervalo para identificar o intervalo.

Portanto, o processo final de um processo que acessa a memória física é o seguinte:

O espaço de endereço e a tabela de páginas (nível de usuário) terão uma cópia para cada processo. 

E quanto a vários processos , e se houver um conflito no local mapeado para a memória física ?

Contanto que você garanta que a tabela de páginas de cada processo mapeie diferentes áreas da memória física, você pode garantir que os processos não interfiram entre si e garantir a independência do processo!

Vamos falar sobre fenômenos estranhos

Olhando para trás agora, observe as razões pelas quais os processos pai e filho acessam a variável global g_val ao mesmo tempo no início, resultando em valores diferentes:

Aqui está uma foto para explicar.

Quando o processo filho não modifica g_val no início, tanto o processo pai quanto o processo filho primeiro passam pelo espaço de endereço virtual, depois passam pela tabela de páginas e, ao mesmo tempo, acessam o g_val do processo pai no físico memória

Quando o processo filho deseja modificar g_val , quando o processo filho passa por seu próprio espaço de endereço de processo e, em seguida, mapeia a tabela de páginas, ele abre um novo espaço no espaço físico neste momento e, em seguida, salva os dados modificados de o processo filho e, em seguida, ao remapear, ele é mapeado para o espaço recém-aberto.Esta operação é chamada de copy-on-write.

Por que uma variável pode conter dois valores diferentes ao mesmo tempo em fork()

Ao mesmo tempo, também pode responder à pergunta deixada no capítulo anterior: como uma variável pode conter dois valores diferentes ao mesmo tempo.

Sabemos que o motivo de fork() ter dois valores de retorno é que ele foi retornado duas vezes.

A essência do retorno é escrever o id.

Neste momento, ocorre copy-on-write durante a escrita, então o processo pai-filho realmente tem seu próprio espaço variável na memória física, mas é marcado com a mesma variável (endereço virtual) na camada do usuário !

Expansão: Quando o programa não é compilado e carregado na memória, há um endereço dentro do programa?

Deixe-me falar sobre a resposta primeiro: já existe um endereço.Quando o programa executável é compilado, já existe um endereço dentro dele.

Não entenda apenas o espaço de endereçamento como algo que precisa ser obedecido dentro do SO, o compilador também deve obedecê-lo! Ou seja, quando o compilador compila o código, ele já formou várias áreas para nós, área de código, área de dados, etc., e, da mesma forma que no kernel do Linux, cada variável e cada linha de código são compiladas. site. Portanto, quando o programa é compilado, cada campo já possui um endereço virtual!

Podemos entender fazendo um desenho.

Em primeiro lugar, no disco, o código que escrevemos já tem um endereço virtual correspondente quando o escrevemos. Por exemplo, a primeira linha do código é 0x1, a segunda linha é 0x10 e a terceira linha é 0x100. Mas para o lógica de execução, a primeira linha O endereço virtual da segunda linha deve ser salvo em uma linha de código e o endereço virtual da terceira linha também deve ser salvo na segunda linha de código. do seguinte modo:

Então, quando o programa estiver em execução, ele será carregado na memória física. Nesse momento, cada linha de código possui o endereço virtual da próxima linha de código e cada linha de código em si possui um endereço no endereço físico. Para exemplo, 0xA, 0xAA, 0xAAA.

Então, neste momento, o processo pode encontrar o endereço físico correspondente por meio do endereço virtual e, em seguida, acessar a tabela de páginas e acessá-la.

Mas a questão é: de onde vieram os dados iniciais do espaço de endereço e da tabela de páginas? 

Após a conclusão da compilação, o compilador preencherá automaticamente os endereços desses códigos no espaço de endereço virtual , e esses endereços de código serão fornecidos a você pelo compilador. Esses endereços virtuais serão preenchidos à esquerda da tabela de páginas , quando carregados na memória, e cada código tem seu próprio endereço físico. Nesse momento, o endereço físico é preenchido no lado direito da tabela de páginas. Dessa forma, cada processo pode encontrar o endereço físico correspondente por meio do endereço virtual no endereço do processo espaço e o relacionamento de mapeamento, codifique e execute-o.

 

Neste momento, a CPU começa a executar. A CPU tem a primeira instrução e obtém 0x1, então de acordo com a área de código no espaço de endereço, após o mapeamento da tabela de páginas, a instrução 0x1 é encontrada na memória física e, em seguida, lida para a CPU Neste momento, a CPU O interior da instrução lida é o endereço virtual 0x10 ! Depois que a CPU executa a instrução 0x1, ela passa pelo espaço de endereço virtual de acordo com 0x10 e, em seguida, encontra a instrução 0x10 por meio da tabela de páginas e a lê de volta para a CPU. Repita isso e a CPU pode executar todas as instruções.,

Esse é todo o processo. 

Este é o fim da explicação do espaço de endereçamento do processo.Sua essência é na verdade um espaço virtual!

Em seguida, ele é mapeado para o endereço físico por meio da tabela de páginas.

Se você tiver alguma dúvida ou não entender, sinta-se à vontade para comentar ou enviar uma mensagem privada~

Acho que você gosta

Origin blog.csdn.net/weixin_47257473/article/details/131735218
Recomendado
Clasificación