申请内存页面清0发生在什么时候

一、问题的引入

对于同一页物理内存,buddy系统可以分配给内核,也可以分配给用户空间,那么分配给内核的页面还给buddy系统后,页面的数据并没有清除,这时再把该物理页面分配给用户空间,那么用户空间不就可以读写该页面的数据了吗?这样内核数据就泄露了呀。

二、物理页面清0

I)用户空间申请2页内存后,发生:

  1. 虚拟地址空间分配2页,修改heap对应VMA节点(权限为r+w)
  2. 修改页表,将两虚拟页对应的页表项修改为一个固定的全0物理页(该全0物 理页是在开机时确定的),权限为r--。如果这时读这两页的内存,全部都是0,一 切正常。(引申问题II)

II)写第一页,发生:

  1. MMU检查该页只有r权限,发生page faultminor,非major);
  2. 内核收到page fault后查看VMA,发现对应VMA记录的r+w权限;
  3. 分配一个物理页修改第一页对应的页表项,并且copy原全0页面的内容,也就 是完成了新物理页的清0,该过程即COWCopy On Write);(引申问题III
  4. 然后返回用户态,重新执行引起page fault的指令,也即该指令实际执行两次。
  5. 此时第二页的页表信息没有发生变化,还是对应全0页面。

III)写第二页,发生与II)相同过程。

以上就是内存分配的lazy机制。

三、问题结论

所以用户态从buddy系统申请到的页面都是清0的,但是清0COW机制做的,与其他没有任何关系。所以内核释放的页面重新分配给用户态,用户态是不会读到内核数据的,因为用户态拿到的页面都是清0的。

四、引申

I)用户空间申请内存比如malloc,根据上面的分析,已经是0了,不需要memset(addr, 0, len)初始化。

答:错误。

  1. Buddy系统是按页分配的大内存。而实际编程中都是申请小内存,如果直接从Buddy分配一页使用,浪费严重;
  2. 所以libc库对内存进行了二级管理:libc库从buddy申请内存后,切出一块申请大小分配给用户,剩余部分管理起来,后面再切分给新申请;用户释放内存后,在没有达到收缩门限(一般为128K)的情况下,该内存不会还给buddy系统,继续由libc库管理;
  3. 这时用户再申请内存,优先从libc库管理的内存中分配,这时申请到的内存不是从buddy系统来,还残留着上次写入的数据(叫就地分配);如果libc管理的内存分配不出那么大内存,只好再从buddy系统申请内存,这时的内存是全0
  4. 由于没办法判断申请到的内存是libc库现有管理的内存,还是从buddy新申请的内存,所以最好的办法是每次用memset初始化。
  5. 以上以malloc为例讲解。libc另外一个申请内存apicalloccalloc函数会将申请到的内存清0,而且只是将申请到的内存清0,不是将整个页面清0,即calloc=malloc+memset

II)修改VMA节点和页表修改先后顺序不确定

III)cow具体过程没有想明白,从全0页面拷贝到新页面,那么需要两个虚拟页表项,全0页面本来有一个页表项,那么新页面对应哪个页表项呢?临时找一个?先拷贝完再修改原页表项,还是先修改再拷贝?

答:内核空间会新页面建立一个临时页表项,然后把旧页数据完全拷贝到新页,最后把原页表项修改为新页面的地址。这个临时页表项,用户态是看不到的。

IV)以上是用户态的情况,kmallocvmalloc申请内存不会有lazy机制,新页面是立即映射的,那么也是从全0页面拷贝清0的吗?

答:不需要清0。kzalloc和vzlloc是kmalloc和vmalloc的清0版本。

 

V)栈空间呢?

 

栈空间是在进程装载是就已经确定地址和大小的。所以也符合上述分析,lazy机制,新页清0,在运行过程中,新的函数调用使用的栈内存也有可能是前面用过的栈内存,有数据残留。

 

参考资料

链接:http://www.runoob.com/cprogramming/c-memory-management.html

序号

函数和描述

1

void *calloc(int num, int size);
在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。

2

void free(void *address); 
该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。

3

void *malloc(int num); 
在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。

4

void *realloc(void *address, int newsize); 
该函数重新分配内存,把内存扩展到 newsize

a) 可能的话,扩张或收缩 ptr 所指向的已存在内存。内容在新旧大小中的较小者范围内保持不变。若扩张范围,则数组新增部分的内容是未定义的。

b) 分配一个大小为 new_size 字节的新内存块,并复制大小等于新旧大小中较小者的内存区域,然后释放旧内存块。

 

原文地址:https://www.cnblogs.com/shihuvini/p/8399288.html