Linux文件描述符的分配机制与重定向实现:揭开“一切皆文件”的面纱
前言
上篇我们介绍了语言和系统层次的基础I/O文件操作,其涉及的知识有:C语言文件操作和系统调用接口两者的关系、文件描述符的概念、操作系统对文件的管理等。本篇我们将承接上一篇文章以继续深入探讨Linux中文件操作。
一、Linux的标准流
在上篇文章中我们遗留了一个问题:
文件描述符0(标准输入)对应键盘、1(标准输出)对应显示器、2(标准错误)对应显示器,为什么后两者都对应显示器,还要用不同的文件描述符标识?是如何实现的呢?今天我们就来回答这些问题。
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 char *str1="normal message\n";
10 char *str2="error message\n";
11 write(1,str1,strlen(str1));
12 write(2,str2,strlen(str2));
13 return 0;
14 }
我们的程序分别向标准输出、标准错误打印不同的信息
现在我们的程序成功执行,并且都打印在了显示器上,这也是符合预期的,我们要想利用标准输出、标准错误需要利用重定向的操作:
在我们刚开是学习Linux指令(如:cat、echo等)时一定听说过重定向,默认方式为输出重定向。
下面这种方式我们就是将向标准输出打印的数据重定向到normal.txt文件中,将向标准错误打印的信息重定向到err.txt文件中,这样我们就完成了信息分类的工作,那么为什么要这样呢?标准错误,大家想在我们执行程序是,我们是十分重视错误信息的,若是可以将错误信息与正常信息分离,这对我们的开发效率还是比较友好的。下面我们看如何实现的,我们先来看下面这个场景:
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 close(1);
10 char *str1="normal message\n";
11 char *str2="error message\n";
12 write(1,str1,strlen(str1));
13 write(2,str2,strlen(str2));
14 return 0;
15 }
我们使用close关闭标识符为1的文件,程序执行结果:
文件描述符1和2,虽然都是对应显示器文件,但是其底层利用引用计数方式来实现,每当多一个文件描述符指向文件,文件所拥有的引用计数就会增减一,相反每当有一个文件描述符关闭,对应文件的文件描述符就会减小一,当文件引用计数为零时,文件就会被关闭。
二、文件描述符分配规则
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 close(0);
10 int fd1=open("ll.txt",O_WRONLY|O_CREAT,0666);
11 int fd2=open("gg.txt",O_WRONLY|O_CREAT,0666);
12 printf("fd1:%d fd2:%d\n",fd1,fd2);
13 return 0;
14 }
我们知道操作系统默认的三个文件描述符为:0、1、2,现在我们关闭标准输入并且打开两个新的文件,查看文件描述符:
此时文件描述符变为0、3,这是因为当你新打开一个文件时,操作系统会在file_struct数组中,找到当前没有被使用的最小下标,并将文件地址存入下表对应空间,返回下标作为新的文件描述符,这里你可以进行多次尝试验证。
三、文件重定向
我们看下面这个简单的程序:
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 close(1);
10 int fd=open("gg.txt",O_WRONLY|O_CREAT);
11 printf("%d\n",fd);
12 return 0;
13 }
结合我们上面介绍的,关闭文件描述符1,此时打开文件gg.txt,1就会为gg.txt的描述符。
可以看到本该打印在显示器上的信息,现在打印在了文件中,而产生这一现象的原因是因为对于printf函数来说,它只是封装了1号文件描述符,当程序执行它时,它就会将数据输出到1号文件描述符对应的文件中,并不会关心该文件是谁,上面这种现象就被称为输出重定向,你也可以尝试关闭0号文件描述符,让scanf从文件中读取,这里就不演示了。
上面这种重定向的行为有一点搓,我们实际运用中一般使用dup2函数来完成重定向工作。
dup2()函数
这个函数需要两个文件描述作为参数,它会用旧的文件描述符所存储的地址覆盖新的文件描述符处的地址,函数调用成功返回新的文件描述符,失败返回-1
man手册对这给函数描述更抽象,所以没有给大家展示,结合下图理解吧
下面我们来尝试使用:
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 int fd=open("gg.txt",O_WRONLY|O_CREAT);
10 dup2(fd,1);
11 printf("dup2(%d,%d)\n",fd,1);
12 return 0;
13 }
我们用gg.txt的文件描述符,覆盖文件描述符1。
这样我们就可以利用系统调用完成重定向工作了,当然这只是输出重定向,大家可以尝试输入重定向,下面我们再次认识一下指令方面的重定向
7 int main()
8 {
9 char *str1="normal message\n";
10 char *str2="error message\n";
11 int fd1=open("normal.txt",O_WRONLY|O_CREAT,0666);
12 int fd2=open("error.txt",O_WRONLY|O_CREAT,0666);
13 write(1,str1,strlen(str1));
14 write(2,str2,strlen(str2));
15 return 0;
16 }
./test 1>normal.txt 2>error.txt
上面我们演示了使用这个指令将他吗重定向到不太文件中,接下来我们演示如何重定向到同一文件中。
./test >myfile 2>&1
这句指令的意思就是将test的执行结果进行输出重定向,并且使用文件描述符1覆盖文件描述符2,这就是对dup2(1,2)的分装,具体原理我们上面已经介绍过了。
四、Linux下一切皆文件
当我们学到这里,就可以理解Linux下一切皆文件的概念了,我们知道计算机的所有操作都是通过进程来完成的,那么对硬件的操作(打开、写入等)也必然是这样,这是如何做到的呢?毕竟每个硬件的操作方式都各有特点(有的可以些,有的只能读。,操作系统将外设全部抽象化,使用结构体来描述他们的属性,虽然属性不同,但是属性的种类还是很接近的,所以操作系统给每个硬件都生成了一个struct_file结构体(这个结构体我们上篇提到过),在结构体中有一个指针,指向一个struct peration_func类型的结构体,这里面就是硬件具体操作方法的指针(函数指针),指针指向每个硬件具体的操作方法,而每个硬件都会提供一个自己的操作方法。
这里将底层实现差异屏蔽,对上提供相同接口的形式,实现了一切接文件的概念。可以说c++中的继承、多态,就像这里的设计思路一样。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/2301_80774875/article/details/146447819