基础IO(2)--文件描述符以及输入输出重定向

news2024/11/21 0:29:40

文件描述符fd

文件操作的本质是进程被打开文件的关系。

进程可以打开多个文件,这些被打开的文件由OS管理,所以操作系统必定要为文件创建对应的内核数据结构标识文件–struct file{}【与C语言的FILE无关】

通过如下程序

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>

#define FILE_NAME(num) "log"#num".txt"
#define FILE_NAME(num) "log"#num".txt"
#define FILE_NAME(num) "log"#num".txt"
#define FILE_NAME(num) "log"#num".txt"
#define FILE_NAME(num) "log"#num".txt"

int main()
{
    umask(0);
    //以 "w"模式 打开文件
    int fd0 = open(FILE_NAME(0), O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fd1 = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fd2 = open(FILE_NAME(2), O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fd3 = open(FILE_NAME(3), O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fd4 = open(FILE_NAME(4), O_WRONLY | O_CREAT | O_TRUNC, 0666);

    //打开失败
    if(fd0 < 0 || fd1 < 0 | fd2 < 0 || fd3 < 0 || fd4 < 0)
    {
        perror("open");
        return -1;
    }

    //为什么文件描述符从3开始标号?0 1 2呢?
    printf("%d\n", fd0);
    printf("%d\n", fd1);
    printf("%d\n", fd2);
    printf("%d\n", fd3);
    printf("%d\n", fd4);

    //关闭文件
    close(fd0);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
}

运行得到的结果我们发现,fd从3开始连续编号。那0、1、2呢?答:标准输入输出流。Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2。

而连续的小整数我们可以想到数组下标!!所以我们猜测进程和文件之间的关系是用数组来描述。

1、以前我们学过三个标准输入输出流

stdin --> 键盘 --> fd:0

stdout --> 显示器 --> fd:1

stderr --> 显示器 --> fd:2

系统中的声明是extern FILE* stdin; extern FILE* stdout; extern FILE* stderr;

2、C语言的FILE是结构体变量,是typedef struct _iobuf FILE;,因为系统接口只认文件描述符,而C库中的f*函数是封装了系统接口来对文件进行操作,所以struct _iobuf {}结构体里必定有一个字段_fileno是文件描述符fd。由此可知键盘/显示器也是当作文件来处理。

每个进程的PCB里都有1个struct files_struct *file指针,指向struct files_struct结构体,结构体中有1个指针数组struct file* fd_array[],指向对应的文件结构体 struct file{},进程和文件就这么联系起来了!

在这里插入图片描述

每个进程创建的时候,默认都会把stdin/stdout/stderr放进去,故他们占据了进程的文件描述表的0/1/2下标!

fd分配规则

关闭0stdin和2stderr,再创建文件,可以看到新创建的文件就占用了0或2文件描述符位置。

规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

重定向

关闭1stdout,再新建myfile.txt,这个文件就会占据fd=1的位置。本来应该输出到显示器上的内容,就输出到了文件myfile当中。这种现象叫做输出重定向。常见的重定向有:>, >>, <。

重定向的本质就是上层用的fd不变,在内核中更改fd对应的struct file*的地址。

dup2

duplicate a file descriptor。系统提供dup函数,其中我们使用dup2函数(在内核中实现文件描述符的拷贝)

#include <unistd.h>
int dup2(int oldfd, int newfd);//把根据oldfd这个下标在文件描述表中的位置所指向的内容struct file{}拷贝给newfd
//让本来要写入newfd的,变成写到oldfd去,返回值是oldfd
--------
dup2(fd0, 1);
就是把fd0指向的struct file{log.txt}拷贝给1(原本1所指向的内容是struct file{stdout}),而上层在调用时还是用fd=1这个传,那内容就会给->1->struct file{log.txt}

那么输出重定向>的核心代码,以libc的"w"方式打开文件

int fd0 = open(FILE_NAME(0), O_WRONLY | O_CREAT | O_TRUNC, 0666);
dup2(fd0, 1);

追加重定向>>核心代码,O_TRUNC改成O_APPEND即可,以libc的"a"方式打开文件

int fd0 = open(FILE_NAME(0), O_WRONLY | O_CREAT | O_APPEND, 0666);
dup2(fd0, 1);

输入重定向<,重写输入重定向时,该文件必须存在,以只读方式打开

int fd0 = open(FILE_NAME(0), O_RDONLY);
dup2(0, fd0);

简易shell支持输入输出重定向

代码逻辑:读取用户输入->判断重定向,若是则保存重定向信息->命令空格分割,保存命令及参数->创建子进程->根据重定向信息打开文件,进行重定向->execvp进程程序替换

//标定3种重定向
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3

#define trimSpace(start) do{\
    while(isspace(*start)) ++start;\
}while(0)//不带;号
--------------------
int redirType = NONE_REDIR;//默认无重定向
char* redirFile = NULL;//无重定向目标文件

// < > >>
void commandCheck(char* commands)
{
    //倒着扫描 效率高些 因为命令和参数比较多
    assert(commands);
    char* start = commands;
    char* end = commands + strlen(commands) - 1;//指向最后一个有效元素
    
    while(end >= start)
    {
        if(*end == '>')
        {
            char* filename = end + 1;
            *end = '\0';
            //判断重定向类型
            end--;
            //"ls -l >> log.txt"
            if(*end == '>')
            {
                //追加重定向
                *end = '\0';
                redirType = APPEND_REDIR;
            }
            //"ls -l >   log.txt"
            else
            {
                //输出重定向
                redirType = OUTPUT_REDIR;
            }
            //提取文件名
            trimSpace(filename);
            redirFile = filename;
            break;
        }
        else if(*end == '<')
        {
            char* filename = end + 1;
            *end = '\0';//拆成左右两部分
            trimSpace(filename);//过滤右边的空格,提取文件名
            //填写重定向信息
            redirType = INPUT_REDIR;
            redirFile = filename;
            break;
        }
        else
        {
            end--;
        }
    }    
}
--------------------
if(id == 0)
{
	//命令是子进程执行的,重定向的工作也要由子进程完成
	//如何重定向,选项 由父进程给子进程
	switch(redirType)
	{
		case NONE_REDIR:
			//什么都不做
			break;
		case INPUT_REDIR:
			{
                int fd = open(redirFile, O_RDONLY);
                if(fd < 0)
                {
                    perror("open:");
                    exit(errno);//打开文件失败之间终止子进程
                }
                //重定向文件打开成功
                dup2(fd, 0);
        	}
        	break;
    	case OUTPUT_REDIR:

    	case APPEND_REDIR:
            {
                umask(0);
                int flags = O_WRONLY | O_CREAT;
                //根据输出重定向 或 追加重定向 写好flags
                if(redirType == APPEND_REDIR)  flags |= O_APPEND;
                else  flags |= O_TRUNC;

                int fd = open(redirFile, flags, 0666);
                if(fd < 0)
                {
                    perror("open:");
                    exit(errno);//打开文件失败之间终止子进程
                }
                //重定向文件打开成功
                dup2(fd, 1);
            }
            break;
        default:
            printf("bug?\n");
            break;
        }

    execvp(myargv[0], myargv);
    exit(1);
}

问题1:子进程在进行重定向操作时,是否会影响父进程的stdin或stdout?

不会,因为进程具有独立性,父进程PCB中的struct files_struct*所指向的file_struct会有一份拷贝,给子进程。

files struct:子进程会拥有1份独立的文件描述符表(父进程文件表的拷贝)! 这个结构体是属于进程的。

struct file:是父子进程共享的,一份就行。这个结构体是属于文件系统的。

子进程只拷贝文件描述符表,不会拷贝文件。

问题2:在程序替换时(也涉及文件替换),会不会影响曾经进程打开的重定向文件吗?

不会,因为这些数据都属于由操作系统维护的内核数据结构,在进行进程程序替换的时候替换的是代码和数据,而不是struct files_struct*的内容。

更进一步理解一切皆文件

我们有键盘keyboard、显示器tty、硬盘disk、网卡netcard等硬件,每种硬件的访问方法一定是不一样的!而硬件的信息和操作方法是写在各自的驱动上,比如sturct keyboard{ int KeyboardRead(){}//读方法};等。

在系统层面,文件系统对应的每个struct file中含有各种文件属性(根据硬件进行调整),每个struct file挨个链起来,比如有函数指针int (*readp) ();int (*writep) ();,这些函数指针就会指向对应硬件的读方法int KeyboardRead(){}和写方法,。

在这里插入图片描述

**上层调用不同的文件,底层调用不同的方法。**所以我们(在struct file上层来看,所有的文件和设备都是struct file)在进行读写操作时,我们不关心硬件到底怎么做,操作系统会去调用指针完成对应的操作。

以上就是多态,一个基类多个子类 。

文件系统:fs.h文件中,struct file{}; 中有以下参数

struct file{
    //...
    atomic_long_t f_count;
    unsigned int f_flags;
    fmode_t f_mode;
    loff_t f_pos;
    struct fown_struct f_owner;
    const struct file_operations *f_op;
};

f_count:是文件的计数引用。打开1.txt,那么文件1的对应的count++,关闭1.txt,则count--,我们常说的关闭一个文件,其实并不是真的关闭了,只有操作系统看到该文件的count==0时才会关闭这个文件。比如父子进程同时打开1个文件,子进程close该文件,只是告诉OS我不用这个文件了,实际上count不为0,所以这个文件还是被打开的,父进程不会受影响,关不关这个文件是OS来决定的。

f_flags:理解为简易shell代码中的这个操作int flags = O_WRONLY | O_CREAT;

f_mode:文件的权限。

f_pos:文件的操作位置。为什么追加就是在末尾追加,因为O_APPEND帮我们选好了末尾的操作位置。读写操作都可以让用户决定操作位置。

f_owner:表示这个文件当前是被谁打开的。

f_op:函数指针,会指向硬件的操作方法

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

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

相关文章

uni-app在真机调试下兼容ethers的方法

目录 一、安装ethers 二、renderjs 三、注意事项 uni-app开发跨平台应用程序&#xff0c;项目搭建主要前端框是Uni-app Vue3 TS Vite&#xff0c;项目搭建参考文章Uni-app Vue3 TS Vite 创建项目 Hbuilderx版本是3.6.17 一、安装ethers yarn add ethers 如果像ether…

【Python】用xpath爬取2022热梗保存到txt中并生成词云

本文收录于《python学习笔记》专栏&#xff0c;这个专栏主要是我学习Python中遇到的问题&#xff0c;学习的新知识&#xff0c;或总结的一些知识点&#xff0c;我也是初学者&#xff0c;可能遇到的问题和大部分新人差不多&#xff0c;在这篇专栏里&#xff0c;我尽可能的分享出…

MySQL 索引 学习

索引 主键索引&#xff08;PRIMARY KEY&#xff09; 唯一标识&#xff0c;主键不可重复&#xff0c;只能有一个主键 唯一索引&#xff08;UNIQUE KEY&#xff09; 索引列 常规索引&#xff08;KEY/INDEX&#xff09;全文索引&#xff08;FullText&#xff09; 可以快速定位数据…

excel拆分技巧:如何快速对金额数字进行分列

金额数字分列&#xff0c;相信是做财务的小伙伴们经常遇到的问题。网上关于金额数字分列的方法很多&#xff0c;但用到的公式大都比较复杂。今天我们就来分享一个最简单的公式&#xff0c;仅用LEFT、RIGHT和COLUMN三个函数&#xff0c;就能达到效果&#xff01;在财务工作中&am…

Tapdata Cloud 场景通关系列:将数据导入阿里云 Tablestore,获得毫秒级在线查询和检索能力

【前言】作为中国的 “Fivetran/Airbyte”, Tapdata Cloud 自去年发布云版公测以来&#xff0c;吸引了近万名用户的注册使用。应社区用户上生产系统的要求&#xff0c;Tapdata Cloud 3.0 将正式推出商业版服务&#xff0c;提供对生产系统的 SLA 支撑。Tapdata 目前专注在实时数…

【论文阅读 CIKM2014】Extending Faceted Search to the General Web

文章目录ForewordMotivationMethodQuery facet generation:Facet feedbackEvaluationForeword This paper is from CIKM 2014, so we only consider the insightsI have read this paper last month and today i share this blogThere are many papers that have not been sha…

Docker网络原理详解

文章目录理解Docker0Docker 是如何处理容器网络访问的&#xff1f;Docker0网络模型图容器互联--Link自定义网络网络连通理解Docker0 查看本机IP ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00…

application.properties的作用

springboot这个配置文件可以配置哪些东西 官方配置过多了解原理 这个properties文件其实是可以删掉的&#xff0c;官方是不推荐使用这个文件的&#xff0c;可以将其换成安排application.yaml。名字不能变&#xff0c;因为SpringBoot使用的是一个全局的配置文件 application.…

linux系统中使用QT实现CAN通信的方法

大家好&#xff0c;今天主要和大家分享一下&#xff0c;如何使用QT中的CAN Bus的具体实现方法。 目录 第一&#xff1a;CAN Bus的基本简介 第二&#xff1a;CAN通信应用实例 第三&#xff1a;程序的运行效果 第一&#xff1a;CAN Bus的基本简介 从QT5.8开始&#xff0c;提供…

C语言-柔性数组与几道动态内存相关的经典笔试题(12.2)

目录 思维导图&#xff1a; 1.柔性数组 1.1柔性数组的特点 1.2柔性数组的使用 1.3柔性数组的优势 2.几道经典笔试题 2.1题目1 2.2题目2 2.3题目3 2.4题目4 写在最后&#xff1a; 思维导图&#xff1a; 1.柔性数组 1.1柔性数组的特点 例&#xff1a; #include <…

javaEE 初阶 — java对于的操作文件

文章目录1. File 类概述2. 代码示例2.1 示例1&#xff1a;以绝对路径为例&#xff0c;演示获取文件路径2.2 示例2&#xff1a;以相对路径为例&#xff0c;演示获取文件路径2.3 示例3&#xff1a;测试文件是否存在、测试是不是文件、测试是不是目录2.4 示例4&#xff1a;创建文件…

27.函数指针变量的定义, 调用函数的方法,函数指针数组

函数指针变量的定义 返回值类型&#xff08;*函数指针变量名&#xff09;&#xff08;形参列表&#xff09;; int( *p )( int , int );//定义了一个函数指针变量p&#xff0c;p指向的函数必须有一个整型的返回值&#xff0c;有两个整型参数。 int max(int x, int y) { } int m…

AMR-IE:一种利用抽象语义表示(AMR)辅助图编码解码的联合信息抽取模型

Abstract Meaning Representation Guided Graph Encoding and Decoding for Joint Information Extraction 论文&#xff1a;2210.05958.pdf (arxiv.org) 代码&#xff1a;zhangzx-uiuc/AMR-IE: The code repository for AMR guided joint information extraction model (NAAC…

【学习笔记】【Pytorch】七、卷积层

【学习笔记】【Pytorch】七、卷积层学习地址主要内容一、卷积操作示例二、Tensor&#xff08;张量&#xff09;是什么&#xff1f;三、functional.conv2d函数的使用1.使用说明2.代码实现四、torch.Tensor与torch.tensor区别五、nn.Conv2d类的使用1.使用说明2.代码实现六、卷积公…

C/C++ noexcept NRVO

为什么需要noexcept为了说明为什么需要noexcept&#xff0c;我们还是从一个例子出发&#xff0c;我们定义MyClass类&#xff0c;并且我们先不对MyClass类的移动构造函数使用noexceptclass MyClass { public:MyClass(){}MyClass(const MyClass& lValue){std::cout << …

使用语雀绘制 Java 中六大 UML 类图

目录 下载语雀 泛化关系&#xff08;Generalization&#xff09; 实现关系&#xff08;Realization&#xff09; 关联关系&#xff08;Association&#xff09; 依赖关系&#xff08;Dependency&#xff09; 聚合关系&#xff08;Aggregation&#xff09; 组合关系&…

【Python学习】列表和元组

前言 前四天每天更新了小白看的基础教程 今天开始就更新一下&#xff0c;深入一点的知识点吧 还是老话&#xff1a;刚接触python的宝子可以点击文章末尾名片进行交流学习的哦 什么是列表和元组 列表是动态的&#xff0c;长度大小不固定&#xff0c;可以随意地增加、删减或…

【软件测试】软件测试基础2

1. 软件测试的生命周期 软件测试的生命周期&#xff1a; 需求分析→测试计划→ 测试设计、测试开发→ 测试执行→ 测试评估 ● 需求分析&#xff1a;站在用户的角度&#xff1a;查看需求逻辑是否正确&#xff0c;是否符合用户的需求和行为习惯&#xff1b;站在开发人员的角度&…

Nexus使用

环境 apache-maven-3.5.4nexus-3.38.1-01 Android开发中常用的maven代理地址 阿里云&#xff1a;http://maven.aliyun.com/nexus/content/groups/public/google&#xff1a;https://dl.google.com/dl/android/maven2/jcenter&#xff1a;https://jcenter.bintray.com/mavenC…

Leetcode:236. 二叉树的最近公共祖先(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 原理思路: 问题描述&#xff1a; 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&…