当前位置: 首页 > 热门 >

微动态丨深入理解Linux的I/O系统

来源:面包芯语    时间:2023-05-04 09:46:11

点击上方“C语言与CPP编程”,选择“关注/置顶/星标公众号”

干货福利,第一时间送达!

传统的 System Call I/O

由 CPU 直接处理数据的传送,数据拷贝时会一直占用 CPU 的资源。


(相关资料图)

读操作

当应用程序执行 read 系统调用读取一块数据的时候,如果这块数据已经存在于用户进程的页内存中,就直接从内存中读取数据。

read(file_fd,tmp_buf,len);

发起数据读取的流程如下:

写操作

当应用程序准备好数据,执行 write 系统调用发送网络数据时,先将数据从用户空间的页缓存拷贝到内核空间的网络缓冲区(Socket Buffer)中,然后再将写缓存中的数据拷贝到网卡设备完成数据发送。

write(socket_fd,tmp_buf,len);

基于传统的 I/O 写入方式,write() 系统调用会触发 2 次上下文切换,1 次 CPU 拷贝和 1 次 DMA 拷贝。

用户程序发送网络数据的流程如下:

网络 I/O

磁盘 I/O

高性能优化的 I/O

页缓存(PageCache)技术。

其中,页缓存(PageCache) 是操作系统对文件的缓存,用来减少对磁盘的 I/O 操作,以页为单位的,内容就是磁盘上的物理块,页缓存能帮助程序对文件进行顺序读写的速度几乎接近于内存的读写速度,主要原因就是由于 OS 使用 PageCache 机制对读写访问操作进行了性能优化。

页缓存读取策略:当进程发起一个读操作 (比如,进程发起一个 read() 系统调用),它首先会检查需要的数据是否在页缓存中:

如果在,则放弃访问磁盘,而直接从页缓存中读取。

如果不在,则内核调度块 I/O 操作从磁盘去读取数据,并读入紧随其后的少数几个页面(不少于一个页面,通常是三个页面),然后将数据放入页缓存中。

页缓存写策略:当进程发起 write 系统调用写数据到文件中,先写到页缓存,然后方法返回。此时数据还没有真正的保存到文件中去,Linux 仅仅将页缓存中的这一页数据标记为 “脏”,并且被加入到脏页链表中。

然后,由 flusher 回写线程周期性将脏页链表中的页写到磁盘,让磁盘中的数据和内存中保持一致,最后清理“脏”标识。在以下三种情况下,脏页会被写回磁盘:

存储设备的 I/O 栈

由图可见,从系统调用的接口再往下,Linux 下的 IO 栈致大致有三个层次:

Linux IO系统

这下一目了然了吧?传统的 Buffered IO 使用 read 读取文件的过程什么样的?假设要去读一个冷文件(Cache 中不存在),open 打开文件内核后建立了一系列的数据结构,接下来调用 read,到达文件系统这一层,发现 Page Cache 中不存在该位置的磁盘映射,然后创建相应的 Page Cache 并和相关的扇区关联。

然后请求继续到达块设备层,在 IO 队列里排队,接受一系列的调度后到达设备驱动层,此时一般使用 DMA 方式读取相应的磁盘扇区到 Cache 中,然后 read 拷贝数据到用户提供的用户态 buffer 中去(read 的参数指出的)。

整个过程有几次拷贝? 从磁盘到 Page Cache 算第一次的话,从 Page Cache 到用户态 buffer 就是第二次了。而 mmap 做了什么?mmap 直接把 Page Cache 映射到了用户态的地址空间里了,所以 mmap 的方式读文件是没有第二次拷贝过程的。

那 Direct IO 做了什么? 这个机制更狠,直接让用户态和块 IO 层对接,直接放弃 Page Cache,从磁盘直接和用户态拷贝数据。好处是什么?写操作直接映射进程的buffer到磁盘扇区,以 DMA 的方式传输数据,减少了原本需要到 Page Cache 层的一次拷贝,提升了写的效率。

对于读而言,第一次肯定也是快于传统的方式的,但是之后的读就不如传统方式了(当然也可以在用户态自己做 Cache,有些商用数据库就是这么做的)。

除了传统的 Buffered IO 可以比较自由的用偏移+长度的方式读写文件之外,mmap 和 Direct IO 均有数据按页对齐的要求,Direct IO 还限制读写必须是底层存储设备块大小的整数倍(甚至 Linux 2.4 还要求是文件系统逻辑块的整数倍)。

所以接口越来越底层,换来表面上的效率提升的背后,需要在应用程序这一层做更多的事情。所以想用好这些高级特性,除了深刻理解其背后的机制之外,也要在系统设计上下一番功夫。

I/O Buffering

如图,当程序调用各类文件操作函数后,用户数据(User Data)到达磁盘(Disk)的流程如图所示。

2. 系统调用的 read/write 和真实的磁盘读写之间也存在一层 buffer,这里用术语 Kernel buffer cache 来指代这一层缓存。

在 Linux 下,文件的缓存习惯性的称之为 Page Cache,而更低一级的设备的缓存称之为 Buffer Cache。

这两个概念很容易混淆,这里简单的介绍下概念上的区别:Page Cache 用于缓存文件的内容,和文件系统比较相关。文件的内容需要映射到实际的物理磁盘,这种映射关系由文件系统来完成;Buffer Cache 用于缓存存储设备块(比如磁盘扇区)的数据,而不关心是否有文件系统的存在(文件系统的元数据缓存在 Buffer Cache 中)。

你好,我是飞宇,本硕均于某中流985 CS就读,先后于百度搜索以及字节某电商部门担任Linux C/C++后端研发工程师。

同时,我也是知乎博主@韩飞宇,日常分享C/C++、计算机学习经验、工作体会,欢迎点击阅读原文围观我分享的学习经验

我组建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起进群交流。

欢迎你添加我的微信,我拉你进技术交流群。此外,我也会经常在微信上分享一些计算机学习经验以及工作体验,还有一些内推机会。

扫描上方二维码,加我微信

我的私人微信

点击阅读原文查看你错过的宝藏

X 关闭

最近更新

Copyright ©  2015-2022 太平洋字画网版权所有  备案号:豫ICP备2022016495号-17   联系邮箱:93 96 74 66 9@qq.com