Linux read()实现

Linux read()实现_第1张图片

-------------------------------------

Linux read()实现_第2张图片


sys_read: 通过fd得到对应的file结构,然后调用vfs_read;  
vfs_read: 各种权限及文件锁的检查,然后调用file->f_op->read(若不存在则调用do_sync_read)。file->f_op是从对应的inode->i_fop而来,而inode->i_fop是由对应的文件系统类型在生成这个inode时赋予的。file->f_op->read很可能就等同于do_sync_read;  
do_sync_read: f_op->read是完成一次同步读,而f_op->aio_read完成一次异步读。do_sync_read则是利用f_op->aio_read这个异步读操作来完成同步读,也就是在发起一次异步读之后,如果返回值是-EIOCBQUEUED,则进程睡眠,直到读完成即可。但实际上对于磁盘文件的读,f_op->aio_read一般不会返回 -EIOCBQUEUED,除非是设置了O_DIRECT标志aio_read,或者是对于一些特殊的文件系统(如nfs这样的网络文件系统); 
f_op->aio_read: 这个函数通常是由generic_file_aio_read或者其封装来实现的;  
generic_file_aio_read: 一次异步读可能包含多个读操作(对应于readv系统调用),对于其中的每一个,调用do_generic_file_read;  
do_generic_file_read: 主要流程是在radix树里面查找是否存在对应的page,且该页可用。是则从page里面读出所需的数据,然后返回,否则通过file->f_mapping->a_ops->readpage去读这个页;  file->f_mapping是从对应inode->i_mapping而来,inode->i_mapping->a_ops是由对应的文件系统类型在生成这个inode时赋予的。而各个文件系统类型提供的a_ops->readpage函数一般是mpage_readpage函数的封装; 
mpage_readpage: 调用do_mpage_readpage构造一个bio,再调用mpage_bio_submit将其提交;  
do_mpage_readpage: 根据page->index确定需要读的磁盘扇区号,然后构造一组bio。其中需要使用文件系统类型提供的get_block函数来对应需要读取的磁盘扇区号;  
mpage_bio_submit: 设置bio的结束回调bio->bi_end_io为mpage_end_io_read,然后调用submit_bio提交这组bio;  
submit_bio: 调用generic_make_request将bio提交到磁盘驱动维护的请求队列中;  
generic_make_request: 一个包装函数,对于每一个bio,调用__generic_make_request; 
__generic_make_request: 获取bio对应的块设备文件对应的磁盘对象的请求队列bio->bi_bdev->bd_disk->queue,调用q->make_request_fn将bio添加到队列;  
q->make_request_fn: 设备驱动程序在其初始化时会初始化这个request_queue结构,并且设置q->make_request_fn和q->request_fn(这个下面就会用到)。前者用于将一个bio组装成request添加到request_queue,后者用于处理request_queue中的请求。一般情况下,设备驱动通过调用blk_init_queue来初始化request_queue,q->request_fn需要给定,而q->make_request_fn使用了默认的__make_request;  
__make_request: 会根据不同的调度算法来决定如何添加bio,生成对应的request结构加入request_queue结构中,并且决定是否调用q->request_fn,或是在kblockd_workqueue任务队列里面添加一个任务,等kblockd内核线程来调用q->request_fn;  
q->request_fn。由驱动程序定义的函数,负责从request_queue里面取出request进行处理。从添加bio到request被取出,若干的请求已经被IO调度算法整理过了。驱动程序负责根据request结构里面的描述,将实际物理设备里面的数据读到内存中。当驱动程序完成一个request时,会调用end_request(或类似)函数,以结束这个request;
end_request。完成request的收尾工作,并且会调用对应的bio的的结束方法bio->bi_end_io,即前面设置的mpage_end_io_read;  
mpage_end_io_read: 如果page已更新则设置其up-to-date标记,并为page解锁,唤醒等待page解锁的进程。最后释放bio对象;  
 
sys_write: 跟sys_read一样,对应的vfs_write、do_sync_write、f_op->aio_write、generic_file_aio_write被顺序调用;  
generic_file_aio_write: 调用__generic_file_aio_write_nolock来进行写的处理,将数据写到磁盘高速缓存中。写完成之后,判断如果文件打开时使用了O_SYNC标记,则再调用sync_page_range将写入到磁盘高速缓存中的数据同步到磁盘(只同步文件头信息);  
__generic_file_aio_write_nolock。进行一些检查之后,调用generic_file_buffered_write;  
generic_file_buffered_write: 调用generic_perform_write执行写,写完成之后,判断如果文件打开时使用了O_SYNC标记,则再调用generic_osync_inode将写入到磁盘高速缓存中的数据同步到磁盘(同步文件头信息和文件内容); 
generic_perform_write: 一次异步写可能包含多个写操作(对应于writev系统调用),对于其中牵涉的每一个page,调用 file->f_mapping->a_ops->write_begin准备好需要写的磁盘高速缓存页面,然后将需要写的数据拷入其中,最后调用file->f_mapping->a_ops->write_end完成写;  file->f_mapping是从对应inode->i_mapping而来,inode->i_mapping->a_ops是由对应的文件系统类型在生成这个inode时赋予的。而各个文件系统类型提供的file->f_mapping->a_ops->write_begin函数一般是block_write_begin函数的封装、file->f_mapping->a_ops->write_end函数一般是generic_write_end函数的封装;  
block_write_begin: 调用grab_cache_page_write_begin在radix树里面查找要被写的page,如果不存在则创建一个。调用__block_prepare_write为这个page准备一组buffer_head结构,用于描述组成这个page的数据块(利用其中的信息,可以生成对应的bio结构); 
generic_write_end: 调用block_write_end提交写请求,然后设置page的dirty标记;  
block_write_end: 调用__block_commit_write为page中的每一个buffer_head结构设置dirty标记;  至此,write调用就要返回了。如果文件打开时使用了O_SYNC标记, sync_page_range或generic_osync_inode将被调用。否则write就结束了,等待pdflush内核线程发现radix树上的脏页,并最终调用到do_writepages写回这些脏页;  
sync_page_range也是调用generic_osync_inode来实现的,而generic_osync_inode最终也会调用到do_writepages;  
do_writepages: 调用inode->i_mapping->a_ops->writepages,而后者一般是mpage_writepages函数的包装;  
mpage_writepages: 检查radix树中需要写回的page,对每一个page调用__mpage_writepage;
__mpage_writepage: 这里也是构造bio,然后调用mpage_bio_submit来进行提交。

你可能感兴趣的:(Linux)