Linux:文件描述符,深度理解重定向(dup2)

文件描述符,深度理解重定向

通过C语言我们知道,C语言中对文件操作的库函数有fopen,fclose,fread,fwrite,在进行文件IO操作时会默认打开三个输入输出流,分别是stdin、stdout、stderr,这三个流的类型都是FILE*,是一个文件指针

那么在操作系统的系统调用层也有对应的接口对文件进行操作,open,close,read,write,我们称为系统调用接口。

首先我们了解一下操作系统,了解一下库函数和系统调用接口的关系。
Linux:文件描述符,深度理解重定向(dup2)_第1张图片
在操作系统中库函数在用户操作接口层面,系统调用接口在system call层面,因此库函数在系统调用接口的上层。可以理解为f系列的函数都是对系统调用的封装,方便二次开发。

open函数的使用

#include 
#include 
#include 

int open(const char *pathname,int flags);
int open(const char *pathname,int flags,mode_t mode);

pathname:要打开文件的路径以及名称
flags:可传入多个参数选项

必选项(这三个参数必须选一个)
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开
可选项
O_CREAT:文件不存在则创建它,存在则打开
O_APPEND:追加写
O_EXCL:与O_CREAT同用,但是文件存在则报错
O_TRUNK:打开文件的同时清空文件原有内容

mode:文件权限
返回值

成功----返回新的文件描述符
失败----返回-1

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0标准输出1标准错误2,0、1、2对应的设备分别为键盘显示器显示器
因此输入输出还可以以这样的形式:
Linux:文件描述符,深度理解重定向(dup2)_第2张图片

一、文件描述符(fd)

以前我们在C语言访问文件用的是文件指针,现在我们在操作系统里用的是文件描述符,需要用系统调用接口。文件描述符就是一些连续的小整数。它的本质是进程的PCB和对应的描述打开文件的结构体中的结构体指针数组的数组的下标。

那么有一个小问题,当我们要访问文件时,要拿到文件描述符还是文件指针呢?
答案是文件描述符,因为文件描述符是更接近操作系统层的概念。同时因为IO相关的函数与系统调用接口对应,并且库函数封装了系统调用,所以本质上,访问文件都是通过fd访问的。

1.文件描述符本质

当进程产生时,会产生一个PCB来描述这个进程,在进程中,当我们每打开一个文件时,操作系统都会创建相应的结构体来描述目标文件,于是就产生了我们描述文件的结构体—file(每打开一个文件都会创建一个描述该文件的结构体)。

那么当我们打开许多文件的时候,操作系统就要管理我们打开的这些文件,那么操作系统怎么管理这些打开的文件呢?

这就有了我们的files_struct,这张表里最重要的就是它包含了一个指针数组,这个指针数组里的每个元素存放的都是指向打开文件的指针!!

每次打开一个文件,就会产生一个描述打开文件的结构体file,然后files_struct里的这个指针数组里就会添加一个指针,指向这个file。那么这里的指针数组是一个数组,它有下标,我们通过这个指针数组的下标就能找到我们对应打开的文件file结构体的指针,就能找到这个file结构体,就能完成相应的文件操作。

所以本质上,文件描述符就是这个指针数组的下标,只要拿着文件描述符就能找到对应的文件。

接下来通过我画的这幅图来加深理解:
Linux:文件描述符,深度理解重定向(dup2)_第3张图片

2.文件描述符分配规则

那么当我们打开一个文件时,我们怎么给它分配文件描述符呢?

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

通俗易懂的讲就是,假如我现在打开了一个文件,那么我会从这个数组的0下标开始找,找没有被使用的一个下标,正常情况下0、1、2是标准输入,标准输出和标准错误,所以此时我打开的这个文件描述符就是3。若我们再基于此打开一个文件,那么此时给它分配的文件描述符就是4。以此类推。

3.inode

文件的信息有两个方面:一个叫文件的内容,一个叫文件的属性。Linux下文件的属性和文件的内容是分开保存的。
文件的属性是数据,也要被保存起来。所以引入了inode,inode存在于硬盘里。
inode是保存文件属性的结构。一个文件就有一个inode,一个inode号。虽然每个文件的inode不同,但是每个inode里的字段都是一样的。打开一个文件时,操作系统就会把对应的文件的inode的部分属性摘录下来放在内存里。
Linux:文件描述符,深度理解重定向(dup2)_第4张图片

Linux下,为了方便用户操作,所以给我们显示的都是一个一个的文件名,所以文件名必定和inode有一定的对应关系。
操作任何一个文件都是在特定目录下查找的,目录也是文件,所以目录里存放的是目录下所有文件的文件名和inode的映射关系。 (所以想要在目录文件里创建文件,需要的是这个文件的写权限,也就是要把这个文件的文件名和inode写到对应的映射关系里。)

二、重定向的本质

通过了解了文件描述符,我们有了一个大胆猜想,既然这些文件都可以关闭,那么标准输出的文件描述符是1,那如果我们把标准输出1关闭,再打开一个文件,那我们打开的这个文件的文件描述符分配的时候从最小的未被使用的开始分配,因为我们把1关闭了,所以此时给这个文件分配的文件描述符就是1,那么此时凡是往1号文件描述符里写的内容都写到了我们新打开的这个文件,那这是不是就是输出重定向呢?

对的!!这就是输出重定向的本质。看下图:
Linux:文件描述符,深度理解重定向(dup2)_第5张图片
printf是C库当中的IO函数,一般是往stdout中输出,但是stdout底层访问文件的时候,找的还是fd:1,但是此时1号文件描述符表示的内容已经改变了,变成了我们新打开的这个文件,不再是显示器文件的地址,所以输出的任何消息都会往文件中写入,进而完成输出重定向。

验证输出重定向

代码如下:
Linux:文件描述符,深度理解重定向(dup2)_第6张图片
在这段代码中,我们就实现了重定向,关闭1号文件描述符,以只读形式创建并打开myfile文件,myfile文件的文件描述符就为1,默认标准输出还是会往1号文件描述符里写入,此时我们已经修改了文件描述符的对应打开的文件,所以最后输出的"fd is:1",就会被重定向到myfile文件夹里面。

dup2的使用(系统调用)

#include //头文件

int dup2(int oldfd,int newfd);//函数使用

这里要拷贝的时候,不是0、1、2的拷贝,而是0、1、2下标对应的数组元素内容的拷贝。
在它的参数中,newfd是oldfd的拷贝,所以最后我们得到的是两个oldfd。前提条件是我们要有两个文件描述符。
因此上述验证重定向代码还可以写为:
Linux:文件描述符,深度理解重定向(dup2)_第7张图片

你可能感兴趣的:(Linux)