1.理解一下struct file内核对象
上一篇文章(文件详解)我们一直在谈,一个文件要被访问就必须要先被打开,打开之前就必须要先把文件加载到内存,同时呢我们的操作系统为了管理文件也会为我们的文件创建相对应的struct file对象,那么这个struct file对象里面应该有什么?
其实struct file里面最核心的两个:一个叫做内容,另一个叫做属性。
因为我们前面说了文件= 内容+属性。所以一个文件被打开之后最重要的要么是内容,要么是属性。
如果我们要读一个文件,那么读文件一定是由进程来读的,其中进程的PCB中包含了一个文件描述符表指针指向struct file * fd_array[]数组的一个指针,正常情况下标准输入,标准输出,标准错误三个流是默认被打开的,也就是对应着文件描述符0,1,2,所以新打开的文件一般是把新打开创建的struct file的地址填到文件描述符为3的空间里,然后将该文件描述符作为返回值传递给上层,上层拿到了文件描述符为3的文件就可以对该文件进行一系列操作了。如果我们要对文件进行读数据,首先要将文件加载至内存,如果要对文件进行写数据时我们不能在磁盘中对文件进行写入,而是要把文件加载到缓冲区(内存空间)才能进行写入操作。所以说无论读写都要先把文件加载到文件缓冲区中!
那么我们在应用层进行数据的读写本质是什么呢?其实本质是将内核缓冲区中的数据进行来回拷贝!
下面我们看一看Linux内核源代码中关于文件的结构体字段的描述
2.文件描述符fd的分配规则
下面我们先看一下以写的方式打开文件如果不存在就创建文件的方式进行代码测试:
运行后:
既然我们说默认文件描述符为0,1,2的文件流默认是打开的,那么我们直接使用文件描述符为0的文件对其进行操作即可验证这一说法,然后改写代码为:
运行之后:
既然我们可以对标准输入进行读,那么自然也可以从标准输出进行写操作,下面我们将代码修改:
运行后:
读了abcdefg,同时写入了abcdefg.
进程默认已经打开了0,1,2,我们可以直接使用0,1,2进行数据访问!
下面我们在进行一些测试:
我直接把文件描述符为0的标准输入给关了
运行之后:
发现我们以写的方式打开的新的文件的文件描述符不再是3了,而是变成了0,一个该现象并不足以说明问题,下面我们把2号标准错误流给改了,看是否打开的新文件的文件描述符会发生变化。
运行结果:
我们发现果然,文件描述符变成了2,这说明了什么问题呢?
当我们打开一个新的文件时,会从文件描述符表中从上往下扫描,寻找文件描述符最小且没有被使用的文件描述符分配为为新的文件的文件描述符。
那么下面我们把文件描述符为1的标准输出流给关了之后会发生什么现象呢?
运行之后:
我们发现什么都没有???原因就在于我们原本printf是要将内容打印到显示上的,但是我们在代码中已经把文件描述符为1的标准输出流给关闭了,所以没有打印出来。
如果我们再把代码改成这样呢?
然后运行之后,再查看一下log.txt文件中的内容:
怎么我们要打印的内容在log.txt文件里面去了?
其实很容易理解,根据我们上面的结论,文件描述符为1的标准输出流关闭了,所以log.txt打开时会被分配最小的没有被使用的文件描述符,也就是1,所以当前情况下log.txt的文件描述符为1,而这些内容会写到log.txt里面原因就在于这个过程叫做输出重定向
3.重定向
printf只认stdout,而stdout只认_fileno=1也就是文件描述符为1所指向的文件,如果文件描述符为1指向的是显示器那就往显示器上打,如果文件描述符为1指向的是log.txt文件,那就往log.txt上打,所以说重定向的本质,其实就是修改特定文件fd的下表中的内容(也就是打开文件的地址)。
下面我们再使用一个与printf比较相似的fprintf来测试:
先看一下fprintf的手册:
编写如下代码:
运行之后:
下面我们把打开方式改一下:
运行之后:
所以我们就很容易的实现了追加重定向,每次运行都是在log.txt后面进行追加。
下面我们再测试一个输入重定向:
查看一下fread的相关手册
修改代码为如下:
运行结果:
我们发现原本应该要从键盘上读取数据,最后是从log.txt的文件上进行读取的。
所以上面就说明了,上层fd不变,底层fd指向的内容在改变。
关于上述的重定向我们感觉太麻烦了,因为它还需要我们关闭相对应文件描述符指向的文件,有没有一种方法是直接把新打开的文件的地址拷贝到对应文件描述符的内容里面去完成重定向呢?也就是通过文件描述符级别的数组内容的拷贝!
答案是有的,一下就是那个接口:
这个接口的用法是将oldfd的内容拷贝到,newfd上,也就是最后两处的文件描述符都是指向oldfd所指向的内容,而这会导致两个文件描述符如何关闭的问题,其实这个问题是文件结构体中会存在一个引用计数,也就是有几个指针指向该文件,如果有多个指针就对计数那个字段进行数量控制,如果有一个指向该文件的文件描述符关闭了,那么该字段就减减,直到减到0才关闭。下面我们以输出重定向为例来测试一下dup2这个接口:
运行结果:
这就叫做重定向。