1.slab、kmalloc/kfree、/proc/slabinfo和slabtop
Buddy 是直面物理内存的,所有的内存分配,最终都通过Buddy的get_free_page/page_alloc分配;
Buddy的粒度太大,最小分配一页(4k); 而我们常常需要分配小内存;
所以Linux引入一个二级分配的概念:
1.内核分配内存,调用kmalloc()/kfree()–调用slab–再调用Buddy ;
2.用户空间malloc/free–调用C库–C库通过brk/mmap调用Buddy;
free释放的内存,只是释放给C库,未必真正释放;释放给C库的内存,其他进程无法使用,共享代码段,数据段独立;
(1)slab原理:
就是从Buddy拿到一个/多个页,分成多个相同大小的块,比如进程控制块task_struct这种内核中常用的结构体,可以先从Buddy申请一个slab池,实际kmalloc分配的时候,直接从slab里分配。可以提高速度,也可以使内存得到充分使用,减少内存碎片;
每一个slab里的所有块,大小一定是相同的;
sudo cat
cat
slab也是一个内存泄漏的源头;
应用程序free内存,未必一定释放,可以通过mallopt设置free内存的触发阈值,触发阈值,free才真正在Buddy
释放;
(2) kmalloc / vmalloc详细过程
(1)kmalloc从低端内存区域分配,该区域是开机就一次性映射好;所以kmalloc申请,不需要做映射, 且在物理内存上是连续的。
phys_to_virt/virt_to_phys只是一个简单的内存线性偏移。
(2)vmalloc申请的虚拟内存,可以从普通物理内存空间分配,也可以从低端内存分配;
调用vmalloc申请,会有一个虚拟地址到物理地址映射过程。
ioremap映射的也是vmalloc虚拟地址,不过不用申请内存,ioremap映射的是寄存器;
映射跟分配是两回事,低端被映射之后,不一定被使用;
sudo cat /proc/vmallocinfo |grep
kmalloc和vmalloc区别大概可总结为:
(1)vmalloc分配的一般为普通内存,只有当内存不够的时候才分配低端内存;kmalloc从低端内存分配。
(2)vmalloc分配的物理地址一般不连续,而kmalloc分配的物理地址连续,两者分配的虚拟地址都是连续的;
(3)vmalloc分配的一般为大块内存,而kmallc一般分配的为小块内存;
2.用户空间malloc/free与内核之间的关系
问题1:malloc:VSS , RSS
p = malloc(100M);//分配过程
1.在进程的堆中找到一个空闲地址,比如1G,创建一个VMA(virtual memory
area),权限可读写;
2.将p=1G~1G+100M全部映射到零页(标记为只读);
3.当读p虚拟内存的时候,全部返回0,实际上任何内存都未分配;
4.当写内存时,比如写1G+30M处,而该地址映射向零页(只读),MMU发现权限错误,报page fault,CPU可以从page fault中得到写地址和权限,检查vma中的权限,如果vma标明可写,那么触发缺页中断,内核分配一个4K物理页,重新填写1G+30M页表,使其映射到新分配的物理页;
这样该页就分配了实际的物理内存,其他所有未使用到的虚拟地址亦然映射到零页。
如果检查vma发现没有可写权限,则报段错误;
如果检查vma合法,但是系统内存已经不够用,则报out of memory(OOM).
在真正写的时候,才去分配,这是按需分配,或者惰性分配;
只申请了VMA,未实际拿到物理内存,此时叫VSS;拿到实际内存后是RSS(驻留内存);
属于按需分配demanding page,或者惰性分配lazy allocation;
对于代码段(实际读时,才实际去分配内存,把代码从硬盘读到内存),数据段都是类似处理,实际使用时,才会实际分配内存;
3 内存耗尽(OOM)、oom_score和oom_adj
在实际分配内存,发现物理内存不够用时,内核报OOM,杀掉最该死进程(根据oom_score打分),释放内存;
打分机制,主要看消耗内存多少(先杀大户),mm/oom_kill.c的badness()给每个进程一个oom_score,一般取决于:
驻留内存、pagetable和swap的使用;
oom_score_adj:oom_score会加上oom_score_adj这个值;
oom_adj:-17~15的系数调整
关掉交换内存分区:
sudo swapoff -a
sudo sh -c ‘echo 1 \> /proc/sys/vm/overconmit_memory’
git grep
dmesg
查看chrome进程的oom_score
pidof chrome
cd /proc/28508/
cat
由上图可知,
1将oom_adj值设置越大,oom_score越大,进程越容易被杀掉;
2.将oom_adj设置更大时,普通权限就可以;要将oom_adj设置更小,需要root权限;
即设置自身更容易死掉,自我牺牲是很容易的,设置自己不容易死,是索取,需要超级权限才可以;
4 Android进程生命周期与OOM
android进程的生命周期靠OOM来驱动;
android手机多个进程间切换时,会动态设置相应oom_adj,调低前台进程的oom_adj,调高后台进程的oom_adj,当内存不够时,优先杀后台进程。所以内存足够大,更容易平滑切换。
对于简单的嵌入式系统,可以设置当oom时,是否panic
cd /proc/sys/vm
cat
mm/oom_kill.c oom_badness()函数不停调整进程oom_score值;
总结一个典型的ARM32位,Linux系统,内存分布简图;
ARM32位内核空间3G-16M~4G
3G-16M~3G-2M用来映射KO
3G-2M~3G:可以用kmap申请高端内存,用kmap,建立一个映射,临时访问页,访问完后kunmap掉;
进程的写时拷贝技术,mm/memory.c/cow_user_page用到kmap映射;
其他练习:
1.看/proc/slabinfo,运行slabtop
运行mallopt.c程序:mallopt等
运行一个很耗费内存的程序,观察oom memory
通过oom_adj调整firefox的oom_score