Talk about those things about memory (based on microcontroller system)

RAM and ROM of MCU

The ROM of the single-chip microcomputer is called the read-only program memory, which is composed of FLASH memory, such as a USB flash drive. Therefore, FLASH and ROM are synonymous. The program of the one-chip computer is written in FLASH.

RAM is a random read/write memory, used as a data memory, to store data when the program is running.

Memory area

The memory is mainly divided into several areas: code area, constant area, static area (global area), heap area, and stack area.

l   Code area: store the program code, that is, the machine instructions executed by the CPU, and it is read-only.

l   Constant area: store constants (amount that cannot be changed during the running of the program, for example: 25, string constant "dongxiaodong", the name of the array, etc.)

l   Static area (global area) : The storage area of ​​static variables and global variables is the same. Once the memory in the static area is allocated, the memory in the static area will not be released until the program ends.

l   Heap area: The programmer calls the malloc() function to actively apply, and the free() function is used to release the memory. If the heap area memory is applied for and then forget to release the memory, it is easy to cause a memory leak

l   Stack area: store local variables, formal parameters and function return values ​​in the function. After the scope of the data in the stack area has passed, the system will reclaim the memory of the automatic management stack area (allocate memory, reclaim memory), without the need for developers to manually manage it. The stack area is like an inn. There are many rooms in it. After the guests come, the rooms are automatically allocated. The guests in the rooms can be changed, which is a dynamic data change.

STM32F103C8T6

The starting address of ROM is: 0x8000000, and the size is: 0x10000 (64K)

Read-only, storing the code area and constant area

The starting address of RAM is: 0x20000000, and the size is: 0x5000 (20K)

Readable and writable, storing static area, stack area and heap area

Detailed introduction of each area of ​​STM32:

Code area:

l The code area stores the compiled CPU instructions

l The function name is a pointer, you can query the memory address where the function name is located, and query the area where the function is stored

Copy code

 1 //Function declaration
 2 void dong();
 3 //Main function definition
 4 int main(void)
 5 { 
 6 //Serial port initialization
 7     Uart1_Init(115200);
 8 //Function call
 9 dong ();
10 //Output test function address
11     printf("dong() addrs : 0x%p\n",dong);
12     
13     while(1);
14 }
15 void dong(){
16 //Output the main function address
17      printf("mian() addrs : 0x%p\n",main);
18 }

Copy code

Output:

It can be seen that both 0x08000c2d and 0x08000be9 are in the code area in ROM

Constant area

The pointer can point to the constant or variable area. Use the pointer (char *p) to test the address change between the constant and the variable.

Copy code

 1 void dongxiaodong_fun(){
 2 char *p=NULL;//Define a pointer variable
 3 //constant
 4 p="2020dongxiaodong";//The pointer points to a constant
 5 printf("addr1:0x%p\r\n",p);//output constant address
 6 //variable
 7      char data[]={"dong1234"};
 8 p=data;//Pointer points to a variable
 9 printf("addr2:0x%p\r\n",p);//output variable address
10 }

Copy code

Output:

It can be seen that the address of the constant is in the constant area in ROM, and the local variable is in the stack space of RAM

Static zone

The static area includes static variables and global variables. Static variables are modified by static, and once initialized, they always occupy RAM space

Copy code

 1 int global_a;//Global variables, the default value is 0
 2 static int global_b;//Static global variables, the default value is 0
 3 void fun(){
 4 static int c;//static variable, the default value is 0
 5     printf("static int c add:0x%p , val:%d \r\n",&c,c);
 6     c++;
 7 }
 8 void dongxiaodong_fun(){
 9 //Output global variables
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 //Call function to view static variables
13     for(int i=0;i<3;i++){
14          fun();
15     }
16 }

Copy code

Output:

Among them, global_a is a global variable, global_b is a global static variable, and c is a local static variable. If they are not assigned an initial value, they will be automatically assigned a value of 0 by the system. The initialization of static variables is always valid and will not be affected by multiple calls to the initialization statement. There are multiple initialization problems. Although it seems that the c variable is initialized three times in the code, it is actually only valid for the first time.

Heap area

The heap area is the memory space requested by calling the malloc function. After this part of the space is used up, the free() function must be called to release the requested space. Void * malloc(size_t); The parameter of the function is the size of the space to be allocated in bytes, and the return is a pointer of type void*, which points to the first address of the allocated space. The pointer of void* type can be converted to any other type of pointer.

l The heap grows upward, that is, grows in the direction of increasing first address

l The space requested by malloc() must be released by free(). If the requested memory is not released, it may cause a memory leak

l malloc() memory application failure will return NULL

l The memory space allocated by malloc is logically continuous, but may not be physically continuous.

l Release can only be released once, and if it is released twice or more, an error will occur (except for the release of a null pointer, the release of a null pointer actually means nothing is done, so it can be released as many times as possible), free() After the space is released, the pointer can be pointed to "NULL" to ensure that the pointer will not become a wild pointer.

STM32C8T6 :

The standard library defines the default heap size as 0x200=512 bytes. It can be considered that the malloc allocation size of the program at the same time cannot be greater than 512 bytes of data.

The heap space is not resident in RAM space by default, but when the malloc keyword appears in the code, the heap space will be allocated the set overall size (512 bytes) to occupy RAM space.

Copy code

void dongxiaodong_fun(){
  //Application
    printf("-----malloc-----\r\n");
    char *p1=malloc(100);
    if(p1==NULL) printf("p1 malloc fail \r\n");
    char *p2=malloc(1024);
    if(p2==NULL) printf("p2 malloc fail \r\n");
    
    //Assignment  
    memcpy(p1,"dongxiaodong123456",strlen("dongxiaodong123456"));
    
    printf("p1 addr:%p  ,val:%s \r\n",p1,p1);
    printf("p2 addr:%p\r\n",p2);
    
    
    //freed
    printf("-----free-----\r\n");
    free(p1);
    free(p2);
    
    printf("p1 addr:%p  ,val:%s \r\n",p1,p1);

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

Copy code

Output:

It can be seen that if the heap space allocation fails, it will return NULL, and the address points to 0x00. When it is released, it is only through free(), which only changes the pointed content to a null value, but the address still exists, so the standard practice is to assign " NULL" value. After the memory is released (the address saved by the pointer variable p itself has not changed after using the free function), you need to assign the value of p to NULL (tether the wild pointer).

The allocated space cannot reach the specified maximum value:

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

Output:

View explanation:

If malloc(n) is used to allocate heap memory, then the allocated memory is larger than n. Why?

0. The memory allocated by malloc is not necessarily contiguous, so the header pointer is needed to link each part

1. The actual allocated heap memory is the Header + n structure. What is returned to the user is the first address of part n, so he still has a part of the memory used to store the header, so it is larger than the original

2. Due to the memory alignment value of 8, the memory alignment mechanism, the actual allocated heap memory is greater than or equal to sizeof(Header) + n

Stack area

The stack area is automatically allocated and released by the compiler. It stores parameter values, return values ​​and local variables defined in the function. It is allocated and released in real time during program operation. The stack area is automatically managed by the operating system without manual management. The stack area is a first-in-last-out principle.

l The stack grows downward, that is, grows in the direction of decreasing first address

l The compiler will not assign an initial value of 0 to uninitialized local variables, so uninitialized local variables are usually a messy value, so it is safest to assign initial values ​​when defining local variables.

STM32C8T6:

The standard library defines the default stack size as 0x400=1024 bytes. It can be considered that the local variables of the program at the same time cannot be larger than 1024 bytes of data.

The number of bytes of the stack space is the resident space. Once initialized, the overall size (1024 bytes) of the setting will be allocated to occupy the RAM space.

Copy code

 1 //Main function definition
 2 int main(void)
 3 { 
 4 //Serial port initialization
 5     Uart1_Init(115200);
 6     printf("start SYS 1\r\n");
 7 char data1[1024]={0}; //1024 bytes
 8     printf("start SYS 2\r\n");
 9 char data2[100]={0}; //100 bytes
10     printf("start SYS 3\r\n");
11 char data3[100]={0}; //100 bytes, 10 bytes can run normally
12     printf("start SYS 4\r\n");
13     while(1);
14 }

Copy code

The actual measurement found that the stack space can run normally up to 1024+100+10 bytes. Is this because STM32 reserves the stack space? 1024 is not a complete mandatory limit.

Address test

Copy code

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

Copy code

Output:

It can be seen that the address of b is smaller than the address of a, which is increasing in the direction of decreasing the first address (increasing downward). The value of b is not assigned an initial value, and its value is confusing. It is recommended to use the initial value.

note:

const modified data

l const modifies the variable name. The reason why it is called const constant means that it cannot be changed and the permission is read-only, but its essence is a variable, but it is an unmodifiable variable

l const modified local variables are stored in the stack area, if modified global variables are stored in the static area (global area)

Data storage (small and small mode)

Data is stored in memory, divided into big-endian mode and little-endian mode

Big-endian mode: The low byte is stored on the high address, and the high byte is stored on the low address.

Little-endian mode: The low byte is stored at the low address, and the high byte is stored at the high address.

Network byte order: TCP/IP protocols define the byte sequence as big-endian mode, so the big-endian mode used in TCP/IP protocol is usually called network byte order.

Copy code

void dongxiaodong_fun(){
    int data=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));
}

Copy code

Output:

It can be seen that the high bit of its value is stored in the low bit of the address, so the variable storage of STM32 is little-endian mode

Fragmentation of dynamic memory application

The standard dynamic memory allocation is managed by a dynamic linked list. Since malloc returns a pointer and the microcontroller has no MMU, the allocated pointers are in the memory like nails until they are released. This will make memory management very difficult, leading to memory fragmentation.

This is an ideal extreme example

The heap space of the single-chip microcomputer is allocated 1KB of space, which is 1024 bytes. For the convenience of explanation and calculation, we ignore the space occupied by the linked list and only calculate the actual storage space.

Step 1: Apply for 64 blocks of memory space, each block of 16 bytes, then 1K bytes of space will be allocated.

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

Step 2: Release the even-numbered memory space

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

third step:

The space we released has reached half the size of the heap, 512 bytes, but they are all discontinuous. 32 blocks of 16 bytes of non-contiguous space, so it is impossible to allocate memory blocks larger than 16 bytes. There is 512 bytes of space but only continuous space less than 16 bytes can be allocated. In some occasions, the original heap space resources of the MCU RAM are very tight, and this insufficient use makes the program stability greatly compromised.

STM32C8T6 real case:

Memory fragmentation can be verified by the following sub:

Copy code

 1 void dongxiaodong_fun(){
 2     char *p[8]={NULL};
 3 //512 bytes of heap space, it seems that only 8*50=400 bytes can be allocated
 4     for(int i=0;i<8;i++){
 5         p[i]=malloc(50);
 6         if(p[i]==NULL) printf("p[%d] malloc fail\r\n",i);
 7     }
 8 //Output the address of one of the numbers
 9     printf("%p\r\n",p[2]);
10     printf("%p\r\n",p[3]);
11 //Release even subscript space
12     for(int i=0;i<8;i+=2){
13         free(p[i]);
14         p[i]=NULL;
15     }
16 //allocation failed, memory fragmentation
17 char *d1=malloc(100); //Feasible
18     if(d1==NULL) printf("d1 100 malloc fail\r\n");
19     
20 //Release an odd space
21     free(p[3]);
22 //The allocation is successful, the allocated space is in the space of p[2] and p[3], and 10 bytes of extra space are added
23     char *d2=malloc(160);
24     if(d2==NULL) printf("d2 100 malloc fail\r\n");
25     printf("%p\r\n",d2);
26 }

Copy code

Output:

This example generally reflects the problem of memory fragmentation, because there are a total of 8 spaces, and the release of odd blocks after application is theoretically 50*4=200 bytes, but the allocation of 100 bytes does not work, important reasons The size of the released even-numbered block is 50, and its address is not continuous. When one of the odd blocks is released, the memory can reach the size of the contiguous block that needs to be allocated, so the allocated space uses the space of p[2], p[3], and p[4].

 

There are several problems:

The space allocated by Malloc can be 512 in total, but one package can only be about 500 effective space, and 8 packages are about 400 effective space. Why is the utilization rate so low?

In the fragmentation test, the size of p[2], p[3], and p[4] should be 3*50=150, and the maximum result can be about 160.

 

View explanation:

If malloc(n) is used to allocate heap memory, then the allocated memory is larger than n. Why?

0. The memory allocated by malloc is not necessarily contiguous, so the header pointer is needed to link each part

1. The actual allocated heap memory is the Header + n structure. What is returned to the user is the first address of part n, so he still has a part of the memory used to store the header, so it is larger than the original

2. Due to the memory alignment value of 8, the memory alignment mechanism, the actual allocated heap memory is greater than or equal to sizeof(Header) + n

 

The main solution to memory fragmentation:

Move the spaced small memories together side by side to free up continuous space

The commonly used segment page memory allocation method is to divide the memory area of ​​the process into different segments, and then each segment is composed of multiple fixed-size pages. Through the page table mechanism, the pages in the segment do not need to be in the same memory area consecutively, thereby reducing external fragmentation. However, there may still be a small amount of internal fragmentation in the same page, but the memory space of a page is inherently small, making it possible There are also fewer internal fragments.

 

The mymalloc() function of the punctual atom

Question 1: Why does this appear in the Malloc function standard library?

Question 2: How to deal with memory fragmentation?

to sum up:

l Can manage the memory of various RAMs, such as external SRAM, which is convenient for managing multiple RAM spaces

l You can view the memory usage

l No memory fragmentation processing

STM32 view FLASH space and RAM space usage

Open the display:

 Output after compilation:

 

Program Size: Code=38356 RO-data=6676 RW-data=400 ZI-data=47544

 

Code: the space occupied by the code

RO-data: where RO stands for Read Only, the size of the read-only constant light

RW-data: where RW stands for Read Write, the size of the readable and writable variable, the initial value has been paid for initialization

ZI-data: ZI stands for Zero Initialize, the size of the readable and writable variable, and the number of bytes assigned to 0 by the system without initial value

 

RAM size:

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

 

ROM size:

ROM =【Code】+【RO-data】+【RW-data】, the size of ROM is the size of the program downloaded into ROM Flash. Why is there RW in Rom? Because all the data in RAM is lost after power-off, the data in RAM is assigned by the program every time the power is turned on. These fixed values ​​are stored in ROM every time, why not The ZI segment is included because the ZI data are all 0, so there is no need to include it, as long as the area where the ZI data is located is cleared before the query is run. Including it actually wastes storage space.

 

Program running:

The image file burned into the ROM is not exactly the same as the actual running ARM program. The MCU execution process is to first move RW from ROM to RAM, because RW is a variable, and variables cannot be stored in ROM. Then clear all the RAM areas where ZI is located, because the ZI area is not in the Image, so the program needs to clear the corresponding RAM area according to the ZI address and size given by the compiler. ZI is also a variable, the same reason: Variables cannot be stored in ROM. In the initial stage of program operation, the C program can access the variable normally after the instructions in the RO have completed these two tasks. Otherwise, only code without variables can be run.

Guess you like

Origin blog.csdn.net/m0_50180963/article/details/109315056