Fale sobre essas coisas sobre memória (com base no sistema de microcontrolador)

RAM e ROM de MCU

A ROM do microcomputador de chip único é chamada de memória de programa somente leitura, que é composta de memória FLASH, como uma unidade flash USB. Portanto, FLASH e ROM são sinônimos. O programa do computador de um chip é escrito em FLASH.

RAM é uma memória de leitura / gravação aleatória, usada como memória de dados, para armazenar dados durante a execução do programa.

Área de memória

A memória é dividida principalmente em várias áreas: área de código, área constante, área estática (área global), área de heap e área de pilha.

l   Área de código: Armazena o código do programa, ou seja, as instruções da máquina executadas pela CPU, e é somente leitura.

l   Área constante: armazenar constantes (quantidade que não pode ser alterada durante a execução do programa, por exemplo: 25, constante de string "dongxiaodong", o nome da matriz, etc.)

l   Área estática (área global) : A área de armazenamento de variáveis ​​estáticas e variáveis ​​globais é a mesma, uma vez que a memória na área estática é alocada, a memória na área estática não será liberada até que o programa termine.

l   Área de heap: O programador chama a função malloc () para se candidatar ativamente a ela e a função free () é necessária para liberar a memória. Se a memória da área de heap for solicitada e depois se esquecer de liberar a memória, é fácil causar um vazamento de memória.

l   Área de pilha: armazena variáveis ​​locais, parâmetros formais e valores de retorno de função na função. Após o escopo dos dados na área de pilha passar, o sistema irá recuperar a memória da área de pilha de gerenciamento automático (alocar memória, recuperar memória), sem a necessidade de desenvolvedores para gerenciá-la manualmente. A área da pilha é como uma pousada. Há muitos quartos nela. Depois que os hóspedes chegam, os quartos são alocados automaticamente. Os hóspedes nos quartos podem ser alterados, o que é uma mudança dinâmica de dados.

STM32F103C8T6

O endereço inicial da ROM é: 0x8000000 e o tamanho é: 0x10000 (64K)

Somente leitura, armazenando a área do código e a área constante

O endereço inicial da RAM é: 0x20000000, e o tamanho é: 0x5000 (20K)

Legível e gravável, armazenando área estática, área de pilha e área de heap

Introdução detalhada de cada área do STM32:

Área de código:

l A área de código armazena as instruções de CPU compiladas

l O nome da função é um ponteiro, você pode consultar o endereço da memória onde o nome da função está localizado e consultar a área onde a função está armazenada

Copiar código

1 // Declaração da função 
 2 void dong (); 
 3 // Definição da função principal 
 4 int main (void) 
 5 { 
 6 // Inicialização da porta serial 
 7 Uart1_Init (115200); 
 8 // Chamada de função 
 9 dong (); 
10 // Saída endereço de função de teste 
11 printf ("dong () addrs: 0x% p \ n", dong); 
12      
13 while (1); 
14} 
15 void dong () { 
16 // saída do endereço de função principal 
17 printf ("mian ( ) addrs: 0x% p \ n ", principal); 
18}

Copiar código

Resultado:

Pode-se ver que tanto 0x08000c2d quanto 0x08000be9 estão na área de código na ROM

Área constante

O ponteiro pode apontar para a área constante ou variável. Use o ponteiro (char * p) para testar a mudança de endereço entre a constante e a variável.

Copiar código

1 void dongxiaodong_fun () { 
 2 char * p = NULL; // define uma variável de ponteiro 
 3 // constante 
 4 p = "2020dongxiaodong"; // o ponteiro aponta para uma constante 
 5 printf ("addr1: 0x% p \ r \ n" , p); // endereço de saída constante 
 6 // variável 
 7 char data [] = {"dong1234"}; 
 8 p = data; // ponteiro aponta para uma variável 
 9 printf ("addr2: 0x% p \ r \ n" , p); // endereço da variável de saída 
10}

Copiar código

Resultado:

Pode-se ver que o endereço da constante está na área constante da ROM, e a variável local está no espaço da pilha de RAM

Zona estática

A área estática inclui variáveis ​​estáticas e variáveis ​​globais. Variáveis ​​estáticas são modificadas por estáticas e, uma vez inicializadas, sempre ocupam espaço na RAM

Copiar código

1 int global_a; // Variáveis ​​globais, o valor padrão é 0 
 2 static int global_b; // Variáveis ​​globais estáticas, o valor padrão é 0 
 3 void fun () { 
 4 static int c; // Variáveis ​​estáticas, o valor padrão é 0 
 5 printf ("static int c add: 0x% p, val:% d \ r \ n", & c, c); 
 6 c ++; 
 7} 
 8 void dongxiaodong_fun () { 
 9 // saída de variáveis ​​globais 
10 printf ("int a add : 0x% p, val:% d \ r \ n ", & global_a, global_a); 
11 printf (" static int b add: 0x% p, val:% d \ r \ n ", & global_b, global_b); 
12 / / Função de chamada para visualizar as variáveis ​​estáticas 
13 para (int i = 0; i <3; i ++) { 
14 fun (); 
15} 
16}

Copiar código

Resultado:

Entre eles, global_a é uma variável global, global_b é uma variável estática global e c é uma variável estática local. Se eles não forem atribuídos a um valor inicial, eles receberão automaticamente um valor 0 pelo sistema. A inicialização de variáveis ​​estáticas é sempre válida e não será afetada por várias chamadas para a instrução de inicialização. Existem vários problemas de inicialização. Embora pareça que a variável c seja inicializada três vezes no código, na verdade ela só é válida pela primeira vez.

Área de pilha

A área de heap é o espaço de memória solicitado ao chamar a função malloc. Depois que essa parte do espaço for usada, a função free () deve ser chamada para liberar o espaço solicitado. Void * malloc (size_t); O parâmetro da função é o tamanho do espaço a ser alocado em bytes, e o retorno é um ponteiro do tipo void *, que aponta para o primeiro endereço do espaço alocado. O ponteiro do tipo void * pode ser convertido em qualquer outro tipo de ponteiro.

l A pilha cresce para cima, ou seja, cresce na direção de aumentar o primeiro endereço

l O espaço solicitado por malloc () deve ser liberado por free (). Se a memória solicitada não for liberada, isso pode causar um vazamento de memória

A falha do aplicativo de memória l malloc () retornará NULL

l O espaço de memória alocado por malloc é logicamente contínuo, mas pode não ser fisicamente contínuo.

l Release só pode ser lançado uma vez, e se for lançado duas ou mais vezes, ocorrerá um erro (exceto para o lançamento de um ponteiro nulo, o lançamento de um ponteiro nulo na verdade significa que nada foi feito, então ele pode ser lançado tantas vezes quanto possível), free () Depois de liberar o espaço, você pode apontar o ponteiro para "NULL" para garantir que o ponteiro não se tornará um ponteiro selvagem.

STM32C8T6 :

A biblioteca padrão define o tamanho de heap padrão como 0x200 = 512 bytes.Pode ser considerado que o tamanho de alocação de malloc do programa ao mesmo tempo não pode ser maior que 512 bytes de dados.

O espaço de heap não é residente no espaço de RAM por padrão, mas quando a palavra-chave malloc aparece no código, o espaço de heap será alocado com o tamanho geral definido (512 bytes) para ocupar espaço de RAM.

Copiar código

void dongxiaodong_fun () { 
  // 申请
    printf ("----- malloc ----- \ r \ n"); 
    char * p1 = malloc (100); 
    if (p1 == NULL) printf ("p1 malloc falhou \ r \ n"); 
    char * p2 = malloc (1024); 
    if (p2 == NULL) printf ("p2 malloc falha \ r \ n"); 
    
    // 赋值  
    memcpy (p1, "dongxiaodong123456", strlen ("dongxiaodong123456")); 
    
    printf ("p1 addr:% p, val :% s \ r \ n", p1, p1); 
    printf ("p2 addr:% p \ r \ n", p2); 
    
    
    // 释放
    printf ("----- free ----- \ r \ n"); 
    livre (p1); 
    livre (p2); 
    
    printf ("p1 addr:% p, val :% s \ r \ n", p1, p1); 

    
    p1 = NULL; 
    printf ("p1 addr:% p \ r \ n", p1); 
    
}

Copiar código

Resultado:

Pode-se ver que se a alocação de espaço de heap falhar, ele retornará NULL, e o endereço aponta para 0x00. Quando é lançado, é apenas por meio de free (), que apenas altera o conteúdo apontado para um valor nulo, mas o endereço ainda existe, então a prática padrão é atribuir " Valor NULL ". Depois que a memória é liberada (o endereço salvo pela variável de ponteiro p em si não mudou depois de usar a função free), você precisa atribuir o valor de p a NULL (amarrar o ponteiro selvagem).

O espaço alocado não pode atingir o valor máximo especificado:

void dongxiaodong_fun () { 
       char * d = malloc (512); 
       // char * d = malloc (500); // 可行
       if (d == NULL) printf ("512 malloc fail \ r \ n"); 
}

Resultado:

Ver explicação:

Se malloc (n) é usado para alocar memória heap, então a memória alocada é maior do que N. Por quê?

0. A memória alocada por malloc não é necessariamente contígua, então o ponteiro do cabeçalho é necessário para ligar cada parte

1. A memória heap real alocada é a estrutura Header + n. O que é retornado ao usuário é o primeiro endereço da parte n, então ele ainda tem uma parte da memória usada para armazenar o cabeçalho, então é maior que o original

2. Devido ao valor de alinhamento de memória de 8, o mecanismo de alinhamento de memória, a memória heap real alocada é maior ou igual a sizeof (Cabeçalho) + n

Área de pilha

A área de pilha é automaticamente alocada e liberada pelo compilador. Ela armazena valores de parâmetros, valores de retorno e variáveis ​​locais definidas na função. É alocada e liberada em tempo real durante a operação do programa. A área de pilha é gerenciada automaticamente pelo sistema operacional sem gerenciamento manual. A área da pilha é um princípio do primeiro a entrar e último a sair.

l A pilha cresce para baixo, ou seja, cresce na direção de diminuir o primeiro endereço

l O compilador não atribuirá um valor inicial de 0 a variáveis ​​locais não inicializadas, portanto, variáveis ​​locais não inicializadas são geralmente um valor confuso, por isso é mais seguro atribuir valores iniciais ao definir variáveis ​​locais.

STM32C8T6:

A biblioteca padrão define o tamanho da pilha padrão como 0x400 = 1024 bytes, podendo-se considerar que as variáveis ​​locais do programa ao mesmo tempo não podem ser maiores que 1024 bytes de dados.

O número de bytes do espaço da pilha é o espaço residente. Uma vez inicializado, o tamanho geral (1024 bytes) da configuração será alocado para ocupar o espaço da RAM.

Copiar código

1 // Definição da função principal 
 2 int main (void) 
 3 { 
 4 // Inicialização da porta serial 
 5 Uart1_Init (115200); 
 6 printf ("start SYS 1 \ r \ n"); 
 7 char data1 [1024] = {0}; // 1024 bytes 
 8 printf ("iniciar SYS 2 \ r \ n"); 
 9 char data2 [100] = {0}; // 100 bytes 
10 printf ("iniciar SYS 3 \ r \ n"); 
11 char data3 [100] = {0}; // 100 bytes, 10 bytes podem ser executados normalmente 
12 printf ("start SYS 4 \ r \ n"); 
13 while (1); 
14}

Copiar código

A medição real descobriu que o espaço da pilha pode funcionar normalmente até 1024 + 100 + 10 bytes. É porque o STM32 reserva o espaço da pilha? 1024 não é um limite obrigatório completo.

Teste de endereço

Copiar código

vazio dongxiaodong_fun () { 
int a = 100; 
    int b; 
    printf ("um addr: 0x% p val:% d \ r \ n", & a, a); 
    printf ("b addr: 0x% p val:% d \ r \ n", & b, b); 
}

Copiar código

Resultado:

Pode-se ver que o endereço de b é menor do que o endereço de a, que está aumentando no sentido de diminuir o primeiro endereço (aumentando para baixo). O valor de b não é atribuído a um valor inicial e seu valor é confuso. Recomenda-se usar o valor inicial.

Nota:

dados modificados const

l const modifica o nome da variável. O motivo pelo qual ela é chamada de constante const significa que ela não pode ser alterada e a permissão é somente leitura, mas sua essência é uma variável, mas é uma variável não modificável

l const variáveis ​​locais modificadas são armazenadas na área da pilha, se variáveis ​​globais modificadas são armazenadas na área estática (área global)

Armazenamento de dados (modo pequeno e pequeno)

Os dados são armazenados na memória, divididos em modo big-endian e modo little-endian

Modo big-endian: O byte inferior é armazenado no endereço superior e o byte superior é armazenado no endereço inferior.

Modo little-endian: O byte inferior é armazenado no endereço inferior e o byte superior é armazenado no endereço superior.

Ordem de bytes da rede: os protocolos TCP / IP definem a sequência de bytes como modo big-endian, portanto, o modo big-endian usado no protocolo TCP / IP é normalmente chamado de ordem de bytes da rede.

Copiar código

void dongxiaodong_fun () { 
    dados internos = 0x12345678; 
    char * p = (char *) & data; 
    printf ("p + 0: 0x% p -> 0x% 02X \ r \ n", p, * (p + 0)); 
    printf ("p + 1: 0x% p -> 0x% 02X \ r \ n", p, * (p + 1)); 
    printf ("p + 2: 0x% p -> 0x% 02X \ r \ n", p, * (p + 2)); 
    printf ("p + 3: 0x% p -> 0x% 02X \ r \ n", p, * (p + 3)); 
}

Copiar código

Resultado:

Pode ser visto que o bit mais alto de seu valor é armazenado no bit mais baixo do endereço, então o armazenamento da variável de STM32 é o modo little-endian

Fragmentação do aplicativo de memória dinâmica

A alocação de memória dinâmica padrão é gerenciada por uma lista vinculada dinâmica. Como malloc retorna um ponteiro e o microcontrolador não tem MMU, os ponteiros alocados ficam na memória como pregos até serem liberados. Isso tornará o gerenciamento da memória muito difícil, levando à fragmentação da memória.

Este é um exemplo extremo ideal

O espaço de heap do microcomputador de chip único é alocado 1 KB de espaço, que é 1024 bytes. Para a conveniência de explicação e cálculo, ignoramos o espaço ocupado pela lista vinculada e calculamos apenas o espaço de armazenamento real.

Etapa 1: Aplicar para 64 blocos de espaço de memória, cada bloco de 16 bytes, então 1K bytes de espaço serão alocados.

char * p [64] = {NULL}; 
para (int i = 0; i <64; i ++) { 
    ptr [i] = malloc (16); 
}

Etapa 2: libere o espaço de memória de numeração par

para (int i = 0; i <64; i + = 2) { 
    livre (ptr [i]); 
    ptr [i] = NULL; 
}

terceiro passo:

O espaço que liberamos atingiu metade do tamanho do heap, 512 bytes, mas todos são descontínuos. 32 blocos de 16 bytes de espaço não contíguo, portanto, é impossível alocar blocos de memória maiores que 16 bytes. Há 512 bytes de espaço, mas apenas espaço contínuo com menos de 16 bytes pode ser alocado.Em algumas ocasiões, os recursos de espaço de heap originais da RAM MCU são muito apertados, e esse uso insuficiente torna a estabilidade do programa bastante comprometida.

STM32C8T6 caso real:

A fragmentação da memória pode ser verificada pelo seguinte sub:

Copiar código

1 void dongxiaodong_fun () { 
 2 char * p [8] = {NULL}; 
 3 // 512 bytes de espaço de heap, parece que apenas 8 * 50 = 400 bytes podem ser alocados 
 4 para (int i = 0; i <8 ; i ++) { 
 5 p [i] = malloc (50); 
 6 if (p [i] == NULL) printf ("p [% d] malloc falha \ r \ n", i); 
 7} 
 8 // Envie o endereço de um dos números 
 9 printf ("% p \ r \ n", p [2]); 
10 printf ("% p \ r \ n", p [3]); 
11 // Libere o espaço subscrito de numeração par 
12 para (int i = 0; i <8; i + = 2) { 
13 livre (p [i]); 
14 p [i] = NULL; 
15} 
16 // falha na alocação, fragmentação da memória 
17 char * d1 = malloc (100); // Viável 
18 if (d1 == NULL) printf ("d1 100 malloc fail \ r \ n"); 
19      
20 // Libera um espaço de bit ímpar 
21 livre (p [3]);
22 // A alocação foi bem-sucedida, o espaço alocado está no espaço de p [2] ep [3] e 10 bytes de espaço extra 
23 char * d2 = malloc (160); 
24 if (d2 == NULL ) printf ("d2 100 malloc fail \ r \ n"); 
25 printf ("% p \ r \ n", d2); 
26}

Copiar código

Resultado:

Este exemplo geralmente reflete o problema de fragmentação da memória, porque há um total de 8 espaços, e a liberação de blocos ímpares após a aplicação é teoricamente 50 * 4 = 200 bytes, mas a alocação de 100 bytes não funciona, razões importantes O tamanho do bloco de numeração par liberado é 50 e seu endereço não é contínuo. Quando um dos blocos ímpares é liberado, a memória pode atingir o tamanho do bloco contíguo que precisa ser alocado, portanto, o espaço alocado usa o espaço de p [2], p [3] e p [4].

 

Existem vários problemas:

O espaço alocado por Malloc pode ser 512 no total, mas um pacote pode ter apenas cerca de 500 espaço efetivo e 8 pacotes têm cerca de 400. Por que a taxa de utilização é tão baixa?

No teste de fragmentação, o tamanho de p [2], p [3] e p [4] deve ser 3 * 50 = 150, e o resultado máximo pode ser cerca de 160.

 

Ver explicação:

Se malloc (n) é usado para alocar memória heap, então a memória alocada é maior do que N. Por quê?

0. A memória alocada por malloc não é necessariamente contígua, então o ponteiro do cabeçalho é necessário para ligar cada parte

1. A memória heap real alocada é a estrutura Header + n. O que é retornado ao usuário é o primeiro endereço da parte n, então ele ainda tem uma parte da memória usada para armazenar o cabeçalho, então é maior que o original

2. Devido ao valor de alinhamento de memória de 8, o mecanismo de alinhamento de memória, a memória heap real alocada é maior ou igual a sizeof (Cabeçalho) + n

 

A principal solução para a fragmentação da memória:

Mova as pequenas memórias espaçadas lado a lado para liberar espaço contínuo

O método de alocação de memória de página de segmento comumente usado é dividir a área de memória do processo em diferentes segmentos e, em seguida, cada segmento é composto de várias páginas de tamanho fixo. Por meio do mecanismo de tabela de páginas, as páginas do segmento não precisam estar na mesma área de memória continuamente, reduzindo assim a fragmentação externa. No entanto, ainda pode haver uma pequena quantidade de fragmentação interna na mesma página, mas o espaço de memória de uma página é inerentemente pequeno, o que torna isso possível Existem também menos fragmentos internos.

 

A função mymalloc () do átomo pontual

Pergunta 1: Por que isso acontece novamente na biblioteca padrão da função Malloc?

Pergunta 2: como lidar com a fragmentação da memória?

Resumindo:

l Pode gerenciar a memória de vários RAMs, como SRAM externa, para facilitar o gerenciamento de vários espaços de RAM

l Você pode ver o uso de memória

l Sem processamento de fragmentação de memória

STM32 ver espaço FLASH e uso de espaço de RAM

Abra a tela:

 Saída após a compilação:

 

Tamanho do programa: Código = 38356 dados RO = 6676 dados RW = 400 dados ZI = 47544

 

Código: o espaço ocupado pelo código

Dados RO: onde RO significa somente leitura, o tamanho da luz constante somente leitura

RW-data: onde RW significa Read Write, o tamanho da variável legível e gravável, o valor inicial foi pago para a inicialização

ZI-data: ZI significa Zero Initialize, o tamanho da variável legível e gravável e o número de bytes atribuídos a 0 pelo sistema sem valor inicial

 

Tamanho da RAM:

RAM = 【RW-data】 + 【ZI-data】

 

Tamanho da ROM:

ROM = 【Código】 + 【dados RO】 + 【dados RW】, o tamanho do ROM é o tamanho do programa baixado para o ROM Flash. Por que há RW na Rom? Como todos os dados na RAM são perdidos após o desligamento, os dados na RAM são atribuídos pelo programa toda vez que a energia é ligada. Esses valores fixos são armazenados na ROM todas as vezes, por que não O segmento ZI é incluído porque os dados ZI são todos 0, então não há necessidade de incluí-lo, desde que a área onde os dados ZI estão localizados seja limpa antes que a consulta seja executada. Incluí-lo realmente desperdiça espaço de armazenamento.

 

Programa em execução:

O arquivo de imagem gravado na ROM não é exatamente igual ao do programa ARM em execução. O processo de execução da MCU é primeiro mover RW da ROM para a RAM, porque RW é uma variável e as variáveis ​​não podem ser armazenadas na ROM. Em seguida, limpe todas as áreas de RAM onde o ZI está localizado. Como a área de ZI não está na imagem, o programa precisa limpar a área de RAM correspondente de acordo com o endereço ZI e o tamanho fornecido pelo compilador. ZI também é uma variável, pelo mesmo motivo: Variáveis ​​não podem ser armazenadas em ROM, no estágio inicial de operação do programa, o programa C pode acessar a variável normalmente após as instruções no RO terem completado essas duas tarefas. Caso contrário, apenas o código sem variáveis ​​pode ser executado.

Acho que você gosta

Origin blog.csdn.net/m0_50180963/article/details/109315056
Recomendado
Clasificación