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

【Linux庖丁解牛】—系统文件I/O !

1. 系统调用open

在外面之前学C语言的时候,学过简单的文件操作,比如fopen。但是这些都是语言层的方案,其实系统才是打开文件最底层的方案!我们接下来先认识一个函数->open

open的第一个参数就是我们打开文件的路径【绝对或相对】没什么好说的!但是,我们注意到第二个参数flags,这其实是位图传标记位的方法,该方法在系统文件IO接口中会使用到。

这个参数有很多种,下面十几个经常用到的:

The  argument  flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR.  These request opening the file read-only, write-only, or read/write, respectively.

 这些参数的本质其实就是宏。

还有第三个参数,这个参数是用来初始化文件权限的。如果你打开一个不存在的文件,你需要在第二个参数中填写O_CREAT来创建文件并且需要填写第三个参数来初始化你的文件权限!

下面我们就来简单使用一下open函数打开一个不存在的文件:

下面open函数返回值的作用还没说,不用管,现在仅需要看看文件是否被创建:

 文件的权限也和我们的预期一样:

2. 系统调用write 

现在我们已近可以打开一个文件,现在我们认识一下write函数,并用其尝试向文件log.txt中写入一些内容。

write的用法和我们之前用的fwrite几乎是一样的,看一下文档就可以明白它的用法了。

下面向log.txt中写入了五条内容:但是,有一点要注意,我们写入时,字符串的末尾\0是不可以写入的,否则写入的内容中就会出现乱码。原因也很简单,字符串末尾的\0是语言层规定的,而对文件写入内容是系统层的。

结果也没有问题: 

 但是,如果我把写入的内容更改一下会发生什么呢:

上面的结果是:我们新写入的内容覆盖式的写入的文件当中,但是,按照我们之前的认知,不应该是先清空再写入吗!实际上,清空是系统要做的事情,如果我们选择用系统级别的函数,我们就不能把语言层的东西拿过来! 

如果我们要在写入时清空文件就必须再传递一个标记为->O_TRUNC。

如果我们想要在文件内追加内容,那我们就需要使用另一个标记位APPEND

文本写入 VS 二进制写入 

我们仔细看会发先write函数的第二个参数的类型是void*,这是为什么呢?

我们在语言层区分了文本写入和二进制写入的概念,比如:在C语言中,我们想要文本写入就会调用fputs之类的接口,如果我们想要二进制写入,就会调用fwrite之类的接口。但是,在系统级别来看,它们都是二进制写入。他们并没有什么区别,fputs和fwrite调用时,底层可能都是调用的write。

如果我们定义了一个整形1234,想要在显示器上输出这个整形,如果我们直接用write写入,那么系统看到的就是1234这个二进制【也就是二进制写入】,导致我们在显示器上面看到的内容是乱码。而我们说过,显示器上面的1234是一个一个的字符,所以我们需要在写入前进行格式转换!

3. 系统调用read 

 read的使用其实也非常简单。

如果我们是读取log.txt中的内容的话,那我们的open函数的参数就需要改变了。文件存在就不需要创建,也不要文件权限了。

 4. 文件描述符

4.1 简单认识文件描述符

通过简单运用open函数,我们知道open函数的返回值是一个整数,如果大于0就说明文件打开成功,失败则会返回-1。但是,它的返回值到底有什么意义呢?

下面,我们先简单看看它的值:

结果如上,像这样open函数成功返回时的返回值,为我们称为文件描述符。在系统层面,我们对文件进行访问时,系统只认文件描述符!但是为什么文件描述符从3开始呢?

我们怎么不见0 1 2呢?

这里就直说了,0 1 2,分别代表标准输入stdin,标准输出stdout,标准错误stderr。因为这些文件是系统默认在启动时就打开的,所以被0,1,2占用了。

怎么验证呢,我们知道,在C语言中,stdin,stdout,stderr时FILE*类型的,而FILE其实是C语言封装的一个结构体,里面有文件的各种信息。我们还知道,C语言中提供的各种访问文件的函数其底层其实都是调用了open,write,read等系统调用,而这些系统调用都只认文件描述符。所以,我们可以大胆预测,在结构体中必然封装了文件描述符!

实也的确如此!_fileno成员就是文件描述符!

既然C语言在语言层会封装对文件操作的接口,那C++,java,PHP,python等这些语言也会如此吗。答案是肯定的!但是,语言为什么要这样做呢?

为了增加语言的可移植性!就拿C语言来说,如果我们用C语言在Linux平台下写的程序所用的是Linux下的系统调用,当我们将这个程序放到Windows下或其他平台下还可以使用吗!当然不行,不同系统的调用接口肯定会有所不同!但是,如果C语言在语言层封装了各种平台下的系统调用,而在语言层就只有一份调用接口。当我们使用语言层的接口对文件进行访问,无论我们怎样移植平台,在C语言库中总会找到其对应的系统调用【底层使用条件编译的方式】!

4.2 什么是文件描述符

前面我们知道文件描述符是一个一个的整型,我们看到0 1 2 3 这样的字样,在计算机中首先想到的就是数组!

在计算机中,可能会同时打开成千上万的文件,后者说有哪些文件即将被打开,哪些文件即将被关闭......那系统是如何维护这些信息呢?很简单,先描述再组织!

所以在内核数据结构中有一种结构体struct file记录这些被打开的文件信息!

这些文件以双向链表的形式链接起来。

我们知道用户访问文件时,实际上就是进程在访问文件!一个进程可能打开多个文件,所以,进程是如何管理自己打开的进程呢!

其实,进程PCB中不仅有页表和进程虚拟地址空间,还有一张表->文件描述符表【 files_struct】,这张表中有一个指针数组fd_array,其他内容暂时不管,每个位置指向的内容就是一个一个的文件【struct file】。而这个数组的下标就是文件描述符!

至此,我们终于明白了什么是文件描述符。而现在我们也可以理解用户层是如何通过read向文件中读取内容了-->系统通过read函数拿到文件描述符fd,用fd扫描文件描述符表,找到对应下标,至此也就可以找到对应的打开文件,而每个打开的文件都有一个文件缓冲区,系统会把在磁盘中的文件内容预加载【我们学习冯诺依曼体系结构时就说过cpu不会直接对磁盘等外设打交道,而这个预加载其实就是拷贝】到文件缓冲区当中,最后再把缓冲区中的内容拷贝到我们在用户层定义的buffer上!至此,read就完成了打开文件到文件中读取内容的工作。

而write写的原理其实也是一样的!这里就不写了。

4.3 文件描述符的分配规则

既然我们已经知道了文件描述符0,1 ,2对应的文件,如果我们在创建一个新的文件之前,关掉其中的某些文件会发生什么呢?

我们关掉了0,2,文件,发现新创建的文件描述符时0。下面就直说了:

文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的⼀个下标,作为新的文件描述符。

4.4 重定向原理

接下来,如果我在打开文件前只关掉1【stdout】会发生什么呢?

根据结果,我们发现,printf果然没有向显示器上打印,这也在意料之中,毕竟我们将显示器文件关掉了嘛。但是,我们发现文件log.txt中有我们打印的结果,根据我们上面说的文件描述符的分配规则,我们其实也可以理解。但这些都不是关键,凡是我们往1号文件描述符写的内容,都写到了myfile当中,而不再写到标准输出【显示器】的这种现象叫做输出重定向!

但是,我们用上面的方式实现重定向还是太矬了!难道,我们每次进行重定向的时候都要关闭打开文件吗?

实际上,重定向采用了文件描述符表中指针覆盖的方式。比如:我们想要向3号文件中进行输出重定向,那么,我们就将1号文件指向3号文件【也就是将3号文件的指针覆盖1号文件】,这样一来,我们输出的内容都会写入3号文件,也就完成了输出重定向!

而我们可以用系统调用dup2轻松搞定!

 在这个函数中新【newfd】是旧【oldfd】的拷贝,所以应该这样填写参数dup(fd,1);

以上是输出重定向的验证,如果我们想要实现追加重定向,怎么办呢?

很简单,在打开文件时更改标志位即可【O_TRUNC->O_APPEND】。

输出重定向如此,那么输入重定向原理也是这样!我们只需要更改文件打开方式 ,所以,所有的重定向都是->文件的打开方式+dup2。

现在如果我们获取命令行参数中的第二个参数【文件名】,那么我们是不是可以对该文件进行各种重定向呢!下面就举一个输入重定向的例子:

相关文章:

  • springCloud2025+springBoot3.5.0+Nacos集成redis从nacos拉配置起服务
  • 基于Java Swing的办公自动化系统设计与实现:附完整源码与论文
  • 创建一个纯直线组成的字体库
  • DenseNet算法 实现乳腺癌识别
  • 算法练习-回溯
  • 【题解-洛谷】P10448 组合型枚举
  • 学而思网校发布AI编程新品,四重升级培育未来创新人才
  • Vue 中 v-show 与 v-if 的深度对比与性能分析
  • 第二十六章 流程控制: case分支
  • 乐观锁与悲观锁的实现和应用
  • Java 泛型技术详解
  • 【判断既约分数】2022-4-3
  • JDK21深度解密 Day 13:性能调优实战案例:高并发系统与内存密集型应用的优化秘籍
  • 【数据结构初阶】--算法复杂度的深度解析
  • Linux编程:2、进程基础知识
  • 后端下载限速(redis记录实时并发,bucket4j动态限速)
  • 如何在 Java 中优雅地使用 Redisson 实现分布式锁
  • 【Redis系列 04】Redis高可用架构实战:主从复制与哨兵模式从零到生产
  • 在Vue或React项目中使用Tailwind CSS实现暗黑模式切换:从系统适配到手动控制
  • [逆向工程] C实现过程调试与钩子安装(二十七)
  • 微信微网站开发教程/网络推广和网络销售的区别
  • 如何注册公司邮箱/中国seo第一人
  • 做网站在阿里云上面买哪个服务/快速排名优化
  • 网站搭建公司排行榜/站内推广有哪些方式
  • wordpress怎样加快访问/汽车seo是什么意思
  • 纪实摄影网站推荐/泰安网站建设优化