- 作者: 雪山肥鱼
- 时间:20210329 21:50
- 目的:DMA与Cache的一致性等问题探讨
# DMA与Cache 的一致性
## 一致性问题
## dma_alloc_coherent的例外
## SMMU|IOMMU
# cgroup
# dirty page 的写回机制
# 内存回收的机制
swappiness
# 相关工具介绍
## getdelays
## vmstat
CPU、内存、I/O,如果不理解内存,很多知识都会模糊。不能贯穿一个体系。比如做I/O的时候要经过内存,内存何时真正的去找文件系统去写。内存又什么时候开始回收。为什么内存的大小又严重影响系统性能。
DMA与Cache 的一致性
一致性问题
mem中有一块报文,cpu会将这块报文读到cache,cpu再读这块,cache hit。则会从cache中取值。
- 如果外设是一张网卡,通过DMA 数据传到内存,将红色这块涂成了绿色。内存已经绿了,但是cpu读这块数据却还是红色。造成内存 cache 不一致。
- 同样 CPU 写红色区域数据的告诉cache, cache 并没有与mem做同步的话,此时数据经过DMA,发送的报文也是有问题的。
解决方案:
- Coherent DMA buffers 一致性
- DMA Streaming Mapping 流式DMA映射
-
Coherent DMA buffers 一致性
示意图.png
对于一个很弱的硬件,当硬件没有对一致性问题有帮助的时候。
dma_alloc_coherent, 写驱动的时候自己申请的一片内存。- cpu 读写不带cache
- dma读写也不带cache
这样就不会出现一致性问题。但是很多情况下你又不能用dma_alloc_coherent, 除非自己写驱动,自己申请的内存。
但是很多情况下,一个tcp/ip 协议栈,有一个 socket buffer, 这块buffer 并不是程序员申请出来的内存。这时候不可能用dma_alloc_coherent.
- DMA Streaming Mapping 流式DMA映射
- 发包
此时可以用 dma_map_single 与 dma_unmap_single, 这个api 会将cache里的非程序员用dma申请的内存做一次flush,同步到内存中。 - 收包
会将cache 里的内容 置换为 invalid, 详情见cache line 那一章节的 关于MESI一致性的阐述。CPU是可以控制cache 的 flag,但他不能访问某块cache的 第几个byte的
还有 dma_map_sg, dma_unmap_sg这两个API,有的dma引擎较强,支持 聚集散列,自动传n个buffer,第一个传完,传第二个,并不需要连续的内存做DMA.可以用上述两个api,可以将多个不连续的 buffer 做自动传输(以后接触到再查资料学习把)。
- 发包
dma_alloc_coherent的例外
一般情况下这个api 是不带cache(绿色)。但是当cpu支持cache互联网络。cache coherent interconnnect,CPU的cache 可以感知到外部设备。硬件做同步。 (就是MESI的同步手段)。此时dam_alloc_coherent申请的内存就可以带上cache
表面上都是 上述关于dma的API,但是后端针对不同的平台,实现的可能不同。
SMMU | IOMMU
DMA 自带 MMU,因此带有SMMU的DMA并不在乎申请的内存是否连续,会将物理地址映射成虚拟连续的。但是申请内存依旧使用dma_alloc_coherent。上述几个不带MMU的DMA 申请的内存 都是通过CMA (管CMA要)申请的连续内存。
但是带有MMU的DMA申请内存可以不连续
由此可以看出硬件帮你做了很多工作后,你就少操心很多啦。
cgroup
进程分组。明显qq的体验会优于word。QOS引入。以后遇到再继续学习
dirty page 的写回机制
脏页的写回有两个维度:
- 时间维度
dirty_expire_centisecs
当APP将内容写进脏页,脏页不能在内存中呆的时间太长。假设dirty_expire_centisecs设置成5个小时,一旦掉电数据全部丢失。时间维度不考虑脏页的数量,哪怕只有一页,只要到了时间点就写到硬盘中。
当然写硬盘的操作对于APP来说是透明的,APP感知不到写硬盘的操作。 - 空间维度
dirty_ratio & dirty_background ratio
主要受到这两个空间维度的影响。
cd /proc/sys/vm
cat dirty_background_ratio
假设 dirty_background_ratio 20%, dirty_ratio 30%. 1G内存
当脏页到达200MB的时候,此时你感知不到 在写硬盘,linux后台会帮你在后台将数据写回。
继续写,脏页到达300MB,后台写硬盘的速度 赶不上 APP 写内存的速度,那么linux会直接堵住前台APP进程。保证dirty_ratio 不能超过30%。
- 比如当你写word的时候,流量达不到 dirty_bg_ratio. 根本感觉不到在写硬盘,真正起到作用的是时间维度。dirty_expire_centisecs.
2.当写的比较剧烈,达到dirty_bg_ratio,但是并没有达到dirty_ratio. 还是感觉不到 - 达到dirty_ratio,linux堵住前台APP 不让你写了。会感觉非常卡顿。
内存回收的机制
内存回收与脏页写回是两回事。内存回收,指的是这块内存不要了,下次再需要的时候,重新再申请。
cat /proc/zoneinfo
cat min_free_kbyes 最小内存。根据最小内存算出 low 与 high水位
a) linux 回收进程直到high水位为止,认为系统内存是足够的
b) min 系统拥有的进程不能比这个再小
假设 malloc 内存后,不断的去写内存。
- 到达low水位后,系统后台启动kswapd reclaim。
- 应用程序此时没有那么疯狂,则应用程序不会被堵死
- 但内存回收的速度并不一定比你写的速度快,继续疯狂的拿内存
- 到达min 水位,linux 堵住前台 直接做 direct_reclaim.(write 有时慢,有时快,因为到达了dirty_ratio,前台被堵死啦)
程序变得卡顿 可以从水位上查看问题。
swappniess
那么回收的内存可能是 swap的,也可能是file_backed ,也有可能是swap。
这里用swappiness 参数决定。swappiness反应是否积极的使用swap空间
swappiness 参数设置的越大,那么就越倾向回收匿名页。
- swappiness = 0 仅在 (free and file-backed pages < high water marked in a zone),说明只回收file-backed的页面是不够的,需要回收swap 空间,也就是说最后才回收swap空间。
- swappiness = 60 默认值
- swappiness = 100 内核将积极的使用swap空间
例外 cgroup 的 swappiness 只负责 这个gourp的,不影响其他group 的 swappiness参数。
- 为什么要保证min_free_kbytes
min_free_kbytes:
This is used to force the linux VM to keep a minimum number of kilobytes free. The VM uses this number to computre a watermark[WMARK_MIN] value for each lowmem zone in the system. Each lowmem zone gets a number of reserved free pages based proportionally on its size.
多个node 多个lowzone?
https://blog.csdn.net/longwang155069/article/details/105451267
linux内核中存在 PF_MEMALLOC 紧急内存,可以忽略内存管理的水位进行分配。比如回收内存的代码也是要申请内存的。会带上PF_MEMALLOC标记。内核发现带有这个标记,那么即使到达了min,还是会申请成功的。突破min水位。
军队征粮,得派出征粮队,再不征粮要饿死啦。但是征粮队也是需要吃饭的。
水位代码:
/develop/linux/mm/ page_alloc.c
vfs_cache_pressure
该文件表示内核内存回收倾向于direcotry 和 inode cache. 针对 slab 级的 可回收。
相关工具介绍
用到再学习
getdelays
//测试代码
int main(int argc, char ** argv)
{
int max = -1;
int mb = 0;
char * buffer;
int i ;
#define SIZE 1000
unsigned int * p = malloc(1024*1024*SIZE);
print("malloc bufer:%p\n", p);
/* trigger swap out, 内存不够,根据水位与swappiness,会发生swap out*/
for(i = 0 ; i< 1024*1024 * (SIZE/sizeof(int)); i ++) {
p[i] = 123;
if((i & 0xFFFFF) == 0) {
printf("%dMB written\n", i>>18);
}
}
/*trigger swap in,因为前面已经out出去了,所以一定要swapin,当然因为内存不够也会伴随swap out*/
for(i = 0 ; i< 1024*1024 * (SIZE/sizeof(int)); i ++) {
volatile unsigned int a;
a = p[i]
if((i & 0xFFFFF) == 0) {
printf("%dMB read\n", i>>18);
}
}
free(p);
return 0
}
- 虚拟机内存很小,代码里申请1g内存
- trigger swap out, 内存不够,根据水位与swappiness,会发生swap out
-
trigger swap in,因为前面已经out出去了,所以一定要swapin,当然因为内存不够也会伴随swap out
sudo ./getdelays -d -c ./swap
得到结果
结果.png
- CPU调度延迟
平均等多久才会被调度到, io动作平均延迟1ms,total delay 总延迟 单位nm,会除count - swap in count, 内存页的换入换出发生了将近3w次的swap in
- 因为不断的申请内存, 到达low水位 或者 min,内存块耗尽,系统帮我护手内存的relcaim 发生了148
可以看出是有内存swap 和 回收的压力的。
明明只是写内存,但是为什么会有内存与硬盘之间的IO操作呢?
- 主要是水位
- 可能也会有一丢丢page 的 LRU
vmstat
man vm stat 有问题找男人 当然也可以用 man man
apropos timer 所有带timer的函数名都找出来了。
将上述代码里 加入while(1)
int main(int argc, char ** argv)
{
int max = -1;
int mb = 0;
char * buffer;
int i ;
#define SIZE 1000
unsigned int * p = malloc(1024*1024*SIZE);
print("malloc bufer:%p\n", p);
/* trigger swap out, 内存不够,根据水位与swappiness,会发生swap out*/
while(1)
{
for(i = 0 ; i< 1024*1024 * (SIZE/sizeof(int)); i ++) {
p[i] = 123;
if((i & 0xFFFFF) == 0) {
printf("%dMB written\n", i>>18);
}
}
/*trigger swap in,因为前面已经out出去了,所以一定要swapin,当然因为内存不够也会伴随swap out*/
for(i = 0 ; i< 1024*1024 * (SIZE/sizeof(int)); i ++) {
volatile unsigned int a;
a = p[i]
if((i & 0xFFFFF) == 0) {
printf("%dMB read\n", i>>18);
}
}
}
free(p);
return 0
}
vmstat 5 5s一次
si so 大量的swapin swapout
in 中断等信息