【Linux】基础IO——文件描述符,重定向

话接上篇: 

 1.文件描述符fd

磁盘文件 VS 内存文件?

        当文件存储在磁盘当中时,我们将其称之为磁盘文件而当磁盘文件被加载到内存当中后,我们将加载到内存当中的文件称之为内存文件。磁盘文件和内存文件之间的关系就像程序和进程的关系一样,当程序运行起来后便成了进程,而当磁盘文件加载到内存后便成了内存文件。

        进程想要访问文件必须先打开文件,一个进程可以打开多个文件,而系统当中又存在大量进程,也就是说,在系统中任何时刻都可能存在大量已经打开的文件,已经打开的文件会被加载到了内存中,这些文件也叫内存文件,反之,没有打开的文件就叫做磁盘文件。那么操作系统就要管理这些打开的文件

        如何管理就是先描述,再组织操作系统为每个已经打开的文件创建各自的struct file结构体,然后将这些结构体以双链表的形式连接起来,那么操作系统对文件的管理也就变成了对这张双链表的增删改查等操作,在每个节点中不仅有链表的指针,还应该存在着文件的内容+属性,这些信息大部分在磁盘中就保留在文件内部了,加载的时候就从磁盘中把数据加载到内存。

        而为了区分已经打开的文件哪些属于特定的某一个进程,我们就还需要建立进程和文件之间的对应关系。

进程和文件之间的对应关系是如何建立的?

当进程运行的时候,操作系统会将该程序的代码和数据加载到内存,然后创建对应的task_struct, mm_struct, 页表等…

        task_struct 里面有一个指针,指向files_struct结构体,结构体里面有名为fd_array的指针数组,该数组的下标就是文件描述符fd。

使用read和write的时候要传入文件描述符,通过文件描述符找到这个数组中的指针,进而对文件访问。

        当进程打开log.txt文件时,我们需要先将该文件从磁盘当中加载到内存,形成对应的struct file,将该struct file连入文件双链表,并将该结构体的首地址填入到fd_array数组当中下标为3的位置,使得fd_array数组中下标为3的指针指向该struct file,最后返回该文件的文件描述符给调用进程即可。

因此,我们只要有某一文件的文件描述符,就可以找到与该文件相关的文件信息,进而对文件进行一系列输入输出操作。

注意: 向文件写入数据时,是先将数据写入到对应文件的缓冲区当中,然后定期将缓冲区数据刷新到磁盘当中。

1.1.文件描述符的分配规则

我们之前连续打开了6个文件,我们发现文件描述符是从3开始的,并且是连续地址的。那真的是一直从3开始吗?下面我们看一段代码:

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

 

我们发现怎么fd从0开始了,而之后的又是从3开始了。现在我们在将2也关了,我们再来看结果会是如何。

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

 我们发现0和2也被用起来了。现在我们就明白了文件描述符的分配规则是从最小的未被使用的下标开始的

事实上

Linux下进程默认会打开三个文件描述符,0:标准输入、1:标准输出、2:标准错误。

        0,1,2对应的物理设备一般是:键盘、显示器、显示器。

我们之前验证了文件描述符默认是从3开始的,也就是说0,1,2是默认被打开的。

  • 0代表的是标准输入流,对应硬件设备为键盘;
  • 1代表标准输出流,对应硬件设备是显示器;
  • 2代表标准错误流,对应硬件设备为显示器。

当一个进程被创建时,OS就会根据键盘、显示器、显示器形成各自的struct file,将这3个struct file链接到文件的双链表当中,并将这3个struct file的地址分别填入fd_array数组下标为0、1、2的位置,至此就默认打开了标准输入流、标准输出流和标准错误流。

        文件描述符的分配规则:分配最小的,没有被占用的。如果我把0号关闭,那么为新文件分配的时候就从最小的0分配。

2.重定向

2.1.输出重定向

1.输入重定项。

我们之前学习过的输出重定向就是,将我们本应该输出到显示器上的数据重定向输出到另一个文件中。那他的原理是什么了?

例如: 如果我们想让本应该输出到“显示器文件”的数据输出到log.txt文件当中,那么我们可以在打开log.txt文件之前将文件描述符为1的文件关闭,也就是将“显示器文件”关闭,这样一来,当我们后续打开log.txt文件时所分配到的文件描述符就是1。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{close(1);// 打开文件int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");exit(1);}// 打开成功printf("fd: %d\n", fd);printf("fd: %d\n", fd);printf("fd: %d\n", fd);printf("fd: %d\n", fd);fprintf(stdout, "hello fprintf\n");const char* s = "hello fwrite\n";fwrite(s, strlen(s), 1, stdout);fflush(stdout);// 关闭文件close(fd);   return 0;
}

通过上面的现象也可以看出,打印的数据没有到显示器上,而是到了磁盘的文件中,这是为什么呢?

        上面就说过,0、1、2默认是被打开的,对应的就要打开显示器,所以stdout的文件描述符就是1,所以C语言的接口fprintf认识的就是stdout或者说就是1,我们一开始就关闭了1号文件描述符,把数组下标为1的位置设置为NULL,然后打开了log.txt文件,此时1没有被占用,所以就把下标为1的位置填入log.txt的结构体的地址log.txt的文件描述符就是1了,但是上层的C语言函数认识的还是1,他们还是继续往1中写入,这样就不能打印到屏幕而是重定向到了文件中。

 

重定向的本质是在操作系统中更改fd对应的内容,上面演示的这就就叫做输出重定向。

2.2.输入重定向       

输入重定向就是,将我们本应该从一个键盘上读取数据,现在重定向为从另一个文件读取数据。

 

 比如说我们的fget函数是从标准输入读取数据,现在我们让它从log1.txt当中读取数据,我们在scanf读取数据之前close(0).这样键盘文件就被关闭,这样一样log1.txt的文件描述符就是0. 

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

关闭了0号文件描述符,所以打卡的新文件的文件描述符就变成了0,然后读取了文件中的第一行数据。 

2.3.追加重定向

还有一种就是追加重定向,更改一下选项就行了。

int main()
{close(1);// 打开文件int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT);if (fd < 0){perror("open");exit(1);}printf("%d\n", fd);fprintf(stdout, "append success\n");fflush(stdout);// 关闭文件close(fd);return 0;
}

【注意】:“>”输出重定向修改的只是1号也就是stdout标准输出,所以尽管程序中有两行代码,一行向1号文件描述符中打印,另一行向2号文件描述符中打印,那么使用输出重定向只会使1号文件描述符重定向,2号还是打印到显示器上。

 

2.4.dup2

我们发现我们上面只能通过close关闭对应的文件描述符实习对应的输出重定向和输出重定向,那我们能不能不关闭呢?

要完成重定向我们只需对fd_array数组当中元素进行拷贝即可。

例如,我们若是将fd_array[3]当中的内容拷贝到fd_array[1]当中,因为C语言当中的stdout就是向文件描述符为1文件输出数据,那么此时我们就将输出重定向到了文件log.txt。而在linux当中就给我们提供了这个系统调用:

  • 函数功能: dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中。
  • 函数返回值:调用成功返回0,失败返回-1

使用的过程中需要注意:

  1. 如果oldfd不是有效的文件描述符,则dup2调用失败,并且此时文件描述符为newfd的文件没有被关闭。
  2. 如果oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作,并返回newfd。

        只需要把想要重定向的文件在数组中拷贝过去,比如我想要输出重定向,重定向到某个文件,那么1就代表标准输出,所以就要改变1的指向,就把3的地址拷贝过去,这样1就指向了重定向的文件。 

 

输入重定向也是一样的,0是标准输入,就要从其他文件输入,就把其他文件的地址拷贝到0的位置。 

下面通过dup2演示一下前面的输出重定向:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<unistd.h>5 #include<fcntl.h>                                                                                                                               6 int main()7 {8   int fd=open("./log.txt",O_WRONLY|O_CREAT,0644);9    dup2(fd,1);10  printf("hello world\n");11  printf("hello world\n");12 13 }

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

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

相关文章

红队内网攻防渗透:内网渗透之Linux内网权限提升技术:udf提权Capability权限LD_PRELOAD环境变量

红队内网攻防渗透 1. 内网权限提升技术1.1 Linux系统提权-Web&用户-数据库udf提权1.1.1 信息收集1.1.2 Web权限获取1.1.3 MYSQL-UDF提权1.1.4 下载到目标上1.1.5 连接确认是否有条件进行导出调用1.1.6 开始进行写入导出调用1.2 Linux系统提权-Web&用户-Capability能力1…

ThinkBook 16 2024 Ubuntu 触控板问题解决

sudo insmod goodix-gt7868q.ko sudo cp local-overrides.quirks /etc/libinput/local-overrides.quirks sudo systemctl restart gdm 有偿解决&#xff0c;无效退款 联系前&#xff0c;请写明笔记本型号和ubuntu版本

生命在于学习——Python人工智能原理(3.3)

三、深度学习 4、激活函数 激活函数的主要作用是对神经元获得的输入进行非线性变换&#xff0c;以此反映神经元的非线性特性。常见的激活函数有线性激活函数、符号激活函数、Sigmod激活函数、双曲正切激活函数、高斯激活函数、ReLU激活函数。 &#xff08;1&#xff09;线性…

【elementui源码解析】如何实现自动渲染md文档-第二篇

目录 1.概要 2.引用文件 1&#xff09;components.json 2&#xff09;json-template/string 3&#xff09;os.EOL 3.变量定义 4.模版填充 5.MAIN_TEMPLATE填充 6.src下的index.js文件 1&#xff09;install 2&#xff09;export 7.总结 1.概要 今天看第二个命令no…

videoJS 视频 + 独一无二皮肤 + mp4/m3u8

推荐和参考文章&#xff1a; video.js调用-腾讯云开发者社区-腾讯云> 一、总结&#xff08;点击显示或隐藏总结内容&#xff09;一句话总结&#xff1a;网上有各种细致的现成的代码可以拿来用&#xff0c;没必要自己死专1、video.js有两种初始化方式&#xff1f;一种是在v…

C++并发之条件变量(std::condition_variable)

目录 1 概述2 使用实例3 接口使用3.1 wait3.2 wait_for3.3 wait_until3.4 notify_one3.5 notiry_all3.5 notify_all_at_thread_exit1 概述 条件变量是一个能够阻塞调用线程直到被通知恢复的对象。   当调用其中一个等待函数时,它使用unique_lock(通过互斥锁)来锁定线程。线程…

hadoop和hbase对应版本关系

https://hbase.apache.org/book.html#configuration

DuDuTalk语音工牌:如何帮助房企打造数字化的案场接待体验

房地产案场接待作为客户体验的第一站&#xff0c;其服务质量直接影响客户的购房决策。然而&#xff0c;传统的案场接待方式存在诸多挑战&#xff0c;如信息记录不准确、服务流程难以标准化、客户反馈收集困难等。语音工牌作为一种创新的智能设备&#xff0c;凭借其独特的功能和…

Office 2021 mac/win版:智慧升级,办公新风尚

Office 2021是微软公司推出的一款高效、智能且功能丰富的办公软件套件。它集成了Word、Excel、PowerPoint等多个经典应用程序&#xff0c;旨在为用户提供更出色的办公体验。 Office 2021 mac/win版获取 Office 2021在继承了前代版本优点的基础上&#xff0c;进行了大量的优化…

接口测试之用Fiddler对手机app进行抓包

Fiddler是一款非常流行并且实用的http抓包工具&#xff0c;它的原理是在本机开启了一个http的代理服务器&#xff0c;然后它会转发所有的http请求和响应&#xff0c;因此&#xff0c;它比一般的firebug或者是chrome自带的抓包工具要好用的多。不仅如此&#xff0c;它还可以支持…

JVC摄像机SD卡变成RAW的恢复方法

JVC小日本胜利公司&#xff0c;公司名字绕口且产品线极广&#xff0c;涉及汽车、影音、娱乐……&#xff0c;而JVC在摄像机产品方面也有涉及&#xff0c;不过市场上极为少见。下边我们来看下这个JVC摄像机MP4恢复案例。 故障存储: 32G存储卡 RAW文件系统 故障现象: 客户无…

万字长文讲解如何快速搭建一个Spring Cloud项目

文章目录 概念基本概念微服务七大组件 初始化Maven父工程整合注册中心组件整合远程调用与负载均衡组件组件整合网关组件整合配置中心组件以gateway模块为例 整合分布式事务组件操作数据库模拟创建订单和扣减库存整合Seata 整合熔断降级组件整合链路追踪组件源码地址参考来源 概…

springcloud gateway转发websocket请求的404问题定位

一、问题 前端小程序通过springcloud gateway接入并访问后端的诸多微服务&#xff0c;几十个微服务相关功能均正常&#xff0c;只有小程序到后端推送服务的websocket连接建立不起来&#xff0c;使用whireshark抓包&#xff0c;发现在小程序通过 GET ws://192.168.6.100:8888/w…

Robot Operating System (ROS)中,发布与订阅

在Robot Operating System (ROS)中&#xff0c;发布与订阅是一种基于主题的异步消息传递机制&#xff0c;用于节点间的通信。ROS的设计是围绕着这一概念&#xff0c;它允许不同节点之间解耦&#xff0c;每个节点专注于自己的任务&#xff0c;通过发布和订阅消息来与其他节点交互…

计算机体系结构重点学习

从外部I/O与上层应用交互的整体软硬件过程 上层应用发出I/O请求&#xff1a;上层应用程序&#xff0c;如一个文本编辑器、网络浏览器或者任何软件应用&#xff0c;需要读取或写入数据时&#xff0c;会通过调用操作系统提供的API&#xff08;如文件操作API、网络操作API等&…

SpringBoot之请求映射原理

前言 我们发出的请求&#xff0c;SpringMVC是如何精准定位到那个Controller以及具体方法&#xff1f;其实这都是 HandlerMapping 发挥的作用&#xff0c;这篇博文我们以 RequestMappingHandlerMapping 为例并结合源码一步步进行分析。 定义HandlerMapping 默认 HandlerMappi…

Docker部署常见应用之桌面版系统ubuntu-desktop

文章目录 ubuntu-desktop 简介ubuntu-desktop 部署参考文章 ubuntu-desktop 简介 colinchang/ubuntu-desktop 是一个Docker镜像&#xff0c;基于KasmWeb⁠的 Ubuntu 22.04 桌面版&#xff08;Web&#xff09; Docker Image。镜像替换了阿里云Ubuntu Jammy镜像源&#xff0c;安…

重生之 SpringBoot3 入门保姆级学习(21、场景整合 Redis 定制对象序列化存储)

重生之 SpringBoot3 入门保姆级学习&#xff08;21、场景整合 Redis 定制对象序列化存储&#xff09; 6.4 定制化 6.4 定制化 需求&#xff1a;保存一个 Person 对象到 redis 创建 Person 类 package com.zhong.redis.entity;import lombok.AllArgsConstructor; import lombok…

浅谈C++基本框架内涵及其学习路线

目录 一.C的内涵本质 1. 面向对象编程&#xff08;OOP&#xff09; 2. 低级控制 3. 模板编程 4. 标准库&#xff08;STL&#xff09; 5. 多范式支持 二.学习路线 1. 基础阶段 C基础语法 函数 数组和指针 2. 面向对象编程 类和对象 继承和多态 运算符重载 3. 高级…

【服务的主从切换实现原理】

文章目录 主从架构介绍zookeeper利用ZK实现主从架构 主从架构介绍 主从服务架构是一种常见的分布式系统设计模式&#xff0c;常用于提高系统的性能、可用性和扩展性。在这种架构中&#xff0c;系统中的节点被分为两类&#xff1a;主节点&#xff08;Master&#xff09;和从节点…