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 /proc/slabinfo
cat /proc/meminfo
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 ioremap
// 可以查看虚拟地址映射物理地址 / 寄存器关系
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 overcommit_memory
dmesg // 查看 oom 进程的 oom_score
查看 chrome 进程的 oom_score
pidof chrome
cd /proc/28508/
cat oom_score
由上图可知,
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 panic_on_oom
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