Linux 重定向

Linux基础IO------重定向原理剖析

本文基于Linux系统IO,讲解重定向是什么、底层原理、dup2系统调用,以及shell中重定向的实现思路。

一、前置知识

1. 文件描述符fd

Linux进程默认打开3个文件描述符:

fd 名称 对应硬件
0 stdin 标准输入 键盘
1 stdout 标准输出 显示器
2 stderr 标准错误 显示器

文件描述符本质:files_struct结构体内部fd_array指针数组的下标。

  • task_struct(进程PCB)里面有*files指针,指向files_struct;
  • files_struct维护file* fd_array[],数组每一个元素是内核file结构体的指针;
  • open打开文件,内核创建file对象,把对象地址填入数组的空闲下标,返回这个下标,就是fd。

fd分配规则 :从fd_array中找最小的未被使用的下标作为新的文件描述符。

比如关闭fd=1,再open新文件,新文件fd就会等于1。

2. 库函数与系统调用

  • C库函数:fopen/fclose/fread/fwrite/printf,属于用户层库,封装系统调用,自带用户缓冲区。
  • 系统调用:open/close/read/write/dup2,操作系统提供接口,没有用户缓冲区。>

FILE*结构体内部封装了文件描述符_fileno,库函数最终还是通过fd调用系统调用。

二、什么是重定向

重定向:不改变用户代码,改变数据流向。本该输出到显示器的数据,输出到文件;本该从键盘读数据,改成从文件读取。

shell常见重定向符号:

  1. >:输出重定向,截断式,command > file,清空文件,把命令输出写入文件。
  2. >>:追加重定向,command >> file,把输出追加到文件末尾,不覆盖原有内容。
  3. <:输入重定向,command < file,命令不再从键盘读取,从文件读取数据。

重定向底层本质

进程的fd_array[1]原本保存显示器对应的file*指针。

如果我们close(1)关闭标准输出,再open目标文件 ,根据fd分配规则,新打开文件就拿到fd=1。

此时,所有往fd=1写入的数据(printf、fprintf、write(1,...)),都会写入这个新打开的文件,而不再输出显示器。

cpp 复制代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    close(1); //关闭标准输出,释放fd=1下标
    int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    //新文件占用fd=1
    printf("hello 重定向\n"); //printf底层操作fd=1,输出到test.txt
    fflush(stdout); //库函数带缓冲区,必须刷新
    close(fd);
    return 0;
}

注意:C标准库有用户缓冲区 ,重定向到文件时stdout变成全缓冲,如果不fflush,进程退出前数据不会落到文件,出现文件为空。

上面手动close+open实现重定向有缺陷:原来打开的文件fd会被浪费,于是有专门的系统调用dup2。

三、dup2系统调用

函数原型

cpp 复制代码
#include <unistd.h>
int dup2(int oldfd, int newfd);

功能:

  1. 如果newfd已经打开,dup2会自动关闭newfd指向的文件;
  2. 将fd_array[oldfd]里面的file*拷贝赋值给fd_array[newfd];
  3. oldfd和newfd,两个下标同时指向同一个内核file对象,引用计数f_count++;
  4. 成功返回newfd,失败返回‑1。

dup2(fd,1):让fd指向的文件,占用标准输出1号描述符,完成输出重定向。

不需要手动close(1),dup2内部帮我们处理。

示例代码:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC,0644);
    dup2(fd,1); //把fd拷贝到1,1现在指向log.txt
    printf("hello dup2重定向"); //输出到log.txt
    fflush(stdout);
    close(fd); //关闭fd,但是fd=1仍然指向该file对象,引用计数没有到0,文件不会关闭
    return 0;
}
复制代码

注意:close(fd)只是关闭我们自己的fd,dup2之后fd=1仍然指向文件,内核file对象引用计数没有归零,文件不会关闭。

四、Shell(minishell)如何实现重定向

整体思路:解析命令字符串识别重定向符号,在子进程执行程序替换之前完成重定向操作。

步骤拆解

  1. 解析命令行字符串
    遍历命令buffer识别符号<、>、>>:
  • <:标记输入重定向,截取后面的文件名,把该符号位置置\0截断命令,命令只保留重定向符号之前的部分;
  • >后面还有一个>就是>>追加重定向;否则是普通输出重定向。
  1. fork创建子进程

重定向操作必须在子进程中执行!

如果父进程shell做dup2,shell本身的输入输出就被重定向,shell本身无法正常交互。

  1. 子进程中执行DoRedir()函数
    根据解析出来的重定向类型,打开文件,调用dup2完成描述符拷贝:
  • 输入重定向<:open(filename,O_RDONLY),dup2(fd,0);
  • 输出重定向>:open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666),dup2(fd,1);
  • 追加重定向>>:open(filename,O_WRONLY|O_CREAT|O_APPEND,0666),dup2(fd,1)。
  1. 子进程执行程序替换execvpe

重点:exec程序替换只会替换进程的代码、数据,不会修改进程的文件描述符表!

所以dup2设置好的重定向,在exec之后依然生效。

  1. 父进程waitpid等待子进程退出。

核心伪代码

复制代码
//子进程内
DoRedir();
execvpe(argv[0],argv,env);
exit(1);

区分:内建命令(cd、export)不创建子进程,直接shell进程内部执行,所以内建命令一般不做重定向。

五、常见坑点总结

  1. 缓冲区坑(printf库函数重定向)
  • 输出到终端:stdout行缓冲,遇到\n自动刷新;
  • 重定向到普通文件:stdout切换成全缓冲。没有换行、缓冲区没满,程序直接close,数据留在用户缓冲区,文件看不到内容。
    解决:fflush(stdout)强制刷新用户缓冲区。>

注意:write系统调用没有用户缓冲区,不受这个问题影响。stderr默认无缓冲。

  1. dup2之后不要忘记:oldfd可以close,但是newfd还在生效。内核file对象靠引用计数管理,所有指向它fd全部close才会真正释放。
  2. exec不会修改fd表,重定向放在fork之后、exec之前。顺序不能颠倒,如果先exec再dup2,已经晚了。
  3. fd分配规则:最小未占用下标,理解为什么close(1)再open拿到fd=1。

六、拓展:Linux一切皆文件和重定向

键盘、显示器、磁盘、socket套接字在内核都抽象成struct file,提供统一的read/write函数指针接口。

重定向本质就是改变fd下标指向哪一个file对象,所以可以把数据重定向到普通文件、管道、socket,实现丰富功能。

相关推荐
wuminyu28 分钟前
C++协程实现接收端的零拷贝Buffer管理原理剖析
java·linux·c语言·jvm·c++
优化Henry34 分钟前
LTE 载波频率与频点配置详解
运维·网络·学习·5g·信息与通信
代码方舟1 小时前
数据科学风控实战:基于天远全能消金报告构建自动化信用评估网关
运维·人工智能·自动化
科力锐品牌君1 小时前
应用级灾备 | 海量非结构化数据如何实现高效数据保护
linux·运维·网络·安全·系统安全·数据安全·灾备
KaiwuDB1 小时前
KaiwuDB 运维实战04:DRBD + KaiwuDB——物联网场景下的低成本数据库高可用方案
运维·数据库·物联网·时序数据库·kaiwudb·aiot·多模数据库
zhangrelay1 小时前
ROS项目设计案例智能大模型正经乱答案例
linux·笔记·学习·ubuntu·机器人
福建佰胜张工2 小时前
A5E00839230西门子工业核心配件详解:参数、安装调试与故障运维全攻略
运维·网络·安全·自动化
流浪0012 小时前
Linux系统篇39——线程(四) pthread库的由来和线程的创建与等待
linux·操作系统·线程·pthread库
Lancker2 小时前
chnroute-linux — 中国大陆 IP 段每日自动更新 + 「仅允许国内访问」落地
linux·运维·tcp/ip
辻弋2012 小时前
【无标题】
大数据·服务器·前端·搜索引擎·开源软件