当前位置 : 主页 > 操作系统 > centos >

[ Linux ] 文件描述符和重定向

来源:互联网 收集:自由互联 发布时间:2023-02-04
在上篇文章我们复习了C文件IO操作并且认识了文件相关的系统调用接口。本篇文章我们要引入文件描述符的概念。 0.文件描述符 0.1引入文件描述符 我们在认识open接口时知道了该接口有

在上篇文章我们复习了C文件IO操作并且认识了文件相关的系统调用接口。本篇文章我们要引入文件描述符的概念。

0.文件描述符

0.1引入文件描述符

我们在认识open接口时知道了该接口有一个int的返回值,但是当时我们并没有重点介绍这个返回值到底是什么,而这里我们将重点介绍这个返回值。因此我们用man手册查一下open函数的返回值。根据手册描述open函数如果打开或创建成功则会返回一个新的文件描述符,否则失败则返回-1。

[ Linux ] 文件描述符和重定向_fd

那我们写一段代码来验证一下这个返回值,看看效果

#include <stdio.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>int main(){ int fd1 = open("log1.txt",O_WRONLY | O_CREAT | O_TRUNC,0666); int fd2 = open("log2.txt",O_WRONLY | O_CREAT | O_TRUNC,0666); int fd3 = open("log3.txt",O_WRONLY | O_CREAT | O_TRUNC,0666); int fd4 = open("log4.txt",O_WRONLY | O_CREAT | O_TRUNC,0666); printf("fd1: %d\n",fd1); printf("fd2: %d\n",fd2); printf("fd3: %d\n",fd3); printf("fd4: %d\n",fd4); return 0;}

[ Linux ] 文件描述符和重定向_重定向_02

我们看到文件被创建出来了,输出的fd也都是大于1的整数,但是这里有两个问题:

  • 为什么fd是从3开始的呢?0,1,2去哪儿了呢?
  • 答:0,1,2被默认打开了。0叫做标准输入(键盘),1叫做标准输出(显示器),2叫做标准错误(显示器)。

    On program startup, the integer file descriptors associated with the streams stdin, stdout, and stderr are 0, 1, and 2,respectively. (在程序启动时,与流stdin、stdout和stderr关联的整数文件描述符分别为0、1和2。)

    [ Linux ] 文件描述符和重定向_重定向_03

    [ Linux ] 文件描述符和重定向_重定向_04

    [ Linux ] 文件描述符和重定向_重定向_05

    #include <stdio.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>int main(){ printf("stdin:%d\n",stdin->_fileno); printf("stdout:%d\n",stdout->_fileno); printf("stderr:%d\n",stderr->_fileno); return 0;}

    [ Linux ] 文件描述符和重定向_文件描述符_06

    函数对应的接口

    数据类型的对应

    fopen/fclose/fread/fwrite.....

    FILE* ->FILE

    open/close/read/write......

    fd

    因此我们使用的C语言接口一定封装了系统调用接口。

    2.fd为什么是0,12,3,4,5......

    一般而言,一个进程可不可以打开多个文件?答案当然是可以的,所以在内核中,一个进程:打开的文件 = 1:n

    所以系统在运行中有可能会存在大量的被打开的文件,操作系统要对被打开的文件进行管理。那么操作系统如何管理这些被打开的文件呢?--->先描述再组织 因此一个文件被打开在内核中要创建被打开文件的内核数据结构--先描述。 struct file 内部包含了大量的内容和属性。操作系统将多个文件的struct file用链表连接起来,因此对被打开文件的管理转换成了对链表的增删查改!那么进程如何和打开的文件建立映射关系呢?因此我们在task_struct中包含一个struct files_srruct * fs指针指向strcut files_struct内部有一个指针数组,存的就是打开文件的fd_array[ ]. 因此要对文件进行操作时,只需要得到这个数组的下标即可。

    [ Linux ] 文件描述符和重定向_重定向_07

    而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了fifile结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*fifiles, 指向一张表fifiles_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

    0.2 文件描述符的分配规则

    通过上文的了解我们知道了文件描述符是从3开始的,因为0,1,2默认分给了stdin,stdout,stderr.那么当我们关闭0或者2我们再重新创建一个文件时,他的文件描述符会是几呢?

    #include <stdio.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>int main(){ close(0); int fd = open("myfile.txt",O_WRONLY | O_CREAT | O_TRUNC,0666); if(fd < 0) { perror("open"); return 1; } printf("fd:%d\n",fd); close(fd); return 0;}

    [ Linux ] 文件描述符和重定向_重定向_08

    [ Linux ] 文件描述符和重定向_重定向_09

    发现是结果是: fd: 0或者fd:2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

    0.3 重定向

    那如果我们关闭fd为1呢?根据文件描述符的分配规则,新建文件的文件描述符fd会是1吗?我们来看代码:

    #include <stdio.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>int main(){ close(1); int fd = open("myfile.txt",O_WRONLY | O_CREAT | O_TRUNC,0666); if(fd < 0) { perror("open"); return 1; } printf("fd:%d\n",fd); close(fd); return 0;}

    [ Linux ] 文件描述符和重定向_文件描述符_10

    此时我们发现什么都没有,这是为什么呢?这时候我们要谈谈重定向了。根据文件描述的分配规则,我们新建的文件描述符fd一定是1,虽然不再指向对应的显示器了,但是已经指向了myfile.txt的底层数据结构了。那么我们如何查看到这个fd呢?这就跟输出重定向相关了,那么我们必须刷新一下才能看到,那么为什么要刷新呢?这是跟缓冲区相关的我们后面解释。

    [ Linux ] 文件描述符和重定向_fd_11

    0.3.1 重定向的本质

    在上述代码中,我们本来要往显示器上面写,最终却变成了向指定文件中写,这就是输出重定向。

    当我们close(1)后,新建一个文件时,根据文件描述符分配规则,1号下标会指向新建的文件,因此凡是往1号文件描述符写的内容都写到myfile当中,而不再写到标准输出了。

    [ Linux ] 文件描述符和重定向_文件描述符_12

    如果我们要进行重定向,上层只认0,1,2,3,4,5这样的fd,我们可以在OS内部通过一定的方式调整数组的特定下标的内容(指向),我们就可以完成重定向操作!

    0.3.2 dup2 -- 重定向的具体操作

    上面的一对数据,都是内核数据结构,只有OS有权限,因此我们用户在使用的时候必须提供接口.因此操作系统提供了dup2

    [ Linux ] 文件描述符和重定向_重定向_13

    dup2的作用:

    makes newfd be the copy of oldfd, closing newfd first if necessary。

    [ Linux ] 文件描述符和重定向_重定向_14

    dup2的使用:

    假设我们要实现刚刚输出重定向的操作,那么根据dup2的描述,他是copy什么呢?参数怎么传?

    dup2是copy数组下标对应的内容(文件地址的拷贝)最终的结果是newfd的内容是oldfd的一份拷贝,因此最后只剩下oldfd的内容。因此dup2是将oldfd拷到newfd内。因此如果要输出重定向,是要将fd为3的内容拷贝到fd为1内部。因此最后的内容和fd为3的内容保持一致。

    因此输出重定向 dup2(3,1); 我们使用代码来验证一下

    #include <stdio.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>int main(){ int fd = open("myfile.txt",O_WRONLY | O_CREAT | O_TRUNC,0666); if(fd < 0) { perror("open"); return 1; } dup2(fd,1); printf("文件的fd:%d\n",fd); fflush(stdout); close(fd); return 0;}

    [ Linux ] 文件描述符和重定向_文件描述符_15

    printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfifile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

    追加重定向:

    [ Linux ] 文件描述符和重定向_fd_16

    输入重定向:

    [ Linux ] 文件描述符和重定向_文件描述符_17

    (本篇完)

    上一篇:Linux 性能优化和内核观测 - CPU 篇(一)
    下一篇:没有了
    网友评论