一、背景:

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

二、linux内核io流程

1. 应用程序调用write,陷入内核执行vfs_write函数,将数据写入页高速缓存(每个缓存页包含若干个缓冲区)。而在写入之前需要(1)检查页是否在回写,如果正在回写则挂起进程,等待回写标志清空时唤醒进程;(2)检查页buffer是否locked,如果locked则挂起进程等待唤醒

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

3. bio结束时执行回调,将页回写标志清除三、相关函数分析(记录主要函数,方便跟踪源码)

三、内核代码分析

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 *);
};

(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_beginwait_on_buffer-->(为页分配缓冲区,如果申请的缓冲区加了锁,挂起进程,等待解锁后唤醒)(次要耗时)static inline void wait_on_buffer(struct buffer_head *bh)
{might_sleep();if (buffer_locked(bh))__wait_on_buffer(bh);
}

(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

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

_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操作,但是会设置映射错误。

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

(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

四、补充

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                        60002                             5                         4800

参考资料:

【linux IO 内核参数调优 之 参数调节和场景分析】https://www.cnblogs.com/ywcz060/p/5629573.html

linux write函数耗时分析相关推荐

  1. linux lock函数,Linux lock_kernel()函数的分析。

    这只是暂时的记录,以后会把它归类到start_kernel()函数的分析.在分析之前,我先要说说几个概念. 内核抢占:在2.6内核加入了抢占的能力,就是说调度程序有办法在一个内核级的任务正在执行的时候 ...

  2. linux C函数之strdup函数分析【转】

    本文转载自:http://blog.csdn.net/tigerjibo/article/details/12784823 linux C函数之strdup函数分析 一.函数分析 1.函数原型: [c ...

  3. Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】...

    原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...

  4. 教你3个python「性能分析」工具,再也不用自己计算函数耗时了

    文章目录 引言 1. cProfile:最便捷的性能分析 保存性能数据 查看性能数据 查看耗时最多的子函数 查看特定名称函数的耗时 2. timeit:计算小代码片段的耗时 3. IDE中的性能分析 ...

  5. linux中request_region()函数的分析

    linux中request_region()函数的分析 struct resource ioport_resource = { .name = "PCI IO", .start = ...

  6. Linux时钟管理clk_get函数透彻分析

    硬件资源越来越庞大和复杂,内核的另一个挑战就是要便捷的管理这些资源.同时,面对如此之多的平台不同的CPU ,管理机制需要统一适用,这就需要对资源的管理抽象到更加通用的层次.CPU中各个模块都需要时钟驱 ...

  7. linux 内核flush,armv8(aarch64)linux内核中flush_dcache_all函数详细分析

    /* *  __flush_dcache_all() *  Flush the wholeD-cache. * Corrupted registers: x0-x7, x9-x11 */ ENTRY( ...

  8. linux C函数之strdup函数分析

    本文转载自:http://blog.csdn.net/tigerjibo/article/details/12784823 linux C函数之strdup函数分析 一.函数分析 1.函数原型: #i ...

  9. iostat IO统计原理linux内核源码分析----基于单通道SATA盘

    iostat IO统计原理linux内核源码分析----基于单通道SATA盘 先上一个IO发送submit_bio流程图,本文基本就是围绕该流程讲解. 内核版本 3.10.96 详细的源码注释:htt ...

最新文章

  1. easyuefi只能在基于uefi启动的_苹果电脑怎么从u盘启动|苹果笔记本按哪个键选u盘启动...
  2. 信息学奥赛一本通 1331:【例1-2】后缀表达式的值
  3. 手机号归属地区编码_Excel隐藏手机号中间4位的6种方法,你见过几种?
  4. C#网站发布在IIS10上,Access数据库读取为空白的解决方案
  5. iOS开发之网络深度优化总结
  6. 多多客接入(拼多多)
  7. 2020服务器虚拟化市场容量,2020年服务器市场的五大技术和市场趋势
  8. configure: error: no usable zlib; please install zlib devel package or equivalent m
  9. 同步 IO 和异步 IO
  10. pl/sql 存储过程实例
  11. 童年修复系列-SNES芯片组介绍及FPGA实现
  12. python给pdf加图片签名_如何在PDF中添加文本和图像(例如签名)?
  13. 计算机excel教程ppt,计算机应用基础教程ppt课件 全套450页.ppt
  14. 【ESP32】arduino中的ESP32实时系统FreeRTOS使用教程(一)
  15. 【开源“青女四轴”,DIY小四轴】
  16. <数据库概论> 如何把E-R图(概念模型)转换为关系模式(逻辑模型)
  17. Murmurhash介绍与实现
  18. 笔记本 ubuntu18.04双系统
  19. 5:Echarts数据可视化-多条曲线、多个子图、TreeMap类似盒图、树形图、热力图、词云...
  20. SQL Server 2016 [修改数据库名称]及[添加次文件和日志文件]

热门文章

  1. Netty中NioEventLoop的两个逻辑点分析
  2. 使用CRM来规范审批流程
  3. 什么叫做类的类?如何获取私有的方法?Java反射机制太强大了,详解Java反射机制【Java养成】
  4. 架构师接龙:金山张宴VS.淘宝岑文初
  5. 15年+码龄,用过的所有笔记本电脑
  6. Python爬虫 | 爬取全书网小说斗罗大陆
  7. clang++.exe: error: linker command failed with exit code 1 错误解决方法
  8. service层加需要加锁吗_面试官:了解乐观锁和悲观锁吗?
  9. 数字货币——区块链技术带来的商机
  10. java jdk注解 使用_JDK 5.0 注解的使用,自定义注解