How does malloc allocate memory?

Hello everyone, my name is Xiaolin.

I wrote an article illustrating virtual memory a long time ago: Awesome! 20 pictures reveal the fog of memory management

Recently, I want to write more articles on memory management. This time, we will use malloc dynamic memory allocation as the entry point. I also did a small experiment in the article:

  • How does malloc allocate memory?
  • Does malloc allocate physical memory?
  • How much memory will malloc(1) allocate?
  • free release memory, will it be returned to the operating system?
  • The free() function only passes in a memory address. How can I know how much memory to free?

Go!

What does the memory distribution of a Linux process look like?

In the Linux operating system, the interior of the virtual address space is divided into two parts: the kernel space and the user space . The system with different digits has different ranges of the address space. For example, the most common 32-bit and 64-bit systems are as follows:

picture

It can be seen from here:

  • 32The kernel space occupied by the bit system is 1Glocated at the top, and the rest 3Gis user space;
  • 64The kernel space and user space of the bit system are both 128T, occupying the highest and lowest parts of the entire memory space, respectively, and the rest of the middle part is undefined.

Let's talk about the difference between kernel space and user space:

  • When a process is in user mode, it can only access user space memory;
  • Only after entering the kernel mode can the memory in the kernel space be accessed;

Although each process has its own independent virtual memory, the kernel address in each virtual memory is actually associated with the same physical memory . In this way, after the process switches to the kernel mode, it can easily access the kernel space memory.

picture

Next, learn more about the division of virtual space. The division of user space and kernel space is different, and the distribution of kernel space will not be discussed.

Let's look at the distribution of user space. Taking a 32-bit system as an example, I drew a diagram to represent their relationship:

From this picture, you can see that user space memory is divided into 6 different memory segments from low to high :

picture

  • Program file segments, including binary executable code;
  • Initialized data segments, including static constants;
  • Uninitialized data segments, including uninitialized static variables;
  • Heap segments, including dynamically allocated memory, grow upwards from low addresses;
  • File mapping segments, including dynamic libraries, shared memory, etc., start from low addresses and grow upwards (related to hardware and kernel versions);
  • The stack segment, including local variables and the context of function calls, etc. The size of the stack is fixed, generally 8 MB. Of course, the system also provides parameters so that we can customize the size;

Among the six memory segments, the memory of the heap and file-mapped segments is dynamically allocated. For example, using the C standard library's malloc()or mmap(), it is possible to dynamically allocate memory in the heap and file-mapped segments, respectively.

How does malloc allocate memory?

In fact, malloc() is not a system call, but a function in the C library for dynamically allocating memory.

When malloc applies for memory, there are two ways to apply for heap memory to the operating system.

  • Method 1: Allocate memory from the heap through the brk() system call
  • Method 2: Allocate memory in the file mapping area through the mmap() system call;

The way to implement the first method is very simple, that is, to move the "heap top" pointer to a high address through the brk() function to obtain a new memory space. As shown below:

picture

Method 2 allocates a piece of memory in the file mapping area by means of "private anonymous mapping" in the mmap() system call, that is, "steals" a piece of memory from the file mapping area. As shown below:

picture

In what scenarios does malloc() allocate memory through brk()? In what scenario is memory allocated through mmap()?

A threshold is defined by default in the malloc() source code:

  • If the memory allocated by the user is less than 128 KB, apply for memory through brk();
  • If the memory allocated by the user is greater than 128 KB, apply for memory through mmap();

Does malloc() allocate physical memory?

No, malloc() allocates virtual memory .

If the allocated virtual memory is not accessed, the virtual memory will not be mapped to the physical memory, so that the physical memory will not be occupied.

Only when accessing the allocated virtual address space, the operating system finds that the page corresponding to the virtual memory is not in the physical memory by looking up the page table, and will trigger a page fault interrupt, and then the operating system will establish a gap between the virtual memory and the physical memory. mapping relationship.

How much virtual memory will malloc(1) allocate?

When malloc() allocates memory, it does not honestly allocate the memory space size according to the number of bytes the user expects to apply for, but pre-allocates a larger space as a memory pool .

How much space will be pre-allocated is related to the memory manager used by malloc. We will analyze it with the default memory manager (Ptmalloc2) of malloc.

Next, let's do an experiment. Use the following code to see how much memory space is actually allocated by the operating system when applying for 1 byte of memory through malloc.

#include <stdio.h>
#include <malloc.h>

int main() {
    
    
  printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
  
  //申请1字节的内存
  void *addr = malloc(1);
  printf("此1字节的内存起始地址:%x\n", addr);
  printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
 
  //将程序阻塞,当输入任意字符时才往下执行
  getchar();

  //释放内存
  free(addr);
  printf("释放了1字节的内存,但heap堆并不会释放\n");
  
  getchar();
  return 0;
}

Execute the code:

picture

We can view the memory distribution of the process through the /proc//maps file. I filtered out the range of memory addresses in the maps file by this 1-byte memory start address.

[root@xiaolin ~]# cat /proc/3191/maps | grep d730
00d73000-00d94000 rw-p 00000000 00:00 0                                  [heap]

The memory allocated in this example is less than 128 KB, so it is the memory applied to the heap space through the brk() system call, so you can see the [heap] logo on the far right.

It can be seen that the memory address range of the heap space is 00d73000-00d94000, and the size of this range is 132KB, which means that malloc(1) actually pre-allocates 132K bytes of memory .

Some students may have noticed that the starting address of the memory printed in the program is d73010, and the maps file shows that the starting address of the heap memory space is d73000, why is there more 0x10(16 bytes)? We will leave this question aside for now, and we will talk about it later.

free release memory, will it be returned to the operating system?

Let's execute the above process and see if the heap memory is still there after the memory is released through the free() function?

picture

As can be seen from the figure below, after releasing the memory through free, the heap memory still exists and is not returned to the operating system.

picture

This is because instead of releasing this 1 byte to the operating system, it is better to cache it and put it in the memory pool of malloc. When the process applies for 1 byte of memory again, it can be reused directly, which is much faster.

Of course, when the process exits, the operating system reclaims all the resources of the process.

The heap memory still exists after the free memory mentioned above is for the memory applied by malloc through the brk() method.

If malloc allocates memory through mmap, it will be returned to the operating system after freeing the memory.

Let's do an experiment to verify that we apply for 128 KB bytes of memory through malloc, so that malloc allocates memory through mmap.

#include <stdio.h>
#include <malloc.h>

int main() {
    
    
  //申请1字节的内存
  void *addr = malloc(128*1024);
  printf("此128KB字节的内存起始地址:%x\n", addr);
  printf("使用cat /proc/%d/maps查看内存分配\n",getpid());

  //将程序阻塞,当输入任意字符时才往下执行
  getchar();

  //释放内存
  free(addr);
  printf("释放了128KB字节的内存,内存也归还给了操作系统\n");

  getchar();
  return 0;
}

Execute the code:

picture

Looking at the memory distribution of the process, you can find that there is no [head] sign on the far right, indicating that the anonymous memory is allocated from the file mapping area through mmap in an anonymous mapping manner.

picture

Then we free this memory and see:

picture

Looking at the starting address of the 128 KB memory again, it can be found that it no longer exists, indicating that it has been returned to the operating system.

picture

For the "memory requested by malloc, will free release the memory and return it to the operating system?", we can make a summary:

  • The memory applied by malloc through the brk() method, when free releases the memory, the memory will not be returned to the operating system, but will be cached in the memory pool of malloc for the next use ;
  • The memory applied by malloc through mmap() , when free releases the memory, it will return the memory to the operating system, and the memory is truly released .

Why don't all use mmap to allocate memory?

Because applying for memory to the operating system requires a system call, and executing a system call requires entering the kernel mode, and then returning to the user mode, the switching of the running mode will take a lot of time.

Therefore, the operation of applying for memory should avoid frequent system calls. If mmap is used to allocate memory, it is equivalent to executing system calls every time.

In addition, because the memory allocated by mmap will be returned to the operating system every time it is released, the virtual address allocated by mmap is in a page-fault state every time, and then when the virtual address is accessed for the first time, it will trigger a missing page. Page breaks.

That is to say, if the memory is frequently allocated by mmap, not only will the running state switch occur every time, but also the page fault interrupt will occur (after the first access to the virtual address), which will lead to high CPU consumption .

In order to improve these two problems, when malloc applies for memory in the heap space through the brk() system call, since the heap space is continuous, it directly pre-allocates a larger memory as a memory pool. When the memory is released, it is cached. in the memory pool.

When you apply for memory next time, just take out the corresponding memory block directly from the memory pool, and the mapping relationship between the virtual address and physical address of this memory block may still exist, which not only reduces the number of system calls, but also reduces The number of page fault interrupts will be greatly reduced, which will greatly reduce the CPU consumption .

Since brk is so awesome, why not use brk for allocating?

Earlier we mentioned that the memory allocated from the heap space through brk will not be returned to the operating system, so let's consider such a scenario.

If we continuously apply for 10k, 20k, and 30k of memory, if the 10k and 20k are released, they become free memory space. If the next memory application is less than 30k, then this free memory space can be reused.

picture

However, if the memory applied for next time is larger than 30k and there is no free memory space available, an application must be made to the OS, and the actual memory used will continue to increase.

Therefore, as the system frequently malloc and free, especially for small blocks of memory, more and more unusable fragments will be generated in the heap, resulting in "memory leaks". This "leak" phenomenon cannot be detected using valgrind.

Therefore, in the implementation of malloc, the differences and advantages and disadvantages of the behavior of sbrk and mmap are fully considered, and a large block of memory (128KB) is allocated by default before using mmap to allocate memory space.

The free() function only passes in a memory address. How can I know how much memory to free?

Remember, I mentioned earlier that the memory starting address returned by malloc to user mode is 16 bytes more than the starting address of the process's heap space?

The extra 16 bytes store the description information of the memory block, such as the size of the memory block.

picture

In this way, when the free() function is executed, free will offset the incoming memory address by 16 bytes to the left, and then analyze the size of the current memory block from this 16-byte data, and naturally know how much memory to release. .

Guess you like

Origin blog.csdn.net/qq_34827674/article/details/124005820