浅谈 Linux IO

梦想与她 提交于 2021-01-31 23:21:27

来源于:360云计算

1

前言


linux IO是文件存储的基础。本文参考了网上博主的一些文章,主要总结了linuxIO的基础知识。



2

linux IO栈


linux文件IO采用分层的设计。分层有两个好处:

  1. 架构清晰;

  2. 功能解耦;



linux文件IO的时候,从通用性和性能的角度考虑,采用了一个折中的方案来满足我们日常写磁盘。


例如:


void foo() {    char *buf = malloc(MAX_SIZE);    strncpy(buf, src, MAX_SIZE);    fwrite(buf, MAX_SIZE, 1, fp);    fclose(fp);}


上面代码的说明如下:


malloc的buf对应图中的application buffer。调用fwrite之后,操作系统将数据从application buffer拷贝到libc buffer,即c库标准IO buffer。fwrite 返回后,数据还保存在libc buffer,如果这个时候进程退出,这些数据将会丢失,没有写到磁盘上。


当调用fclose的时候,fclose只会刷新libc buffer到page cache,如果确保数据写到磁盘,kernel buffer也必须flush。例如使用sync,fsync。除了fclose方法外,还有一个主动刷新接口fflush函数,但是fflush函数只是将数据从libc buffer拷贝到page cache,并没有刷新到磁盘上,从page cache刷新到磁盘上,可以通过调用fsync完成。


sync会告诉OS,你要将这些数据刷新到磁盘上,但并不能真正保证确实已经写到磁盘上了。属于尽力而为的操作。( According to the standard specification (e.g., POSIX.1-2001), sync() schedules the writes, but may return before the actual writing is done. )sync会将page cache数据送到 disk cache层,至于什么时候写入物理磁盘介质上,则由磁盘控制器自己决定。


从上面可以看到,一个常用的fwrite执行过程,需要经历多次数据拷贝才能最终到达磁盘。

如果我们不想通过fwrite+fflush这种方式,而是想直接写到page cache。这就是我们linux常用的系统调用read/write函数。调用write函数时,实际上是直接通过系统调用将数据从application buffer拷贝到page cache中。但是我们知道,系统调用read/write会触发用户态到内核态的转换这将会一定性能消耗。


如果我们想绕过page cache,直接将数据送到磁盘设备上怎么办?可以通过open的时候携带O_DIRECT属性。这时write该文件,就是直接写到设备上。


如果我们想直接写到磁盘扇区有没有办法?这就是RAW设备写,绕开了文件系统,直接写扇区,例如:fdisk,dd之类的操作就是。



3

IO调用链


fwrite是系统提供的最上层接口,也是最常用的接口。它在用户进程空间开辟了一个buffer,将多次小数据量相邻写操作先缓存起来,然后合并,最终调用write系统调用一次性写入(或者将大块数据分解多次write调用)。


write函数通过调用系统调用接口,将数据从应用层拷贝到内核,因此write会触发用户态到内核态的切换。当数据到达page cache后,内核并不会立即将数据往下传递,而是返回用户空间。数据什么时候写入磁盘,是由内核IO调度决定,所以write是个异步调用。这一点和read不同,read调用先检查page cache里面是否有数据,如果有,就取回来并返回给用户,如果没有,就同步等待下去并等待有数据再返回给用户。所以read是个同步过程。如果想把write的异步过程改成同步过程,可以将open文件的时候,携带O_SYNC。


数据到了page cache之后,内核有pdflush线程不停的检测脏页,判断是否要写回到磁盘中。然后把需要写回的页提交到IO队列(即:IO调度队列),由IO调用队列的调度策略决定何时写回。



4

IO调度层


加入IO调度队列的任务并不一定会立即执行,调度层会从全局出发,尽量让整体磁盘IO性能最大化。大致的工作方式是让磁头类似电梯那样工作,先往一个方向走,走到尽头再回来,这样磁盘效率会比较高;磁盘是单向旋转的,不会反复逆时针转动,因为磁头寻道时间比较耗时。


内核中有多种IO调度算法,例如:noop,deadline和cfg,在你机器上,通过dmesg | grep -i scheduler 命令可以查看你的linux支持的算法。当磁盘是SSD时,采用的是随机读写,并没有磁道、磁头,应用于传统机械磁盘的调度算法反而不适用。调度算法中的noop算法,适合配置SSD硬盘。


任务从IO队列出来后,就到了驱动层,驱动层通过DMA,将数据写入磁盘cache。


至于磁盘cache何时写入磁盘介质,这是由磁盘控制器自己决定。如果想确认要写到磁盘介质上,就调用fsync函数。



5

一致性和安全性

5.1 安全性



从上可以看到,数据没有到达磁盘介质之前,可能处于不同的物理内存cache中,那么这个时候如果出现进程退出,内核挂掉,物理机器掉电的情况,数据会丢失吗?

  • 当进程退出后:在application buffer或者libc buffer的数据会丢失。如果数据到了page cache,此时进程退出,即使数据还没有到达硬盘,数据也不会丢失。

  • 当内核挂掉:只要数据还没有到disk cache,都将会丢失。

  • 物理机掉电:此时所有数据都会丢失。



5.2 一致性



Q:同一个进程A中,多次open同一个文件,然后write数据会怎么样?


fd1 = open("file", O_RDWR|O_TRUNC);fd2 = open("file", O_RDWR|O_TRUNC);
while (1) { write(fd1, "hello \n", 6); write(fd2, "world \n", 6);}


A:先写的数据是会被覆盖的。原因在于同一进程中不同的文件描述符(fd),各自对应一个独立的打开文件表,在打开文件表中有属于自己的文件位移量,开始都是0,然后各自从0开始写,每写一个字节向后移动一个字节,写的位置是重叠的,因此会覆盖。


如何解决被覆盖的问题呢?

必须为每个open指定O_APPEND。文件长度是共享的,当文件被写入数据后,文件长度就会被更新,指定O_APPEND之后,使用不同fd写数据时,都会使用文件长度更新自己的文件位移量,保证每次都是在文件的最末尾写数据,这样就不会出现覆盖。


这里补充一点:同一个进程中多次open同一个文件时,文件描述符是不同的,在同一进程中某个文件描述符被占用,在close之前,是不会被再次分配。


Q:两个进程A和进程B,open同一个文件,然后write数据会怎么样?

A:先写的数据是会被覆盖的。覆盖的原因也是各自有独立的文件位移量。同样指定O_APPEND,使用文件长度更新文件位移量,保证各自操作时,都在文件尾部操作,不会出现相互覆盖的情况。


这里补充一点:不同进程打开同一个文件时,各自使用的fd可能是相等的,之所以相同,是因为不同进程有自己单独的文件描述符池,默认是0~1023,各自分配各自的,是有可能分配到相等的fd


Q:A进程写,B进程读取会写脏吗?

A:会的。读取进程对文件内容变化毫无感知,只是按部就班的读取,直到文件结束EOF。



5.3 读文件过程



linux read文件的流程大致如下:

  1. lib中的read函数首先进入系统调用sys_read;

  2. 接着sys_read再进入VFS中的vfs_read、generic_file_read等函数;

  3. 接着VFS中的generic_file_read会判断是否缓存命中,如果命中则返回;

  4. 如果没有命中,内核在page cache中分配一个新页框,发出缺页中断;

  5. 内核向通用块层发起IO请求,块设备屏蔽disk、U盘等差异;

  6. 通用块层将bio代表的IO请求发送到IO请求队列中;

  7. IO调度层通过电梯算法来调度队列中的请求;

  8. 驱动程序向磁盘控制器发出读取命令控制,DMA方式直接填充到page cache中的新页框;

  9. 控制器发出中断通知;

  10. 内核将用户需要的数据填充到用户内存中;

  11. 然后你的进程被唤醒;



6

性能问题


磁盘的寻道时间时相当的慢,平均寻道大概在10ms,也就是每秒只能100-200次寻道。


磁盘转速也是影响性能的关键,如果是15000rpm,大概每秒500转。一般情况下,盘片转太快,磁头跟不上,所以需要多转几圈才能完全读出磁道内容。


机械硬盘顺序写 0~30MB左右,顺序读取速率一般 0~50MB左右,性能好的可以达到100多MB;SSD读取达到0~400MB左右。



相关参考文章

  • http://blog.chinaunix.net/uid-27105712-id-3270102.html?page=2

  • https://zhuanlan.zhihu.com/p/138371910

  • https://meik2333.com/posts/linux-many-proc-write-file/

  • https://blog.csdn.net/qq_43648751/article/details/104151401



如果大家有什么建议或疑问,可以在下方留言交流。



后台回复“加群”,带你进入高手如云交流群


推荐阅读:

虚拟路由器冗余协议 VRRP 详解

Kubernetes 万字实战教程 (2021最新版)

Kubernetes 常见问题总结

一文详解负载均衡和反向代理的真实区别

这本《算法小炒》火了

带宽、延时、吞吐率、PPS 这些都是啥?

一文读懂你身边的网络

Linux 环境变量配置全攻略

如何定位软中断CPU使用率过高的问题?

10大高性能开发利器

安全容器gVisor详解

TCP协议灵魂 12 问,总会用得到

QUIC也不是万能的

超详干货!Linux环境变量配置全攻略

为什么要选择智能网卡?

60,000毫秒内对Linux进行性能诊断

为什么Linux需要Swapping

Linux系统常用命令速查手册

一文读懂容器网络发展

一文搞懂CDN加速原理

8 个问题彻底搞透 DNS 协议

三张图彻底搞懂iptables和netfilter

故障排查:K8s中Pod无法正常解析域名

网络排错大讲解~

OVS 和 OVS-DPDK 对比

微软出品的最新K8S学习指南3.0下载



喜欢,就给我一个“在看”



10T 技术资源大放送!包括但不限于:云计算、虚拟化、微服务、大数据、网络、Linux、Docker、Kubernetes、Python、Go、C/C++、Shell、PPT 等。在公众号内回复「1024,即可免费获取!!

本文分享自微信公众号 - Linux云计算网络(cloud_dev)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!