Linux系统:详解文件描述符与重定向原理以及相关接口(open,read,write,dup2)

本节重点 

  • 从狭义与广义角度理解文件
  • 理解文件描述符
  • 掌握open,write,read系统调用
  • 理解重定向的概念与原理
  • 掌握重定向的指令操作
  • stdout与stderr的比较
  • 为什么存在stderr?

一、理解“文件”

1.1 狭义角度

在狭义层面,Linux文件是磁盘或存储设备上连续或分散的数据块集合,具有明确的元数据(如文件名、权限、所有者等),通过文件系统进行管理。其核心特征包括:

1.1.1 数据存储载体

  • 文本文件(如.txt.conf):存储人类可读字符。
  • 二进制文件(如.exe.o):编译后的程序或库文件,需特定程序解析。
  • 设备文件(如/dev/sda/dev/null):通过文件接口与硬件或内核交互(如/dev/null丢弃所有写入数据)。

1.1.2 元数据

每个文件由inode(索引节点)描述,包含:

  • 文件类型(普通文件、目录、符号链接等)
  • 权限(rwx)与所有者(UID/GID)
  • 时间戳(创建、修改、访问时间)
  • 实际数据块的磁盘地址(通过直接/间接指针)。

1.2 广义角度

在广义层面,Linux将几乎所有系统资源抽象为文件,通过统一的文件操作接口(open、write、read等)访问,形成“一切皆文件”的设计哲学。

1.3 系统角度

用户对文件的操作本质是进程对文件的操作,文件的管理者是操作系统,对文件的操作是通过文件相关的系统调用接口来实现的。

二、回顾C语言文件接口

https://blog.csdn.net/yue_2899799318/article/details/146305837?fromshare=blogdetail&sharetype=blogdetail&sharerId=146305837&sharerefer=PC&sharesource=yue_2899799318&sharefrom=from_link

三、文件相关系统调用

3.1、open

在Linux系统中系统调用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是打开方式

三参数模式:创建并打开新文件时使用,pathname是文件路径,flags是打开方式,mode用来设置新文件创建时的权限。

参数解析 :

pathname:

要打开或创建的文件路径,可以是绝对路径也可以是相对路径

flags:

必选标志:(只能选其一)

  • O_RDONLY,只读打开
  • O_WRONLY,只写打开
  • O_RDWR,读写打开

可选标志:(可组合使用)

  • O_CREAT:若文件不存在则创建,需配合mode参数。
  • O_NOFOLLOW:不跟随符号链接。
  • O_DIRECTORY:要求路径必须是目录,否则失败。
  • O_CLOEXEC:执行exec时自动关闭文件描述符。
  • O_SYNC:同步写入,确保数据写入物理设备。
  • O_NONBLOCK:非阻塞模式打开,适用于设备文件或管道。
  • O_APPEND:追加写入,每次写操作从文件末尾开始。
  • O_TRUNC:若文件存在且以写模式打开,则将其长度截断为0。
  • O_EXCL:与O_CREAT一起使用时,若文件已存在则返回错误,确保原子性创建。

mode:

使用mode参数时说明进程想要创建并打开一个新文件,此时mode表示创建文件时初始化文件权限。具体如下:

注意:mode参数只有O_CREAT参数被指定时有效,用来设置新文件的权限

常用权限宏(定义在<sys/stat.h>中):

  • S_IRUSR(用户读权限)、S_IWUSR(用户写权限)、S_IXUSR(用户执行权限)。
  • S_IRGRP(组读权限)、S_IWGRP(组写权限)、S_IXGRP(组执行权限)。
  • S_IROTH(其他用户读权限)、S_IWOTH(其他用户写权限)、S_IXOTH(其他用户执行权限)。

实际上由于文件掩码的存在,文件实际的权限=mode&~umask

返回值:

成功时:返回文件描述符(非负整数)

失败时:返回-1,并设置全局变量errno指示错误类型

代码演示:

#include<stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include<string.h>
#include<errno.h>
int main()
{umask(0);int ret=open("./text.txt",O_WRONLY|O_CREAT,0666);if(ret==-1){perror("open fail!\n");printf("%s\n",strerror(errno));return errno;}printf("文件描述符为%d\n",ret);return 0;
}

3.2、write

在Linux系统中,write系统调用用来向文件描述符所指定的文件中写入数据。

函数原型:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数解析:

fd:文件描述符,通过open等系统调用获取,标识要读取的文件,管道,套接字等

buf:用户空间缓冲区指针,存储待写入的数据。

count:请求写入的字节数

返回值:

成功时:返回实际写入的字节数,可能会小于count。

失败时:返回-1,并设置全局变量errno指示错误类型。

代码示例:

向指定文件中写入字符串并读取打印

#include<stdio.h>
#include <sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include <fcntl.h>
#include<string.h>
#include<errno.h>
int main()
{//读写方式打开方便我们将写入数据后打印出来int ret=open("./text.txt",O_RDWR);if(ret==-1){printf("open fail! %s\n",strerror(errno));return 1;}//打开成功:char buff[]={"jinnzhiqi yuejianhua"};int n=write(ret,buff,sizeof(buff));if(n==-1){printf("write fail! %s\n",strerror(errno));return 2;}printf("写入数据成功!\n"); lseek(ret,0,SEEK_SET);char buff1[1024];int sz=read(ret,buff1,sizeof(buff1)-1);buff1[sz]='\0';printf("%s\n",buff1);return 0;
}

3.3、read

在Linux系统中,系统调用read表示从文件描述符所指定的文件中读取数据。

函数原型:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

参数解析 :

fd:文件描述符,通过open等系统调用获取,标识要读取的文件,管道,套接字等。

buf:用户空间缓冲区指针,用来存储读取到的数据。

count:请求读取的最大字节数。

返回值:

成功时:返回实际读取到的字节数。

  • 若返回值小于cout,说明数据不足read已经读到文件末尾
  • 若返回值等于0,表示已经读到文件末尾或连接失败

失败时:返回-1,并设置全局变量errno指示错误类型。 

代码示例:

从指定文件中读取字符串:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<errno.h>
int main()
{int ret=open("./text.txt",O_RDONLY);if(ret==-1){printf("open fail! %s\n",strerror(errno));return 1;}//打开成功:char buff[1024];int n=read(ret,buff,sizeof(buff)-1);if(n==-1){printf("read fail! %s\n",strerror(errno));return 2;}//读取成功:buff[n]='\0';printf("%s\n",buff);return 0;
}

  

四、文件描述符

在Linux系统中,文件描述符(File Descriptor,简称FD)是操作系统内核为每个进程维护的一个非负整数标识符,用于抽象地引用进程已打开的文件、套接字(Socket)、管道(Pipe)、设备文件等I/O资源。它是进程与内核交互时管理I/O操作的核心机制。

4.1 核心概念

文件描述符是一个索引值,指向进程打开文件表(Open File Table)中的条目,而非直接指向文件本身。每个描述符对应一个内核维护的struct file结构体,记录文件的元数据(如偏移量、权限、引用计数等)。

4.2 理解文件描述符

与进程管理类似,Linux系统对已经打开的文件也采取“先描述再组织”的管理方法。当用户(进程)打开磁盘上的文件时,系统在系统层面会创建一个struct file结构体用来描述所打开的文件并存储相关文件信息。

在系统层面,当有多个文件被打开时,为了更高效地管理各个已打开的文件,系统会将每个struct file结构体用双链表的方式链接起来,此时对文件的管理就成了对该双链表的增删查改。

我们知道,Linux系统天然支持多进程,当多个进程打开多个文件时,一方面系统会给每个打开的文件创建struct file结构体并链入到全局链表中,另一方面,每个进程PCB中都会管理和维护一张文件描述符表(本质是以struct file* 为元素的指针数组)用来指明当前进程打开了多少个文件。

所以本质上,每个进程都有自己的文件描述符表(指针数组),文件描述符就是数组下标。

4.3 文件描述符的分配机制

4.3.1 分配流程

查找最小可用FD:

当进程调用open等系统调用时,内核会从进程的文件描述符表(File Descriptor Table)中搜索一个最小的未被占用的整数作为新描述符。

初始化描述符条目:

内核将该FD将一个内核维护的文件对象(struct file)进行关联,记录文件操作指针、偏移量、权限标志等信息。

4.3.2 关键数据结构

进程级文件描述符表:

每个进程都管理或维护一个独立的FD表,由用户态的int fd索引到内核态的struct file对象。

系统级打开文件表:

所有进程共享的全局表,存储struct file的引用计数,inode指针等,避免重复加载文件元数据。

4.4 分配规则的核心逻辑

在Linux系统中文件描述符总是默认从低到高顺序分配,也就是说内核默认优先分配最小的可用的FD,例如,到当前进程打开了FD:0、1、2则下一个分配的文件描述符就是3。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>int main()
{// 分别打印三个标准流的文件描述符printf("stdin: %d\n", stdin->_fileno);printf("stdout: %d\n", stdout->_fileno);printf("stderr: %d\n", stderr->_fileno);umask(0);int n = open("./text.txt", O_RDONLY |O_CREAT,0666);printf("open: %d\n",n);return 0;
}

 

五、 重定向

5.1 概念

在Linux系统中,文件重定向是用于控制程序输入/输出(I/O)流向的核心机制,允许用户将命令的标准输入(stdin)、标准输出(stdout)或标准错误(stderr重新定向到文件、设备或其他进程,而非默认的终端(键盘/屏幕)

5.2 重定向的类型与语法

5.2.1 输出重定向

>:覆盖目标文件(若文件已存在则清空)

$ echo "Hello" > output.txt  # 将"Hello"写入output.txt(覆盖原有内容)

>>:追加内容到目标文件

$ echo "World" >> output.txt # 在output.txt末尾追加"World"

5.2.2 输入重定向

<:从文件读取并输入(替代键盘输入)

$ wc -l < input.txt  # 统计input.txt的行数(等价于wc -l input.txt)

5.2.3 错误重定向

2>:将标准错误输出到文件(覆盖)

$ ls /nonexistent 2> error.log  # 将错误信息写入error.log

2>>:将标准错误追加到文件

$ ls /nonexistent1 /nonexistent2 2>> error.log  # 追加多个错误

5.3 底层原理(dup2系统调用)

5.3.1 dup2

dup2是Linux系统中的一个核心系统调用,用于复制文件描述符。其核心作用是将一个现有的文件描述符(oldfd)复制到指定的目标文件描述符(newfd),使newfd指向与oldfd相同的文件表项。这一机制是文件重定向、进程间通信(如管道)等操作的基础

函数原型:

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

 参数解析:

oldfd:需要复制的源文件描述符

newfd:目标文件描述符,若newfd已被占用,dup2会先关闭它

返回值:

成功时:返回newfd

失败时:返回-1,并设置全局变量errno指明错误原因

特殊情况:

  • 若newfd与oldfd相同,则dup2会直接返回newfd不会关闭它
  • 如果oldfd无效则dup2会直接返回-1,并设置errno为EBADF

代码演示:

 输出重定向:

#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{umask(0);int fd=open("./text.txt",O_CREAT|O_WRONLY,0666);if(fd<0){perror("open fail!\n");return 1;}int newfd=dup2(fd,1);if(newfd<0){perror("dup2 fail!\n");return 2;}printf("hello world!\n");printf("hello world!\n");printf("hello world!\n");printf("hello world!\n");return 0;
}

5.4 stdout与stderr

5.4.1 重定向

标准输出流(stdout)与标准错误流(stderror)都是进程启动时默认打开的I/O流,属于Unix/Linux系统的标准文件描述符(0=stdin, 1=stdout, 2=stderr)。

若为显式重定向,两者均会输出到当前终端(如命令行界面)。

#include<iostream>
#include<cstdio>int main()
{    std::cout<<"hello cout"<<std::endl;std::cerr<<"hello error"<<std::endl;fprintf(stderr,"hello error\n");return 0;
}

 

 若进行重定向:

./code 1> text.txt //或者./code > text.txt

此时会发现stdout的内容会写入文件,而strerr的内容会仍然显式在终端

 如果想让stderr的内容也重定向到文件text.txt中可以使用以下指令:

//将stdout重定向到text.txt后再追加stderr中的内容
./code 1> text.txt 2>>text.txt
./code 1> text.txt 2>&1

维度标准输出(stdout)标准错误(stderr)
设计目的输出程序的正常结果(如计算结果、用户提示)。输出程序的错误信息(如语法错误、运行时异常)。
默认行为与标准输入(stdin)关联,通常输出到终端或文件。与标准输入/输出独立,默认也输出到终端,但可重定向。
缓冲机制通常是行缓冲(遇到换行符或缓冲区满时刷新)。无缓冲立即刷新,确保错误信息及时显示。
重定向方式使用 > 或 1> 重定向到文件(如 command > file)。使用 2> 或 &> 重定向到文件(如 command 2> error.log)。
文件描述符默认文件描述符为 1默认文件描述符为 2
典型内容程序运行后的正常输出(如 echo "Hello")。程序异常时的警告或错误(如 ls /nonexistent)。

 5.4.2 为什么要存在stderr?

stderror是工程化设计的必然选择:

  • 错误隔离:将异常信息与正常数据分离,提升系统可维护性。
  • 实时响应:无缓冲机制确保关键错误即时暴露。
  • 灵活控制:通过重定向和管道实现精细化的输出管理。

如果没有stderr导致无论是正常信息还是异常信息都会通过stdout来进行输出,就会导致严重错误:

  • 用户可能因错误信息被截断或延迟而困惑,甚至无法感知程序失败。
  • 监控脚本无法区分正常数据与错误,导致误报或漏报

为了区分两者我们必须花费大量时间来过滤信息,这样做低效且会增加代码复杂度。

而通过系统级机制stderr将异常信息与正常数据分离,可以提升系统可维护性,也可以通过重定向和管道实现精细化的输出管理。

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

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

相关文章

美国市场变局:沃尔玛95%覆盖率的3个流量入口重构策略

过去几年&#xff0c;美国零售市场经历了极大的变化。电商发展迅猛&#xff0c;加上疫情影响&#xff0c;消费者购物习惯出现转向。而作为美国零售巨头&#xff0c;沃尔玛&#xff08;Walmart&#xff09;凭借高达95%的线下覆盖率&#xff0c;始终是品牌和卖家不可忽视的渠道。…

一文详解 Linux下的开源打印系统CUPS(Common UNIX Printing System)

文章目录 前言一、CUPS 简介二、CUPS 常用指令解析2.1 安装 CUPS2.2 启动/重启服务2.3 添加打印机&#xff08;核心操作&#xff09;2.4 设置默认打印机2.5 打印文件2.6 查看打印任务2.7 取消打印任务2.8 查看、移除已添加的打印机 三、调试与常见问题3.1 日志查看3.2 驱动问题…

React useCallback函数

应用场景&#xff1a;父组件向子组件传递函数类型的props时

python 桌面程序开发简述及示例

Python桌面程序开发简述及示例 Python凭借其简洁的语法和丰富的库支持,非常适合开发跨平台的桌面应用程序。本文将介绍Python桌面开发的主要方法,并提供实际代码示例。 一、Python桌面开发主要方法 1.1 Tkinter(标准库) Python内置的GUI库,适合开发简单桌面应用 1.2 …

数字智慧方案5875丨智慧交通枢纽综合解决方案(43页PPT)(文末有下载方式)

篇幅所限&#xff0c;本文只能提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2301_78256053/89575708 资料解读&#xff1a;智慧交通枢纽综合解决方案 详细资料请看本解读文章的最后内容。 随着城市化进程的加速和交通需求的不断增…

企业级分布式 MCP 方案

飞书原文档链接地址&#xff1a;https://ik3te1knhq.feishu.cn/wiki/D8kSwC9tFi61CMkRdd8cMxNTnpg 企业级分布式 MCP 方案 [!TIP] 背景&#xff1a;现阶段 MCP Client 和 MCP Server 是一对一的连接方式&#xff0c;若当前 MCP Server 挂掉了&#xff0c;那么 MCP Client 便不…

【AI提示词】奥卡姆剃刀思维模型专家

提示说明 一位专注于奥卡姆剃刀思维模型的专业人士&#xff0c;擅长将简洁性原则应用于复杂问题的分析与解决。 提示词 # Role: 奥卡姆剃刀思维模型专家## Profile - language: 中文 - description: 一位专注于奥卡姆剃刀思维模型的专业人士&#xff0c;擅长将简洁性原则应用…

2.1 行列式

引言 行列式是线性代数的核心工具&#xff0c;贯穿矩阵运算、特征值计算与微分方程求解。本文系统梳理2.1节核心考点&#xff0c;结合公式速查与典型例题&#xff0c;助你高效突破行列式难点&#xff01; 考点一&#xff1a;数值型行列式计算 1️⃣ 行列式的定义 (1) 定义方…

单词规律(简单)

思路和同构字符串那道题一样。、但是这道题要注意的地方就是&#xff0c;检查 pattern 和 s 的单词数量是否一致以及在进行字符串比较的时候应该用equals来进行比较&#xff0c;而不能用“&#xff01;”&#xff0c;“&#xff01;”比较的是对象引用而非内容。 class Soluti…

【C++】认识map和set

目录 前言&#xff1a; 一&#xff1a;认识map和set 二&#xff1a;map和set的使用 1.set的使用 2.map的使用 三&#xff1a;map的insert方法返回值 四&#xff1a;map的[ ]的使用 五&#xff1a;multiset和multimap 六&#xff1a;map和set的底层数据结构 七&#x…

Mybatis中的一级二级缓存扫盲

思维导图&#xff1a; MyBatis 提供了一级缓存和二级缓存机制&#xff0c;用于提高数据库查询的性能&#xff0c;减少对数据库的访问次数。&#xff08;本质上是减少IO次数&#xff09;。 一级缓存 1. 概念 一级缓存也称为会话缓存&#xff0c;它是基于 SqlSession 的缓存。在同…

uniapp 实现低功耗蓝牙连接并读写数据实战指南

在物联网应用场景中&#xff0c;低功耗蓝牙&#xff08;BLE&#xff09;凭借其低能耗、连接便捷的特点&#xff0c;成为设备间数据交互的重要方式。Uniapp 作为一款跨平台开发框架&#xff0c;提供了丰富的 API 支持&#xff0c;使得在多个端实现低功耗蓝牙功能变得轻松高效。本…

OpenSSL应用实践:嵌入式数据安全实战指南

文章目录 OpenSSL应用实践:嵌入式数据安全实战指南一、嵌入式安全现状与OpenSSL适配方案1.1 嵌入式安全挑战1.2 OpenSSL精简方案二、开发环境搭建2.1 交叉编译工具链2.2 OpenSSL交叉编译三、核心功能实现3.1 AES-GCM加密实践四、实战项目:安全OTA升级4.1 系统架构4.2 关键代码…

harmonyOS 手机,双折叠,平板,PC端屏幕适配

由于HarmonyOS设备的屏幕尺寸和分辨率各不相同&#xff0c;开发者需要采取适当的措施来适配不同的屏幕。 1.EntryAbility.ets文件里&#xff1a;onWindowStageCreate方法里判断设备类型&#xff0c; 如果是pad&#xff0c;需全屏展示&#xff08;按客户需求来&#xff0c;本次…

跟韩学AiOps系列之2025学MySQL系列_如何在MySQL中开启和提交事务?!

跟韩学AiOps系列之2025学MySQL系列_如何在MySQL中开启和提交事务&#xff1f;! 文章目录 一、事务的基本操作1. 开启事务2. 执行事务内操作3. 提交事务4. 回滚事务 二、验证示例&#xff08;适用于 MySQL 5.7&#xff09;步骤 1&#xff1a;准备测试表和数据步骤 2&#xff1a…

Java生成微信小程序码及小程序短链接

使用wx-java-miniapp-spring-boot-starter 生成微信小程序码及小程序短链接 在pom.xml文件中引入依赖 <dependency><groupId>com.github.binarywang</groupId><artifactId>wx-java-miniapp-spring-boot-starter</artifactId><version>4.7…

如何让通义千问大模型支持结构化输出?

之前的文章提到通义千问API无法通过with_structured_output/json schema的方式支持结构化输出&#xff0c;如果就是想使用通义千问大模型做结构化输出&#xff0c;应该怎么办呢&#xff1f;有两种办法 使用Ollama来运行通义千问大模型 从Ollama博客文章 Structured output 中…

一条 SQL 查询语句是如何执行的(MySQL)

第一讲&#xff1a;一条 SQL 查询语句是如何执行的 总览图示 MySQL 查询的执行流程可以大致分为以下步骤&#xff08;如图所示&#xff09;&#xff1a; 连接器&#xff08;Connection&#xff09;查询缓存&#xff08;Query Cache&#xff0c;MySQL 8.0 已废弃&#xff09;…

汽车OTA在线升级法规分析

摘要 本文介绍了R156法规即《关于批准车辆的软件升级和软件升级管理体系统一规定的法规》、该法规专注于汽车软件升级功能&#xff0c;并为此提出了一系列具体要求&#xff0c;旨在确保软件升级流程的安全性、可控性和合规性&#xff0c;从而顺应汽车行业智能化、联网化的发展趋…

Notepad编辑器实现换行符替换

在不同的Note编辑器中&#xff0c;批量把换行替换为空的方法有所不同&#xff0c;以下是常见编辑器的操作方法&#xff1a; Notepad 打开文件后&#xff0c;按CtrlH打开“查找和替换”对话框&#xff0c;在“查找”字段中输入\r\n&#xff0c;在“替换为”字段中输入一个空格…