一、DMA与cache的一致性
1、解决方法:
(1) 一致性DMA:
访问 DMA 内存区域是不带有cache的,这样cpu和外设看到的DMA的内存数据时时刻刻都是一致的
API函数:
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);
一般来说,只有自己写驱动时,会用一致性DMA。
(2) 流式DMA:
CPU在访问内存时,其实是访问的cache,只有在需要时,才会将cache中的数据与内存的数据进行同步;而外设是通过DMA直接访问的物理内存。所以存在外设和cpu看到的内存数据存在不一致的情况。
API函数:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction);
//该组函数的使用需要DMA支持散列/聚集(scatter/gather )操作
int dma_map_sg(struct device *dev, struct scatterlist *list, int nents, enum dma_data_direction direction);
void dma_unmap_sg(struct device *dev, struct scatterlist *list, int nents, enumdma_data_direction direction);
上述api,在cpu向内存写入数据时,可以将cache中的数据flush到实际的物理内存当中;当外设通过DMA写入内存后,可以invaldate cache ,使得cpu再次访问内存时,不是cache中的数据,而是真正更新过后的内存数据。
invaldate cache: 该操作主要为解除内存与Cache的绑定关系。例如操作DMA进行数据搬移时,如果目标内存配置为可Cache,那么后续通过CPU读取该内存数据时候,若Cache命中,则可能读取到的数据不是DMA搬移后的数据,那么在进行DMA搬移之前,先进行Cache Invalidate操作,保证后续CPU读取到的数据是DMA真正搬移的数据。
Demo:
int dad_transfer(struct dad_dev *dev, int write, void *buffer, size_t count) {
dma_addr_t bus_addr;
/* Map the buffer for DMA */
dev->dma_dir = (write ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
dev->dma_size = count;
bus_addr = dma_map_single(&dev->pci_dev->dev, buffer, count,
dev->dma_dir);
dev->dma_addr = bus_addr;
/* Set up the device */
writeb(dev->registers.command, DAD_CMD_DISABLEDMA);
writeb(dev->registers.command, write ? DAD_CMD_WR : DAD_CMD_RD);
writel(dev->registers.addr, cpu_to_le32(bus_addr));
writel(dev->registers.len, cpu_to_le32(count));
/* Start the operation */
writeb(dev->registers.command, DAD_CMD_ENABLEDMA);
return 0;
}
2、由于现在的硬件比较强悍,cache与内存的一致性可以由硬件来确保,所以内核的api此时也就成了门面函数了。
3、关于DMA更多更详细的解释请参考《宋宝华: 关于DMA ZONE和dma alloc coherent若干误解的彻底澄清》;关于流式DMA一致性的图解可参考《图解数据读写与Cache操作》。
二 、cgroup
cgroups,其名称源自控制组群(control groups)的简写,是Linux内核的一个功能,用来限制、控制与分离一个进程组的资源(如CPU、内存、磁盘输入输出等)。
1.这里我们通过配置cgroup内存资源的配置,来对进程的内存资源进行限制。
1.设置cgroup A的最大内存为200M
(1) cd /sys/fs/cgroup/memory
(2) mkdir A
(3) cd A
(4) echo $((200*1024*1024)) > memory.limit_in_bytes
2.将进程放到cgroup A中执行 cgexec-g memory:A ./a.out,当该进程申请的内存达到200M时,就会被kill掉
2.cgroup 可以用来做基于质量的服务控制(QOS)
三、脏页的写回
1. dirty_writeback_centisecs
内核清理线程将定期唤醒并将“旧”数据写入磁盘。这个可调参数以百分之一秒的时间来表表示清理线程唤醒的间隔。将此值设置为零将完全
禁用周期性回写。
2 dirty_expire_centisecs
这个可调参数用于定义脏数据的存在的时间是否达到可以由内核清除线程写入的资格。它是以百分之一秒来表示的。超过这个时间间隔的内
存脏数据将在下一次刷新线程唤醒时被写入。
3. dirty_background_ratio
内存里的脏数据百分比达到这个指定值时,内核刷新线程会被唤醒,将脏数据写入到磁盘当中。
2. dirty_ratio
是绝对的脏数据限制,内存里的脏数据百分比不能超过这个值。如果脏数据超过这个数量,新的IO请求将会被阻挡,
直到脏数据被写进磁盘。这是造成IO卡顿的重要原因,但这也是保证内存中不会存在过量脏数据的保护机制。
其中1、2条参数是从时间维度去维护脏页写回,3、4是从空间维度维护脏页写回。
四、内存回收
1.内存回收与脏页写回是不一样的,脏页写回是把内存的数据写入到磁盘当中;而内存回收,是把该页丢弃变成一个free的内存,当下次再去读文件时,重新申请一页,并将磁盘读取的数据写到新申请的页当中去。
2.内存回收的相关参数
1、min_free_kbytes
这用于强制Linux VM 保持空闲的最小千字节数。
VM使用这个数字为系统中的每个lowmem区域计算watermark[WMARK_MIN]值。
每个lowmem区域根据其大小按比例获得若干保留的空闲页。
需要满足PF_MEMALLOC分配的最小内存量;
如果将此值设置为低于1024KB,系统将很容易崩溃,并且在高负载下容易出现死锁。
设置得太高会立刻导致机器内存溢出。
2、watermark[min/low/high]
用于计算影响内存回收的三个参数
watermark[high] > watermark [low] > watermark[min],各个zone各一套。
在系统空闲内存低于 watermark[low]时,开始启动内核线程kswapd进行内存回收,直到该zone的空闲内存数量达到watermark[high]后停止回
收。如果上层申请内存的速度太快,导致空闲内存降至watermark[min]后,内核就会进行direct reclaim(直接回收),即直接在应用程序的进
程上下文中进行回收,再用回收上来的空闲页满足内存申请,因此实际会阻塞应用程序,带来一定的响应延迟,而且可能会触发系统OOM。这是因为
watermark[min]以下的内存属于系统的自留内存,用以满足特殊使用,所以不会给用户态的普通申请来用。
watermark[min/low/high]参数描述参考《内存管理参数min_free_kbytes 分析》
2.内存回收的优先级—swapness
swappiness:反映是否积极的使用swap空间(/proc/sys/vm/swappiness)
swappiness = 0:仅在内存空间不足(带文件背景的页面都回收完了,空闲内存任然达不到watermark[high])的情况下使用swap空间
swappiness = 60:是默认值
swappiness = 100:积极的使用swap空间(优先回收匿名页)
五、其他工具
1、getdelays
位置:Documentation/accounting/getdelays.c
功能:测量调度、I/O、SWAP、Reclaim(内存回收)的延迟
2、vmstat
可以展现给定时间间隔的服务器状态值,包括Linux的cpu使用率,内存使用率,虚拟空间交换情况,IO读写等。