3.5释放的原理和细节
用户空间malloc/free与内核之间的关系
问题1:malloc:VSS , RSS
p = malloc(100M);//分配过程
1.在进程的堆中找到一个空闲地址,比如1G,创建一个VMA(virtual memoryarea),权限可读写;
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;
对于代码段(实际读时,才实际去分配内存,把代码从硬盘读到内存),数据段都是类似处理,实际使用时,才会实际分配内存;
内存耗尽(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权限;
即设置自身更容易死掉,自我牺牲是很容易的,设置自己不容易死,是索取,需要超级权限才可以;
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
四、进程的内存消耗和内存泄漏
4.1进程的VMA
(1)进程地址空间
在Linux系统中,每个进程都有自己的虚拟内存空间0~3G;
内核空间只有一个3G~4G;
进程通过系统API调用,在内核空间申请内存,不统计在任何用户进程;进程消耗内存,单指用户空间内存消耗;
(2)VMA列表
LINUX用task_struct来描述进程,其中的mm_struct是描述内存的结构体,mm_struct有一个vma列表,管理当前进程的所有vma段。
每个进程的内存由多个vma段组成:
(3)查看VMA方法:
1.pmap
由图知,从接近0地址开始,第一个4K是只读代码段,第二个4K是只读数据段,还有其他共享库代码段,堆栈等;
可见一个进程的VMA涵盖多个地址区域**,但并没有覆盖所有地址空间**。VMA未覆盖的地址空间是illegal的,访问这些地址,缺页中断,发生pagefault.
2.cat /proc/pid/maps
读文件形式,与pmap一一对应;
3.cat /proc/pid/smaps
更详细的描述
00400000-00401000 r-xp 00000000 08:15 20316320 /home/leon/work/linux/mm/a.out Size: 4 kb KernelPageSize: 4 kB MMUPageSize: 4 kB Rss: 4 kB Pss: 4 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 4 kB Private_Dirty: 0 kB Referenced: 4 kB Anonymous: 0 kB LazyFree: 0 kB AnonHugePages: 0 kB ShmemPmdMapped: 0 kB Shared_Hugetlb: 0 kB Private_Hugetlb: 0 kB Swap: 0 kB SwapPss: 0 kB Locked: 0 kB VmFlags: rd ex mr mw me dw sd
查看VMA的三个方法对比
VMA的来源,代码段,数据段,堆栈段。
VMA是linux最核心数据结构之一。
4.2page fault的几种可能性,major和minor
mmu给cpu发page fault时,可以从寄存器读到两个元素,pagefault地址,pagefault原因。
(1)访问Heap堆(首次申请,不是从Libc获取),第一次写,发生pagefault,linux检查VMA权限,发现权限合法,发缺页中断,申请一页内存,并更新页表。
(2)访问空区域,访问非法,发段错误;
(3)访问代码段, 在此区域写,报pagefault,检查权限发现错误,报段错误;
(4)访问代码段,在此区域读/执行,linux检查权限合法,若代码不在内存,那么申请内存页,把代码从硬盘读到内存
伴随I/O的pagefault, 叫major pagefault, 否则minor pagefault.
major pagefault耗时远大于 minor pagefault.
\time -v python hello.py
内存是如何被瓜分的: :vss、rss、pss和uss
rss是不是代表进程的内存消耗呢,NO。
- VSS:单个进程全部可访问的地址空间,但未必所有虚拟地址都已经映射物理内存;
- RSS:驻留内存,单个进程实际占用的物理内存大小(不十分准确的描述);上图的进程1
- PSS:比例化的内存消耗,相对RSS,将共享内存空间按被共享进程数量比例化;上图的C库4被三个进程共享,所以4/3;
- USS:进程独占内存,比如上图的堆6。
案例,连续运行两次a.out,查看内存占用情况
运行程序a.out
./a.out & pidof a.out cat /proc/pid/smaps |more
./a.out & pidof a.out
cat /proc/pid_2/smaps |more
PSS变化shared_cleanprivate_clean
4.3应用内存泄漏的界定方法
- 统计系统的内存消耗,查看PSS。
- 检查有没有内存泄漏,检查USS
./smem smem –pie=command
smem –bar=command
android里面有类似的工具,procmem/procrank
smem分析系统内存使用是通过/proc/smaps的,procrank是通过分析/proc/kpagemap。
4.4应用内存泄漏的检测方法:valgrind和addresssanitizer
其他查询内存泄漏工具:
valgrind: gcc -g leak-example.c -o leak-example valgrind --tool=memcheck --leak-check=yes ./leak-example
20468 HEAP SUMMARY: 20468 in use at exit: 270,336 bytes in 22 blocks 20468 total heap usage: 44 allocs, 22 frees, 292,864 bytes allocated 20468 20468 258,048 bytes in 21 blocks are definitely lost in loss record 2 of 2 20468 at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) 20468 by 0x4005C7: main (leak-example.c:6)
valgrid是在虚拟机跑APP,速度很慢;
新版gcc4.9以后集成了asan
asan: gcc -g -fsanitize=address ./leak-example.c gcc -fuse-ld=gold -fsanitize=address -g ./lsan.c
#1 0x40084e in main lsan.c:9
内存泄漏不一定在用户空间,排查内核空间,检测slab , vmalloc
slaptop, 检查申请和释放不成对。
在内核编译,打开kmemleak选项。
4.5工程调试
内存泄漏问题一般步骤:
(1) meminfo, free 多点采样确认有内存泄漏。
(2)定位,smem检查用户空间USS在不断增加。
(3) slab,检查内核空间。
cat /proc/slabinfo
其他查看内存信息的方法
cat /proc/meminfo cat /proc/buddyinfo cat /proc/zoneinfo cat /proc/meminfo
五、内存与I/O的交换
5.1page cache页缓存
Linux读写文件过程;
read:用户进程调用read命令,内核查询读取的文件内容是否在内存(内核pagecache)中,若该页内容缓存在内存中,直接读取返回给用户进程;若缓存不存在,则启动BIO,从硬盘读取该页面到内存,再送给用户进程;
write过程:比如往某文件5K处写入10byte,内核先查询该页是否在内存缓存(内核pagecache),不在,同read,从硬盘读取该页4~8K到内存,再往5k处写入10byte,标明该页为脏页;
写回磁盘时机,则由内存管理的BIO机制决定。
Linux读写文件两种方式:read/write,mmap
相同点:都经过page cache中介操作磁盘文件;
区别:
- read/write:有一个用户空间和内核空间的拷贝过程;
- mmap:指针直接操作文件映射的虚拟地址,省略了用户空间和内核空间的拷贝过程。
缺点是,很多设备不支持mmap,比如串口,键盘等都不支持。
5.2free命令的详细解释
total:系统总的内存
第一行used: 所有用掉的内存,包括app,kernel,page cached(可回收);
第一行free: =total-used
第一行buffers/cached:page cached内存
第二行free = 第一行free+buffers+cached
114380=31040+8988+74352
第二行used = 第一行used-buffers-cached
40072=123412-8988-74352
第一行used是从buddy的角度,统计所有用掉的内存(包括page cached);
第二行uesed,是从进程角度,包括app/kernel/ , 不含page cached(可回收)。
cached/buffer区别:
cached:通过文件统访问;
buffer:裸地址直接访问,存放文件系统的元数据(组织数据的数据);
新版free,去掉了buffer/cached的区别,加入一个available,预估可用内存
在cat /proc/meminfo也可以看到预估项MemAvailable
查看实现方法git grep MemAvailable
vim fs/proc/meminfo.c
搜MemAvailable,实现函数si_mem_available
git grep si_mem_available vim mmpage_alloc.c-- >long si_mem_available(void)
5.3read、write和mmap
mmap映射文件过程,实际上在进程创建一个vma,映射到文件,当真正读/写文件时,分配内存,同步磁盘文件内存。
其本质是虚拟地址映射到page cached, page cached再跟文件系统对应。
这里的page cached是可以回收的。
5.4file-backed的页面和匿名页
在一个elf文件中,代码段的本质,就是对应page cached;
真正执行到的代码,才会从磁盘读入内存,并且还可能被踢走,下次再执行,可能需要重新从磁盘读取。
Reclaim可回收的有文件背景的页面,叫file-back page, 可回收
匿名页:anon, 不可回收,常驻内存;
pmap pid
Anon: stack、heap、 CoW pages
File-backed:代码段,映射文件,可回收。
5.5swap以及zRAM
案例,当同时运行word和qq,word需要400M匿名页,qq需要300M匿名页,而物理内存只有512M,如何运行呢,此时伪造一个可swap的文件,供anon匿名页交换。
在内核配置CONFIG_SWAP,支持匿名页swap,不配置,普通文件swap依然支持;
SWAP分区,对应windows的虚拟内存文件pagefile.system。
5.6页面回收和LRU
局部性原理:最近活跃的就是将来活跃的,最近不活跃的,以后也不活跃。
包括时间局部性,空间局部性。
LRU:Least Recently Used最近最少使用,是一种常用的页面置换算法,
真实统计过程,无关进程,只统计页面的活跃度;
案例,开启浏览器chrome;长期不使用,运行oom.c消耗掉系统所有内存,再去第一次打开网页时,速度会很慢,因为需要重新加载被踢出去的不活跃页面。
**sudo swapoff –a** **echo 1 > /proc/sys/vm/overcommit_memory** **cat /proc/pid/smps >1** **./oom.out** **cat /proc/pid/smaps >2** **meld 1 2**
可见命中的部分page cached,被LRU踢走。
CPU>>mm>>io
嵌入式设备,一般不用swap,不使能swap,因为:
- 1.嵌入式磁盘速度很慢;
- 2.FLASH读写寿命有限;
zRAM
在物理内存划分一部分,作为“swap”分区,用来做页面回收置换,利用强大CPU算力换取更大内存空间;
内核使能swap
echo $((48*1024*1024)) > /sys/block/zram0/disksize
打开swap分区:
- swapon –p 10 /dev/zram0
- cat /proc/swaps
- swapoff –a 不能关掉文件背景页面。
- 不带pagecached的IO, direct IO。 在用户态根据业务特点做cached
- 类似于CPU跳过cache,直接访问mem;
六、其他工程问题以及调优
6.1DMA和cache一致性
(1)不带CACHE
自己写驱动,申请DMA,可以用Coherent DMA buffers
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle,gfp_t flag); void dma_free_coherent (struct device *dev, size_t size,void *cpu_addr, dma_addr_t dma_handle);
CMA和此API自动融合,调用dma_alloc_coherent()将从CMA获得内存,不带cache。
(2)流式DMA
操作其他进程的地址,比如tcp/ip报文的缓存,无法控制DMA地址源,用dma_map_single,自动设置cache flush,CPU无法访问cache,这个是硬件自动完成的;但是CPU可以控制cache,使能cache为非法(破坏命中),会自动与memory同步。
DMA Streaming Mapping dma_addr_t dma_map_single(...); void dma_unmap_single (...);
有的强大DMA,不需要连续内存做DMA操作,scater/getter可以用
int dma_map_sg(...); void dma_unmap_sg (...);
有一些强大硬件,DMA能感知cache网络互联;这3套API,对任何硬件都成立;实现API后端,兼容不同硬件。
有些新的强大硬件,有支持IOMMU/smmu
Dma可以从不连续内存,CMA申请内存dma_alloc_coherent不需要从CMA申请内存,支持MMU,可以用物理不连续的内存来实现DMA操作;
6.2内存的cgroup
./swapoff –a echo 1 \> /proc/sys/vm/overcommit_memory // /sys/fs/cgroup/memory mkdir A cd A sudo echo \$(100\*1024\*1024) \> memory.limit_in_bytes //限制最大内存100M //a.out放到A cgroup执行; sudo cgexec –g memory:A ./a.out
性能方面的调优:page in/out, swapin/out
6.3Dirty ratio的一些设置
脏页写回
时间维度:时间到,脏页写回;dirty_expire_centisecs
空间维度:Dirty_ratio
Dirty_background_ratio:
cd /proc/sys/vm
假如某个进程在不停写数据,当写入大小触发dirty_background_ratio_10%时,脏页开始写入磁盘,写入数据大小触发dirty_ratio_20%时(磁盘IO速度远比写内存慢),如果继续写,会被内核阻塞,等脏页部分被写入磁盘,释放pagecached后,进程才能继续写入内存;
所有的脏页flush,都是后台自动完成,当写入速度不足以触发dirty_ratio时,进程感知不到磁盘的存在。
6.4swappiness
内存回收reclaim,涉及三个水位
回收哪里的内存,back_groudpages, swap,由swappiness决定
Swappiness越大,越倾向于回收匿名页;即使Swappiness设置为0,先回收背景页,以使达到最低水位;假使达不到,还是会回收匿名页;
有个例外,对于cgroup而言,设置swappiness=0,该group的swap回收被关闭;但是全局的swap还是可以回收;
为什么设定最低水位,在Linux有一些紧急申请,PF_MEMALLOC,可以突破min水位,(回收内存的过程,也需要申请内存,类似征粮队自身需要吃粮食)。
最低水位,可以通过lewmem_reserve_ratio修改;
关于最低水位的计算方法:
回收inode/dentry的slab
设置drop_caches
getdelays工具
getdelays 测量调度、I/O、SWAP、Reclaim的延迟,需要内核打开相应选项
CONFIG_TASK_DELAY_ACCT=y CONFIG_TASKSTATS=y Documentation/accounting/delay-accounting.txt
vmstat
用法,man vmstate
模糊查找
apropos timer (base) leon\@pc:\~/work/myHub/linux/kernel/linux/Documentation/accounting\$ apropos vmstat vmstat (8) - Report virtual memory statistics (base) leon\@pc:\~/work/myHub/linux/kernel/linux/Documentation/accounting\$
精品文章推荐阅读: