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,实现丰富功能。

相关推荐
LRL_43 分钟前
离线/纯内网环境下,Linux 服务器时间不准如何强制修改?(以 Oracle Linux 9 / RHEL 9 为例)
linux·服务器·oracle
世岩清上1 小时前
展厅不同分区播放差异化视频,怎样做到内容串联不割裂整体叙事?
运维·人工智能·音视频·展厅改造
wuminyu1 小时前
Kafka利用sendfile与Page Cache实现高性能传输剖析
java·linux·c语言·jvm·c++
Titan20241 小时前
网络学习:网络层与数据链路层知识梳理
服务器·网络·学习·tcp/ip
志栋智能1 小时前
运维超自动化中的配置管理实践
运维·网络·数据库·自动化
一条泥憨鱼1 小时前
【从0开始学习计算机网络】| TIME_WAIT 为什么是 2MSL,CLOSE_WAIT / TIME_WAIT 堆积怎么排查
服务器·开发语言·网络·计算机网络·网络安全
sibylyue1 小时前
【云原生系列3】Docker Compose单机多容器编排工具
运维·docker·容器
dingdingfish1 小时前
TLPI 第36章 读书笔记:Process Resource
linux·resource·getrusage·tlpi·setrlimit·getrlimit
阿钱真强道1 小时前
04 嵌入式操作系统 | 常用命令Ⅱ:用户权限、进程与网络
linux·ubuntu·进程管理·用户权限·网络排查