1.背景:嵌入式设备写入SD卡时,偶尔会调用write卡住,内核linux-3.4.y2。Linux内核io流程1、应用程序调用write,落入内核执行vfs_write函数,将数据写入pagecache(每个cachepage包含若干个buffer)。写入前,需要[1]检查页面是否正在回写。如果是回写,挂起进程,等待回写标志清零,唤醒进程。[2]检查页面缓冲区是否被锁定。如果被锁住,挂起进程等待唤醒2.内核有一个驻留线程,为每个bdi创建一个线程,定时检查是否需要writeback,必要时提交bio,让驱动写入sd卡3.bio结束时,执行回调,清除pagewritebackflag3。相关函数分析(记录主要函数,便于跟踪源码)3.1写pagecache3.1.1重要结构:conststructfile_operationsfat_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,#ifdefCONFIG_COMPAT.compat_ioctl=fat_generic_compat_ioctl,#endif.fsync=fat_file_fsync,.splice_read=generic_file_splice_t_operation,};s结构地址{)(结构页*页,结构writeback_control*wbc);int(*readpage)(structfile*,structpage*);int(*sync_page)(structpage*);int(*writepages)(structaddress_space*,structctwriteback_control*);int(*set_page_dirty)(structpage*page);int(*readpages)(structfile*filp,structaddress_space*mapping,structlist_head*pages,unsignednr_pages);int(*write_begin)(structfile*,structaddress_space*mapping,loff_tpos,unsignedlen,unsignedflags,structpage**pagep,void**fsdata);int(*write_end)(structfile*,structaddress_space*mapping,loff_tpos,unsignedlen,unsignedcopied,structpage*page,void*fsdata);sector_t(*bmap)(structaddress_space*,sector_t);int(*invalidatepage)(structpage*,unsignedlong);int(*releasepage)(structpage*,int);void(*freepage)(structpage*);ssize_t(*direct_IO)(int,structkiocb*,conststructiovec*iov,loff_toffset,unsignedlongnr_segs);结构页面*(*get_xip_page)(结构地址空间*,扇区t,int);/*迁移一个页面的内容到指定的目标*/int(*migratepage)(structpage*,structpage*);int(*launder_page)(structpage*);int(*error_remove_page)(structmapping*mapping,structpage*page);int(*swap_activate)(structfile*);int(*swap_deactivate)(structfile*);};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-->(应用到页面后,如果改变的page正在回写,需要挂起当前进程,等待回写后唤醒)(主要是消耗when)__block_write_beginwait_on_buffer-->(为page分配一个buffer,如果申请的buffer被锁住,暂停进程,等待unlock唤醒)(二次耗时)staticinlinevoidwait_on_buffer(structbuffer_head*bh){might_sleep();if(buffer_locked(bh))__wait_on_buffer(bh);}3.1.3参考资料:https://www.cnblogs.com/children/p/3420430.htmlhttps://www.jianshu.com/p/d33ec2707e7fhttp://blog.chinaunix.net/uid-14528823-id-4289180.htmlhttps://my.oschina.net/u/2475751/blog/535859https://blog.csdn.net/wh8_2011/article/details/51787282https://www.cnblogs.com/honpey/p/4931962.htmlhttps://blog.csdn.net/ctoday/article/details/379662333.2内核回写线程:3.2.1经过linux3.2的功能分析,有规律的内存-驻留线程bdi_forker_thread负责为bdi_object创建一个bdi_writeback_thread线程,同时检测到如果bdi_writeback_thread线程长时间空闲,就会被销毁。bdi_writeback_thread线程在fs/fs-writeback中。回写,然后执行调度函数等待唤醒。内核每隔固定时间唤醒线程,此时可以查看文件/proc/sys/vm/dirty_writeback_centisecs。bdi_writeback_thread调用wb_do_writeback函数进行回写wb_do_writeback处理bdi-work_list中需要回写的工作,也会从两个方面检查是否有pagecache需要回写。一是是否有存在时间过长的脏页,但是脏页的比例是否达到了设定的上限,对应的文件是/proc/sys/vm/dirty_expire_centisecs和/proc/sys/vm/dirty_background_ratiowb_do_writeback-->wb_writeback-->writeback_sb_inodes-->writeback_single_inode-->do_writepages-->(mm/page-writeback.c)映射->a_ops->writepages-->fat32注册映射->a_ops->writepages为fat_writepages(fs/fat/inode.c),fat_writepages调用mpage_writepages(fs/mpage.c),mpage_writepages调用__mpage_writepage【复制】----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------文件的核心接口。代码的大致流程如下:如果页面有buffer_head,则磁盘映射完成,代码只支持写所有页面都设置为脏页,除非没有设置为脏页的页面放在文件末尾,即要求该页设置为脏页连续性。如果该页没有buffer_head,则接口中的所有页都设置为脏页。如果所有块都是连续的,直接进入bio请求流程,否则返回writepage映射流程。使用page_has_buffers判断当前页是否有buffer_head(bh),如果有则使用page_buffers将当前页转换为buffer_head的bh指针,然后使用bh->b_this_page遍历当前页的所有bh,调用buffer_locked(bh)锁定buffer——head,即使有bh没有被映射,也会进入混淆过程。first_unmapped记录第一个未映射的bh。除了要保证所有的bhs都被映射外,还需要保证所有的bhs都设置为脏页并完成uptodate。如果每页的块号不为0(通过判断first_unmapped是否不为0),则直接进入当前页已映射的进程page_is_mapped,否则进入混淆进程。如果当前页没有buffer_head(bh),需要将当前页映射到磁盘,用buffer_head变量map_bh封装,在buffer_head和bio之间进行转换。如果page_is_mapped进程中有bio资源,检测到当前页和上一页的磁盘块号不连续(代码对应bio&&mpd->last_block_in_bio!=blocks[0]–1,blocks[0]表示第一个磁盘block),使用mpage_bio_submit提交一个accumulationbio请求,将之前连续的block写入设备。否则,进入alloc_new进程。在alloc_new过程中,如果判断bio为空(说明刚刚提交了一个bio),需要使用mpage_alloc重新申请一个bio资源,然后使用bio_add_page将当前页面添加到bio中.如果bio的长度不能容纳本次添加的page的整个长度,先将添加到bio的数据提交给bio请求mpage_bio_submit,剩下的数据重新进入alloc_new进程进行bio申请操作。如果一次性将页面中的所有数据添加到bio中,如果页面有缓冲区,则必须清除所有缓冲区的脏页位。使用set_page_writeback设置页面回写状态,并解锁页面(unlock_page)。当设置了bh的边界或者当前页和上一页的磁盘块号不连续时,先提交一个累积连续块的bio。否则,则表示当前页中的所有块都是连续的,并且与上一页中的块也是连续的。此时不需要提交bio,只是将上一页的磁盘块号mpd->last_block_in_bio更新为当前页的磁盘块号mpd->last_block_in_bio。最后一个块号后,退出检查下一页的连续性,提交bio,直到遇到不连续。bio操作会在混淆过程中提交,但是会设置映射错误。【结束】----------------------------------------简而言之,__mpage_writepage函数调用mpage_end_io提交bio,驱动将脏页写入sd卡,在此过程中对页面进行保护。bio完成后执行回调bio->bi_end_io=mpage_end_io,清除页面的writebackflag3.2.2参考:https://blog.csdn.net/asmxpl/article/details/21548129http://blog.sina.com.cn/s/blog_6f5549150102vaoz.htmlhttp://blog.chinaunix.net/uid-7494944-id-3833328.htmlhttps://blog.csdn.net/zhufengtianya/article/details/421459854。补充[1]bufferheadlockandUnlock暂未分析[2]我们在调用write函数写pagecache时,检查page的writebackflag。如果是回写,则进程被挂起等待唤醒,所以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)5106000254800
