深入理解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\$

精品文章推荐阅读:

相关文章
|
5天前
|
缓存 算法 Linux
深入理解Linux内核调度器:公平性与性能的平衡####
真知灼见 本文将带你深入了解Linux操作系统的核心组件之一——完全公平调度器(CFS),通过剖析其设计原理、工作机制以及在实际系统中的应用效果,揭示它是如何在众多进程间实现资源分配的公平性与高效性的。不同于传统的摘要概述,本文旨在通过直观且富有洞察力的视角,让读者仿佛亲身体验到CFS在复杂系统环境中游刃有余地进行任务调度的过程。 ####
26 6
|
3天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
19 9
|
2天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
18 6
|
3天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
18 5
|
1天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
14 4
|
3天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
4天前
|
存储 监控 安全
Linux内核调优的艺术:从基础到高级###
本文深入探讨了Linux操作系统的心脏——内核的调优方法。文章首先概述了Linux内核的基本结构与工作原理,随后详细阐述了内核调优的重要性及基本原则。通过具体的参数调整示例(如sysctl、/proc/sys目录中的设置),文章展示了如何根据实际应用场景优化系统性能,包括提升CPU利用率、内存管理效率以及I/O性能等关键方面。最后,介绍了一些高级工具和技术,如perf、eBPF和SystemTap,用于更深层次的性能分析和问题定位。本文旨在为系统管理员和高级用户提供实用的内核调优策略,以最大化Linux系统的效率和稳定性。 ###
|
3天前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。
|
6天前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
22 6
|
6天前
|
机器学习/深度学习 负载均衡 算法
深入探索Linux内核调度机制的优化策略###
本文旨在为读者揭开Linux操作系统中至关重要的一环——CPU调度机制的神秘面纱。通过深入浅出地解析其工作原理,并探讨一系列创新优化策略,本文不仅增强了技术爱好者的理论知识,更为系统管理员和软件开发者提供了实用的性能调优指南,旨在促进系统的高效运行与资源利用最大化。 ###