找回密码
 注册
关于网站域名变更的通知
查看: 307|回复: 1
打印 上一主题 下一主题

linux IO子系统和文件系统读写流程分析

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-4-17 09:47 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
本帖最后由 thinkfunny 于 2020-4-17 18:35 编辑 9 ?+ C+ |' ?5 @; u* q& v/ x( @* j

9 y3 u# Y' i$ W% U! S; O3 z5 D以下内容的分析是基于2.6.32及其后的内核.
- |7 j/ u: d( P* Z: |5 V' c0 l9 i, |# |8 ]( ~) J! i
我们在linux上总是要保存数据,数据要么保存在文件系统里(如ext3),要么就保存在裸设备里。我们在使用这些数据的时候都是通过文件这个抽象来访问的,操作系统会把我们需要的数据提交给我们,而我们则无需和块设备打交道。) |6 c2 `; u, P5 P  O. ]
6 }4 {$ r6 M8 _9 q; |
从下图,我们可以清除的看到:
+ N5 ^6 ~1 |# ^: F& K
9 U$ l* I  W. b. c6 y; C. U 6 g: u! O1 T) O  ~
) @: i5 N8 A* p. D
I/O子系统是个层次很深的系统,数据请求从用户空间最终到达磁盘,经过了复杂的数据流动。
. \7 A7 [+ Y1 F& _2 x& j( E5 C: T" \# ^- n! c5 E, C$ M0 y% k, N! f
对设驱开发人员或与此相关的设计人员,特别是IO很密集,我们就需要搞清楚IO具体是如何动作的,免得滥用IO和导致设计问题。- ?/ @7 d7 L# Q4 L& Q! s; T
) L  s! B, t! Y; p7 f" I
# ~: Z+ u$ K. {
* \. P$ c* a6 N" R8 |) b+ Z' y8 c
IBM developworks中,〈read系统调用剖析〉阐述就很清楚。
# c( v. a! l$ }0 X+ j9 V% T8 a# E7 h; F" S
read系统调用的处理分为用户空间和内核空间处理两部分。其中,用户空间处理只是通过0x80中断陷入内核,接着调用其中断服务例程,即sys_read以进入内核处理流程。
, O2 Q/ T" ~; e* m9 u) E& w6 ]0 U' C! D. c4 m# O
对于read系统调用在内核的处理,如上图所述,经过了VFS、具体文件系统,如ext2、页高速缓冲存层、通用块层、IO调度层、设备驱动层、和设备层。其中,VFS主要是用来屏蔽下层具体文件系统操作的差异,对上提供一个统一接口,正是因为有了这个层次,所以可以把设备抽象成文件。具体文件系统,则定义了自己的块大小、操作集合等。引入cache层的目的,是为了提高IO效率。它缓存了磁盘上的部分数据,当请求到达时,如果在cache中存在该数据且是最新的,则直接将其传递给用户程序,免除了对底层磁盘的操作。通用块层的主要工作是,接收上层发出的磁盘请求,并最终发出IO请求(BIO)。IO调度层则试图根据设置好的调度算法对通用块层的bio请求合并和排序,回调驱动层提供的请求处理函数,以处理具体的IO请求。驱动层的驱动程序对应具体的物理设备,它从上层取出IO请求,并根据该IO请求中指定的信息,通过向具体块设备的设备控制器发送命令的方式,来操纵设备传输数据。设备层都是具体的物理设备。
" I3 p8 V) P/ _' I) z0 c- T5 m: M8 u4 L/ Z( a  G- s% T

' t# n4 `" _9 e& q
4 G& z, e1 Z# ]) @& `; pVFS层:- U7 ^. K+ e" N" k9 V' V

) T; S' x# \, P1 ~& F7 v3 {内核函数sys_read是read系统调用在该层的入口点。# V$ }3 Z, T& M
, `* S" |0 A$ x) @: e$ N/ ?
它根据文件fd指定的索引,从当前进程描述符中取出相应的file对象,并调用vfs_read执行文件读取操作。
% h) P. t$ _$ m! |" U* ~7 U; t* d/ h4 J" i, Z
vfs_read会调用与具体文件相关的read函数执行读取操作,file->f_op.read。
: s  u; X( \) i- }; R! F: M+ R# z" T) e3 G! z+ }! @! F
然后,VFS将控制权交给了ext2文件系统。(ext2在此作为示例,进行解析)0 O% {, U3 |0 O" m$ f) X
9 p2 A+ ^7 E" x9 Z

  C1 u7 g9 X' r4 f0 [" O
+ h" _9 y% G2 b) u  O: [/ O( P6 k3 tExt2文件系统层的处理5 c8 c- \9 Q( l+ |+ \$ K2 u: W  z8 e
" m" O3 j& ^. C/ Z1 s" T, n$ @- }0 i
通过ext2_file_operations结构知道,上述函数最终会调用到do_sync_read函数,它是系统通用的读取函数。所以说,do_sync_read才是ext2层的真实入口。9 W* J$ q4 ]8 Z% _
  s# A4 U8 Y3 C, f7 a- Z
该层入口函数 do_sync_read 调用函数 generic_file_aio_read ,后者判断本次读请求的访问方式,如果是直接 io (filp->f_flags 被设置了 O_DIRECT 标志,即不经过 cache)的方式,则调用 generic_file_direct_IO 函数;如果是 page cache 的方式,则调用 do_generic_file_read 函数。它会判断该页是否在页高速缓存,如果是,直接将数据拷贝到用户空间。如果不在,则调用page_cache_sync_readahead函数执行预读(检查是否可以预读),它会调用mpage_readpages。如果仍然未能命中(可能不允许预读或者其它原因),则直接跳转readpage,执行mpage_readpage,从磁盘读取数据。6 ~: J$ h5 X5 X( m

  A/ |* B+ A, Y* F在mpage_readpages(一次读多个页)中,它会将连续的磁盘块放入同一个BIO,并延缓BIO的提交,直到出现不连续的块,则直接提交BIO,再继续处理,以构造另外的BIO。
. Z/ G/ E" E8 G. n/ b; ^3 D2 a6 i) l; K& Q
* z4 D2 E& x; ?- c
2 {# G- C) G+ p4 h  N
文件的 page cache 结构
' C: e% O8 ~% u3 b& X: ^, J3 L. R0 s4 [2 Y( X! m
图5显示了一个文件的 page cache 结构。文件被分割为一个个以 page 大小为单元的数据块,这些数据块(页)被组织成一个多叉树(称为 radix 树)。树中所有叶子节点为一个个页帧结构(struct page),表示了用于缓存该文件的每一个页。在叶子层最左端的第一个页保存着该文件的前4096个字节(如果页的大小为4096字节),接下来的页保存着文件第二个4096个字节,依次类推。树中的所有中间节点为组织节点,指示某一地址上的数据所在的页。此树的层次可以从0层到6层,所支持的文件大小从0字节到16 T 个字节。树的根节点指针可以从和文件相关的 address_space 对象(该对象保存在和文件关联的 inode 对象中)中取得(更多关于 page cache 的结构内容请参见参考资料)。
5 N$ B6 H% y1 o8 [9 M2 Z
# }7 U: d; m  @1 z  t2 ~% X ' Q, L- z2 @3 W9 _" `# j' s

4 F, E7 Z# g/ t  n6 Umpage处理机制就是page cache层要处理的问题。2 I6 B$ h' s4 b7 e3 A( Y. r
% r5 {1 U' @! L/ W
* d% l0 Q5 }% q* L/ Z6 M4 x0 q" g
; F1 }4 c4 `. Z( r3 K' g
通用块层
- z$ j5 h) q# m% M2 d! C
/ y2 o7 {2 F- J/ F! i/ n在缓存层处理末尾,执行mpage_submit_bio之后,会调用generic_make_request函数。这是通用块层的入口函数。+ b: `) Z3 _% k/ s/ ^
; C" O8 J. K! |; c& j
它将bio传送到IO调度层进行处理。
- v! m+ g9 a6 j0 T# P9 J
; H8 p0 @8 V8 W% b" t/ G
& \% w6 {; e( \# b5 G( Q! D
( A0 |7 y% z2 OIO调度层) X& N% l; U- D5 e! i2 ^
) K: l, X9 K2 ~0 u5 i) J
对bio进行合并、排序,以提高IO效率。然后,调用设备驱动层的回调函数,request_fn,转到设备驱动层处理。3 u) {- [: K0 r
% W. D" [2 L; t7 ?1 i
- S  y: T- {; c9 e

, [: j. P2 v- S; a4 b) G设备驱动层# B2 m  O' z" o' u6 q& y) C7 D
( U0 ~, U4 b/ w
request函数对请求队列中每个bio进行分别处理,根据bio中的信息向磁盘控制器发送命令。处理完成后,调用完成函数end_bio以通知上层完成。& V& j' d# B1 V8 R" ^  g

+ x3 X2 H) L8 H" P8 M4 j# O/ e4 e) l5 W1 I

. U# u' a5 l* H1 r2 b; w4 f* W
$ q! X  D5 f3 Q) F5 w- A

该用户从未签到

2#
发表于 2020-4-17 18:36 | 只看该作者
linux IO子系统和文件系统读写流程分析
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-11-25 16:43 , Processed in 0.187500 second(s), 27 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表