【Linux学习笔记】系统文件IO之重定向原理分析

news2025/5/11 9:20:19

【Linux学习笔记】系统文件IO之重定向原理分析

🔥个人主页大白的编程日记

🔥专栏Linux学习笔记


文章目录

  • 【Linux学习笔记】系统文件IO之重定向原理分析
    • 前言
    • 一. 系统文件I/0
      • 1.1 一种传递标志位的方法
      • 1.2 hello.c写文件:
      • 1.3 hello.c读文件
      • 1.4 接口介绍
      • 1.5 open函数返回值
      • 1.6 文件描述符fd
        • 1.6.1 0&1&2
        • 1.6.2 文件描述符的分配规则
        • 1.6.3 重定向
        • 1.6.4 使用dup2系统调用
        • 1.6.5 在minishell中添加重定向功能
    • 后言

前言

哈喽,各位小伙伴大家好!上期我们讲了进程替换和自定义shell 今天我们讲的是系统文件IO之重定向原理分析。话不多说,我们进入正题!向大厂冲锋!
在这里插入图片描述

一. 系统文件I/0

打开文件的方式不仅仅是fopen,ifstream等流式,语言层的方案,其实系统才是打开文件最底层的方案。不过,在学习系统文件IO之前,先要了解下如何给函数传递标志位,该方法在系统文件IO接口中会使用到:

1.1 一种传递标志位的方法

#include <stdio.h>
 #define ONE    
#define TWO    
0001 //0000 0001
 0002 //0000 0010
 #define THREE  0004 //0000 0100
 void func(int flags) {
	 if (flags & ONE) printf("flags has ONE! ");
	 if (flags & TWO) printf("flags has TWO! ");
	 if (flags & THREE) printf("flags has THREE! ");
	 printf("\n");
 }
 int main() {
	 func(ONE);
	 func(THREE);
	 func(ONE | TWO);
	 func(ONE | THREE | TWO);
	 return 0;
 }

操作文件,除了上小节的C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来]进行文件访问,先来直接以系统代码的形式,实现和上面一模一样的代码:

1.2 hello.c写文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
	umask(0);
	int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
	if (fd < 0) {
		perror("open");
		return 1;
	}
	int count = 5;
	const char* msg = "hello bit!\n";
	int len = strlen(msg);
	while (count--) {
	}
	write(fd, msg, len);
	//fd: 后⾯讲,msg:缓冲区⾸地址,len :本次读取,期望写⼊多少个字节的数据。
	//返回值:实际写了多少字节数据
	close(fd);
	return 0;
}

1.3 hello.c读文件

 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <string.h>
 int main()
 {
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }
    const char *msg = "hello bit!\n";
    char buf[1024];
    while(1){
        ssize_t s = read(fd, buf, strlen(msg));//类⽐write 
        if(s > 0){
            printf("%s", buf);
        }else{
            break;
        }
    }
    close(fd);
    return 0;
 }

1.4 接口介绍

open man open

 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 int open(const char *pathname, int flags);
 int open(const char *pathname, int flags, mode_t mode);
 pathname: 
要打开或创建的⽬标⽂件flags: 
打开⽂件时,可以传⼊多个参数选项,⽤下⾯的⼀个或者多个常量进⾏“或”运算,构成flags
。
参数
:
O_RDONLY: 只读打开 
O_WRONLY: 只写打开  
O_RDWR  : 读,写打开                          
这三个常量,必须指定⼀个且只能指定⼀个
O_CREAT : 若⽂件不存在,则创建它。需要使⽤mode选项,来指明新⽂件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的⽂件描述符
失败:-1

mode_t理解:直接man手册,比什么都清楚。
open函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open
write read closelseek,类比c文件相关接口。

1.5 open函数返回值

在认识返回值之前,先来认识一下两个概念:系统调用和库函数

  • 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
  • 而 open close read write lseek 都属于系统提供的接口,称之为系统调用接口
  • 回忆一下我们讲操作系统概念时,画的一张图
    在这里插入图片描述

系统调用接口和库函数的关系,一目了然。
所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

1.6 文件描述符fd

  • 通过对open函数的学习,我们知道了文件描述符就是一个小整数
1.6.1 0&1&2
  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2.
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器
    所以输入输出还可以采用如下方式:
 #include <stdio.h>
 #include <sys/types.h>
  #include <sys/stat.h>
 #include <fcntl.h>
 #include <string.h>
 int main()
 {
 char buf[1024];
 ssize_t s = read(0, buf, sizeof(buf));
 if(s > 0){
 buf[s] = 0;
 write(1, buf, strlen(buf));
 write(2, buf, strlen(buf));
 }
 return 0;
 }

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files,指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件
描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

1.6.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或者fd2,可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

1.6.3 重定向

那如果关闭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。这种现象叫做输出重定向。常见的重定向有:>,>>,<

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

1.6.4 使用dup2系统调用

函数原型如下:

#include <unistd.h>
 int dup2(int oldfd, int newfd);
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    int fd = open("./log", O_CREAT | O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    close(1);
    dup2(fd, 1);
    for (;;) {
        char buf[1024] = {0};
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);
        if (read_size < 0) {
            perror("read");
            break;
        }
        printf("%s", buf);
        fflush(stdout);
    }
    return 0;
}

printf是C库当中的IO函数,一般往stdout中输出,但是stdout底层访问文件的时候,找的还是fd:1,但此时,fd:1下标所表示内容,已经变成了myfifile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。那追加和输入重定向如何完成呢?

1.6.5 在minishell中添加重定向功能

先定义一个整数记录重定向方式 一个字符串记录重定向文件

TrimSpace跳过重定向字符后的空格 指向文件名开始位置
每次调用RedirCheck先清空文件和让文件描述符为0
然后从后向前查找 > >> <
每次让命令行参数表清空重定及其之后的内容 设置为\0即可 命令正常执行
然后根据找到的符号 调用跳过空格函数 在让文件名指向end之后的内容
设置重定向方式即可
Execute根据redir 调用open打开文件 然后dup2重定向即可
之后再让子进程正常执行即可

  • 问题:进程替换会不会影响重定向结果?

不会 因为进程替换只是替换程序的代码和数据
而文件结构体和文件描述符等内核数据结构不受到影响。

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
int redir = NONE_REDIR;
std::string filename;
void RedirCheck(char cmd[])
{
	redir = NONE_REDIR;
	filename.clear();
	int end = strlen(cmd)-1;
	while (end > 0)
	{
		if (cmd[end] == '>')
		{
			if (cmd[end - 1] == '>')
			{
				cmd[end - 1] = 0;
				redir = APPEND_REDIR;
			}
			else
			{
				cmd[end] = 0;
				redir =OUTPUT_REDIR;
			}
			TrimSpace(cmd, ++end);
			filename = cmd + end;
			break;
		}
		else if (cmd[end] == '<')
		{
			cmd[end] = 0;
			redir = INPUT_REDIR;
			TrimSpace(cmd, ++end);
			filename = cmd + end;
			break;
		}
		else
		{
			end--;
		}
	}
}
int Execute()
{
	pid_t id = fork();
	if (id == 0)
	{
		int fd = -1;
		// 子进程检测重定向情况
		if (redir == INPUT_REDIR)
		{
			fd = open(filename.c_str(), O_RDONLY);
			if (fd < 0) exit(1);
			dup2(fd, 0);
			close(fd);
		}
		else if (redir == OUTPUT_REDIR)
		{
			fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
			if (fd < 0) exit(2);
			dup2(fd, 1);
			close(fd);
		}
		else if (redir == APPEND_REDIR)
		{
			fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
			if (fd < 0) exit(2);
			dup2(fd, 1);
			close(fd);
		}
		execvp(g_argv[0], g_argv);
		exit(1);
	}
	int status = 0;
	pid_t rid=waitpid(id, &status, 0);
	if (rid > 0)
	{
		lastcode = WEXITSTATUS(status);
	}
	return 0;
}
int main()
{
	//初始化环境变量表
	InitEnv()**加粗样式**;
	while (1)
	{
		//打印命令行提示符
		PrintCommandPrompt();
		//获取命令行输入
		char commandline[COMMAND_SIZE];
		if (!GetCommandLine(commandline, sizeof(commandline)))
		{
			continue;
		}
		RedirCheck(commandline);
		cout << redir << "->" << filename << endl;
		//填充命令行参数表
		if (!CommandParse(commandline))
		{
			continue;
		}
		//处理内建命令
		if (CheckAndExecBuiltion())
		{
			continue;
		}
		//执行命令
		Execute();
	}
	return  0;
}

在这里插入图片描述

后言

这就是系统文件IO之重定向原理分析。大家自己好好消化!今天就分享到这! 感谢各位的耐心垂阅!咱们下期见!拜拜~

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

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

相关文章

SpringBoot中使用MCP和通义千问来处理和分析数据-连接本地数据库并生成实体类

文章目录 前言一、正文1.1 项目结构1.2 项目环境1.3 完整代码1.3.1 spring-mcp-demo的pom文件1.3.2 generate-code-server的pom文件1.3.3 ChatClientConfig1.3.4 FileTemplateConfig1.3.5 ServiceProviderConfig1.3.6 GenerateCodeController1.3.7 Columns1.3.8 Tables1.3.9 Fi…

实现滑动选择器从离散型的数组中选择

1.使用原生的input 详细代码如下&#xff1a; <template><div class"slider-container"><!-- 滑动条 --><inputtype"range"v-model.number"sliderIndex":min"0":max"customValues.length - 1"step&qu…

基于Credit的流量控制

流量控制(Flow Control)&#xff0c;也叫流控&#xff0c;它是控制组件之间发送和接收信息的过程。在总线中&#xff0c;流控的基本单位称为flit。 在标准同步接口中(比如AXI协议接口)&#xff0c;握手信号如果直接采用寄存器打拍的方式容易导致信号在不同的方向上出现偏离。因…

【金仓数据库征文】金仓数据库KingbaseES: 技术优势与实践指南(包含安装)

目录 前言 引言 一 : 关于KingbaseES,他有那些优势呢? 核心特性 典型应用场景 政务信息化 金融核心系统&#xff1a; 能源通信行业&#xff1a; 企业级信息系统&#xff1a; 二: 下载安装KingbaseES 三:目录一览表: 四:常用SQL语句 创建表&#xff1a; 修改表结构…

金丝猴食品:智能中枢AI-COP构建全链路数智化运营体系

“金丝猴奶糖”&#xff0c;这个曾藏在无数人童年口袋里的甜蜜符号&#xff0c;如今正经历一场数智焕新。当传统糖果遇上数字浪潮&#xff0c;这家承载着几代人味蕾记忆的企业&#xff0c;选择以数智化协同运营平台为“新配方”&#xff0c;将童年味道酿成智慧管理的醇香——让…

java的输入输出模板(ACM模式)

文章目录 1、前置准备2、普通输入输出API①、输入API②、输出API 3、快速输入输出API①、BufferedReader②、BufferedWriter 案例题目描述代码 面试有时候要acm模式&#xff0c;刷惯leetcode可能会手生不会acm模式&#xff0c;该文直接通过几个题来熟悉java的输入输出模板&…

鸿蒙 所有API缩略图鉴

从HarmonyOS NEXT Developer Preview1&#xff08;API 11&#xff09;版本开始&#xff0c;HarmonyOS SDK以 Kit 维度提供丰富、完备的开放能力&#xff0c;涵盖应用框架、应用服务、系统、媒体、AI、图形在内的六大领域&#xff0c;共计30000个API

【Docker系列】使用格式化输出与排序技巧

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

基础语法(二)

Mysql基础语法&#xff08;二&#xff09; Mysql基础语法&#xff08;二&#xff09;主要介绍Mysql中稍微进阶一点的内容&#xff0c;会稍微有一些难度&#xff08;博主个人认为&#xff09;。学习完基础语法&#xff08;一&#xff09;和基础语法&#xff08;二&#xff09;之…

TOA的定位,建模与解算的步骤、公式推导

TOA(到达时间)定位的核心是通过测量信号从目标到多个基站的传播时间,将其转换为距离信息,并利用几何关系解算目标位置。本文给出具体的建模与解算步骤及公式推导 文章目录 通用模型建立非线性方程组构建线性化处理(最小二乘法)最大似然估计(ML)高斯-牛顿迭代法误差分析…

2025年PMP 学习七 -第5章 项目范围管理 (5.4,5.5,5.6 )

2025年PMP 学习七 -第5章 项目范围管理 5.4 创建 WBS 1.定义与作用 定义把项目可交付成果和项目工作分解成较小的&#xff0c;更易于管理的组件作用对所要交付的内容提供一个结构化的视图 2.输入&#xff0c;输出&#xff0c;工具与技术 3. 创建WBS的依据&#xff08;输入&…

CAD属性图框值与Excel联动(CAD块属性导出Excel、excel更新CAD块属性)——CAD c#二次开发

CAD插件实现块属性值与excel的互动&#xff0c;效果如下&#xff1a; 加载dll插件&#xff08;CAD 命令行输入netload &#xff0c;运行xx即可导出Excel&#xff0c;运行xx1即可根据excel更新dwg块属性值。&#xff09; 部分代码如下 // 4. 开启事务更新CAD数据using (Transact…

【HarmonyOS 5】鸿蒙中进度条的使用详解

【HarmonyOS 5】鸿蒙中进度条的使用详解 一、HarmonyOS中Progress进度条的类型 HarmonyOS的ArkUI框架为开发者提供了多种类型的进度条&#xff0c;每种类型都有其独特的样式&#xff0c;以满足不同的设计需求。以下是几种常见的进度条类型&#xff1a; 线性进度条&#xff08;…

Spring Cloud: Nacos

Nacos Nacos是阿里巴巴开源的一个服务发现&#xff0c;配置管理和服务管理平台。只要用于分布式系统中的微服务注册&#xff0c;发现和配置管理&#xff0c;nacos是一个注册中心的组件 官方仓库&#xff1a;https://nacos.io/ Nacos的下载 Releases alibaba/nacos 在官网中…

Win11安装APK方法详解

1、官方win11系统 预览版 开发版 正式版 都行 2、同时你还需要开启主板 BIOS 虚拟化选项&#xff08;具体名称不同主板略有不同&#xff09; 这一步自行百度 开始&#xff1a;先去确定有没有开启虚拟化 任务管理器检查—— 虚拟化是否已经开启&#xff0c;如果没有自己去BIO…

SSH终端登录与网络共享

SSH 是较可靠&#xff0c;专为远程登录会话和其他网络服务提供安全性的协议 注意 SSH终端登录的前提是&#xff1a;电脑和板卡都能够通过网络相连接及通信 与连接互联网不一样&#xff0c;SSH可以不用互联网&#xff0c;只要电脑和板卡组成一个小型网络即可 网络方案 如果您…

Android 13 默认打开 使用屏幕键盘

原生设置里&#xff0c;系统-语言和输入法-实体键盘-使用屏幕键盘 选项&#xff0c; 关闭时&#xff0c;外接物理键盘&#xff0c;如USB键盘&#xff0c;输入时不会弹出软键盘。 打开时&#xff0c;外接物理键盘&#xff0c;如USB键盘&#xff0c;输入时会弹出软键盘。 这个选…

操作系统学习笔记第2章 (竟成)

第 2 章 进程管理 【考纲内容】 1.进程与线程&#xff1a; (1) 进程 / 线程的基本概念&#xff1b; (2) 进程 / 线程的状态与转换&#xff1b; (3) 线程的实现&#xff1a;内核支持的线程&#xff1b;线程库支持的线程&#xff1b; (4) 进程与线程的组织与控制&#xff1b; (5)…

复合机器人案例启示:富唯智能如何以模块化创新引领工业自动化新标杆

在国产工业机器人加速突围的浪潮中&#xff0c;富唯智能复合机器人案例凭借其高精度焊接与智能控制技术&#xff0c;成为行业标杆。然而&#xff0c;随着制造业对柔性化、全场景协作需求的升级&#xff0c;复合机器人正从单一功能向多模态协同进化。作为这一领域的创新者&#…

Linux在web下http加密和配置虚拟主机及动态页面发布

web服务器的数据加密 1.简介&#xff1a;由于http协议以明文方式发送&#xff0c;不提供任何方式的数据加密&#xff0c;也不适合传输一些重要的信息&#xff0c;如银行卡号、密码等&#xff0c;解决该缺陷设计了安全套接字层超文本传输协议https&#xff1b; 2.https的握手流…