深入理解Linux内核内存管理机制与实现(下)

简介: 深入理解Linux内核内存管理机制与实现

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分区:

  1. swapon –p 10 /dev/zram0
  2. cat /proc/swaps
  3. swapoff –a 不能关掉文件背景页面。
  4. 不带pagecached的IO, direct IO。 在用户态根据业务特点做cached
  5. 类似于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\$

精品文章推荐阅读:

相关文章
|
6天前
|
安全 Linux 编译器
探索Linux内核的奥秘:从零构建操作系统####
本文旨在通过深入浅出的方式,带领读者踏上一段从零开始构建简化版Linux操作系统的旅程。我们将避开复杂的技术细节,以通俗易懂的语言,逐步揭开Linux内核的神秘面纱,探讨其工作原理、核心组件及如何通过实践加深理解。这既是一次对操作系统原理的深刻洞察,也是一场激发创新思维与实践能力的冒险。 ####
|
2天前
|
存储 运维 Java
💻Java零基础:深入了解Java内存机制
【10月更文挑战第18天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
15 1
|
9天前
|
网络协议 Linux 调度
深入探索Linux操作系统的心脏:内核与系统调用####
本文旨在揭开Linux操作系统中最为核心的部分——内核与系统调用的神秘面纱,通过生动形象的语言和比喻,让读者仿佛踏上了一段奇妙的旅程,从宏观到微观,逐步深入了解这两个关键组件如何协同工作,支撑起整个操作系统的运行。不同于传统的技术解析,本文将以故事化的方式,带领读者领略Linux内核的精妙设计与系统调用的魅力所在,即便是对技术细节不甚了解的读者也能轻松享受这次知识之旅。 ####
|
5天前
|
缓存 算法 安全
深入理解Linux操作系统的心脏:内核与系统调用####
【10月更文挑战第20天】 本文将带你探索Linux操作系统的核心——其强大的内核和高效的系统调用机制。通过深入浅出的解释,我们将揭示这些技术是如何协同工作以支撑起整个系统的运行,同时也会触及一些常见的误解和背后的哲学思想。无论你是开发者、系统管理员还是普通用户,了解这些基础知识都将有助于你更好地利用Linux的强大功能。 ####
13 1
|
6天前
|
缓存 编解码 监控
深入探索Linux内核调度机制的奥秘###
【10月更文挑战第19天】 本文旨在以通俗易懂的语言,深入浅出地剖析Linux操作系统内核中的进程调度机制,揭示其背后的设计哲学与实现策略。我们将从基础概念入手,逐步揭开Linux调度策略的神秘面纱,探讨其如何高效、公平地管理系统资源,以及这些机制对系统性能和用户体验的影响。通过本文,您将获得关于Linux调度机制的全新视角,理解其在日常计算中扮演的关键角色。 ###
25 1
|
13天前
|
网络协议 Linux 芯片
Linux 内核 6.11 RC6 发布!
【10月更文挑战第12天】
76 0
Linux 内核 6.11 RC6 发布!
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
295 0
|
5天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
13 1
|
9天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
13天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。