做人呢,最紧要就系开心啦

Linux内核之内存2: 内存的动态申请、释放的原理和细节

686次阅读
没有评论

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 库的内存,其他进程无法使用,共享代码段,数据段独立;

Linux 内核之内存 2: 内存的动态申请、释放的原理和细节

(1)slab 原理:

就是从 Buddy 拿到一个 / 多个页,分成多个相同大小的块,比如进程控制块 task_struct 这种内核中常用的结构体,可以先从 Buddy 申请一个 slab 池,实际 kmalloc 分配的时候,直接从 slab 里分配。可以提高速度,也可以使内存得到充分使用,减少内存碎片;

每一个 slab 里的所有块,大小一定是相同的;

sudo cat /proc/slabinfo

Linux 内核之内存 2: 内存的动态申请、释放的原理和细节

cat /proc/meminfo

Linux 内核之内存 2: 内存的动态申请、释放的原理和细节

slab 也是一个内存泄漏的源头;

应用程序 free 内存,未必一定释放,可以通过 mallopt 设置 free 内存的触发阈值,触发阈值,free 才真正在 Buddy 释放;

(2) kmalloc / vmalloc 详细过程

(1)kmalloc 从低端内存区域分配,该区域是开机就一次性映射好;所以 kmalloc 申请,不需要做映射, 且在物理内存上是连续的。phys_to_virt/virt_to_phys 只是一个简单的内存线性偏移。

(2)vmalloc 申请的虚拟内存,可以从普通物理内存空间分配,也可以从低端内存分配;
调用 vmalloc 申请,会有一个虚拟地址到物理地址映射过程。

ioremap 映射的也是 vmalloc 虚拟地址,不过不用申请内存,ioremap 映射的是寄存器;

Linux 内核之内存 2: 内存的动态申请、释放的原理和细节

映射跟分配是两回事,低端被映射之后,不一定被使用;

sudo cat /proc/vmallocinfo |grep ioremap
// 可以查看虚拟地址映射物理地址 / 寄存器关系

Linux 内核之内存 2: 内存的动态申请、释放的原理和细节

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).

在真正写的时候,才去分配,这是按需分配,或者惰性分配;

Linux 内核之内存 2: 内存的动态申请、释放的原理和细节

只申请了 VMA,未实际拿到物理内存,此时叫 VSS;拿到实际内存后是 RSS(驻留内存);

Linux 内核之内存 2: 内存的动态申请、释放的原理和细节

属于按需分配 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

Linux 内核之内存 2: 内存的动态申请、释放的原理和细节

由上图可知,

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 系统,内存分布简图;

Linux 内核之内存 2: 内存的动态申请、释放的原理和细节

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

正文完
 
admin
版权声明:本站原创文章,由 admin 2022-01-04发表,共计2729字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)