当前位置: 首页 > news >正文

Linux中基础IO(下)

目录

理解一切皆文件

缓冲区

为什么要引⼊缓冲区机制

缓冲类型

综合理解


理解一切皆文件

我们之前老是说一切皆文件,但是没深入说过。

⾸先,在windows中是⽂件的东西,它们在linux中也是⽂件;其次⼀些在windows中不是⽂件的东 西,⽐如进程、磁盘、显⽰器、键盘这样硬件设备也被抽象成了⽂件,你可以使⽤访问⽂件的⽅法访 问它们获得信息;甚⾄管道,也是⽂件;将来我们要学习⽹络编程中的socket(套接字)这样的东西, 使⽤的接⼝跟⽂件接⼝也是⼀致的。

总之一句话,在linux中把所有的东西都抽象成文件。

在OS看来一个个硬件都是文件,访问读写硬件都是本质上都是硬件文件的访问读写。

这样做最明显的好处是,开发者仅需要使⽤⼀套API和开发⼯具,即可调取Linux系统中绝⼤部分的资源。

举个简单的例⼦,Linux中⼏乎所有读(读⽂件,读系统状态,读PIPE)的操作都可以⽤ read 函数来进⾏;⼏乎所有更改(更改⽂件,更改系统参数,写PIPE)的操作都可以⽤ 数来进⾏。

看底层代码:

struct file 中的 f_op 指针指向了⼀个 file_operations 结构体,这个结构 体中的成员除了structmodule*owner其余都是函数指针。file_operation 就是把系统调⽤和驱动程序关联起来的关键数据结构,这个结构的每⼀个成员都 对应着⼀个系统调⽤。读取 file_operation 中相应的函数指针,接着把控制权转交给函数,从⽽ 完成了Linux设备驱动程序的⼯作。

这样对硬件的读写,只需要统一通过它们的read,write函数指针方法进行访问即可!

上图中的外设,每个设备都可以有⾃⼰的read、write,但⼀定是对应着不同的操作⽅法!!但通过 struct file 下 file_operation 中的各种函数回调,让我们开发者只⽤file便可调取Linux系 统中绝⼤部分的资源!!这便是“linux下⼀切皆⽂件”的核⼼理解。

缓冲区

缓冲区是内存空间的⼀部分。也就是说,在内存空间中预留了⼀定的存储空间,这些存储空间⽤来缓 冲输⼊或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输⼊设备还是输出设 备,分为输⼊缓冲区和输出缓冲区。

为什么要引⼊缓冲区机制

  1. 读写⽂件时,如果不会开辟对⽂件操作的缓冲区,直接通过系统调⽤对磁盘进⾏操作(读、写等),那么 每次对⽂件进⾏⼀次读写操作时,都需要使⽤读写系统调⽤来处理此操作,即需要执⾏⼀次系统调 ⽤,执⾏⼀次系统调⽤将涉及到CPU状态的切换,即从⽤⼾空间切换到内核空间,实现进程上下⽂的 切换,这将损耗⼀定的CPU时间,频繁的磁盘访问对程序的执⾏效率造成很⼤的影响。
  2. 为了减少使⽤系统调⽤的次数,提⾼效率,我们就可以采⽤缓冲机制。⽐如我们从磁盘⾥取信息,可 以在磁盘⽂件进⾏操作时,可以⼀次从⽂件中读出⼤量的数据到缓冲区中,以后对这部分的访问就不 需要再使⽤系统调⽤了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数, 再加上计算机对缓冲区的操作⼤⼤快于对磁盘的操作,故应⽤缓冲区可⼤⼤提⾼计算机的运⾏速度。
  3. ⼜⽐如,我们使⽤打印机打印⽂档,由于打印机的打印速度相对较慢,我们先把⽂档输出到打印机相 应的缓冲区,打印机再⾃⾏逐步打印,这时我们的CPU可以处理别的事情。可以看出,缓冲区就是⼀ 块内存区,它⽤在输⼊输出设备和CPU之间,⽤来缓存数据。它使得低速的输⼊输出设备和⾼速的 CPU能够协调⼯作,避免低速的输⼊输出设备占⽤CPU,解放出CPU,使其能够⾼效率⼯作。

我们这里说的缓冲区是用户级,语言级,C语言提供的库缓冲区。

但是OS中也有一个内核级的文件缓冲区,当利用库函数将内容写入文件时(fwrite等),写入的是C语言库缓冲区,并不是内核级的缓冲区,所以才有我们为什么有时候printf没有加\n时,打印的内容不会立马出现。

由库函数写入C语言库缓冲区的内容,然后会写入内核级的缓冲区,最终才会真正写入磁盘文件。

调用系统调用将内容写入文件,是直接写入内核级的缓冲区,所以根本不需要考虑刷新问题。

那这个C语言提供的库缓冲区是要怎么才会刷新到内核级别的缓冲区?

刷新到了内核缓冲区,最后写入到磁盘文件是由OS决定的,我们目前认为只要内容到了内核缓冲区就是写入到了磁盘文件。

缓冲类型

标准I/O提供了3种类型的缓冲区。

  • 全缓冲区:这种缓冲⽅式要求填满整个缓冲区后才进⾏I/O系统调⽤操作。对于磁盘⽂件的操作通 常使⽤全缓冲的⽅式访问。(这种效率最高)
  • ⾏缓冲区:在⾏缓冲情况下,当在输⼊和输出中遇到换⾏符时,标准I/O库函数将会执⾏系统调⽤ 操作。当所操作的流涉及⼀个终端时(例如标准输⼊和标准输出),使⽤⾏缓冲⽅式。因为标准 I/O库每⾏的缓冲区⻓度是固定的,所以只要填满了缓冲区,即使还没有遇到换⾏符,也会执⾏ I/O系统调⽤操作,默认⾏缓冲区的⼤⼩为1024字节(1KB)。
  • ⽆缓冲区:⽆缓冲区是指标准I/O库不对字符进⾏缓存,直接调⽤系统调⽤。标准出错流stderr通 常是不带缓冲区的,这使得出错信息能够尽快地显⽰出来。

一句话,对于显示器写入,一般是行缓冲,对于磁盘普通文件写入,一般是全缓冲。

除了上述列举的默认刷新⽅式,下列特殊情况也会引发缓冲区的刷新:

  1. 缓冲区满时
  2. 强制刷新(执⾏flush语句)
  3. 进程退出

看代码:

理解:

我们看到是立马就能看到,且一直打印。

循环打印到显示器,是行刷新,也就是说要写满缓冲区一行(1024字节)才刷新,但是写的很快,一下就写满了1024,所以可以立马看到(也就是说立马就刷新到了内核缓冲区)。

再看代码:

理解:

现象:一直看不到打印内容。

因为打印到显示器是行刷新,只有写满了1024字节(缓冲区的一行)就刷新,每3s写入,而不是立马写入,写的很慢,所以一直看不到打印内容。

再看代码:

理解:

现象:每三秒看见打印内容。

因为打印到显示器是行刷新,本来要写满1024字节(一行)才刷新,但是加上了\n可以立马刷新,也就是说执行一次printf就刷新一次,所以可以一下看到内容。

再看:

理解:

因为打印到显示器是行刷新,但是只打印5次,肯定不满1024字节(一行),但是为什么又能在显示器上看到?因为进程退出,会自动刷新!

综合理解

1.每个文件都有一个自己的内核级缓冲区!

2.一个进程对文件去进行读写操作,毫无疑问,在内存中,有进程pcb,文件描述符表,还有一个个文件结构体(struct file),也有每个文件对应的内核级缓冲区,当进程对文件做任何操作,都必须将文件加载到对应的内核级文件缓冲区(磁盘内容到内存的拷贝),然后对文件的操作,只需要调用对应file结构体中的函数指针即可!操作完毕,就会将内核级中的文件缓冲区写回磁盘文件(这涉及文件系统知识,讲文件系统部分详说)。

3.计算机数据流动的本质就是拷贝。

看代码:

理解:

我们本来想使⽤重定向思维,让本应该打印在显⽰器上的内容写到“log.txt”⽂件中,但我们发现, 程序运⾏结束后,⽂件中并没有被写⼊内容:

这是由于我们将1号描述符重定向到磁盘⽂件后,缓冲区的刷新⽅式成为了全缓冲。⽽我们写⼊的内容 并没有填满整个缓冲区,导致并不会将缓冲区的内容刷新到磁盘⽂件中。怎么办呢?可以使⽤fflush强 制刷新下缓冲区。

 还有⼀种解决⽅法,刚好可以验证⼀下stderr是不带缓冲区的,代码如下:

这种⽅式便可以将2号⽂件描述符重定向⾄⽂件,由于stderr没有缓冲区,“helloworld”不⽤fflash 就可以写⼊⽂件:

看代码:

理解:

将msg内容写入log.txt,但是并没有写入,因为fwrite将内容写入到了C语言级的缓冲区,而且是全缓冲,只写入一行肯定不会刷新(缓冲区要满才会刷新),按理来说,进程结束会刷新,但是结果却是没有写入到磁盘文件,因为在进程结束之前,调用了系统调用,将内核级的缓冲区关闭了,所以就算进程结束刷新了,也不会刷新到内核级缓冲区!所以没有写入到磁盘文件。

再看代码:

理解:

fclose并没有关闭对应文件的内核缓冲区,所以进程结束写入了磁盘文件。

所以当使用C语言库函数对文件进行读写操作时,尽量别写系统调用close。

而使用系统调用去对文件进行读写操作时,使用fclose没有影响(系统调用是直接对内核文件缓冲区进行操作的)。

使用fclose依然写入了。

再看代码:

理解:

为什么文件中的内容和直接运行的内容不一样?

直接运行,是往显示器写入,是行刷新,但是有\n,可直接刷新,而系统调用不需要经过缓冲所以从上至下依次打印。

重定向操作,是往磁盘文件写入,是全刷新,要等到缓冲区刷新完才能真正写到文件中(所以此执行到C语言库函数根本不会往文件中写入),而系统调用不需要经过缓冲(所以先看到系统调用的打印信息),然后创建子进程,子进程也会有一个log.txt文件的缓冲区(拷贝的父进程的,所以此时父子进程缓冲区的内容一摸一样),最终父子进程都退出,进程退出,缓冲区刷新(父子进程都刷新,也就是说刷新两次)。所以看到文件中的内容系统调用写入的内容只有一次,而C语言库函数写入的内容有两次。

好了我们下期见!

相关文章:

  • Flink CEP实践总结:使用方法、常见报错、优化与难点应对
  • 【Redis】基本架构
  • java导入excel
  • Android-Room + WorkManager学习总结
  • Python生成ppt(python-pptx)N问N答(如何绘制一个没有背景的矩形框;如何绘制一个没有背景的矩形框)
  • Pytest 是什么
  • Function calling和mcp区别
  • 【pycharm】如何连接远程仓库进行版本管理(应用版本)
  • OpenWrt 插件安装失败的常见问题和解决方法
  • LeetCode 169:多数元素 - 摩尔投票法的精妙解法
  • 8.5 Q1|中山大学CHARLS发文 | 甘油三酯葡萄糖-腰身高比指数与中国中老年人心血管疾病的关系
  • 删除队列中整数
  • nova14 ultra,是如何防住80°C热水和10000KPa水压冲击的?
  • Windows下的Qtxlsx下载和编译打包成库
  • 办公效率王Word批量转PDF 50 +文档一键转换保留原格式零错乱
  • Dockerfile正确写法之现代容器化构建的最佳实践
  • LeetCode hot100-6
  • day12 leetcode-hot100-20(矩阵3)
  • 五、web安全--XSS漏洞(1)--XSS漏洞利用全过程
  • Spring MVC极简入门:从@Reuest到Postman的全链路开发
  • 自己服务器建设网站外网访问/seo怎么才能优化好
  • 做网站绿标/今日新闻快讯
  • 做网站499/爱战网关键词
  • 宁波房产信息网官方网站/网站优化联系
  • 网站关键词库怎么做有什么效果/服装市场调研报告范文
  • 网站的建设与维护步骤/网络营销整合推广