linux write函数小结,linux write函数耗时分析

1. 背景:

嵌入式设备写SD卡的时候,偶尔会出现调用write卡顿,内核linux-3.4.y

2. linux内核io流程

1.应用程序调用write,陷入内核执行vfs_write函数,将数据写入页高速缓存(每个缓存页包含若干个缓冲区)。而在写入之前需要

[1] 检查页是否在回写,如果正在回写则挂起进程,等待回写标志清空时唤醒进程

[2] 检查页buffer是否locked,如果locked则挂起进程等待唤醒

2.内核有一个常驻线程,为每个bdi创建一个线程,定时检查是否需要回写,需要则提交bio,让驱动写入sd卡

3.bio结束时执行回调,将页回写标志清除

3. 相关函数分析(记录主要函数,方便跟踪源码)

3.1 写页高速缓存

3.1.1 重要结构:

const struct file_operations fat_file_operations = {

.llseek = generic_file_llseek,

.read = do_sync_read,

.write = do_sync_write,

.aio_read = generic_file_aio_read,

.aio_write = generic_file_aio_write,

.mmap = generic_file_mmap,

.release = fat_file_release,

.unlocked_ioctl = fat_generic_ioctl,

#ifdef CONFIG_COMPAT

.compat_ioctl = fat_generic_compat_ioctl,

#endif

.fsync = fat_file_fsync,

.splice_read = generic_file_splice_read,

};

struct address_space_operations {

int (*writepage)(struct page *page, struct writeback_control *wbc);

int (*readpage)(struct file *, struct page *);

int (*sync_page)(struct page *);

int (*writepages)(struct address_space *, struct writeback_control *);

int (*set_page_dirty)(struct page *page);

int (*readpages)(struct file *filp, struct address_space *mapping,

struct list_head *pages, unsigned nr_pages);

int (*write_begin)(struct file *, struct address_space *mapping,

loff_t pos, unsigned len, unsigned flags,

struct page **pagep, void **fsdata);

int (*write_end)(struct file *, struct address_space *mapping,

loff_t pos, unsigned len, unsigned copied,

struct page *page, void *fsdata);

sector_t (*bmap)(struct address_space *, sector_t);

int (*invalidatepage) (struct page *, unsigned long);

int (*releasepage) (struct page *, int);

void (*freepage)(struct page *);

ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,

loff_t offset, unsigned long nr_segs);

struct page* (*get_xip_page)(struct address_space *, sector_t,

int);

/* migrate the contents of a page to the specified target */

int (*migratepage) (struct page *, struct page *);

int (*launder_page) (struct page *);

int (*error_remove_page) (struct mapping *mapping, struct page *page);

int (*swap_activate)(struct file *);

int (*swap_deactivate)(struct file *);

};

3.1.2 函数调用流程:

vfs_write-->

do_sync_write-->

f_op->aio_write(generic_file_aio_write)-->(mm/filemap.c)

__generic_file_aio_write-->

generic_file_buffered_write-->

generic_perform_write-->(重要函数)

a_ops->write_begin(block_write_begin)-->(fs/buffer.c)主要耗时在下面两个函数

grab_cache_page_write_begin-->

wait_on_page_writeback-->(申请到页之后,如果改页正在被回写,需要挂起当前进程,等待回写之后的唤醒)(主要耗时)

__block_write_begin

wait_on_buffer-->(为页分配缓冲区,如果申请的缓冲区加了锁,挂起进程,等待解锁后唤醒)(次要耗时)

static inline void wait_on_buffer(struct buffer_head *bh)

{

might_sleep();

if (buffer_locked(bh))

__wait_on_buffer(bh);

}

3.1.3 参考资料:

https://www.cnblogs.com/children/p/3420430.html

https://www.jianshu.com/p/d33ec2707e7f

http://blog.chinaunix.net/uid-14528823-id-4289180.html

https://my.oschina.net/u/2475751/blog/535859

https://blog.csdn.net/wh8_2011/article/details/51787282

https://www.cnblogs.com/honpey/p/4931962.html

https://blog.csdn.net/ctoday/article/details/37966233

3.2 内核回写线程:

3.2.1 函数分析

linux3.2之后,内核中有一个常驻内存的线程bdi_forker_thread,负责为bdi_object创建bdi_writeback_thread线程,同时检测如果bdi_writeback_thread线程长时间处于空闲状态,便会将其销毁。

bdi_writeback_thread线程在fs/fs-writeback.c中,它在一个while循环中检查是否需要回写,然后执行调度函数等待唤醒。内核每隔固定时间唤醒该线程,这个时间可以查看文件/proc/sys/vm/dirty_writeback_centisecs。

bdi_writeback_thread调用wb_do_writeback函数进行回写

wb_do_writeback处理bdi-work_list需要回写的work,同时也从两个方面检查有没有页高速缓存需要回写,一是有没有脏页存在过长的时间,而是脏页比例是否达到了设置的上限,相应的文件为/proc/sys/vm/dirty_expire_centisecs和/proc/sys/vm/dirty_background_ratio

wb_do_writeback-->

wb_writeback-->

writeback_sb_inodes-->

writeback_single_inode-->

do_writepages-->(mm/page-writeback.c)

mapping->a_ops->writepages-->

fat32注册的mapping->a_ops->writepages即为fat_writepages(fs/fat/inode.c),fat_writepages调用mpage_writepages(fs/mpage.c), mpage_writepages调用__mpage_writepage

【copy】--------------------------------------

_mpage_writepage函数是写文件的核心接口。代码大致流程如下:如果page有buffer_head,则完成磁盘映射,代码只支持所有page都被设为脏页的写,除非没有设为脏页的page放到文件的尾部,即要求page设置脏页的连续性。如果page没有buffer_head,在接口中所有page被设为脏页。如果所有的block都是连续的则直接进入bio请求流程,否则重新回到writepage的映射流程。

用page_has_buffers判断当前page是否有buffer_head(bh),如果有则用page_buffers将当前page转换为buffer_head的bh指针,之后用bh->b_this_page遍历当前page的所有bh,调用buffer_locked(bh)加锁buffer——head,即使出现一个bh没有被映射都会进入confused流程,first_unmapped记录了第一个没有映射的bh,除了要保证所有的bh都被映射,还要保证所有的bh都被置为脏页并且完成了uptodate。如果每个page的block数不为0(通过判断first_unmapped是否非0),则直接进入当前page已经被映射的流程page_is_mapped,否则进入confused流程。

如果当前page没有buffer_head(bh),需要将当前page映射到磁盘上,使用buffer_head变量map_bh封装,做buffer_head和bio之间的转换。

page_is_mapped流程中如果有bio资源并且检测到当前的页面和前面一个页面的磁盘块号不连续(代码对应bio && mpd->last_block_in_bio != blocks[0] – 1,blocks[0]表示第一个磁盘块),则用mpage_bio_submit来提交一个积累bio请求,将之前的连续block写到设备中。否则进入alloc_new流程。

alloc_new流程中,判断bio为空(表示前面刚刚提交了一个bio)则需要用mpage_alloc重新申请一个bio资源,之后用bio_add_page向bio中添加当前page,如果bio中的长度不能容纳下这次添加page的整个长度,则先将添加到bio上的数据提交bio请求mpage_bio_submit,剩下的数据重新进入到alloc_new流程做bio的申请操作。如果一次性将page中的所有数据全部添加到bio上,在page有buffer的情况下要将所有的buffer全部清除脏页位。用set_page_writeback设置该page为写回状态,给page解锁(unlock_page)。当bh的boundary被设置或者当前页面和前面一个页面的磁盘块号不连续,就先提交一个累积连续block的bio。否则说明当前page中的所有block都是连续的,并且与之前的page中block也是连续的,这种情况下不需要提交bio,只更新前面一个页面的磁盘块号mpd->last_block_in_bio为当前page的最后一个block号,之后退出进行下一个page的连续性检查,直到碰到不连续的再做bio提交。

confused流程中会提交bio操作,但是会设置映射错误。

【end】----------------------------------------

总之,__mpage_writepage函数调用mpage_end_io提交bio,驱动将脏页写入sd卡,这个过程中对页进行保护。bio完成后执行回调bio->bi_end_io = mpage_end_io,清除页的writeback标志

3.2.2 参考资料:

https://blog.csdn.net/asmxpl/article/details/21548129

http://blog.sina.com.cn/s/blog_6f5549150102vaoz.html

http://blog.chinaunix.net/uid-7494944-id-3833328.html

https://blog.csdn.net/zhufengtianya/article/details/42145985

4. 补充

[1]

buffer head的lock和unlock目前还没有分析

[2]

我们调用write函数写页高速缓存的时候,检查页的writeback标志,如果正在回写,就挂起进程等待唤醒,就write函数阻塞了;bio执行结束后调用回调清除页的writeback标志,应用程序被唤醒。

之前说过内核每隔固定时间(/proc/sys/vm/dirty_writeback_centisecs)做一次回写检查,一般当脏页比例达到/proc/sys/vm/dirty_background_ratio就进行回写。我们同时减小这个两个参数的值,发现bio消耗时间的峰值减低了

dirty_writeback_centisecs(s) dirty_background_ratio(%) bio_max_time(ms)

5 10 6000

2 5 4800

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值