Hablar de esas cosas sobre la memoria (basado en el sistema de microcontroladores)

RAM y ROM de MCU

La ROM de la microcomputadora de un solo chip se denomina memoria de programa de solo lectura, que se compone de una memoria FLASH, como una unidad flash USB. Por lo tanto, FLASH y ROM son sinónimos. El programa de la computadora de un chip está escrito en FLASH.

La RAM es una memoria de lectura / escritura aleatoria, utilizada como memoria de datos, para almacenar datos cuando el programa se está ejecutando.

Área de memoria

La memoria se divide principalmente en varias áreas: área de código, área constante, área estática (área global), área de pila y área de pila.

l   Área de código: almacena el código del programa, es decir, las instrucciones de la máquina ejecutadas por la CPU, y es de solo lectura.

l   Área constante: almacena las constantes (cantidad que no se puede cambiar durante la ejecución del programa, por ejemplo: 25, constante de cadena "dongxiaodong", el nombre de la matriz, etc.)

l   Área estática (área global) : El área de almacenamiento de variables estáticas y variables globales es la misma, una vez asignada la memoria en el área estática, la memoria en el área estática no se liberará hasta que finalice el programa.

l   Área de montón: el programador llama a la función malloc () para aplicar activamente, y la función free () se usa para liberar la memoria. Si se solicita la memoria del área de montón y luego se olvida de liberar la memoria, es fácil causar una pérdida de memoria

l   Área de pila: almacena variables locales, parámetros formales y valores de retorno de función en la función. Una vez que ha pasado el alcance de los datos en el área de la pila, el sistema recuperará la memoria del área de la pila de administración automática (asignar memoria, recuperar memoria), sin la necesidad de que los desarrolladores la administren manualmente. El área de la pila es como una posada. Hay muchas habitaciones en ella. Después de que llegan los huéspedes, las habitaciones se asignan automáticamente. Los huéspedes en las habitaciones pueden cambiar, lo cual es un cambio de datos dinámico.

STM32F103C8T6

La dirección inicial de la ROM es: 0x8000000, y el tamaño es: 0x10000 (64K)

Solo lectura, almacena el área de código y el área constante

La dirección inicial de RAM es: 0x20000000 y el tamaño es: 0x5000 (20K)

Legible y escribible, almacenando área estática, área de pila y área de pila

Introducción detallada de cada área de STM32:

Área de código:

l El área de código almacena las instrucciones de CPU compiladas

l El nombre de la función es un puntero, puede consultar la dirección de memoria donde se encuentra el nombre de la función y consultar el área donde se almacena la función

Copiar codigo

1 // Declaración de función 
 2 void dong (); 
 3 // Definición de función principal 
 4 int main (void) 
 5 { 
 6 // Inicialización del puerto serie 
 7 Uart1_Init (115200); 
 8 // Llamada a función 
 9 dong (); 
10 // Salida dirección de función de prueba 
11 printf ("dong () addrs: 0x% p \ n", dong); 
12      
13 while (1); 
14} 
15 void dong () { 
16 // dirección de función principal de salida 
17 printf ("mian ( ) addrs: 0x% p \ n ", principal); 
18}

Copiar codigo

Salida:

Se puede ver que tanto 0x08000c2d como 0x08000be9 están en el área de código en ROM

Área constante

El puntero puede apuntar al área de la constante o variable. Utilice el puntero (char * p) para probar el cambio de dirección entre la constante y la variable.

Copiar codigo

1 void dongxiaodong_fun () { 
 2 char * p = NULL; // definir una variable de puntero 
 3 // constante 
 4 p = "2020dongxiaodong"; // puntero apunta a una constante 
 5 printf ("addr1: 0x% p \ r \ n" , p); // dirección constante de salida 
 6 // variable 
 7 char data [] = {"dong1234"}; 
 8 p = data; // puntero apunta a una variable 
 9 printf ("addr2: 0x% p \ r \ n" , p); // dirección de variable de salida 
10}

Copiar codigo

Salida:

Se puede ver que la dirección de la constante está en el área constante en la ROM y la variable local está en el espacio de pila de la RAM.

Zona estática

El área estática incluye variables estáticas y variables globales. Las variables estáticas son modificadas por static, y una vez inicializadas, siempre ocupan espacio RAM

Copiar codigo

1 int global_a; // Variables globales, el valor predeterminado es 0 
 2 static int global_b; // Variables globales estáticas, el valor predeterminado es 0 
 3 void fun () { 
 4 static int c; // Variables estáticas, el valor predeterminado es 0 
 5 printf ("static int c add: 0x% p, val:% d \ r \ n", & c, c); 
 6 c ++; 
 7} 
 8 void dongxiaodong_fun () { 
 9 // variables globales de salida 
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 / / Llamar a la función para ver las variables estáticas 
13 para (int i = 0; i <3; i ++) { 
14 fun (); 
15} 
16}

Copiar codigo

Salida:

Entre ellos, global_a es una variable global, global_b es una variable estática global y c es una variable estática local. Si no se les asigna un valor inicial, el sistema les asignará automáticamente un valor de 0. La inicialización de las variables estáticas es siempre válida y no se verá afectada por múltiples llamadas a la instrucción de inicialización. Hay varios problemas de inicialización. Aunque parece que la variable c se inicializa tres veces en el código, en realidad solo es válida por primera vez.

Área de montón

El área del montón es el espacio de memoria solicitado al llamar a la función malloc. Una vez que esta parte del espacio se agota, se debe llamar a la función free () para liberar el espacio solicitado. Void * malloc (size_t); El parámetro de la función es el tamaño del espacio a asignar en bytes, y el retorno es un puntero de tipo void *, que apunta a la primera dirección del espacio asignado. El puntero de tipo void * se puede convertir a cualquier otro tipo de puntero.

l El montón crece hacia arriba, es decir, crece en la dirección de aumentar la primera dirección

l El espacio solicitado por malloc () debe ser liberado por free (). Si la memoria solicitada no se libera, puede causar una pérdida de memoria

l falla de la aplicación de memoria malloc () devolverá NULL

l El espacio de memoria asignado por malloc es lógicamente continuo, pero puede no serlo físicamente.

l La liberación solo se puede lanzar una vez, y si se libera dos o más, se producirá un error (excepto por la liberación de un puntero nulo, la liberación de un puntero nulo en realidad significa que no se ha hecho nada, por lo que se puede liberar tantas veces como sea posible), gratis () Después de liberar el espacio, puede apuntar el puntero a "NULL" para asegurarse de que el puntero no se convierta en un puntero salvaje.

STM32C8T6:

La biblioteca estándar define el tamaño de pila predeterminado como 0x200 = 512 bytes Se puede considerar que el tamaño de asignación de malloc del programa al mismo tiempo no puede ser mayor que 512 bytes de datos.

El espacio de pila no reside en el espacio de RAM de forma predeterminada, pero cuando la palabra clave malloc aparece en el código, se asignará al espacio de pila el tamaño total establecido (512 bytes) para ocupar el espacio de RAM.

Copiar codigo

void dongxiaodong_fun () { 
  // 申请
    printf ("----- malloc ----- \ r \ n"); 
    char * p1 = malloc (100); 
    if (p1 == NULL) printf ("p1 malloc falla \ r \ n"); 
    char * p2 = malloc (1024); 
    if (p2 == NULL) printf ("p2 malloc falla \ r \ n"); 
    
    // 赋值  
    memcpy (p1, "dongxiaodong123456", strlen ("dongxiaodong123456")); 
    
    printf ("p1 dirección:% p, val :% s \ r \ n", p1, p1); 
    printf ("p2 direccion:% p \ r \ n", p2); 
    
    
    // 释放
    printf ("----- libre ----- \ r \ n"); 
    libre (p1); 
    libre (p2); 
    
    printf ("p1 dirección:% p, val :% s \ r \ n", p1, p1); 

    
    p1 = NULO; 
    printf ("p1 dirección:% p \ r \ n", p1); 
    
}

Copiar codigo

Salida:

Se puede ver que si falla la asignación de espacio de pila, devolverá NULL y la dirección apunta a 0x00. Cuando se libera, es solo a través de free (), que solo cambia el contenido señalado a un valor nulo, pero la dirección aún existe, por lo que la práctica estándar es asignar " Valor nulo. Una vez que se libera la memoria (la dirección guardada por la variable de puntero p no ha cambiado después de usar la función libre), debe asignar el valor de p a NULL (atar el puntero salvaje).

El espacio asignado no puede alcanzar el valor máximo especificado:

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

Salida:

Ver explicación:

Si se usa malloc (n) para asignar memoria de pila, entonces la memoria asignada es mayor que n. ¿Por qué?

0. La memoria asignada por malloc no es necesariamente contigua, por lo que se necesita el puntero del encabezado para vincular cada parte

1. La memoria de pila realmente asignada es la estructura Header + n. Lo que se le devuelve al usuario es la primera dirección de la parte n, por lo que todavía tiene una parte de la memoria utilizada para almacenar el encabezado, por lo que es más grande que el original.

2. Debido al valor de alineación de memoria de 8, el mecanismo de alineación de memoria, la memoria del montón asignada real es mayor o igual al tamaño de (Encabezado) + n

Área de pila

El compilador asigna y libera automáticamente el área de la pila. Almacena los valores de los parámetros, los valores de retorno y las variables locales definidas en la función. Se asigna y libera en tiempo real durante la operación del programa. El sistema operativo administra automáticamente el área de la pila sin una gestión manual. El área de la pila es un principio de primero en entrar, último en salir.

l La pila crece hacia abajo, es decir, crece en la dirección de disminuir la primera dirección

l El compilador no asignará un valor inicial de 0 a las variables locales no inicializadas, por lo que las variables locales no inicializadas suelen ser un valor desordenado, por lo que es más seguro asignar valores iniciales al definir variables locales.

STM32C8T6:

La biblioteca estándar define el tamaño de pila predeterminado como 0x400 = 1024 bytes Se puede considerar que las variables locales del programa al mismo tiempo no pueden tener más de 1024 bytes de datos.

El número de bytes del espacio de la pila es el espacio residente Una vez inicializado, el tamaño total (1024 bytes) de la configuración se asignará para ocupar el espacio RAM.

Copiar codigo

1 // Definición de la función principal 
 2 int main (void) 
 3 { 
 4 // Inicialización del puerto serie 
 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 pueden ejecutarse normalmente 
12 printf ("start SYS 4 \ r \ n"); 
13 while (1); 
14}

Copiar codigo

La medición real encontró que el espacio de la pila puede funcionar normalmente hasta 1024 + 100 + 10 bytes ¿Esto se debe a que STM32 ha reservado el espacio de la pila? 1024 no es un límite obligatorio completo.

Prueba de dirección

Copiar codigo

void dongxiaodong_fun () { 
int a = 100; 
    int b; 
    printf ("una dirección: 0x% p val:% d \ r \ n", & a, a); 
    printf ("b dirección: 0x% p val:% d \ r \ n", & b, b); 
}

Copiar codigo

Salida:

Se puede ver que la dirección de b es menor que la dirección de a, la cual está aumentando en la dirección de disminuir la primera dirección (aumentando hacia abajo). Al valor de b no se le asigna un valor inicial, y su valor es confuso. Se recomienda usar el valor inicial.

Nota:

datos modificados const

l const modifica el nombre de la variable. La razón por la que se llama constante const significa que no se puede cambiar y el permiso es de solo lectura, pero su esencia es una variable, pero es una variable no modificable

l las variables locales modificadas const se almacenan en el área de la pila, si las variables globales modificadas se almacenan en el área estática (área global)

Almacenamiento de datos (modo pequeño y pequeño)

Los datos se almacenan en la memoria, divididos en modo big-endian y modo little-endian

Modo Big-endian: el byte bajo se almacena en la dirección alta y el byte alto se almacena en la dirección baja.

Modo Little-endian: el byte bajo se almacena en la dirección baja y el byte alto se almacena en la dirección alta.

Orden de bytes de red: los protocolos TCP / IP definen la secuencia de bytes como modo big-endian, por lo que el modo big-endian utilizado en el protocolo TCP / IP se suele llamar orden de bytes de red.

Copiar codigo

void dongxiaodong_fun () { 
    int datos = 0x12345678; 
    char * p = (char *) & datos; 
    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 codigo

Salida:

Se puede ver que el bit alto de su valor se almacena en el bit bajo de la dirección, por lo que el almacenamiento de variables de STM32 es en modo little-endian

Fragmentación de la aplicación de memoria dinámica

La asignación de memoria dinámica estándar se gestiona mediante una lista enlazada dinámica. Dado que malloc devuelve un puntero y el microcontrolador no tiene MMU, los punteros asignados están en la memoria como clavos hasta que se liberan. Esto dificultará mucho la gestión de la memoria, lo que provocará su fragmentación.

Este es un ejemplo extremo ideal

Al espacio de pila de la microcomputadora de un solo chip se le asigna 1 KB de espacio, que es 1024 bytes. Para facilitar la explicación y el cálculo, ignoramos el espacio ocupado por la lista enlazada y solo calculamos el espacio de almacenamiento real.

Paso 1: Solicite 64 bloques de espacio de memoria, cada bloque de 16 bytes, luego se asignará 1K bytes de espacio.

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

Paso 2: Libere el espacio de memoria par

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

tercer paso:

El espacio que liberamos ha alcanzado la mitad del tamaño del montón, 512 bytes, pero todos son discontinuos. 32 bloques de 16 bytes de espacio no contiguo, por lo que es imposible asignar bloques de memoria de más de 16 bytes. Hay 512 bytes de espacio, pero solo se pueden asignar espacios contiguos de menos de 16. En algunas ocasiones, los recursos de espacio de pila originales de la memoria RAM de la MCU son muy reducidos y este uso insuficiente hace que la estabilidad del programa se vea muy comprometida.

Caso real STM32C8T6:

La fragmentación de la memoria se puede verificar mediante el siguiente sub:

Copiar codigo

1 void dongxiaodong_fun () { 
 2 char * p [8] = {NULL}; 
 3 // 512 bytes de espacio de pila, parece que solo 8 * 50 = 400 bytes se pueden asignar 
 4 para (int i = 0; i <8 ; i ++) { 
 5 p [i] = malloc (50); 
 6 if (p [i] == NULL) printf ("p [% d] malloc falla \ r \ n", i); 
 7} 
 8 // Muestra la dirección de uno de los números 
 9 printf ("% p \ r \ n", p [2]); 
10 printf ("% p \ r \ n", p [3]); 
11 // Libera el espacio del subíndice de número par 
12 for (int i = 0; i <8; i + = 2) { 
13 free (p [i]); 
14 p [i] = NULL; 
15} 
16 // falló la asignación, fragmentación de la memoria 
17 char * d1 = malloc (100); // Factible 
18 if (d1 == NULL) printf ("d1 100 malloc fail \ r \ n"); 
19      
20 // Libera un espacio de bits impar 
21 free (p [3]);
22 // La asignación es exitosa, el espacio asignado está en el espacio de p [2] yp [3], y 10 bytes de espacio extra
23 char * d2 = malloc (160); 
24 if (d2 == NULL) printf ("d2 100 malloc falla \ r \ n"); 
25 printf ("% p \ r \ n", d2); 
26}

Copiar codigo

Salida:

Este ejemplo generalmente refleja el problema de la fragmentación de la memoria, porque hay un total de 8 espacios, y la liberación de bloques impares después de la aplicación es teóricamente 50 * 4 = 200 bytes, pero la asignación de 100 bytes no funciona, razones importantes El tamaño del bloque de número par liberado es 50 y su dirección no es continua. Cuando se libera uno de los bloques impares, la memoria puede alcanzar el tamaño del bloque contiguo que necesita ser asignado, por lo que el espacio asignado usa el espacio de p [2], p [3] yp [4].

 

Hay varios problemas:

El espacio asignado por Malloc puede ser 512 en total, pero un paquete solo puede tener aproximadamente 500 de espacio efectivo y 8 paquetes son aproximadamente 400 de espacio efectivo ¿Por qué la tasa de utilización es tan baja?

En la prueba de fragmentación, el tamaño de p [2], p [3] yp [4] debe ser 3 * 50 = 150, y el resultado máximo puede ser aproximadamente 160.

 

Ver explicación:

Si se usa malloc (n) para asignar memoria de pila, entonces la memoria asignada es mayor que n. ¿Por qué?

0. La memoria asignada por malloc no es necesariamente contigua, por lo que se necesita el puntero del encabezado para vincular cada parte

1. La memoria de pila realmente asignada es la estructura Header + n. Lo que se le devuelve al usuario es la primera dirección de la parte n, por lo que todavía tiene una parte de la memoria utilizada para almacenar el encabezado, por lo que es más grande que el original.

2. Debido al valor de alineación de memoria de 8, el mecanismo de alineación de memoria, la memoria del montón asignada real es mayor o igual al tamaño de (Encabezado) + n

 

La principal solución a la fragmentación de la memoria:

Mueva los pequeños recuerdos espaciados juntos uno al lado del otro para liberar espacio continuo

El método de asignación de memoria de página de segmento comúnmente utilizado es dividir el área de memoria del proceso en diferentes segmentos, y luego cada segmento se compone de múltiples páginas de tamaño fijo. A través del mecanismo de tabla de páginas, las páginas del segmento no necesitan estar en la misma área de memoria consecutivamente, lo que reduce la fragmentación externa. Sin embargo, puede haber una pequeña cantidad de fragmentación interna en la misma página, pero el espacio de memoria de una página es inherentemente pequeño, lo que hace posible También hay menos fragmentos internos.

 

La función mymalloc () del átomo puntual

Pregunta 1: ¿Por qué vuelve a suceder esto en la biblioteca estándar de funciones de Malloc?

Pregunta 2: ¿Cómo lidiar con la fragmentación de la memoria?

para resumir:

l Puede administrar la memoria de varias RAM, como SRAM externa, que es conveniente para administrar múltiples espacios de RAM

l Puede ver el uso de la memoria

l Sin procesamiento de fragmentación de memoria

STM32 ve el espacio FLASH y el uso del espacio RAM

Abra la pantalla:

 Salida después de la compilación:

 

Tamaño del programa: Código = 38356 Datos RO = 6676 Datos RW = 400 Datos ZI = 47544

 

Código: el espacio que ocupa el código.

Datos RO: donde RO significa Solo lectura, el tamaño de la luz constante de solo lectura

RW-data: donde RW significa lectura y escritura, el tamaño de la variable legible y de escritura, el valor inicial se ha pagado por la inicialización

ZI-data: ZI significa Zero Initialize, el tamaño de la variable legible y de escritura, y el número de bytes asignados a 0 por el sistema sin valor inicial

 

Tamaño de RAM:

RAM = 【datos RW】 + 【datos ZI】

 

Tamaño ROM:

ROM = 【Código】 + 【RO-data】 + 【RW-data】, el tamaño de la ROM es el tamaño del programa descargado en ROM Flash. ¿Por qué hay RW en Rom? Debido a que todos los datos en la RAM se pierden después del apagado, el programa asigna los datos en la RAM cada vez que se enciende. Estos valores fijos se almacenan en la ROM cada vez, ¿por qué no? El segmento ZI se incluye porque los datos ZI son todos 0, por lo que no es necesario incluirlo, siempre que el área donde se encuentran los datos ZI se borre antes de ejecutar la consulta. Incluirlo realmente desperdicia espacio de almacenamiento.

 

Programa en ejecución:

El archivo de imagen grabado en la ROM no es exactamente el mismo que el programa ARM en ejecución. El proceso de ejecución de la MCU consiste en mover primero RW de la ROM a la RAM, porque RW es una variable y las variables no se pueden almacenar en la ROM. Luego borre todas las áreas de RAM donde se encuentra ZI. Debido a que el área de ZI no está en la imagen, el programa necesita borrar el área de RAM correspondiente de acuerdo con la dirección de ZI y el tamaño proporcionado por el compilador. ZI también es una variable, lo mismo es cierto: las variables no se pueden almacenar en la ROM En la etapa inicial de operación del programa, el programa C puede acceder a la variable normalmente después de que las instrucciones en la RO hayan completado estas dos tareas. De lo contrario, solo se puede ejecutar código sin variables.

Supongo que te gusta

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