从0开始linux(21)——文件(2)文件重定向

欢迎来到博主的专栏:从0开始linux
博主ID:代码小豪

文章目录

    • 设备文件
    • 文件缓冲区
    • 重新认识文件描述符
    • 重定向

设备文件

在前一篇文章博主提到,当一个c/c++进程运行时,会默认打开三个文件流,分别是stdin,stdout,stderr。分别对应键盘,显示器,显示器,我们将这三个文件流称为标准文件流。但是大家有没有思考过这么一个问题?那就是为什么外部输入设备,竟然可以用文件打开?文件不是一个数据吗?

不知道大家有没有听过这么一句话,叫做linux下,一切皆文件。比如我们的指令,它们是文件,保存在/usr/bin目录下,而设备也是文件,保存在/dev目录下。

如果我们仅拿这个论证,证明设备是文件,其实是倒果为因了,因为/dev中存在设备文件,是linux将设备设计成文件的结果之一。因此我们想搞清楚linux是如何将设备描述成文件?又是为什么要将其设计成文件?这才是真正重要的东西。

为了搞清楚这点,我们需要回到系统内核本身来看,在前面的章节中,我们了解到linux内核的主要功能有:管理进程,管理文件,外设交互等等的功能。这里博主就要抛出一个概念了,叫做linux对于管理对象,总是先描述在组织

如何理解先描述在组织呢?简单来说,就是linux会先设计出管理的对象的数据结构,接着通过这些数据结构,再去进行对象的行为操作。比如linux为了管理进程,会创建出task_struct来描述进程,接着在通过task_struct来管理进程,为了管理文件,就创建了struct file来描述文件,接着在设计出针对这些对象的方法,比如关闭进程,打开文件,关闭文件等。

linux则是将设备描述成struct device结构体,以我们常见的pc为例,外部输入设备有键盘,显示器,磁盘,网卡等。而每个设备,其对应的功能又不同,(从系统的角度上看)比如显示器可写不可读,键盘可读不可写。因此不同的设备,其对应的struct device的具体属性是不同的。
在这里插入图片描述
但是由于不同的设备,存在差异,对于系统来说,与外设进行交互实在是太频繁了,而且设备的数量随着时代的发展,也会越来越多,如果系统与外设交互时,都需要创建出特殊的对象,那实在是太麻烦了。

那么linux的设计者想了想,发现了这么一个特性:对于设备来说,种类很多,但是系统的交互无外乎就是读和写两件事,而文件呢?每个文件在结构上基本都是相同的,而且也能进行读和写的操作。那么如果我们借助文件的结构,来描述设备,是不是就能完成统一了?

于是有趣的地方来了,我们的进程,需要与外设进行交互(比如printf),为了让用户的使用方便,在struct device和task_struct之间,建立了一个struct file,作为两者沟通桥梁。于是乎进程对于外设的写入,就变成对文件的写入,而对外设的读取,就变成了文件的读取,方便性得到了大大的提升。
在这里插入图片描述

在上图中,博主将表现这种关系的代码写成了c++风格的代码,但是实际上linux是由C语言写成的,实际上struct
file当中read和write在源码当中其实是函数指针,指向对应设备的write的read函数,这么写只是方便理解罢了。

这里博主为了验证这个观点,放出linux当中的源代码:
在这里插入图片描述
可以看到,在struct file当中,存在一个名叫file_operations的成员,该成员当中保存着各种各样的与文件操作相关的函数指针,因此,如果我们的进程想要与外设进行交互,系统就会将该外设对应的文件打开,然后对文件执行,对应的读写操作。由于存在函数指针这一层的封装,对文件的读写操作,就会转变成,对于外设的读写操作。

文件缓冲区

当我们读写普通文件或者设备文件时,并非是直接向对应的文件直接进行读写的,而是在文件与cpu之间,创建一个文件缓冲区,该缓冲区存在在内存当中,当我们写入文件时,实际上是向文件缓冲区进行写入,读取文件时,首先将文件当中的数据写入到文件缓冲区当中,接着再向文件缓冲区当中读取我们想要的数据。

首先我们要搞清楚一点,就是打开文件的是进程,读写文件的也是进程,操作文件的行为,都是进程执行的。比如我们想要像显示器写入数据,是不是要在进程当中调用printf()?我们想要从键盘当中读取数据,是不是要写scanf?因此操作文件的并非用户,而是进程,或者说是用户在进程当中对文件进行操作。

以普通文件log.txt为例,当我们打开文件时,首先操作系统会在内存当中生成对应的struct file结构。在struct file当中会存在一个指针,该指针指向文件缓冲区(buffer)。
在这里插入图片描述
此时进程1调用write()函数,向文件写入"hello world",实际上并不是向磁盘当中的log.txt文件写入“hello world”,而是向buffer当中写入"hello world".在这里插入图片描述
此时,磁盘当中的log.txt其实并没有被写入数据,只有当操作系统刷新缓冲区时,这个数据才会被写入到磁盘当中的log.txt里面,比如我们在写word文档时,实际上并不是将文本写入到磁盘当中的word文档,而是写在其对应的文件缓冲区当中。只有我们点击了保存以后,才算是真正将数据写入到磁盘当中的word文档,否则写的文本在下次打开word文档时就会消失(不过现在有自动保存,这个现象很少能见到了)。

那么当我们读取文件的数据时,也并不是直接读取磁盘当中的内容,而是操作系统会先将文件的数据加载到文件缓冲区当中,然后进程在文件缓冲区当中读取,比如我们现在运行进程2,让进程2读取log.txt文件的数据(“hello world”)。
在这里插入图片描述
为什么要创建一个文件缓冲区而不是直接读取磁盘当中的数据呢?其实还是效率问题,首先我们要知道,cpu的计算速度是很快的,而外设与内存之间的交互是很慢的,cpu与内存的交互速度远快于内存与外设的交互速度。因此如果频繁的让内存与磁盘进行交互,运行速度会非常慢,因此有了文件缓冲区,让内存一次性获取磁盘文件的大量数据,这样就能减少内存与磁盘交互的次数。运行效率会快上不少。(我们的读写操作,从cpu->内存->磁盘,变成cpu->内存,当必要时才让内存与磁盘交互,这样就减少了内存与磁盘的交互次数。)

重新认识文件描述符

我们在前一篇章节当中重点讲解了文件描述符,但是有一个细节博主只是简单带过了,因为这个细节与文件的重定向操作有关,因此博主将其放在这篇文章当中讲述,因为后面就要讲解与重定向相关的内容了。

首先,文件是由进程打开的,因此在进程的pcb(task_struct)当中,存在一个指针,指向当前已被打开的文件。由于一个进程可以打开多个文件,因此只有一个指针是不够的(要指向多个文件),因此pcb当中管理被打开文件是一个文件指针数组fd_array[N]。
在这里插入图片描述

一个进程被运行,会创建一个对应的task_struct结构,而每一个打开的文件,都有一个对应struct file结构,而一个进程可以打开多个文件,因此就有多个struct file结构被创建。这个fd_array,保存的是指向struct file的指针。

在上一篇博客中。博主简单了的提了一下,open函数会返回一个文件的文件描述符fd,如果我们要在进程中使用write,read函数对文件进行读写操作,首先是要告诉write,read函数,我们要操作的文件的fd是多少,因此fd对于文件来说,是起一个指向作用的,就好比进程的pid一样。
在这里插入图片描述

在这里插入图片描述

当我们使用open函数时,会打开一个在磁盘当中的文件,这个打开文件的操作,实际上是分成一下几步的:
(1)创建描述该文件的struct file
(2)生成该文件的对应的文件缓冲区
(3)将该文件对应的struct file,记录在task_struct的fd_array当中
(4)该文件的fd,实际上是在fd_array数组的下标。

如何验证这一点呢?还记得博主在上一篇文章提到的吗?每一个c/c++程序运行,都会默认打开三个文件流,分别是stdin,stdout,stderr。因此最先加载到fd_array的文件就是这三个。因此我们每一个进程,最初的fd_array都是这样的:
在这里插入图片描述
而被打开的文件对应的fd,实际上是它们在fd_array[N]的数组下标,因此stdin的fd为0,stdout的fd为1,stderr的fd为2,如果此时我们再让进程,打开log.txt文件,此时它就会被放在fd_array数组的3号下标处。因此其fd就是3.
在这里插入图片描述
不信?不信我们就来写一份代码验证一下。

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{int fd1=open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);//open的返回值,是被打开的文件的fdprintf("stdin fd:%d\n",stdin->_fileno);//stdin->_fileno是stdin的fd,后者同理printf("stdout fd:%d\n",stdout->_fileno);printf("stderr fd:%d\n",stderr->_fileno);printf("log.txt fd:%d\n",fd1);
}

接着我们运行该程序,并且查看结果。
在这里插入图片描述
从打印的结果来看,证明了我们上面所言非虚,fd其实是被打开的文件在fd_array当中的数组下标。

重定向

这里给大家补充一个知识点,是关于printf()函数的,这printf谁不会啊?从刚开始学C语言的时候就用过了。不就是向显示器打印字符串嘛。

这里大家有没有想过,stdout是显示器的文件流,而stdout的fd是1,而printf函数是向显示器输出数据,也就是向stdout输出数据,实际上也就是向fd等于1的文件输出数据,如果我们先用close函数关闭fd为1的文件(stdout),接着打开log.txt,此时log.txt就会顺位变成fd为1的文件。那么此时printf会向什么文件进行写入呢?

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>int main()
{close(1);//关闭stdoutopen("log.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);//log.txt会顺位变成fd为1printf("hello world\n");printf("hello world\n");printf("hello world\n");printf("hello world\n");printf("hello world\n");fflush(stdout);//将stdout的缓冲区刷新return 0;}

接着我们运行该程序,此时我们惊讶的发现,printf竟然没有向屏幕打印"hello world",接着我们查看一下log.txt文件。可以发现printf将数据都输出到了log.txt当中。
在这里插入图片描述
这是因为,stdout其实就是fd为1的文件,如果我们先将fd为1的文件关闭,也就是将进程与显示器之间的输出流关闭,接着我们打开log.txt,而此时log.txt会顺位进入fd_array的1数组下标的位置,即fd为1的文件变成了log.txt。此时,stdout就从显示器,变成了log.txt(stdout并非显示器,而是fd为1的文件!!!只是默认是显示器)。而printf是向stdout写入数据,由于stdout流向了log.txt,于是printf就变成向log.txt写入数据了。

我们将这种文件流进行修改的操作,叫做文件重定向。而文件重定向当中又分为输出重定向,输入重定向,追加重定向,但是它们的原理都是差不多的,就是将文件的fd进行替换。详细的内容我们后面再讲。、

文件重定向的系统叫做dup系列,分别为duo,dup2,dup3。

#include <unistd.h>int dup(int oldfd);
int dup2(int oldfd, int newfd);#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>int dup3(int oldfd, int newfd, int flags);

这里博主重点介绍dup2。dup2又两个参数,叫做newfd和oldfd,其中,newfd是被替换的文件的fd,oldfd是替换的文件的fd。比如我们想让log.txt替换掉fd为1的文件,我们就应该这么写:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>int main()
{int fd= open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);dup2(fd,1);//fd替换掉1的文件printf("hello world\n");//向stdout写入fprintf(stdout,"hello byte\n");//向stdout写入const char str[]="hello code\n";fwrite(str,sizeof(str),1,stdout);//向stdout写入return 0;
}

接着我们运行该程序,并且查看log.txt文件的内容。
在这里插入图片描述
这里我们谈谈dup2函数的原理是什么,首先我们都知道,文件的fd其实就是被打开的struct file在fd_array的数组下标。而dup2函数则是将oldfd中的struct file,拷贝到newfd当中,其中newfd的原结构可能会被删除,而oldfd的原结构则会被保留。

在这里插入图片描述
从上图可以看到,其实替换的实质,是一个赋值操作,即将fd(3)号下标的struct file对象,赋值给fd_array的1号下标。原来的三号下标的指向的文件依然不变。但是如果某个文件在fd_array当中的指针个数变为了0,那么这个文件流就会被关闭。

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

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

相关文章

Claude 3.5 新功能 支持对 100 页的PDF 图像、图表和图形进行可视化分析

Claude 3.5 Sonnet发布PDF图像预览新功能&#xff0c;允许用户分析长度不超过100页的PDF中的视觉内容。 此功能使用户能够轻松上传文档并提取信息&#xff0c;特别适用于包含图表、图形和其他视觉元素的研究论文和技术文档。 视觉PDF分析&#xff1a;用户现在可以从包含各种视觉…

【SQL server】数据库远程连接配置

SQL server远程连接配置 1、数据库远程配置1.身份验证2. 建立入站规则3. SQLServer服务的启动 1、数据库远程配置 1.身份验证 所以在安装过程中需要注意涉及到的的身份验证中&#xff0c;要使用混合模式&#xff0c;并设置密码。2. 建立入站规则 在控制面板中的防火墙管理中…

【Vue 全家桶】5、Vuex(更新中)

目录 概念何时使用搭建vuex环境基本使用getter的使用四个map方法的使用vuex模块化命名空间 概念 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。使用 Vuex 可…

首推!AI大模型课程2024年最新版!从零基础到入行大模型算法工程师,看完这一篇就够了,学完来找我内推!

“ 技术学习无非涵盖三个方面&#xff0c;理论&#xff0c;实践和应用**”** 大模型技术爆火至今已经有两年的时间了&#xff0c;而且大模型技术的发展潜力也不言而喻。因此&#xff0c;很多人打算学习大模型&#xff0c;但又不知道该怎么入手&#xff0c;因此今天就来了解一下…

一站式搭建线上线下交友平台/全开源码交付前后端安装说明

功能亮点 灵魂匹配&#xff1a;基于个人喜好和兴趣&#xff0c;为你推荐最合适的交友对象。 真实认证&#xff1a;所有用户都经过严格认证&#xff0c;确保交友环境真实可靠。 隐私保护&#xff1a;强大的隐私设置&#xff0c;让你轻松掌控个人信息和交友动态。 互动便捷&…

基于数组实现的Huffman树和Huffman编码

一、Huffman树简介 1、定义 树的带权路径长度&#xff0c;就是树中所有的叶节点的权值乘上其到根节点的路径长度。 在含有n 个带权叶子结点的二叉树中&#xff0c;其中带权路径长度&#xff08;Weighted Path Length, WPL&#xff09;最小的二叉树称为哈夫曼树&#xff0c; 也…

图说复变函数论重大错误:将无穷多各异平面误为同一面

黄小宁 医学若将前所未知的“新冠”病毒误为已熟知的流感病毒&#xff0c;后果...&#xff1b;数学将前所未知的点集误为已熟知的集就会引出一连串的重大错误。 h定理&#xff1a;点集AB的必要条件是A≌B。 证&#xff1a;&#xff08;1&#xff09;任何图≌自己是几何学最起码…

SDL简介和初次尝试

文章目录 SDL的用途和概念SDL下载 SDL的用途和概念 SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台开发库 &#xff0c;使用C语言写成&#xff0c;SDL提供了数种 操作 图像 &#xff0c;声音输入输出的函数&#xff0c;让开发者使用 相识的代码 就能够开发出跨平台的…

WiFi一直获取不到IP地址是怎么回事?

在当今这个信息化时代&#xff0c;WiFi已成为我们日常生活中不可或缺的一部分。无论是家庭、办公室还是公共场所&#xff0c;WiFi都为我们提供了便捷的无线互联网接入。然而&#xff0c;有时我们可能会遇到WiFi连接后无法获取IP地址的问题&#xff0c;这不仅影响了我们的网络使…

【车道线检测】一、传统车道线检测:基于霍夫变换的车道线检测史诗级详细教程

1、定义图像显示函数 首先定义一个函数&#xff0c;函数的作用是通过plt库显示两幅图&#xff0c;为后续实验做准备。该函数的主要功能是&#xff1a; 从指定路径加载图像显示图像的基本信息将图像从BGR格式转换为RGB格式并在一个图形窗口中显示两幅图像进行对比 import nump…

Ftrans数据跨境传输方案:保护隐私与促进合作

数据跨境传输是指在不同国家、地区和法律框架下进行的数据交换和传输&#xff0c;数据跨境传输流程周期是数据产生--数据传输--数据接收&#xff0c;而困境来源也来自这3个环节&#xff1a; 1.本地合规限制 数据出口国&#xff08;数据输出国&#xff09;的法律对于数据收集的…

Mybatis学习笔记(三)

十、MyBatis的逆向工程 (一)逆向工程介绍 MyBatis的一个主要的特点就是需要程序员自己编写sql&#xff0c;那么如果表太多的话&#xff0c;难免会很麻烦&#xff0c;所以mybatis官方提供了一个逆向工程&#xff0c;可以针对单表自动生成mybatis执行所需要的代码&#xff08;包…

Github 2024-11-08Java开源项目日报 Top9

根据Github Trendings的统计,今日(2024-11-08统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目9Vue项目1经验丰富的Java(后端)开发人员核心面试问题和答案 | 互联网Java工程师进阶知识完全扫盲 创建周期:2085 天开发语言:Java协议…

【新闻文本分类识别】Python+CNN卷积神经网络算法+深度学习+人工智能+机器学习+文本处理

一、介绍 文本分类识别系统。本系统使用Python作为主要开发语言&#xff0c;首先收集了10种中文文本数据集&#xff08;“体育类”, “财经类”, “房产类”, “家居类”, “教育类”, “科技类”, “时尚类”, “时政类”, “游戏类”, “娱乐类”&#xff09;&#xff0c;然…

数据结构 ——— 链式二叉树的前中后序遍历递归实现

目录 前言 链式二叉树示意图​编辑 手搓一个链式二叉树 链式二叉树的前序遍历 链式二叉树的中序遍历 链式二叉树的后序遍历 前言 在上一章学习了链式二叉树的前中后序遍历的解析 数据结构 ——— 链式二叉树的前中后序遍历解析-CSDN博客 接下来要学习的是代码实现链式…

<项目代码>YOLOv8 pcb板缺陷检测<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

yarn报错`warning ..\..\package.json: No license field`:已解决

出现这个报错有两个原因 1、项目中没有配置许可证 在项目根目录package.json添加 {"name": "next-starter","version": "1.0.0",# 添加这一行"license": "MIT", }或者配置私有防止发布到外部仓库 {"priv…

大模型学习笔记------CLIP模型解读与思考

大模型学习笔记------CLIP模型详解 1、为什么提出CLIP模型2、CLIP模型详解3、CLIP模型的意义4、一些思考 上文说到&#xff0c;多模态大模型应该是非常有发展前景的&#xff0c;首先来学习 CLIP&#xff08;Contrastive Language-Image Pretraining&#xff09;这个多模态模型…

昇思25天学习打卡营第1天|快速入门

昇思25天学习打卡营第1天|快速入门 目录 昇思25天学习打卡营第1天|快速入门实操教程 一、MindSpore内容简介 主要特点&#xff1a; MindSpore的组成部分&#xff1a; 二、入门实操步骤 1. 安装必要的依赖包 2. 下载并处理数据集 3. 构建网络模型 4. 训练模型 5. 测试…

【Python TensorFlow】入门到精通

TensorFlow 是一个开源的机器学习框架&#xff0c;由 Google 开发&#xff0c;广泛应用于机器学习和深度学习领域。本篇将详细介绍 TensorFlow 的基础知识&#xff0c;并通过一系列示例来帮助读者从入门到精通 TensorFlow 的使用。 1. TensorFlow 简介 1.1 什么是 TensorFlow…