AIRobot

AIRobot quick note


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

[内存管理] 其他工程问题以及调优

发表于 2022-03-03
本文字数: 4.3k 阅读时长 ≈ 4 分钟

其他工程问题以及调优

  • DMA和cache一致性
  • 内存的cgroup
  • memcg子系统分析
  • 性能方面的调优: page in/out, swap in/out
  • Dirty ratio的一些设置
  • swappiness

DMA和cache一致性

image

工程中,DMA可以直接在内存和外设进行数据搬移,而CPU访问内存时要经过MMU。DMA访问不到CPU内部的cache,所以会出现cache不一致的问题。因为CPU读写内存时,如果在cache中命中,就不会再访问内存。

当CPU 写memory时,cache有两种算法:write_back ,write_through。一般都采用write_back。cache的硬件,使用LRU算法,把cache中的数据替换到磁盘。

image

cache一致性问题,主要靠以上两类api来解决这个问题。一致性DMA缓冲区api,和流式DMA映射api。CPU通过MMU访问DMA区域,在页表项中可以配置这片区域是否带cache。

现代的SoC,DMA引擎可以自动维护cache的同步。

内存的cgroup

进程分group,内存也分group。

进程调度时,把一组进程加到一个cgroup,控制这一组进程的CPU权重和最大CPU占用率。在/sys/fs/cgroup/memory创建一个目录,把进程放到这个group。可以限制某个group下的进程不用swap,每个group的swapiness都可以配置。

比如,当你把某个group下的swapiness设置为0,那么这个group下进程的匿名页就不允许交换了。

/proc/sys/vm/swapiness是控制全局的swap特性,不影响加到group中的进程。

也可以控制每个group的最大内存消耗为200M,当这个group下进程使用的内存达到200M,就oom。

demo: 演示用memory cgroup来限制进程group内存资源消耗的方法

1
2
3
4
5
6
7
8
9
10
11
12
swapoff -a
echo 1 > /proc/sys/vm/overcommit_memory # 进程申请多少资源,内核都允许

root@whale:/sys/fs/cgroup/memory# mkdir A
root@whale:/sys/fs/cgroup/memory# cd A
root@whale:/sys/fs/cgroup/memory/A# echo $((200*1024*1024)) > memory.limit_in_bytes

cgexec -g memory:A ./a.out


[ 560.618666] Memory cgroup out of memory: Kill process 5062 (a.out) score 977 or sacrifice child
[ 560.618817] Killed process 5062 (a.out) total-vm:2052084kB, anon-rss:204636kB, file-rss:1240kB

memory cgroup子系统分析

memcg v1的参数有25个, 通过数据结构 res_counter 来计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
~~~
/* * The core object. the cgroup that wishes to account for some
* resource may include this counter into its structures and use
* the helpers described beyond */

struct res_counter {
unsigned long long usage; /* * 目前资源消费的级别 */
unsigned long long max_usage; /* *从counter创建的最大使用值 */
unsigned long long limit; /* * 不能超过的使用限制 */
unsigned long long soft_limit; /* * 可以超过使用的限制 */
unsigned long long failcnt; /* * 尝试消费资源的失败数 */
spinlock_t lock; /* * the lock to protect all of the above.
* the routines below consider this to be IRQ-safe */
struct res_counter *parent; /* * Parent counter, used for hierarchial resource accounting */
};

内存的使用量 mem_cgroup_usage 通过递归RSS和page cache之和来计算。

struct mem_cgroup是负责内存 cgroup 的结构

1
2
3
4
5
6
struct mem_cgroup {        
struct cgroup_subsys_state css; // 通过css关联cgroup.
struct res_counter res; // mem统计变量
res_counter memsw; // mem+sw的和
struct res_counter kmem; // 内核内存统计量 ...
}

这些参数的入口都在mm/memcontrol.c下,比如说memory.usage_in_bytes的读取调用的是mem_cgroup_read函数, 统计的入口是mem_cgroup_charge_common(),如果统计值超过限制就会在cgroup内进行回收。调用者分别是缺页时调用的mem_cgroup_newpage_charge和 page cache相关的mem_cgroup_cache_charge。

当进程进入缺页异常的时候就会分配具体的物理内存,当物理内存使用超过高水平线以后,换页daemon(kswapd)就会被唤醒用于把内存交换到交换空间以腾出内存,当内存恢复至高水平线以后换页daemon进入睡眠。

缺页异常的入口是 __do_fault,

RSS在page_fault的时候记录,page cache是插入到inode的radix-tree中才记录的。

RSS在完全unmap的时候减少计数,page cache的page在离开inode的radix-tree才减少计数。

即使RSS完全unmap,也就是被kswapd给换出,可能作为SwapCache存留在系统中,除非不作为SwapCache,不然还是会被计数。

一个换入的page不会马上计数,只有被map的时候才会,当进行换页的时候,会预读一些不属于当前进程的page,而不是通过page fault,所以不在换入的时候计数。

脏页写回的“时空”控制

image

“脏页”:当进程修改了高速缓存里的数据时,该页就被内核标记为脏页,内核将会在合适的时间把脏页的数据写到磁盘中去,以保持高速缓存中的数据和磁盘中的数据是一致的。

通过时间(dirty_expire_centisecs)和比例,控制Linux脏页返回。

dirty_expire_centisecs:当Linux中脏页的时间到达dirty_expire_centisecs,无论脏页的数量多少,必须立即写回。通过在后台启动进程,进行脏页写回。

默认时间设置为30s。

dirty_ratio,dirty_background_ratio 基于空间的脏页写回控制。

不能让内存中存在太多空间的脏页。如果一个进程在循环调用write,当达到dirty_background_ratio后,后台进程就开始写回脏页。默认值5%。当达到第2个阈值dirty_ratio时,应用进程被阻塞。当内存中的脏页在两个阈值之间时,应用程序是不会阻塞。

内存何时回收:水位控制

脏页写回不是 内存回收。

脏页写回:是保证在内存不在磁盘的数据不要太多。

水位控制:是指内存何时开始回收。

image

由/pro/sys/vm/min_free_kbytes 控制,根据内存大小算出来的平方根。

pf_mem_alloc,允许内存达到低水位以下,还可以继续申请。内存的回收,在最低水位以上就开始回收。

image

每个Zone都有自己的三个水位,最小的水位是根据min_free_kbytes控制。5/4min_free_kbytes =low 3/2min_free_kbytes =high ,

Zone的最小内存达到5/4的low 水位,Linux开始后台回收内存。直到达到6/4的high水位,开始不回收。

当Zone的最小内存达到min水位,应用程序的写会直接阻塞。

实时操作系统,

image

当你要开始回收内存时,回收比例通过swappiness越大,越倾向于回收匿名页;swappiness越小,越倾向于回收file-backed的页面。

当把cgroup中的swapiness设置为0,就不回收匿名页了。

当你的应用会经常去访问数据malloc的内存,需要把swapiness设置小。dirty的设置,水位的设置都没有一个标准,要看应用使用内存的情况而定。

getdelays工具:用来评估应用等待CPU,内存,IO,的时间。

linux/Documents/accounting

image

CONFIG_TASK_DELAY_ACCT=y
CONFIG_TASKSTATS=y

vmstat 可以展现给定时间间隔的服务器的状态值,包括Linux的CPU使用率,内存使用,虚拟内存交换情况,IO读写情况。

1
vmstat 1

Documents/sysctl/vm.txt 中有所有参数最细节的描述。

[内存管理] 内存与IO的交换

发表于 2022-03-03 分类于 system
本文字数: 4.4k 阅读时长 ≈ 4 分钟

内存与I/O的交换

堆、栈、代码段是否常驻内存?本文主要介绍两类不同的页面,以及这两类页面如何在内存和磁盘间进行交换?以及内存和磁盘的颠簸行为- swaping,和硬盘的swap分区。

page cache

file-backed的页面:(有文件背景的页面,比如代码段、比如read/write方法读写的文件、比如mmap读写的文件;他们有对应的硬盘文件,因此如果要交换,可以直接和硬盘对应的文件进行交换),此部分页面进page cache。

匿名页:匿名页,如stack,heap,CoW后的数据段等;他们没有对应的硬盘文件,因此如果要交换,只能交换到虚拟内存-swapfile或者Linux的swap硬盘分区),此部分页面,如果系统内存不充分,可以被swap到swapfile或者硬盘的swap分区。

image

内核通过两种方式打开硬盘的文件,任何时候打开文件,Linux会申请一个page cache,然后把文件读到page cache里。page cache 是内存针对硬盘的缓存。

Linux读写文件有两种方式:read/write 和 mmap

1)read/write: read会把内核空间的page cache,往用户空间的buffer拷贝。

参数 fd, buffer, size ,write只是把用户空间的buffer拷贝到内核空间的page cache。

2)mmap:可以避免内核空间到用户空间拷贝的过程,直接把文件映射成一个虚拟地址指针,指向linux内核申请的page cache。也就知道page cache和硬盘里文件的对应关系。

参数 fd,

文件对于应用程序,只是一部分内存。Linux使用write写文件,只是把文件写进内存,并没有sync。而内存的数据和硬盘交换的功能去完成。

ELF可执行程序的头部会记录,从xxx到xxx是代码段。把代码段映射到虚拟地址,0~3 G, 权限是RX。这段地址映射到内核空间的page cache, 这段page cache又映射到可执行程序。

page cache,会根据LRU算法(最近最少使用)进行替换。

demo演示 page cache会多大程度影响程序执行时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
echo 3 > /proc/sys/vm/drop_caches
time python hello.py
\time -v python hello.py

root@whale:/home/gzzhangyi2015# \time -v python hello.py
Hello World! Love, Python
Command being timed: "python hello.py"
User time (seconds): 0.01
System time (seconds): 0.00
Percent of CPU this job got: 40%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.03
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 6544
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 10
Minor (reclaiming a frame) page faults: 778
Voluntary context switches: 54
Involuntary context switches: 9
Swaps: 0
File system inputs: 6528
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0

root@whale:/home/gzzhangyi2015# \time -v python hello.py
Hello World! Love, Python
Command being timed: "python hello.py"
User time (seconds): 0.01
System time (seconds): 0.00
Percent of CPU this job got: 84%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.01
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 6624
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 770
Voluntary context switches: 1
Involuntary context switches: 4
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0

总结:Linux有两种方式读取文件,不管以何种方式读文件,都会产生page cache 。

free命令的详细解释

1
2
3
4
             total       used       free     shared    buffers     cached
Mem: 49537244 1667532 47869712 146808 21652 421268
-/+ buffers/cache: 1224612 48312632
Swap: 4194300 0 4194300

image

buffers/cache都是文件系统的缓存,当访问ext3/ext4,fat等文件系统中的文件,产生cache。当直接访问裸分区(/dev/sdax)时,产生buffer。

访问裸分区的用户,主要是应用程序直接打开 or 文件系统本身。dd命令 or 硬盘备份 or sd卡,会访问裸分区,产生的缓存就是buffer。而ext4文件系统把硬盘当作裸分区。

buffer和cache没有本质的区别,只是背景的区别。

-/+ buffer/cache 的公式

used buffers/cache = used - buffers - cached

free buffers/cache = free + buffers + cached

新版free

available参数:评估出有多少空闲内存给应用程序使用,free + 可回收的。

image

File-backed和Anonymous page

  • File-backed映射把进程的虚拟地址空间映射到files

    • 比如 代码段
    • 比如 mmap一个字体文件
  • Anonymous映射是进程的虚拟地址空间没有映射到任何file

    • Stack
    • Heap
    • CoW pages

anonymous pages(没有任何文件背景)分配一个swapfile文件或者一个swap分区,来进行交换到磁盘的动作。

read/write和 mmap 本质上都是有文件背景的映射,把进程的虚拟地址空间映射到files。在内存中的副本,只是一个page cache。是page cache就有可能被踢出内存。CPU 内部的cache,当访问新的内存时,也会被踢出cache。

demo:演示进程的代码段是如何被踢出去的?

1
2
3
4
pidof firefox
cat /proc/<pid>/smaps

运行 oom.c

swap以及zRAM

数据段,在未写过时,有文件背景。在写过之后,变成没有文件背景,就被当作匿名页。linux把swap分区,当作匿名页的文件背景。

1
2
swap(v.),内存和硬盘之间的颠簸行为。 
swap(n.),swap分区和swap文件,当作内存中匿名页的交换背景。在windows内,被称作虚拟内存。pagefile.sys

页面回收和LRU

image

回收匿名页和 回收有文件背景的页面。

后台慢慢回收:通过kswapd进程,回收到高水位(high)时,才停止回收。从low -> high

直接回收:当水位达到min水位,会在两种页面同时进行回收,回收比例通过swappiness越大,越倾向于回收匿名页;swappiness越小,越倾向于回收file-backed的页面。当然,它们的回收方法都是一样的LRU算法。

Linux Page Replacement

用LRU算法来进行swap和page cache的页面替换。

image

1
2
3
现在cache的大小是4页,前四次,1,2,3,4文件被一次使用,注意第七次,5文件被使用,系统评估最近最少被使用的文件是3,那么不好意思,3被swap出去,5加载进来,依次类推。

所以LRU可能会触发page cache或者anonymous页与对应文件的数据交换。

嵌入式系统的zRAM

image

zRAM: 用内存来做swap分区。从内存中开辟一小段出来,模拟成硬盘分区,做交换分区,交换匿名页,自带透明压缩功能。当应用程序往zRAM写数据时,会自动把匿名页进行压缩。当应用程序访问匿名页时,内存页表里不命中,发生page fault(major)。从zRAM中把匿名页透明解压出来,还到内存。

[内存管理] 进程的内存消耗和泄漏

发表于 2022-03-03 分类于 system
本文字数: 2.4k 阅读时长 ≈ 2 分钟

进程的内存消耗和泄漏

  • 进程的VMA
  • 进程内存消耗的4个概念: vss、rss、pss和uss
  • page fault的几种可能性, major 和 minor
  • 应用内存泄漏的界定方法
  • 应用内存泄漏的检测方法:valgrind 和 addresssanitizer

本节重点阐述 Linux的应用程序究竟消耗了多少内存?

一是,看到的内存消耗,并不是一定是真的消耗。

二是,Linux存在大量的内存共享的情况。

动态链接库的特点:代码段共享内存,数据段写时拷贝。

把一个应用程序跑两个进程,这两个进程的代码段也是共享的。

当我们评估进程消耗多少内存时,就是指在用户空间消耗的内存,即虚拟地址在0~3G的部分,对应的物理地址内存。内核空间的内存消耗属于内核,系统调用申请了很多内存,这些内存是不属于进程消耗的。

进程的虚拟地址空间VMA

image

task_struct里面有个mm_struct指针, 它代表进程的内存资源。pgd,代表 页表的地址; mmap 指向vm_area_struct 链表。 vm_area_struct 的每一段代表进程的一个虚拟地址空间。vma的每一段,都可能是可执行程序的某个数据段、某个代码段,堆、或栈。一个进程的虚拟地址,是在0~3G之间任意分布的。

image

上图 提供三种方式,看到进程的VMA空间。

pmap 3474

基地址,size, 权限,

通过以上的方式,可以看到进程的虚拟地址空间,分布在0~3G,任意一小段一小段分布的。

应用程序运行起来,就是一堆各种各样的VMA。VMA对应着 堆、栈、代码段、数据段、等,不在任何段里的虚拟地址空间,被认为是非法的。

image

当指针访问地址时,落在一个非法的地址,即不在任何一个VMA区域。相当于访问一个非法的地址,这些虚拟地址没有对应的物理地址。应用程序收到page fault,查看原因,访问非法位置,返回segv。

在VMA的东西,不等于在内存。调malloc申请了100M内存,立马会多出一个100M的 VMA,代表这段vma区域有r+w权限。

应用程序访问内存,必须落在一个VMA里。其次,落在一个VMA里也不一定对。把100M的堆申请出来,100M内存页全部映射为0页。页表里每一页写的只读,页表和硬件对应,MMU只查页表。而在页表项中指向物理地址的权限是只读,所以在任何时候,去写其中任何一页,硬件都会发生缺页中断。

Linux 内核在缺页中断的处理程序,通过MMU寄存器读出发生page fault的地址和原因。发现此时page fault的原因是写一个页表里记录只读的物理地址,而vma记录的虚拟地址又是r+w,此时,linux会申请一页内存。同时把页表中的权限改为r+w。

总结:

Linux 内核通过VMA管理进程每一段虚拟地址空间和权限。一旦发生page fault,如果没有落在任何一个vma区域,会干掉。

VMA的起始地址+size,用来限定程序访问的地址是否合法。VMA中每一段的权限,是来界定访问这段地址是否使用正确的方式访问。

把所有的vma加起来,构成进程的虚拟地址空间,但这并不代表进程真实耗费的内存。拿到之后才是真实耗费的内存,RSS。耗费的虚拟内存,是VSS。

page fault的几种可能性

image

1、申请堆内存vma,第一次写,页表里的权限是R ,发生page fault,linux会去申请一页内存,此时把页表权限设置为 R+W。

2、内存访问落在空白非法区域,程序收到segv段错误。

3、代码段在VMA记录是R+X,此时如果对代码段执行写,程序会收到segv段错误。

minor 和major 缺页

缺页,分为两种情况:主缺页 和次缺页。

主缺页 和次缺页,区别就是 申请内存时,是否需要读硬盘。前者需要。

如上图第4种情况,在代码段里执行时,出现缺页。linux申请一页内存,而且要从硬盘中读取代码段的内容,此时产生了IO,称为 major缺页。

无论是代码段还是堆,都是边执行边产生缺页中断,申请实际的内存给代码段,且从硬盘中读取代码段的内容到内存。这个过程时间比较长。

minor: malloc的内存,产生缺页中断。去申请一页内存,没有产生IO的行为。major缺页处理时间,远大于minor。

image

vss、rss、pss和uss的区别

image

1
2
3
4
5
6
VSS - Virtual Set Size
RSS - Resident Set Size
PSS - Proportional Set Size
USS - Unique Set Size
ASAN - AddressSanitizer
LSAN - LeakSanitizer

如上图,中间是一根内存条。三个进程分别是1044,1045,1054, 每一个进程对应一个page table,页表项记录虚拟地址如何往物理地址转换。硬件里的寄存器,记录页表的物理地址。当linux做进程上下文切换时,页表也跟着一起切换。

image

三个进程都需要使用libc的代码段。

VSS = 1 +2 +3

RSS = 4 +5 +6

PSS= 4/3 + 5/2 + 6 比例化的

USS= 6 独占且驻留的

工具:smem ,查看进程使用内存的情况。

一般来讲,进程使用的内存量,还是看PSS,强调公平性。看内存泄漏看USS 就好了。

内存泄漏 界定和检测方法

界定:连续多点采样法,随着时间越久,进程耗费内存越多。

主要由内存申请和释放不是成对引起。RSS/USS曲线,

观察方法:使用smem工具查看多次进程使用内存,USS使用量。

检查工具:

1、valgrind ,会跑一个虚拟机,运行时检查进程的内存行为。会放慢程序的速度。不需要重新编译程序。

2、addressanitizer,需要重新编译程序。编译时加参数,-fsanitize

gcc 4.9才支持,只会放慢程序速度2~3倍。

[内存管理] 内存的动态申请和释放

发表于 2022-03-03 分类于 system
本文字数: 3.8k 阅读时长 ≈ 3 分钟

内存的动态申请和释放

内核空间 和用户空间申请的内存最终和buddy怎么交互?以及在页表映射上的区别?虚拟地址到物理地址,什么时候开始映射?

Buddy的问题

分配的粒度太大
buddy算法把空闲页面分成1,2,4页,buddy算法会明确知道哪一页内存空闲还是被占用?

4k,8k,16k

无论是在应用还是内核,都需要申请很小的内存。

从buddy要到的内存,会进行slab切割。

slab原理:

比如在内核中申请8字节的内存,buddy分配4K,分成很多个小的8个字节,每个都是一个object。

slab,slub,slob 是slab机制的三种不同实现算法。

Linux 会针对一些常规的小的内存申请,数据结构,会做slab申请。

cat /proc/slabinfo 可以看到内核空间小块内存的申请情况,也是slab分配的情况。

:每个slab一共可以分出多少个obj,
:还可以分配多少个obj,
< pagesperslab>:每个slab对应多少个pages,
< objperslab>:每个slab可以分出多少个object,
< objsize>:每个obj多大,

slab主要分为两类:

一、常用数据结构像 nfsd_drc, UDPv6,TCPv6 ,这些经常申请和释放的数据结构。比如,存在TCPv6的slab,之后申请 TCPv6 数据结构时,会通过这个slab来申请。

image

二、常规的小内存申请,做的slab。例如 kmalloc-32,kmalloc-64, kmalloc-96, kmalloc-128

image

image

注意,slab申请和分配的都是只针对内核空间,与用户空间申请分配内存无关。用户空间的malloc和free调用的是libc。

slab和buddy的关系?

1、slab的内存来自于buddy。slab相当于二级管理器。

2、slab和buddy在算法上,级别是对等的。

两者都是内存分配器,buddy是把内存条分成多个Zone来管理分配,slab是把从buddy拿到的内存,进行管理分配。

同理,malloc 和free也不找buddy拿内存。 malloc 和free不是系统调用,只是c库中的函数。

mallopt

在C库中有一个api是mallopt,可以控制一系列的选项。

image

M_TRIM_THRESHOLD:控制c库把内存还给内核的阈值。

-1UL 代表最大的正整数。

此处代表应用程序把内存还给c库后,c库并不把内存还给内核。

<\do your RT-thing>

程序在此处申请内存,都不需要再和内核交互了,此时程序的实时性比较高。

kmalloc vs. vmalloc/ioremap

内存空间: 内存+寄存器

register –> LDR/STR

所有内存空间的东西,CPU去访问,都要通过虚拟地址。
CPU –> virt –> mmu –> phys

cpu请求虚拟地址,mmu根据cpu请求的虚拟地址,查页表得物理地址。

buddy算法,管理这一页的使用情况。

两个虚拟地址可以映射到同一个物理地址。

image

页表 -> 数组,

任何一个虚拟地址,都可以用地址的高20位,作为页表的行号去读对应的页表项。而低12位,是指页内偏移。(由于一页是4K,2^12 足够描述)

kmalloc 和 vmalloc 申请的内存,有什么区别?
答:申请之后,是否还要去改页表。一般情况,kmalloc申请内存,不需要再去改页表。同一张页表,几个虚拟地址可以同时映射到同一个物理地址。

寄存器,通过ioremap往vmalloc区域,进行映射。然后改进程的虚拟地址页表。

总结:所有的页,最底层都是用buddy算法进行管理,用虚拟地址找物理地址。理解内存分配和映射的区别,无论是lowmem还是highmem 都可以被vmalloc拿走,也可能被用户拿走,只不过拿走之后,还要把虚拟地址往物理地址再映射一遍。但如果是被kmalloc拿走,一般指低端内存,就不需要再改进程的页表。因为这部分低端内存,已经做好了虚实映射。

1
cat /proc/vmallocinfo |grep ioremap

可以看到寄存器中的哪个区域,被映射到哪个虚拟地址。

vmalloc区域主要用来,vmalloc申请的内存从这里找虚拟地址 和 寄存器的ioremap映射。

Linux内存分配的lazy行为

Linux总是以最lazy的方式,给应用程序分配内存。

image

malloc 100M内存成功时,其实并没有真实拿到。只有当100M内存中的任何一页,被写一次的时候,才成功。

vss:虚拟地址空间。 rss:常驻内存空间

malloc 100M内存成功时,Linux把100M内存全部以只读的形式,映射到一个全部清0的页面。

当应用程序写100M中每一页任意字节时,会发出page fault。 linux 内核收到缺页中断后,从硬件寄存器中读取到,包括缺页中断发生的原因和虚拟地址。Linux从内存条申请一页内存,执行cow,把页面重新拷贝到新申请的页表,再把进程页表中的虚拟地址,指向一个新的物理地址,权限也被改成R+W。

调用brk 把8k变成 16k。

针对应用程序的堆、代码、栈、等,会使用lazy分配机制,只有当写内存页时,才会真实请求内存分配页表。但,当内核使用kmalloc申请内存时,就真实的分配相应的内存,不使用lazy机制。

内存OOM

当真实去写内存时,应用程序并不能拿到真实的内存时。Linux启动OOM,linux在运行时,会对每一个进程进行out-of-memory打分。打分主要基于,耗费的内存。耗费的内存越多,打分越高。

1
cat /proc/<pid>/oom_score

demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
int max = -1;
int mb = 0;
char *buffer;
int i;
#define SIZE 2000
unsigned int *p = malloc(1024 * 1024 * SIZE);
printf("malloc buffer: %p\n", p);
for (i = 0; i < 1024 * 1024 * (SIZE/sizeof(int)); i++) {
p[i] = 123;
if ((i & 0xFFFFF) == 0) {
printf("%dMB written\n", i >> 18);
usleep(100000);
}
}
pause();
return 0;
}

设定条件:

总内存1G

1、swapoff -a 关掉swap交换

2、echo 1 > /proc/sys/vm/overcommit_memory

3、内核不去评估系统还有多少空闲内存

Linux进行OOM打分,主要是看耗费内存情况,此外还会参考用户权限,比如root权限,打分会减少30分。

还有OOM打分因子:/proc/pid/oom_score_adj (加减)和 /proc/pid/oom_adj (乘除)。

image

总结:

1、slab的作用,针对在内核空间小内存分配,和常用数据结构的申请。

2、同样的二次分配器,在用户空间是C库。malloc和free的时候,内存不一定从buddy分配和还给buddy。

3、kmalloc,vmalloc 和malloc的区别

  • kmalloc:申请内存,一般在低端内存区。申请到时,内存已经映射过了,不需要再去改进程的页表。所以,申请到的物理页是连续的。
  • vmalloc:申请内存,申请到就拿到内存,并且已经修改了进程页表的虚拟地址到物理地址的映射。vmalloc()申请的内存并不保证物理地址的连续。
  • 用户空间的malloc:申请内存,申请到并没有拿到,写的时候才去拿到。拿到之后,才去改页表。申请成功,页表只读,只有到写时,发生page fault,才去buddy拿内存。
  • kmalloc和vmalloc针对内核空间,malloc针对用户空间。这些内存,可以来自任何一个Zone。
  • 无论是kmalloc,vmalloc还是用户空间的malloc,都可以使用内存条的不同Zone,无论是highmem zone、lowmem zone 和 DMA zone。

4、如果在从buddy拿不到内存时,会触发Linux对所有进程进行OOM打分。当Linux出现内存耗尽,就kill一个oom score 最高的那个进程。oom_score,可以根据 oom_adj (-17~25)。

安卓的程序,不停的调整前台和后台进程oom_score,当被切换到后台时,oom_score会被调整的比较大。以保证前台的进程不容易因为oom而kill掉。

[内存管理] 硬件管理和分页管理

发表于 2022-03-03 分类于 system
本文字数: 3.6k 阅读时长 ≈ 3 分钟

前言

内存管理相对复杂,涉及到硬件和软件,从微机原理到应用程序到内核。比如,硬件上的cache,CPU如何去寻址内存,页表, DMA,IOMMU。 软件上,要知道底层怎么分配内存,怎么管理内存,应用程序怎么申请内存。

常见的误解包括:

对free命令 cache和buffer的理解。

1、应用程序申请10M内存,申请成功其实并没有分配。内存其实是边写边拿。代码段有10M,并不是真的内存里占了10M。

2、内存管理学习难,一是网上的资料不准确,二是学习时执行代码,具有欺骗性。看到的东西不一定真实,要想理解必须陷入Linux本身。

学习时,不要过快陷入太多细节,而要先对整个流程整个框架理解。

先理清楚脉络和主干,从硬件到最底层内存的分配算法,–>到内核的内存分配算法,–>应用程序与内核的交互,–>到内存如何做磁盘的缓存, –> 内存如何和磁盘替换。

再动手实践demo。

硬件原理 和 分页管理

本文主要让大家理解内存管理最底层的buddy算法,内存为什么要分成多个Zone?

  • CPU寻址内存,虚拟地址、物理地址
  • MMU 以及RWX权限、kernel和user模式权限
  • 内存的zone: DMA、Normal和HIGHMEM
  • Linux内存管理Buddy算法
  • 连续内存分配器(CMA)

内存分页

image

CPU 一旦开启MMU,MMU是个硬件。CPU就只知道虚拟地址了。如果地址是32位,0x12345670 。

假设MMU的管理是把每一页的内存分成4K,那么其中的670是页内偏移,作为d;0x12345 是页号,作为p。通过虚拟地址去查对应的物理地址,用0x12345去查一张页表,页表(Page table)本身在内存。

硬件里有寄存器,记录页表的基地址,每次进程切换时,寄存器就会更新一次,因为每个进程的页表不同。

CPU一旦访问虚拟地址,通过页表查到页表项,页表项记录对应的物理地址。

总结:一旦开启MMU,CPU只能看到虚拟地址,MMU才能看到物理地址。

虚拟地址是指针,物理地址是个整数。内存中的一切均通过虚拟地址来访问。

1
typedef u64 phys_addr_t;

去内存里读取页表会比较慢,CPU里有个高速单元tlb,它是页表的高速缓存。CPU就不需要在内存里读页表,直接在tlb中读取,从虚拟地址到物理地址的映射。如果tlb中读取不到,才回到内存里读取页表映射,并且在tlb中命中。

虚拟地址:0x12345 670 –> 1M

物理地址:1M+670 MMU去访问这个物理地址。

内存的映射以页为单位。

页表(Page table)记录的页权限

cpu虚拟地址,mmu根据cpu请求的虚拟地址,访问页表,查得物理地址。

每个MMU中的页表项,除了有虚拟地址到物理地址的映射之外,还可以标注这个页的 RWX权限和 kernel和user模式权限(用户空间,内核空间读取地址的权限),它们是内存管理两个的非常重要的权限。

一是,这一页地址的RWX权限 ,标记这4k地址的权限。一般用来做保护。

Pagefault,是CPU提供的功能。两种情况会出现Pagefault,一是,CPU通过虚拟地址没有查到对应的物理地址。二是,MMU没有访问物理地址的权限。

MPU,memory protection unit.

二是,MMU的页表项中,还可以标注这一页的地址:可以在内核态访问,还是只能在用户态访问。用户一般映射到0~3G,只有当CPU陷入到内核模式,才可以访问3G以上地址。

程序在用户态运行,处于CPU非特权模式,不能访问特权模式才能访问的内存。内核运行在CPU的特权模式,从用户态陷入到内核态,发送 软中断指令,CPU进行切环,x86从3环切到0环,到一个固定的地址去执行。软件就从非特权模式,跳到特权模式去执行。

MMU,能把某一段地址指定为只有特权模式才能够访问,会把内核空间3G以上的页表项里的每一行,指定为只有CPU 0环才能访问。应用程序没有陷入到内核态,是无法访问内核态的东西。

intel的漏洞meltdown,就是让用户可以在用户态读到内核态的东西。

meltdown 攻击原理: 基于时间的 旁路攻击 side-channel

李小璐买汉堡的故事 –> 安全的基于时间的旁路攻击技巧。

比如试探用户名,密码。比如一个软件比较傻,每次第一个字母就不对,就不对比第2个字母了。那我每次26个字母实验换一次,看哪个字母反弹地最慢,就证明是这个字母的密码。

密码是abc, 我敲了d,那么第一个字母就不对,软件这个时候如果快速的返回出错,我知道首字母不是d,我可以实验出来首字母是a,然后接着一个个字母实,就可以把密码试探出来了。类似地原理。。

下面的这个例子,演示 page table记录的RWX权限的作用

image

页表的权限,RWX权限,和 用户空间,内核空间读取的权限。

内存分Zone

下面解释内存为什么分Zone? DMA zone.

image

内存的分Zone,全都是物理地址的概念。内存条,被分为三个Zone。

分DMA Zone的原因,是DMA引擎的缺陷。DMA引擎 可以直接访问内存空间的地址,但不一定能够访问到所有的内存,访问内存时会存在一定的限制。

当CPU 和DMA同时访问内存时,硬件上会有仲裁器,选择优先级高的去访问内存。

为什么要切DMA zone?
DMA Zone的大小,是由硬件决定的。访问不到更高的内存。

什么叫做 normal zone? highmem zone?

highmem和lowmem 都是指的内存条,在虚拟地址空间,只能称为highmem,lowmem映射区。

如上图,内存虚拟地址空间0~4G,3~4G是内核空间的虚拟地址,0-3G 是用户空间的虚拟地址。

内核空间,访问任何一片内存都要虚拟地址。Linux为了简化内存访问,开机就把lowmem的物理地址一一映射到虚拟地址。highmem 地址包括了 normal + DMA。

image

lowmem是开机就直接映射好的内存,CPU访问这片内存,也是通过3G以上的虚拟地址。这段地址的虚拟地址和物理地址是直接线性映射,通过linux的两个api (phys_to_virt / virt_to_phys)在虚拟和物理之间进行映射, highmem 不能直接用。

内核空间一般不使用highmem,内核一般使用kmalloc在lowmem申请内存,使用 kmmap在highmem 申请内存。lowmem 映射了,并不代表被内核使用掉了,只是不需要重建页表。内核使用lowmem内存,同样是要申请。 应用程序一样可以申请 lowmem 和highmem。

总结:

内存分highmem zone的原因,地址空间整体不够。

DMA zone产生的原因,硬件DMA引擎的访问缺陷。

image

硬件层的内存管理- buddy算法

每个zone都会使用buddy算法,把所有的空闲页面变成2的n次方进行管理。

/proc/buddyinfo

通过/proc/buddyinfo,可以看出空闲内存的情况

image

CPU寻址内存的方法:通过MMU提供的虚拟地址到物理地址的映射访问。

如何处理内存碎片

image

X86 linux 内核有一个线程 compaction, 会进行内存碎片整理,会尽量移出大内存。

CMA:continuous memory allocation

内核把虚拟地址 指向新的物理地址,让应用程序毫无知觉情况,把64M内存腾出来给DMA。当用DMA的api申请内存,会走到CMA。在dts中指定哪块区域做CMA。

Documentation/devicetree/bindings

reserved-memory/reserved-memory.txt

dma_alloc_coherent

CMA, iommu,

CMA主要是给需要连续内存的DMA用的。但是为了避免DMA不用的时候浪费,才在DMA不用的时候给可移动的页面用。不能移动的页面,不能从CMA里面拿。所以主要是APP和文件的page cache的内存,才可以在CMA区域拿。

这样当DMA想拿CMA区域的时候,要么移走,要么抛弃。总之,必须保证DMA需要这片CMA区域的时候,之前占着CMA的统统滚蛋。

不具备滚蛋能力的内存,不能从CMA区域申请。你申请也滚蛋不了,待会DMA上来用的时候,DMA就完蛋了。

要搞清楚CMA的真正房东是那些需要连续内存的DMA,其他的人都只是租客。DMA要住的时候,租客必须走。哪个房东会把房子租给一辈子都不准备走的人?内核绝大多数情况下的内存申请,都是无法走的。应用走起来很容易,改下页面就行了。

CMA和不可移动之间,没有任何交集。CMA唯一的好处是,房东不住的时候,免得房子空置。

123…26
AIRobot

AIRobot

AIRobot quick note
130 日志
15 分类
23 标签
GitHub E-Mail
Creative Commons
0%
© 2023 AIRobot | 716k | 10:51