【Linux初阶】基础IO - 文件管理(深入理解文件描述符) | 重定向

news2024/11/26 19:37:27

文章目录

  • 一、文件管理引入
  • 二、理解文件描述符
  • 三、文件描述符表
  • 四、文件描述符的分配规则
  • 五、重定向
  • 六、使用 dup2 系统调用实现重定向
    • 1.模拟实现 >(输出)
    • 2.模拟实现 >>(追加)
    • 3.模拟实现 <(输入)
  • 结语


一、文件管理引入

我们在前面的文章中就曾提及过,进程操作的本质:进程与被打开文件的关系

那么问题来了,一个进程可以打开多个文件吗?

可以 -> 系统中一定会存在大量被打开的文件 -> 被打开的文件需不需要被 OS管理起来呢?要的 -> 如何管理? -> 先描述,在组织 -> 操作系统为了管理对应的打开文件,必定要为文件创建对应的 内核数据结构标识文件 -> files_struct {} -> 包含了大量的文件属性

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


#define FILE_NAME(number) "log.txt"#number //宏中+#,使两个字符串具有链接特性

int main()
{
    umask(0);//设置当前进程的umask值

    int fd0 = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd1 = open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd2 = open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd3 = open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd4 = open(FILE_NAME(5), O_WRONLY | O_CREAT | O_APPEND, 0666);

    printf("fd: %d\n", fd0);
    printf("fd: %d\n", fd1);
    printf("fd: %d\n", fd2);
    printf("fd: %d\n", fd3);
    printf("fd: %d\n", fd4);

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

在这里插入图片描述

通过观察上面的代码和对应的运行结果,我们发现了两个问题:

  1. fd获取的是文件操作符,那么为什么文件操作符是从3开始的呢?
  2. 为什么文件操作符是连续的小整数呢,这些连续的小整数是什么?

其实,这些连续的小整数就是数组的下标,至于为什么是数组下标,我们在后面结合其他知识再行讲解。


二、理解文件描述符

我们还是以 open的代码为例,我们知道,open接口调用成功后会返回文件描述符。实际上,文件描述符,就是一个整数

int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入(stdin)0, 标准输出(stdout)1, 标准错误(stderr)2。0,1,2对应的物理设备一般是:键盘,显示器,显示器。也就是说,在操作系统层面,我们可以把键盘和显示器理解成为文件

这就回答了我们上面的第一个问题:fd获取的是文件操作符,那么为什么文件操作符是从3开始的呢?
因为前三个文件描述符被占用了。下面,我们对这个结论进行验证。

C语言的库函数接口中,我们可以看到下面这样的代码

FILE *fd = fopen();

我们知道,fopen底层必须调用系统接口,系统接口调用访问文件,又必须用文件描述符。那这个 FILE又是什么呢?事实上,FILE代表的是一个结构体,通过推导,我们不难得出,这个结构体中,必定有一个字段存储文件描述符。

stdin、stdout、stderr都是 FILE*结构体

在这里插入图片描述
对此,我们修改我们上面的代码,可以得到新的运行结果

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


#define FILE_NAME(number) "log.txt"#number //宏中+#,使两个字符串具有链接特性

int main()
{
    printf("stdin->fd: %d\n", stdin->_fileno);//输出结构体中的文件描述符
    printf("stdout->fd: %d\n", stdout->_fileno);
    printf("stderr->fd: %d\n", stderr->_fileno);
    umask(0);

    int fd0 = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd1 = open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd2 = open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd3 = open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd4 = open(FILE_NAME(5), O_WRONLY | O_CREAT | O_APPEND, 0666);

    printf("fd: %d\n", fd0);
    printf("fd: %d\n", fd1);
    printf("fd: %d\n", fd2);
    printf("fd: %d\n", fd3);
    printf("fd: %d\n", fd4);

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

在这里插入图片描述

【总结】Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入(stdin)0, 标准输出(stdout)1, 标准错误(stderr)2。因此,我们自己文件的文件操作符通常从 3开始。


三、文件描述符表

我们将在本小节解决开头的第2个问题,为什么文件操作符是连续的小整数,为什么我们可以将这些连续的小整数理解成数组的下标?

下面是进程与文件的关系图解

在这里插入图片描述

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

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


四、文件描述符的分配规则

通过上面的学习,我们知道,我们的文件描述符默认0、1、2都被占用了,假如我们关掉一个会怎么样呢?直接看代码

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

int main()
{
 	int fd = open("myfile", O_RDONLY);
 	if(fd < 0){
 		perror("open");
 		return 1;
 	}
	printf("fd: %d\n", fd);
	
 	close(fd);
 	return 0;
}

输出发现 fd = 3
我们可以关闭 0 或 2 再看

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

int main()
{
	close(0);
	//close(2);
	int fd = open("myfile", O_RDONLY);
	if (fd < 0) {
		perror("open");
		return 1;
	}
	printf("fd: %d\n", fd);
	
	close(fd);
	return 0;
}

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


五、重定向

那如果关闭1呢?看代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main()
{
	close(1);
	int fd = open("myfile", O_WRONLY | O_CREAT, 00644);
	if (fd < 0) {
		perror("open");
		return 1;
	}
	printf("fd: %d\n", fd);
	fflush(stdout);

	close(fd);
	exit(0);
}

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>(输出), >>(追加), <(输入)

那重定向的本质是什么呢?

在这里插入图片描述
重定向的本质:上层用的 fd不变,在内核中更改 fd对应的 struct file*的地址

以上图为例,文件 1代表的是标准输出,但是该文件被我们 close掉了,假如此时我们新创建一个文件,我们的文件就会占用文件描述符 1。

所以,文件描述符 1没有变化,但是它底层struct file*的地址改变了,指向了一个新的文件。


六、使用 dup2 系统调用实现重定向

函数原型如下:

#include <unistd.h>
int dup2(int oldfd, int newfd);//将旧的拷贝到新的,最终我们的内容都和 old一样

【注意】dup2不是将文件描述符互相拷贝,而是将文件描述符对应的 struct file*的地址做互相拷贝。

1.模拟实现 >(输出)

直接输出在文件中

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

int main()
{
    //close(0);
    //close(2);
    //close(1);
    umask(0);
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    dup2(fd, 1);

    printf("open fd: %d\n", fd); // printf -> stdout
    fprintf(stdout, "open fd: %d\n", fd); // printf -> stdout

    fflush(stdout);
    close(fd);
    return 0;
}

在这里插入图片描述

———— 我是一条知识分割线 ————

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);
    //close(1);
    umask(0);
    //int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666); //追加
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    dup2(fd, 1);

    printf("open fd: %d\n", fd); // printf -> stdout
    fprintf(stdout, "open fd: %d\n", fd); // printf -> stdout

	const char *msg= "hello world";
    write(1, msg, strlen(msg));//strlen - #include <string.h>

    fflush(stdout);
    close(fd);
    return 0;
}

【注意】代码中的 “hello world” 没有加换行

在这里插入图片描述

———— 我是一条知识分割线 ————

3.模拟实现 <(输入)

可以直接从文件中输入显示器,不用在键盘中输入

示例如下,我们先在 log.txt中写入信息,再运行

#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);
    //close(1);
    umask(0);
    //int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    //int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd = open("log.txt", O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    dup2(fd, 0); //输入重定向

    char line[64];

    while(1)
    {
        printf("> "); 
        if(fgets(line, sizeof(line), stdin) == NULL) break; //stdin->0
        printf("%s", line);
    }

    close(fd);
    return 0;
}

在这里插入图片描述
在这里插入图片描述


结语

🌹🌹 文件管理(深入理解文件描述符) 的知识大概就讲到这里啦,博主后续会继续更新更多C++ 和 Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

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

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

相关文章

【观察】浪潮信息:自研液环式真空CDU技术,将被动应对变为主动防御

毫无疑问&#xff0c;在“双碳”战略的大环境下&#xff0c;数据中心走向绿色低碳和可持续发展已成为“不可逆”的大趋势&#xff0c;特别是随着全国一体化大数据中心、新型数据中心等政策文件的出台、“东数西算”工程的正式启动&#xff0c;数据中心的建设规模和数量呈现出快…

老胡周刊QA微信机器人(基于ChatGPT)

背景 先做个介绍吧&#xff0c;老胡的信息周刊是我从2021-08-16创立的周刊&#xff0c;截止到目前(2023-05-29)将近两年时间&#xff0c;目前已经有92期周刊&#xff0c;中间基本没有断更过&#xff0c;一共发布资源统计如下&#xff1a; &#x1f3af; 项目 288&#x1f916; …

Ae:稳定运动

使用跟踪器 Tracker面板的稳定运动 Stabilize Motion功能&#xff0c;可通过手动添加和设置跟踪点来跟踪对象的运动&#xff0c;将获得的跟踪数据对图层本身进行反向变换&#xff0c;从而达到稳定画面的目的。 Ae菜单&#xff1a;窗口/跟踪器 Tracker 点击跟踪器面板上的“稳定…

长文教你如何正确使用ChatGPT提高学习效率!

最近 Chat GPT 很&#x1f525;&#xff0c;被大家评为无所不能的最强AI。据说&#xff0c;有百分之八十的留学生已经在用ChatGPT 来写作业了&#xff0c;因为ChatGPT真的是有问必答&#xff0c;光速回复&#xff0c;复制粘贴都没有它回答的快。 目录 Part.1 什么是ChatGPT&a…

驱动开发:内核读写内存浮点数

如前所述&#xff0c;在前几章内容中笔者简单介绍了内存读写的基本实现方式&#xff0c;这其中包括了CR3切换读写&#xff0c;MDL映射读写&#xff0c;内存拷贝读写&#xff0c;本章将在如前所述的读写函数进一步封装&#xff0c;并以此来实现驱动读写内存浮点数的目的。内存浮…

centos安装KVM

文章目录 一、centos安装KVM步骤 1. 检查硬件支持 2. 安装 KVM 相关软件包 3. 启动 libvirtd 服务 4. 设置 libvirtd 服务自启动 5. 验证 KVM 安装 二、出现问题的解决方法 1. 检查网络连接 2. 检查 DNS 解析 3. 检查软件源设置 4. 禁用 IPv6 前言 本篇主要介绍cen…

教育最大的失败,是普通家庭富养孩子

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) 著名教育家马卡连柯曾说&#xff1a;“一切都给孩子&#xff0c;牺牲一切&#xff0c;甚至牺牲自己的幸福&#xff0c;这是父母给孩子最可怕的礼物。”前些天刷到一个挺扎心的视频&#xff0c;不知道算…

商业智能 (BI) 对企业中每个员工的 5 大好处

本文由葡萄城技术团队于博客园原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 众所周知&#xff0c;商业智能 (BI) 是探索企业数据价值的强大工具&#xff0c;能够帮助企业做出明智…

全网最全2W字-基于Java+SpringBoot+Vue+Element实现小区生活保障系统(建议收藏)

博主介绍&#xff1a;✌全网粉丝30W,CSDN特邀作者、博客专家、新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推…

破局35岁危机:技术人如何做好职业规划?

见字如面&#xff0c;我是军哥。 最近有一位读者工作 8 年了&#xff0c;后端做了 3 年&#xff0c;算法做了 5 年&#xff0c;换了 6/7 家公司&#xff0c;基本上每一家公司只干 1 年左右&#xff0c;换了 N 个行业&#xff0c;现在工作出现瓶颈&#xff0c;也不知道未来的路怎…

十、Git代码仓库

一、Git概述 Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。 也是为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。 二、Git常用命令 查看git配置 git config -l设置用户名和邮箱 git config --global u…

带电更换柱上变压器(综合不停电作业法)

一、现场复勘 1.核对工作线路双重名称、杆号及设备双重名称 2.检查杆身质量 3.检查线路装置是否符合带电作业要求 4.检查待更换变压器容量 满足旁路作业要求 5.检查气象条件 作业前进行湿度和风速的测量&#xff0c;风力大于5级或湿度大于80%时&#xff0c;不宜带电作业&…

开源“模仿”ChatGPT,居然效果行?UC伯克利论文,劝退,还是前进?

原创&#xff1a;谭婧ChatGPT 从“古”至今&#xff0c;AI的世界&#xff0c;是一个开源引领发展的世界。 虽然Stable Diffusion作为开源的图像生成模型&#xff0c;将图像生成提到了全新境界&#xff0c;但是ChatGPT的出现&#xff0c;似乎动摇了一些人的信念。 因为ChatGPT是…

16. Vue-element-template记住密码

Vue-element-template 记住密码 1. 在登录页面添加记住密码按钮 新增参数 rememberMe # resources/src/views/login/index.vueloginForm: {username: admin,password: 123456,rememberMe: false},添加复选框 # resources/src/views/login/index.vue<div style"margin-…

一、STM32开发环境的搭建(Keil+STM32CubeMX)

1、STM32开发环境所需的东西 (1)KeilMDK安装包。 (2)STM32CubeMX。 (3)Keil软件对应的单片机pack包。 (4)STM32Cube MCU包。 2、Keil简介及安装 略 3、CubeMX简介及安装 3.1、CubeMX简介 (1)STM32CubeMX是一种图形工具&#xff0c;通过分步过程可以非常轻松地配置STM3…

盘点!Instruction Tuning 时代的大模型(下)

作者 | Kevin吴嘉文 整理 | NewBeeNLP 公众号 https://zhuanlan.zhihu.com/p/617302168 Alpaca&#xff0c;ChatGLM 6B 等模型的效果可以接受&#xff0c;下文总结部分笔记&#xff0c;为训练自定义小型化&#xff08;7B&#xff09;模型提供点知识储备。 之前我们分享了LaM…

Spring Boot 整合 分布式搜索引擎 Elastic Search 实现 我附近的、酒店竞排

文章目录 ⛄引言一、我附近的酒店⛅需求分析⚡源码编写 二、酒店竞价排名⌚需求分析⏰修改搜索业务 ✅效果图⛵小结 ⛄引言 本文参考黑马 分布式Elastic search Elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中…

Maven安装与配置(图解)

Maven是一个基于 Java 的项目管理工具&#xff0c;因此最基本的要求是在计算机上安装 JDK。 Maven 对系统要求如下表&#xff1a; JDKJDK 7.0 及以上。内存没有最低要求。磁盘空间Maven 安装本身大约需要 10MB。除此之外&#xff0c;其他磁盘空间将用于本地 Maven 存储库。本地…

【Vue2.0源码学习】虚拟DOM篇-Vue中的DOM-优化更新子节点

1. 前言 在上一篇文章中&#xff0c;我们介绍了当新的VNode与旧的oldVNode都是元素节点并且都包含子节点时&#xff0c;Vue对子节点是 先外层循环newChildren数组&#xff0c;再内层循环oldChildren数组&#xff0c;每循环外层newChildren数组里的一个子节点&#xff0c;就去…

《HelloGitHub》第 86 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 …