EDA365电子论坛网

标题: linux IO子系统和文件系统读写流程分析 [打印本页]

作者: thinkfunny    时间: 2020-4-17 09:47
标题: linux IO子系统和文件系统读写流程分析
本帖最后由 thinkfunny 于 2020-4-17 18:35 编辑
! g" L% a% l  t# M; Y# u+ s
# y! Y4 S( _9 n& j8 s以下内容的分析是基于2.6.32及其后的内核.
; J8 o% I2 ^8 d: T5 v. i9 e
4 v, Y) ^$ f9 \3 o# y我们在linux上总是要保存数据,数据要么保存在文件系统里(如ext3),要么就保存在裸设备里。我们在使用这些数据的时候都是通过文件这个抽象来访问的,操作系统会把我们需要的数据提交给我们,而我们则无需和块设备打交道。
- n8 x5 n: L- C) {, B% d5 c3 l( G) y, h* E; J
从下图,我们可以清除的看到:0 J2 w; s2 M3 {- K: h, W  ~

6 h8 |# I. G3 {4 s # Q# D6 }, L8 O
- i/ U' F9 e% {; E  |" s
I/O子系统是个层次很深的系统,数据请求从用户空间最终到达磁盘,经过了复杂的数据流动。& \" s$ a, a2 a) m* U: a

0 \) z) n0 w- Y, s! d对设驱开发人员或与此相关的设计人员,特别是IO很密集,我们就需要搞清楚IO具体是如何动作的,免得滥用IO和导致设计问题。) F) Z1 ~$ d7 M: p: L

% F8 w( [) V" H
7 d, i' @& h% Y, s& ?/ h/ [" ?- @1 X4 i
4 }1 P# |* B* a& t6 [IBM developworks中,〈read系统调用剖析〉阐述就很清楚。+ w' g1 {% ]. @. Z; t

  Y) W* \7 N- Vread系统调用的处理分为用户空间和内核空间处理两部分。其中,用户空间处理只是通过0x80中断陷入内核,接着调用其中断服务例程,即sys_read以进入内核处理流程。2 z4 N+ G! I" T2 [- V# Z* C
+ T& D" u/ R2 x) f) J* k5 p
对于read系统调用在内核的处理,如上图所述,经过了VFS、具体文件系统,如ext2、页高速缓冲存层、通用块层、IO调度层、设备驱动层、和设备层。其中,VFS主要是用来屏蔽下层具体文件系统操作的差异,对上提供一个统一接口,正是因为有了这个层次,所以可以把设备抽象成文件。具体文件系统,则定义了自己的块大小、操作集合等。引入cache层的目的,是为了提高IO效率。它缓存了磁盘上的部分数据,当请求到达时,如果在cache中存在该数据且是最新的,则直接将其传递给用户程序,免除了对底层磁盘的操作。通用块层的主要工作是,接收上层发出的磁盘请求,并最终发出IO请求(BIO)。IO调度层则试图根据设置好的调度算法对通用块层的bio请求合并和排序,回调驱动层提供的请求处理函数,以处理具体的IO请求。驱动层的驱动程序对应具体的物理设备,它从上层取出IO请求,并根据该IO请求中指定的信息,通过向具体块设备的设备控制器发送命令的方式,来操纵设备传输数据。设备层都是具体的物理设备。
9 g/ c3 Z2 Z8 \* y
+ K  j. b  V3 B4 }) R' d
0 n2 ^2 K$ D' ]# T; C/ N' o& j
; \7 X) `  Y/ L) D3 _VFS层:
! D4 c8 w$ T' X3 e, ?8 F( g; h8 X! N9 ?$ X+ \* B& r: `
内核函数sys_read是read系统调用在该层的入口点。
3 h0 k/ f0 Z: G  E; \0 H; m
- ^- P. A( p( @5 R6 ~; a7 j  R它根据文件fd指定的索引,从当前进程描述符中取出相应的file对象,并调用vfs_read执行文件读取操作。
6 b: Q( c* a' O# {1 i# L1 Z; G. b. E: j% k( C; K5 k( L: E( X# o- a
vfs_read会调用与具体文件相关的read函数执行读取操作,file->f_op.read。
0 o/ U/ }+ z8 n1 F0 P) \
4 N& p9 Q) J  S! D# H5 z' q- L然后,VFS将控制权交给了ext2文件系统。(ext2在此作为示例,进行解析)
% n, C( c  x5 R: |4 I/ l; H/ M* {7 a! b9 Z  J$ D3 B6 u
6 M$ Z) L2 y5 Y% Z
# r, b: R/ ^9 ^& ~$ K4 I+ G; w, h
Ext2文件系统层的处理
/ J2 S% U- {4 P8 ^" p. m1 h: a" X0 X/ r. P: o4 [  l
通过ext2_file_operations结构知道,上述函数最终会调用到do_sync_read函数,它是系统通用的读取函数。所以说,do_sync_read才是ext2层的真实入口。
: S% _* Q/ q1 `; h9 F+ X) {! l
) H( g" Q+ g. Q8 j6 H该层入口函数 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,从磁盘读取数据。
, f/ r) G6 M% A1 Z1 d. @, F6 o9 l& ]- f( y, G  Y. o' K% {8 \
在mpage_readpages(一次读多个页)中,它会将连续的磁盘块放入同一个BIO,并延缓BIO的提交,直到出现不连续的块,则直接提交BIO,再继续处理,以构造另外的BIO。- f4 V$ K; u4 u8 J  }) t2 z

- {: k( B: U2 `, j" Q# |5 L6 v  v5 P: X: a
9 V' P4 S0 ?( V; |- ]
文件的 page cache 结构6 k/ E2 e$ e7 k* L6 k5 r
+ p* b' L( K: j; \8 p1 S7 X9 b
图5显示了一个文件的 page cache 结构。文件被分割为一个个以 page 大小为单元的数据块,这些数据块(页)被组织成一个多叉树(称为 radix 树)。树中所有叶子节点为一个个页帧结构(struct page),表示了用于缓存该文件的每一个页。在叶子层最左端的第一个页保存着该文件的前4096个字节(如果页的大小为4096字节),接下来的页保存着文件第二个4096个字节,依次类推。树中的所有中间节点为组织节点,指示某一地址上的数据所在的页。此树的层次可以从0层到6层,所支持的文件大小从0字节到16 T 个字节。树的根节点指针可以从和文件相关的 address_space 对象(该对象保存在和文件关联的 inode 对象中)中取得(更多关于 page cache 的结构内容请参见参考资料)。3 F- Q" x: e/ i
& ?3 W5 }) M) U& `( r
& u, h9 l- w% H1 ?

9 D7 m4 b. v* h3 X2 Ampage处理机制就是page cache层要处理的问题。% i( X  [) h8 e# i2 E: @* J

+ L. d- B7 E+ f, ?5 \/ l$ z* u1 {7 z5 Y2 f: e7 J

% z/ n4 Y+ q( u" M% Z+ A( k! d通用块层) v( u5 x: r. W! X) v7 X

  [7 |" c5 E1 E) M* |( C6 s# w在缓存层处理末尾,执行mpage_submit_bio之后,会调用generic_make_request函数。这是通用块层的入口函数。
$ g$ x( e# G2 r7 d! \9 e1 @, v& J: D+ Z  R
它将bio传送到IO调度层进行处理。2 d  T' h: V1 u

! r8 o) J" z" d. R4 ]! `( ~
0 I& X6 G1 U( S& E" C6 j1 r# W6 b( ~- `3 P5 q
IO调度层
& a* z3 e6 d/ I9 `- ?
7 D9 x2 h4 o4 }! Z0 t- [  x对bio进行合并、排序,以提高IO效率。然后,调用设备驱动层的回调函数,request_fn,转到设备驱动层处理。
: J3 C4 Q+ ?; t% X5 e- R: t
8 I/ x6 W$ s% `% [0 u9 E1 K
2 W# C1 n  S& z6 U6 Z4 z  Z5 m. A7 K$ g
设备驱动层
" ^- r. M9 q4 s% w
: U! B( w1 I" mrequest函数对请求队列中每个bio进行分别处理,根据bio中的信息向磁盘控制器发送命令。处理完成后,调用完成函数end_bio以通知上层完成。
, z7 e9 X. ~( `5 r3 l8 u
# H* B$ n4 V! B2 ^5 v  k" S0 c; M# n2 f5 g. L7 p
3 K% f; B) K# |
8 D- j: L+ E& n* \8 I7 J7 U

作者: ExxNEN    时间: 2020-4-17 18:36
linux IO子系统和文件系统读写流程分析




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2