之前在学习C语言时,从语言层面对文件有了浅薄的理解,并不深刻,现在学习了操作系统,并对文件的学习进行了深化,不再是语言层,而是深入到系统,深入到底层去理解它。
文件读写的本质
再C语言中,我们学习过如何通过 fopen 函数来打开文件。

在Linux下,我们可以使用系统原生的 open 函数打开文件。

而C的 fopen 本质,是对系统的 open 函数进行了封装,所以调用 fopen ,其实是调用 open 来进行工作,同理的 fclose 也是封装了 close。
而我们打开文件的操作的本质,是进程打开文件,那么文件的路径问题就有了答案,而引入进程,我们也就可以尝试一窥文件的调用奥秘。
但在那之前,先补充一个概念,文件描述符。
文件描述符
在调用系统的 open 函数时,我们使用一个 fd 来接收其返回值。这个返回值就是文件描述符,其本质是一个从 0 开始的小整数,且优先使用当前最小的未被使用的整数。
当我们打开一个文件,操作系统需要在内存中创建相应的数据结构用来描述目标文件,这个数据结构是一个数组,而文件描述符就是这个数组的下标,数组里面存放着对应的一个文件指针,所以我们通过文件描述符,就可以找到对应的文件。
或许你无法很好地理解以上的解释,但没关系,先记住。
文件打开的底层结构
我们刚才说,打开文件本质是进程打开,那么会发生什么呢?
我们知道进程有一个 task_struct 结构体,也就是 PCB。PCB 里有一个 files 指针,这个指针又指向一个数组,叫做 files_struct ,也就是刚才说的,用来描述目标文件的数据结构,里面存放的就是我们的文件指针。我们通过一张图片来直观感受:

我们也可以通过一些代码来试着查看文件描述符:

上图中,我们试着打印 open 的返回值,但结果好像并不完全符合我们的预期。我们说了,文件描述符是一个从 0 开始的小整数,从代码运行的结果来看,fd 确实是一个小整数没错,但为什么我打开的第一个文件,它的文件描述符不是 0 而是 3 呢?而且当我关闭 test1 ,打开 test3,它的文件描述符也是 3,那岂不是说当前最小未分配整数是 3 咯!也从侧面证明,前面的 0,1,2 已经被占用。那么,谁占用了这前三个位置呢?
你可还记得,在C语言学习文件操作时,我们其实就知道,在程序启动时,系统会默认打开三个流,标准输入、标准输出、标准错误。而刚开始学习 Linux 时,也了解过一句话------" Linux下一切皆文件 "。没错,在Linux下,我们运行程序,系统也默认打开了这三个流,也就是文件,所以其实就是这三个文件,占用了文件描述符的 0,1,2 位置!
重定向的原理
原理
了解了文件底层的大概细节,现在不妨搞清楚我们开始学习 Linux 时了解到的重定向操作的实现原理。先来复习一下重定向操作,当时我们学习到的解释是 重定向是把本来输出到屏幕 / 从键盘读的数据,改成输出到文件 / 从文件读 ,这种简单的输出重定向 通过符号 > 来完成,此外还有追加重定向 >> ,输入重定向 < 。
而用我们现在的目光来开,重定向是做了一个什么工作呢?回到刚才说的文件结构,我们是通过文件指针来找到文件的,而找到文件指针使用到的就是那个数组的下标(文件描述符)。而重定向,其实就是对文件指针位置的改变,因为我们总不能改变数组下标的位置吧。
文件描述符 1 位置的是标准输出,我们可以暂时理解为显示器,接下来我们用 文件描述符 3 位置表示我们要重定向到的文件。那么重定向操作就是把 3 位置的指针覆盖到 1 位置就好了,就像这样:

重定向之后,在进程下,原来 1 位置的文件,也就是标准输出,会被关闭,如果后面还想用,得提前备份,比如保存到 10 位置。3 位置会被清空,不再指向文件。
dup2
我们之前用到的重定向操作符,其实是 shell 命令,所以我们只能在终端里使用它。如果想在代码里面使用重定向,我们需要使用到一个系统函数,dup2。
先来看看函数原型:

其中:
oldfd:要被复制的源文件描述符
newfd:目标文件描述符(如果它已经打开,会先被自动关闭)
成功返回 newfd ;失败返回**-1** ,并设置**errno。**
如果我们想用 dup2 实现 "把本来输出到屏幕,改成输出到文件",那么 oldfd 参数应该设置为 目标文件的 fd,newfd 设置为标准输出的 1。