【Linux】文件描述符及重定向

news2024/12/23 7:04:41

目录

文件描述符的引入

什么是文件描述符

文件描述符的分配规则

重定向

输出重定向

输入重定向

追加重定向

dup2()


文件描述符的引入

上一章,我们讲解了系统接口,了解了open()函数的返回值是一个整数.那么这个整数究竟是什么呢?我们可以用以下代码来看一下这种现象:

  int main()    
  {    
    umask(0);    
    int fd1 = open("log1.txt",O_WRONLY | O_CREAT | O_APPEND,0666);    
    printf("open success, fd: %d\n",fd1);                                                                                               
    int fd2 = open("log2.txt",O_WRONLY | O_CREAT | O_APPEND,0666);    
    printf("open success, fd: %d\n",fd2);    
    int fd3 = open("log3.txt",O_WRONLY | O_CREAT | O_APPEND,0666);    
    printf("open success, fd: %d\n",fd3);    
    int fd4 = open("log4.txt",O_WRONLY | O_CREAT | O_APPEND,0666);    
    printf("open success, fd: %d\n",fd4);  

    close(fd1);    
    close(fd2);    
    close(fd3);    
    close(fd4);    
  }  

我们分别打开了4个文件.然后退出make编译然后运行.

 我们发现:这些的fd是连续,并且fd是从3开始的,那么0,1,2去哪里了呢?

我们上一章提到了,执行程序时,会默认打开3个输入输出流,分别为:stdin,stdout,stderr.

这三个分别占据了0,1,2的位置.


我们可以使用一个例子来验证它们之间的关联.

  int main()    
  {    
    umask(0);    
    //向stdout(显示器)中输出
    fprintf(stdout,"hello,stdout\n");    
     
    //向fd为1的文件写入                                                                                                                                   
    const char* s = "hello,1\n";    
    write(1,s,strlen(s)); 
  }

我们可以发现,这两条语句都成功的在显示屏中输出了.

这就说明fd为1的文件 确实是对应着显示器.


我们在前一章讲了fopen()这个接口,我们再来看一下

 我们别的不再说,我们看它的返回值是FILE*.我们知道这是个指针.

但是这个FILE是什么呢?FILE是一个struct结构体,它的内部有很多和文件相关的成员,它是由C标准库提供的.

C语言 库函数内部一定要调用 系统接口的!在系统角度,系统只认fd,像FILE这些其它变量,系统是不会识别的.所以这个FILE结构体里面一定封装了fd.

话都说到这了,那么stdin,stdout,stderr的类型也是FILE*,它的内部也一定封装了fd.我们可以打印输出来证明一下.

int main()      
{    
  printf("stdin: %d\n",stdin->_fileno);    
  printf("stdout: %d\n",stdout->_fileno);    
  printf("stderr: %d\n",stderr->_fileno);

  return 0; 
}

运行结果图:

结果确实如我们所想的那样.便成功验证了我们刚才所说的.


所以我们最终的结论是:

1.C语言的文件调用接口 和 系统接口是有一定关系的.

2.在用户层面上是stdin,stdout,stderr,在系统层面上只能用数字0,1,2,来表示。这些数字便是文件描述符。

什么是文件描述符

上面一直在说fd,知道它是一个整数,那么它到底是什么呢?

进程要访问文件,首先必须打开文件。

一个进程可以打开多个文件,所以一般而言,进程:文件 = 1:n

文件要被访问,前提是加载到内存中,才能被直接访问.

既然一个进程可以打开多个文件,那么如果多个进程都打开自己的文件呢?

这个时候系统中便会存在大量被打开的文件,所以OS需要把这么多的文件管理起来.

管理方式:先描述 + 再组织。

在内核中,如何看待打开的文件? OS为了管理每一个被打开的文件,需要先将其抽象成struct结构体,然后再描述,这个结构体叫做struct file,大致内容如下:

 有很多这样的文件需要组织管理起来,那么采用了双链表进行管理.

但是进程是如何知道哪个是我打开的文件呢,所以现在唯一需要处理的就是进程和文件之间的对应关系.

这里先提前说一句:

操作系统负责处理这些进程文件操作请求,管理文件描述符和维护文件状态,但文件的具体内容和处理是由进程来控制和管理的。

所以不要因为一会操作系统管理,一会进程管理而混淆了.

我们知道文件描述符fd是从0开始递增的,在我们所学到的容器里面,vector容器的下标也是从0开始向后递增的。

所以这个对应关系其实本质上是一个数组,然后这个数组的每个下标指向相应的file_struct,这个数组里的每个下标便叫做文件描述符(fd).这个数组叫做文件描述符表.

它作为一个成员在进程task_struct结构体中.

 在task_struct结构体中,我们通过内核源代码可以看到:

 内部有一个结构体指针,这个指针指向files_struct这个结构体,然后我们来看一下这个结构体里面有什么内容.

 重点看最后一个结构体fd_array,它的类型是一个指针数组类型,它就是文件描述符表,每个元素都是一个指针,指向file结构体,那我们接下来看看这个file里面有什么内容.

这个里面就包含了一个文件所有内容,包括属性和内容相关的,我们现在知道通过fd_array便可以找到所有的文件即可.


总结一下以上的关系:

进程task_struct结构体中,有一个结构体指针指向了files_struct,file_struct里又有一个结构体数组fd_array,fd_array数组里每个下标便是文件描述符,下标对应的内容便是fd为该下标文件的内容.

然后我们再整体理顺一下之间的关系。以及是如何一步步推导过来的.

文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。

而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

文件描述符的分配规则

我们就先创建一个新的文件,然后看看它的文件描述符是多少,代码如下:

int main()    
{    
    int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC);    
    if(fd < 0)    
    {    
      perror("open");    
      return 1;    
    }    
    printf("fd: %d\n",fd);                                                                                                              
    close(fd);    
    
    return 0;    
}   

 然后我们退出vim,然后make编译并运行.

可以看到,fd现在是3,这也没有问题,因为0,1,2已经默认被stdin,stdout,stderr占用了.


此时如果我们把fd为0的文件关掉,此时再看一下文件的fd是多少.

close(0);

 然后此时运行结果显示,fd为0.

同样地,如果我们把fd为2的文件关掉.

close(2);

 

此时的文件fd又成了2.

我们大概知道了,新打开文件的fd默认为第一个没有被占用的fd.

所以这里fd的分配规则是:给新文件分配最小的,且没有被占用的文件描述符.

重定向

输出重定向

我们前面只说了关闭fd为0和2的,但是如果关闭fd为1的文件呢?

我们输入以下代码

int main()
{
    close(1);
    int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    if(fd < 0)
    {
      perror("open");
      return 1;
    }
    printf("fd: %d\n",fd); //stdout->FILE[fileno=1]->log.txt                                                                          
    printf("fd: %d\n",fd);
    printf("fd: %d\n",fd);

    //close(fd); //后面解释

    return 0;
}

然后我们再运行这段代码.

我们发现什么都没有输出,这不应该啊,按理来说,应该输出三个1才对.

但是我们如果输出一下log.txt里面的内容:

我们发现,本应该输出在显示器上的内容输出到了log.txt这个文件中.

当我们不关闭fd=1的文件时,我们再次编译并运行.

 此时我们发现正常输出到显示器上了.

以上是因为:printf默认是向stdout(标准输出)里面打印的,即fd为1的文件。但是此时fd=1的文件被关掉,然后被新建立的文件占用了,所以此时printf再输出,便会直接输出到这个文件当中了.

这种原本向显示器中打印的,但是最后却写入(显示)到了别的文件中,这个行为便叫做输出重定向.

下面是一张重定向原理图:

原来fd=1指向的文件是显示器,然后被更改指向myfile,所以之前写的向显示器上输出的内容全部输出到了myfile这个文件中.

所以重定向的本质是:在OS内部,更改fd对应内容的指向. 

输入重定向

我们知道了重定向的原理,输入重定向也很好理解.无非是从键读取的内容变成了从别的文件中读取.

先来看这代码:

int main()    
{    
    int fd = open("log.txt",O_RDONLY,0666);    
    if(fd < 0)    
    {    
      perror("open");    
      return 0;    
    }    
    printf("打开的文件fd为:%d\n",fd);    
    
    char buffer[64];    
    fgets(buffer,sizeof(buffer),stdin);    
    
    printf("%s\n",buffer);  
}

我们先打开了一个文件,然后输出它的fd,接着,我们写了一个fgets,第三个参数为stdin,表示从键盘中读取数据,并将读取的数据输出到buffer中,并输出buffer.

 我们可以看到它完整地将我的输入输出了出来.

但是此时如果我们在前面加上close(0),同时为了效果明显,我们在log.txt文件中写入以下内容:

然后我们再运行 代码

 我们发现它此时没有卡着等待我的键盘输入,而是直接从文件中读取了。

相当于是把fd=0所指向的键盘文件 变成了log.txt文件.

这个过程便叫做输入重定向.

追加重定向

这个和输出重定向类似,只不过是把open函数中O_TRUNC换成了O_APPEND.

代码如下:

int main()    
{    
    close(1);    
    int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);    
    if(fd < 0)    
    {    
      perror("open");    
      return 0;    
    }    
    fprintf(stdout,"you can see me\n"); 
}

这段程序本来每次是向显示器中输出“you can see me”,然后经过重定向,最后追加到了log.txt中.

 这便是追加重定向.

dup2()

以上的代码只是为了演示重定向的原理,真正使用重定向时,我们并不会那样使用,而且每次使用时都得先关闭文件再打开文件,这样写出来的代码可读性并不高.

所以为了解决这个问题,我们可以使用dup2一个系统调用来解决.

我们先看一下dup2的用法:

 然后再看一下解释:

 它的意思是newfd是oldfd的一份拷贝,即将oldfd拷贝给newfd. 最终要和oldfd一致.

如果oldfd不是有效的文件描述符,那么调用失败,newfd不会被关闭.

如果oldfd是有效的文件描述符,那么将会把oldfd的值拷贝给newfd。


回到刚开始说的输出重定向.

我们最终改变了fd=1的所指向的内容,即将原来的fd=3的文件指针 拷贝给了 fd=1的文件指针.

最终结果和fd=3一致,正如上面这句话所说“ 最终要和oldfd一致” ,所以fd=3是oldfd,fd=1则是newfd.这样便理清了关系.


知道了这些,我们赶紧来用用dup2.

int main(int argc, char* argv[])  
{  
    if(argc != 2)  
    {  
      return 2;  
    }  
    int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);  
    if(fd < 0)  
    {  
      perror("open");  
      return 1;  
    }  
    dup2(fd,1);//将fd(3)的内容拷贝到fd=1当中  
    fprintf(stdout,"%s\n",argv[1]); 
}

 argc和argv是命令行参数,具体可以参照我之前的文章:传送门,里面有详细的解释.

然后此时我们运行程序:

这样便把我们的本该输出到显示器上的内容输出到文件中了.

 这样dup2的大致用法也就说完了.

本文章到这里就结束了,如有疑问或者错误的地方,欢迎评论区指正或私信哦.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/787048.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

非Spring环境 | Mybatis-Plus插入数据返回主键两种方式(注解或XML)

废话不多说&#xff0c;直接撸代码: <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace&qu…

vr海上安全作业模拟培训软件降低风险隐患

VR虚拟现实技术在军事训练中的应用越来越广泛。其中&#xff0c;将VR技术应用到出海安全知识教育培训中&#xff0c;可以有效地提高学员的安全知识和技能水平&#xff0c;降低实际操作中的风险。以下是VR出海安全知识教育培训的价值。 VR技术是一种利用计算机生成的虚拟环境来模…

【SpirngCloud】分布式事务解决方案

【SpirngCloud】分布式事务解决方案 文章目录 【SpirngCloud】分布式事务解决方案1. 理论基础1.1 CAP 理论1.2 BASE 理论1.3 分布式事务模型 2. Seata 架构2.1 项目引入 Seata 3. 强一致性分布式事务解决方案3.1 XA 模式3.1.1 seata的XA模式3.1.2 XA 模式实践3.1.3 总结 4. 最终…

ARP协议(地址解析协议)

文章目录 ARP协议&#xff08;地址解析协议&#xff09;MAC地址ARP协议ARP具体实现同一链路不同链路 ARP 缓存缓存查询 APR请求/响应报文 ARP协议&#xff08;地址解析协议&#xff09; MAC地址 MAC 地址的全称是 Media Access Control Address&#xff0c;即媒体访问控制地址…

全网最细,时序数据库InfluxDB详解,你不知道的都在这...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 直接上图片&#…

新一代信息技术浪潮下的DPU力量 中科驭数亮相2023中国互联网大会

7月18日&#xff0c;由中国互联网协会主办&#xff0c;北京经济技术开发区管委会、北京市通信管理局、工业和信息化部新闻宣传中心联合主办的2023&#xff08;第二十二届&#xff09;中国互联网大会如约而至&#xff01;中科驭数出席本次大会&#xff0c;高级副总裁张宇应邀在新…

Python内置函数系统学习(3)——数据转换与计算(详细语法参考 + 参数说明 + 具体示例)详解 min()函数 | lambda 真的牛啊

两岸猿声啼不住&#xff0c;轻舟已过万重山&#xff01; &#x1f3af;作者主页&#xff1a; 追光者♂&#x1f525; &#x1f338;个人简介&#xff1a; &#x1f496;[1] 计算机专业硕士研究生&#x1f496; &#x1f31f;[2] 2022年度博客之星人工智能领域TOP4&#x1f…

java项目之社区生活超市管理系统(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的社区生活超市管理系统。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框…

【计算机视觉 | 目标检测】arxiv 计算机视觉关于目标检测的学术速递(7 月 25 日论文合集)

文章目录 一、Automotive Object Detection via Learning Sparse Events by Temporal Dynamics of Spiking Neurons1.2 Exposing the Troublemakers in Described Object Detection1.3 AMAE: Adaptation of Pre-Trained Masked Autoencoder for Dual-Distribution Anomaly Dete…

【档案专题】四、电子档案形成与收集

导读&#xff1a;主要针对电子档案形成与收集相关内容介绍。对从事电子档案管理信息化的职业而言&#xff0c;不断夯实电子档案管理相关理论基础是十分重要。只有通过不断梳理相关知识体系和在实际工作当中应用实践&#xff0c;才能走出一条专业化加职业化的道路&#xff0c;从…

25.4 matlab里面的10中优化方法介绍——最速下降法(matlab程序)

1.简述 matlab实现最速下降法 定义&#xff1a;沿负梯度方向进行搜索的算法&#xff08;负梯度方向为最速下降方向&#xff09; 算法步骤&#xff1a; 步0&#xff1a;选取初始点x0&#xff0c;容许误差是e[0~1],令k1 步1&#xff1a;计算目标函数的梯度gk▽f(xk)) 若 ||g…

网工内推 | 初级网工、Linux运维,IP证书优先

01 光联世纪 招聘岗位&#xff1a;初级网络工程师 职责描述&#xff1a; 1.负责日常基础网络运维工作&#xff1b; 2.负责日常网络监控工作&#xff1b; 3.负责现场基础技术支持工作&#xff1b; 4.主动进行自我迭代&#xff0c;不断提升技术能力和个人素质。 任职要求&#x…

行为型模式 - 观察者模式

概述 定义&#xff1a; 又被称为发布-订阅&#xff08;Publish/Subscribe&#xff09;模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时&#xff0c;会通知所有的观察者对象&#xff0c;使他们能够…

jQuery的DOM操作之笔记总结

jQuery的DOM操作之笔记总结 首先我们来介绍一下什么是DOM 简述&#xff1a; 1.DOM全称Document Object Model&#xff08;文档对象模型&#xff09;。 2.每个文档都是一棵DOM结构的树&#xff0c;文档里的很多元素&#xff0c;就像树上的很多节点&#xff0c;或是分叉的树枝…

【Git】git仓库完整迁移

代码仓库&#xff0c;在公司有两个团队在做&#xff0c;并且gitlab所在环境不互通。有一个团队做的时间久一点&#xff0c;另一个团队想要用并做一些定制。就需要将代码转移到另一个gitlab管理。 参考&#xff1a;【Git】git仓库完整迁移&#xff08;代码&#xff0c;分支&…

ubuntu挂载ext4文件系统

文章目录 1.虚拟机分配10G磁盘用来挂载ext4文件系统2.磁盘分区3.创建文件系统4.挂载文件系统5.卸载文件系统6.使用ior测试ext4三种日志模式&#xff08;1&#xff09;ordered&#xff08;2&#xff09;journal&#xff08;3&#xff09;writeback 1.虚拟机分配10G磁盘用来挂载e…

编码类型 ASCII URLcode编码 Unicode编码 utf编码理解

编码类型 ASCII URLcode编码 Unicode编码 utf编码理解 bin是二进制 oct是八进制 hex是16进制 Ord()检测ASCII码&#xff0c;python3也可查中文 HTML实体编码能被html页面解析&#xff0c;使用ord&#xff08;&#xff09;对单个字符查看转换后结果&#xff0c;字母就是ASCII…

B2B2C开源多语言多商户跨境外贸网站部署开发

随着全球化的发展&#xff0c;跨境外贸成为了许多企业拓展业务的重要方向。搭建一个B2B2C开源多语言多商户跨境外贸网站&#xff0c;将有助于实现企业的全球化经营。那么&#xff0c;如何搭建一个B2B2C跨境外贸网站呢&#xff1f; 一、选择合适的开源平台 在搭建一个B2B2C跨境…

【设计模式】单例设计模式详解(包含并发、JVM)

文章目录 1、背景2、单例模式3、代码实现1、第一种实现&#xff08;饿汉式&#xff09;为什么属性都是static的&#xff1f;2、第二种实现&#xff08;懒汉式&#xff0c;线程不安全&#xff09;3、第三种实现&#xff08;懒汉式&#xff0c;线程安全&#xff09;4、第四种实现…

用i18next使你的应用国际化-Vue

ref: https://www.i18next.com/ 在vue项目中安装相关依赖&#xff1a; i18nexti18next-vuei18next-browser-languagedetector&#xff0c;用于检测用户语言 npm install i18next i18next-vue i18next-browser-languagedetector创建i18n.js文件&#xff1a; import i18next f…