上一篇我们了解了内存在内核态是如何管理的,本篇文章我们一起来看下内存在用户态的使用情况,如果上一篇文章说是内核驱动工程师经常面对的内存管理问题,那本篇就是应用工程师常面对的问题 。
相信大家都知道对用户态的内存消耗对象是进程,应用开发者面对的所有代码操作最后的落脚点都是进程,这也是说为什么内存和进程两个知识点的重要性,理解了内存和进程两大法宝,对所有软件开发的理解都会有了全局观(关于进程的知识以后再整理和大家分享) 。
下面闲话少说,开始本篇的内容——进程的内存消耗和泄漏
进程的虚拟地址空间VMA(Virtual Memory Area)在linux操作系统中,每个进程都通过一个task_struct的结构体描叙,每个进程的地址空间都通过一个mm_struct描叙,C语言中的每个段空间都通过vm_area_struct表示,他们关系如下 :
![Linux用户态进程的内存管理](http://img.jiangsulong.com/220406/1534552308-0.jpg)
文章插图
上图中,task_struct中的mm_struct就代表进程的整个内存资源,mm_struct中的pgd为页表,mmap指针指向的vm_area_struct链表的每一个节点就代表进程的一个虚拟地址空间,即一个VMA 。一个VMA最终可能对应ELF可执行程序的数据段、代码段、堆、栈、或者动态链接库的某个部分 。
VMA的分布情况可以有通过pmap命令,及maps,smaps文件查看,如下图:
![Linux用户态进程的内存管理](http://img.jiangsulong.com/220406/15345555P-1.jpg)
文章插图
另,VMA的具体内容可参考下图 。
![Linux用户态进程的内存管理](http://img.jiangsulong.com/220406/1534552307-2.jpg)
文章插图
page fault的几种可能性我们先来看张图:
![Linux用户态进程的内存管理](http://img.jiangsulong.com/220406/15345554G-3.jpg)
文章插图
【Linux用户态进程的内存管理】(此图来源于宋宝华老师)
- 如,调用malloc申请100M内存,IA32下在0~3G虚拟地址中立刻就会占用到大小为100M的VMA,且符合堆的定义,这一段VMA的权限是R+W的 。但由于Lazy机制,这100M其实并没有获得,这100M全部映射到一个物理地址相同的零页,且在页表中记录的权限为只读的 。当100M中任何一页发生写操作时,MMU会给CPU发page fault(MMU可以从寄存器读出发生page fault的地址;MMU可以读出发生page fault的原因),Linux内核收到缺页中断,在缺页中断的处理程序中读出虚拟地址和原因,去VMA中查,发现是用户程序在写malloc的合法区域且有写权限,Linux内核就真正的申请内存,页表中对应一页的权限也修改为R+W 。
- 如,程序中有野指针飞到了此程序运行时进程的VMA以外的非法区域,硬件就会收到page fault,进程会收到SIGSEGV信号报段错误并终止 。如,程序中有野指针飞到了此程序运行时进程的VMA以外的非法区域,硬件就会收到page fault,进程会收到SIGSEGV信号报段错误并终止 。
- 如,代码段在VMA中权限为R+X,如果程序中有野指针飞到此区域去写,则也会发生段错误 。(另,malloc堆区在VMA中权限为R+W,如果程序的PC指针飞到此区域去执行,同样发生段错误 。)
- 如,执行代码段时会发生缺页,Linux申请1页内存,并从硬盘读取出代码段,此时产生了IO操作,为major主缺页 。如,执行代码段时会发生缺页,Linux申请1页内存,并从硬盘读取出代码段,此时产生了IO操作,为major主缺页 。
![Linux用户态进程的内存管理](http://img.jiangsulong.com/220406/1534551431-4.jpg)
文章插图
(此图来源于宋宝华老师)
综上,page fault后,Linux会查VMA,也会比对VMA中和页表中的权限,体现出VMA的重要作用 。
malloc分配的原理malloc的过程其实就是把VMA分配到各种段当中,这时候是没有真正分配物理地址的 。malloc 调用后,只是分配了内存的逻辑地址,在内核的mm_struct 链表中插入vm_area_struct结构体,没有分配实际的内存 。当分配的区域写入数据是,引发页中断,建立物理页和逻辑地址的映射 。下图表示了这个过程 。
![Linux用户态进程的内存管理](http://img.jiangsulong.com/220406/1534552217-5.jpg)
文章插图
从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存) 。
- malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系)
- malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0)
推荐阅读
- 如何在 Linux 中删除文本中的回车字符
- linux如何用ftp脚本自动下载文件
- 接吻时长揭露男人性态度
- 从微信状态看出人的心理和性格
- 云南台地茶茶种与生长形态
- 金泰梨|《二十五,二十一》金泰梨顶逆龄刘海披白纱,31岁完美状态如同少女!
- 茶叶形态的沿革从紧压茶到散茶
- 普洱茶拼配的好处 拼配才是常态
- 借你的服务器挖下矿!新型Linux恶意软件“Skidmap”来袭
- 人生追求的一种最佳状态 人生最好是小满