本文基于内核4.2
问题描述
长时间运行的服务容易出现cache或者buffer占用内存过多的问题; 另外,在嵌入式设备中,内存相对较少,如果出现大量数据的拷贝,很容易出现拷贝进程被oops杀死的情况。
cache:内核用于读优化。简而言之,我们调取read函数, 内核会先查找cache中是否存在该数据,如果命中,那么read系统调用将直接拷贝cache中的数据; 如果没有命中,内核再将数据从存储介质中读出,放入cache。
buffer:内核用于写优化。 数据先写入page cache,然后直接返回;由后备存储线程负责数据的刷回操作。
注:用户空间看到的buffer和cache对内核而言都是page cache,即address_space;只不过操作函数和使用目的不同而已。
具体操作
读操作
读取的过程可以参考这里 中的最后一幅流程图部分
在流程图中是裸块设备操作, 因此涉及到的操作都在fs/block_dev.c中,vfs_read调用的f->op->read操作为blkdev_read_iter (因为f_op->read在block文件系统中未定义)
const struct file_operations def_blk_fops = {
……
.read_iter = blkdev_read_iter,
.write_iter = blkdev_write_iter,
……
};
在blkdev_read_iter之后,除去一些访问的检查,最终将调用do_generic_file_read。
do_generic_file_read实现查找cache的缓存,即利用基数树进行查找; 正如之前所言,这里会产生两条分支:
- 未找到相应的page cache,那么调用readpage,将数据读入缓存;这里的操作大同小异, 最终目的都是将page cache转换成bio,加入到设备的I/O请求队列中。
- 找到相应的page cache,则将数据拷贝至用户空间copy_page_to_iter
注:在调用readpage时,page页面已经被lock(lock_page_killable);在提交io之后, PageUptodate将判断页面是否被读出,如果仍未读出,将再调用lock_page_killable,使读操作进入阻塞。
PageUptodate的尝试次数将有三次。若三次均未被读出,那么返回失败。
static const struct address_space_operations def_blk_aops = {
.readpage = blkdev_readpage,
.readpages = blkdev_readpages,
.writepage = blkdev_writepage,
.write_begin = blkdev_write_begin,
.write_end = blkdev_write_end,
.writepages = generic_writepages,
……
};
以上是读操作的大致过程。
写操作
与读类似,写操作调用__generic_file_write_iter,如果是bio, 那么该函数会调用generic_perform_write,该函数中会调用page cache的方法write_begin和writepages, 以及balance_dirty_pages_ratelimited。
最终数据是否同步由函数balance_dirty_pages_ratelimited决定, 该函数适用于所有的文件系统。
balance_dirty_pages_ratelimited中, 其主要函数为balance_dirty_pages。该函数主要涉及以下结构体, 该结构体控制了page cache刷回的阈值。
struct dirty_throttle_control {
#ifdef CONFIG_CGROUP_WRITEBACK
struct wb_domain *dom;
struct dirty_throttle_control *gdtc; /* only set in memcg dtc's */
#endif
struct bdi_writeback *wb;
struct fprop_local_percpu *wb_completions;
unsigned long avail; /* dirtyable */
unsigned long dirty; /* file_dirty + write + nfs */
unsigned long thresh; /* dirty threshold */
unsigned long bg_thresh; /* dirty background threshold */
unsigned long wb_dirty; /* per-wb counterparts */
unsigned long wb_thresh;
unsigned long wb_bg_thresh;
unsigned long pos_ratio;
};
方法
(待完整)