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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
本帖最后由 thinkfunny 于 2020-4-17 18:35 编辑 8 \1 b9 V( C& K* C% s
# I5 w$ q1 H* t
以下内容的分析是基于2.6.32及其后的内核.
! }1 A) t- i  \* S* C! U( j4 W0 E2 U% I: @" e/ ?9 k
我们在linux上总是要保存数据,数据要么保存在文件系统里(如ext3),要么就保存在裸设备里。我们在使用这些数据的时候都是通过文件这个抽象来访问的,操作系统会把我们需要的数据提交给我们,而我们则无需和块设备打交道。
7 p7 N+ b$ r4 t5 x
/ W: K, \: w/ Y从下图,我们可以清除的看到:6 |+ N! K6 A/ G. e0 f5 y( k
# s$ l4 V1 C7 N% d4 j& l, y

" q0 }: G/ X+ g, G7 ^8 U4 ?# x' s9 k1 ^8 C9 R# S' n) y, r
I/O子系统是个层次很深的系统,数据请求从用户空间最终到达磁盘,经过了复杂的数据流动。% v/ U* F* j! k: b$ I' D% h5 B' s
$ B# t- e+ [6 B- C! e# s
对设驱开发人员或与此相关的设计人员,特别是IO很密集,我们就需要搞清楚IO具体是如何动作的,免得滥用IO和导致设计问题。
& A5 H2 q% f+ @6 k( z9 L  C% c9 Y8 f! m9 X# V

% P. I; E; X2 d- x, F
/ G, d! T* I! q2 H2 P- v3 aIBM developworks中,〈read系统调用剖析〉阐述就很清楚。* n$ k6 `2 K6 N: T- e

' `# R8 R& L4 C9 A7 T3 p( \! Nread系统调用的处理分为用户空间和内核空间处理两部分。其中,用户空间处理只是通过0x80中断陷入内核,接着调用其中断服务例程,即sys_read以进入内核处理流程。4 o. I" _, J; D5 {( L

, e/ A5 z  N' ]6 L( @对于read系统调用在内核的处理,如上图所述,经过了VFS、具体文件系统,如ext2、页高速缓冲存层、通用块层、IO调度层、设备驱动层、和设备层。其中,VFS主要是用来屏蔽下层具体文件系统操作的差异,对上提供一个统一接口,正是因为有了这个层次,所以可以把设备抽象成文件。具体文件系统,则定义了自己的块大小、操作集合等。引入cache层的目的,是为了提高IO效率。它缓存了磁盘上的部分数据,当请求到达时,如果在cache中存在该数据且是最新的,则直接将其传递给用户程序,免除了对底层磁盘的操作。通用块层的主要工作是,接收上层发出的磁盘请求,并最终发出IO请求(BIO)。IO调度层则试图根据设置好的调度算法对通用块层的bio请求合并和排序,回调驱动层提供的请求处理函数,以处理具体的IO请求。驱动层的驱动程序对应具体的物理设备,它从上层取出IO请求,并根据该IO请求中指定的信息,通过向具体块设备的设备控制器发送命令的方式,来操纵设备传输数据。设备层都是具体的物理设备。
8 |6 T9 U8 h% o: a9 p& ?/ Q& m) y* A" w5 G

( \0 a& k( i7 a' b& @, m4 c3 f: @) }; ?
) Z, _/ [2 v5 F& g' HVFS层:: F9 X  i! \8 p; u/ q. u
- ?7 ~* S; i% G. K0 U$ ?$ Q0 S" o
内核函数sys_read是read系统调用在该层的入口点。
, O+ [, o6 s# N, o" e: t, g' _  U$ A( Q# O5 _
它根据文件fd指定的索引,从当前进程描述符中取出相应的file对象,并调用vfs_read执行文件读取操作。
$ r8 B. n1 q0 \4 |* F6 `5 _0 W! t2 J( T+ K, j  r
vfs_read会调用与具体文件相关的read函数执行读取操作,file->f_op.read。6 p- e2 G" b! g1 O3 o
! o% a9 D& b+ |( C/ \( V
然后,VFS将控制权交给了ext2文件系统。(ext2在此作为示例,进行解析); U) u9 R# |# m! ?1 z: k

% h, E% i5 E$ W) c; S/ L+ q, L1 B1 ]4 s3 q

# C0 b# p5 b  ]8 P4 H; `; [, iExt2文件系统层的处理
6 A4 F" T- D# o- @" X; J
% q, f% j: I- e( ^通过ext2_file_operations结构知道,上述函数最终会调用到do_sync_read函数,它是系统通用的读取函数。所以说,do_sync_read才是ext2层的真实入口。+ _3 n# |( g! s% o- r. x7 A' ^
+ I" H& i2 P" Z4 I* {
该层入口函数 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,从磁盘读取数据。3 B1 ^1 \. I2 W) [% V# y& W

) i2 s; ^2 c% b/ J) e5 H+ e在mpage_readpages(一次读多个页)中,它会将连续的磁盘块放入同一个BIO,并延缓BIO的提交,直到出现不连续的块,则直接提交BIO,再继续处理,以构造另外的BIO。8 Y/ ~( Z8 V/ ~1 h( |/ Z

8 K# v$ x' Z2 J! ~$ {4 U& D/ O! x1 V8 M# e+ v- F  c

* w* X* D: Q4 P+ D- }! o$ U6 Z文件的 page cache 结构/ C0 [/ \8 ^) O2 G, [! a5 r; C

. X  I+ R+ y/ w( ?+ d5 p9 Z3 u1 k4 J) X图5显示了一个文件的 page cache 结构。文件被分割为一个个以 page 大小为单元的数据块,这些数据块(页)被组织成一个多叉树(称为 radix 树)。树中所有叶子节点为一个个页帧结构(struct page),表示了用于缓存该文件的每一个页。在叶子层最左端的第一个页保存着该文件的前4096个字节(如果页的大小为4096字节),接下来的页保存着文件第二个4096个字节,依次类推。树中的所有中间节点为组织节点,指示某一地址上的数据所在的页。此树的层次可以从0层到6层,所支持的文件大小从0字节到16 T 个字节。树的根节点指针可以从和文件相关的 address_space 对象(该对象保存在和文件关联的 inode 对象中)中取得(更多关于 page cache 的结构内容请参见参考资料)。
# a* `5 e3 f5 Z: c6 f9 c
1 F/ g& g2 g% `" Z ' D5 k' K  w! [2 B( N* [$ R) Z3 n
/ [. b" z# A5 ?6 x# D# v
mpage处理机制就是page cache层要处理的问题。( }% D) b% B' O$ k

; H4 u; X; @/ w8 l7 |% `7 o8 f% K* b( ~1 I
0 ^9 s+ c% }. N
通用块层$ G  Q: P: A) I# I
5 j) C+ ?+ U9 p& T. ?
在缓存层处理末尾,执行mpage_submit_bio之后,会调用generic_make_request函数。这是通用块层的入口函数。7 X" f- h: h  o* ?# Y

8 V6 q- r; V7 J$ a6 M6 p它将bio传送到IO调度层进行处理。) i& Y( @% d( R4 Z3 b
7 {8 G& m4 k; I

( I- b9 X9 I) Q& t" E1 i$ h& v) F
1 E0 {9 o+ q; UIO调度层' f! T1 H/ Z  t2 F& i) U0 M
2 `: @" Z; `, t: \8 G1 Z( c
对bio进行合并、排序,以提高IO效率。然后,调用设备驱动层的回调函数,request_fn,转到设备驱动层处理。6 N# N# g* j8 B# I

" Q7 ?: |) U3 E2 w2 r6 r1 ~  L, W1 f+ a/ Y) {4 m; o

5 p6 |3 ^& k/ n! F: |设备驱动层  O2 Q! c; ~; k5 J

# U  V8 A( |! v3 T% {) R0 {request函数对请求队列中每个bio进行分别处理,根据bio中的信息向磁盘控制器发送命令。处理完成后,调用完成函数end_bio以通知上层完成。
6 G  N1 K0 R$ ?( i" v2 F3 [6 P' N% W: h3 J) W3 F
' U3 _. Z% w! f

" n- d* q+ x6 N! [/ w% D
, I2 K: F+ j7 E+ B: W: s+ L" k

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-11-25 21:15 , Processed in 0.171875 second(s), 26 queries , Gzip On.

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

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

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