申请内存页面清0发生在什么时候
一、问题的引入
对于同一页物理内存,buddy系统可以分配给内核,也可以分配给用户空间,那么分配给内核的页面还给buddy系统后,页面的数据并没有清除,这时再把该物理页面分配给用户空间,那么用户空间不就可以读写该页面的数据了吗?这样内核数据就泄露了呀。
二、物理页面清0
I)用户空间申请2页内存后,发生:
- 虚拟地址空间分配2页,修改heap对应VMA节点(权限为r+w);
- 修改页表,将两虚拟页对应的页表项修改为一个固定的全0物理页(该全0物 理页是在开机时确定的),权限为r--。如果这时读这两页的内存,全部都是0,一 切正常。(引申问题II)
II)写第一页,发生:
- MMU检查该页只有r权限,发生page fault(minor,非major);
- 内核收到page fault后查看VMA,发现对应VMA记录的r+w权限;
- 分配一个物理页修改第一页对应的页表项,并且copy原全0页面的内容,也就 是完成了新物理页的清0,该过程即COW(Copy On Write);(引申问题III)
- 然后返回用户态,重新执行引起page fault的指令,也即该指令实际执行两次。
- 此时第二页的页表信息没有发生变化,还是对应全0页面。
III)写第二页,发生与II)相同过程。
以上就是内存分配的lazy机制。
三、问题结论
所以用户态从buddy系统申请到的页面都是清0的,但是清0是COW机制做的,与其他没有任何关系。所以内核释放的页面重新分配给用户态,用户态是不会读到内核数据的,因为用户态拿到的页面都是清0的。
四、引申
I)用户空间申请内存比如malloc,根据上面的分析,已经是0了,不需要memset(addr, 0, len)初始化。
答:错误。
- Buddy系统是按页分配的大内存。而实际编程中都是申请小内存,如果直接从Buddy分配一页使用,浪费严重;
- 所以libc库对内存进行了二级管理:libc库从buddy申请内存后,切出一块申请大小分配给用户,剩余部分管理起来,后面再切分给新申请;用户释放内存后,在没有达到收缩门限(一般为128K)的情况下,该内存不会还给buddy系统,继续由libc库管理;
- 这时用户再申请内存,优先从libc库管理的内存中分配,这时申请到的内存不是从buddy系统来,还残留着上次写入的数据(叫就地分配);如果libc管理的内存分配不出那么大内存,只好再从buddy系统申请内存,这时的内存是全0。
- 由于没办法判断申请到的内存是libc库现有管理的内存,还是从buddy新申请的内存,所以最好的办法是每次用memset初始化。
- 以上以malloc为例讲解。libc另外一个申请内存api为calloc,calloc函数会将申请到的内存清0,而且只是将申请到的内存清0,不是将整个页面清0,即calloc=malloc+memset。
II)修改VMA节点和页表修改先后顺序不确定
III)cow具体过程没有想明白,从全0页面拷贝到新页面,那么需要两个虚拟页表项,全0页面本来有一个页表项,那么新页面对应哪个页表项呢?临时找一个?先拷贝完再修改原页表项,还是先修改再拷贝?
答:内核空间会新页面建立一个临时页表项,然后把旧页数据完全拷贝到新页,最后把原页表项修改为新页面的地址。这个临时页表项,用户态是看不到的。
IV)以上是用户态的情况,kmalloc和vmalloc申请内存不会有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); |
2 |
void free(void *address); |
3 |
void *malloc(int num); |
4 |
void *realloc(void *address, int newsize); a) 可能的话,扩张或收缩 ptr 所指向的已存在内存。内容在新旧大小中的较小者范围内保持不变。若扩张范围,则数组新增部分的内容是未定义的。 b) 分配一个大小为 new_size 字节的新内存块,并复制大小等于新旧大小中较小者的内存区域,然后释放旧内存块。 |