malloc

139 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情

malloc()函数是C语言中内存分配函数,学习C语言的初学者经常会有如下的困扰。 假设系统中有进程A和进程B,分别使用testA和testB函数分配内存:

//进程A分配内存
void testA(void)
{
   char * bufA = malloc100);
   ……
   *buf = 100;
   ……
}
//进程B分配内存
void testB(void)
{
   char * bufB = malloc100);
   mlock(buf, 100);
   ……
}


malloc()函数返回的内存是否马上就分配物理内存?testA和testB分别在何时分配物理内存?假设不考虑libc的因素,malloc分配100Byte,那么实际上内核是为其分配100 Byte吗?假设使用printf打印指针bufA和bufB指向的地址是一样的,那么在内核中这两块虚拟内存是否“打架”了呢?vm_normal_page()函数返回的什么样页面的struct page数据结构?为什么内存管理代码中需要这个函数?请简述get_user_page()函数的作用和实现流程。请简述follow_page()函数的作用的实现流程。 malloc()函数是C函数库封装的一个核心函数,C函数库会做一些处理后调用Linux系统调用接口brk,所以大家并不太熟悉brk的系统调用,原因在于很少有人会直接使用系统调用brk向系统申请内存,而总是通过malloc()之类的C函数库的API函数。如果把malloc()想象成零售,那么brk就是代理商。malloc函数的实现为用户进程维护一个本地小仓库,当进程需要使用更多的内存时就向这个小仓库要货,小仓库存量不足时就通过代理商brk向内核批发。

brk实现

brk系统调用主要实现在mm/mmap.c函数中。

[mm/mmap.c]
0 SYSCALL_DEFINE1(brk, unsigned long, brk)
1 {
2  unsigned long retval;
3  unsigned long newbrk, oldbrk;
4  struct mm_struct *mm = current->mm;
5  unsigned long min_brk;
6  bool populate;
7 
8  down_write(&mm->mmap_sem);
9  min_brk = mm->end_data;
10 if (brk < min_brk)
11    goto out;
12   
13 if (check_data_rlimit(rlimit(RLIMIT_DATA), brk, mm->start_brk,
14         mm->end_data, mm->start_data))
15    goto out;
16
17 newbrk = PAGE_ALIGN(brk);
18 oldbrk = PAGE_ALIGN(mm->brk);
19 if (oldbrk == newbrk)
20    goto set_brk;
21
22 /* Always allow shrinking brk.*/
23 if (brk <= mm->brk) {
24    if (!do_munmap(mm, newbrk, oldbrk-newbrk))
25       goto set_brk;
26    goto out;
27 }
28
29 /* Check against existing mmap mappings.*/
30 if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))
31    goto out;
32
33 /* Ok, looks good - let it rip.*/
34 if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)
35    goto out;
36
37set_brk:
38 mm->brk = brk;
39 populate = newbrk > oldbrk && (mm->def_flags & VM_LOCKED) != 0;
40 up_write(&mm->mmap_sem);
41 if (populate)
42    mm_populate(oldbrk, newbrk - oldbrk);
43 return brk;
44
45out:
46 retval = mm->brk;
47 up_write(&mm->mmap_sem);
48 return retval;
49}



在32位Linux内核中,每个用户进程拥有3GB的虚拟空间。内核如何为用户空间来划分这3GB的虚拟空间呢?用户进程的可执行文件由代码段和数据段组成,数据段包括所有的静态分配的数据空间,例如全局变量和静态局部变量等。这些空间在可执行文件装载时,内核就为其分配好这些空间,包括虚拟地址和物理页面,并建立好二者的映射关系。如图2.15所示,用户进程的用户栈从3GB虚拟空间的顶部开始,由顶向下延伸,而brk分配的空间是从数据段的顶部end_data到用户栈的底部。所以动态分配空间是从进程的end_data开始,每次分配一块空间,就把这个边界往上推进一段,同时内核和进程都会记录当前的边界的位置。