)
一、dup2 系统调用实现重定向前面我们了解重定向的底层原理关闭旧的文件描述符再 open 新文件利用文件描述符分配规则优先分配最小的未被使用的 fd让新文件占据 1 号标准输出、0 号标准输入、2 号标准错误完成重定向。但是手动close再open的写法比较麻烦Linux 提供dup2系统调用专门用来做文件描述符拷贝实现重定向。函数原型#include unistd.h int dup2(int oldfd, int newfd);oldfd已经打开的有效文件描述符newfd想要让 oldfd 拷贝到的目标文件描述符逻辑如果newfd原本是打开状态dup2 会自动先关闭newfd然后让fd_array[newfd]指向oldfd对应的内核file对象。返回值成功返回 newfd失败返回‑1。注意dup2 不会复制磁盘文件只是复制进程内文件描述符表的指针内核file结构体引用计数f_count会 1。示例代码实现输出重定向把原本打印到显示器的内容输出到 log 文件#include stdio.h #include unistd.h #include fcntl.h int main() { int fd open(./log, O_CREAT | O_RDWR, 0644); if (fd 0) { perror(open); return 1; } // 把fd拷贝到1号描述符等价输出重定向 log dup2(fd, 1); char buf[1024] {0}; ssize_t read_size read(0, buf, sizeof(buf)-1); if(read_size 0) { printf(%s, buf); fflush(stdout); } close(fd); return 0; }这里printf是 C 库函数底层访问依旧去找 fd1现在 fd1 指向 log 文件输出全部写入文件完成重定向。二、给 minishell 增加重定向功能shell 中常见三种重定向输入重定向把文件内容当作程序标准输入输出重定向截断式输出清空原有文件再写入追加重定向在文件末尾追加内容不删除原有内容实现思路解析命令行扫描命令字符串识别//分割出真正执行的命令和重定向文件名标记重定向类型。重定向逻辑放在子进程fork()创建子进程之后程序替换exec*之前执行重定向。重点程序替换只会替换进程代码和数据不会修改进程的文件描述符表所以子进程做 dup2 重定向之后exec 执行外部命令重定向依旧生效。父进程等待子进程退出。核心逻辑片段// 重定向类型 #define NoneRedir 0 #define InputRedir 1 // #define OutputRedir 2 // #define AppRedir 3 // int redir NoneRedir; char *filename nullptr; // 执行重定向子进程调用 void DoRedir() { if(redir InputRedir) { int fd open(filename, O_RDONLY); dup2(fd,0); close(fd); } else if(redir OutputRedir) { // O_TRUNC截断清空文件 int fd open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666); dup2(fd,1); close(fd); } else if(redir AppRedir) { // O_APPEND追加写 int fd open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666); dup2(fd,1); close(fd); } }执行流程pid_t id fork(); if(id 0) { DoRedir(); //子进程完成重定向 execvpe(gargv[0], gargv, genv); //程序替换 exit(7); } waitpid(id, status, 0);小知识点cd、export属于 shell内建命令不能 fork 子进程执行。子进程修改工作目录、环境变量只会影响子进程父 shell 环境不会变化必须 shell 进程自己调用chdir、修改环境变量。三、Linux “一切皆文件” 真正含义狭义文件磁盘上普通文件。 广义Linux 下键盘、显示器、网卡、磁盘、管道、socket 套接字全部抽象成文件。内核层面原理每个打开的资源在内核中都有一个struct file结构体结构体内部有一个struct file_operations *f_op指针。file_operations是一个巨大的函数指针表里面包含read、write、llseek、open、release等函数指针。不同硬件 / 设备驱动实现自己的 read、write 方法磁盘驱动的 read从磁盘扇区读取数据键盘驱动 read等待键盘输入显示器驱动 write把数据输出到屏幕socket 套接字 read/write收发网络数据包。对上层用户来说不管操作什么设备只需要调用统一的系统调用read / write。操作系统根据 fd 找到struct file调用file-f_op-read/write底层就会走到对应设备驱动的函数。这就是一切皆文件的核心对外提供同一套系统调用接口底层驱动各自实现差异化操作。四、IO 缓冲区详解缓冲区本质是内存空间用来缓存数据减少系统调用次数。一次系统调用会发生用户态↔内核态切换开销大缓冲区攒一批数据再调用一次系统调用大幅提升 IO 效率。三种缓冲模式C 标准库用户态缓冲区全缓冲缓冲区写满才触发系统调用。普通磁盘文件默认全缓冲。行缓冲遇到换行符\n或者缓冲区写满刷新缓冲区。标准输出 stdout 指向终端显示器默认行缓冲。无缓冲不缓存调用库函数立刻执行系统调用。stderr标准错误无缓冲保证错误信息立刻输出。触发缓冲区刷新的条件缓冲区存满fflush()手动强制刷新程序进程正常退出会自动刷新用户缓冲区。⚠️坑点重定向现象 当 stdout 原本指向显示器行缓冲一旦重定向到普通磁盘文件stdout 自动切换为全缓冲模式。 此时 printf 打印的内容带有\n也不会立刻刷新只有缓冲区满、fflush、进程退出才会写入。示例坑代码int main() { close(1); int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666); printf(hello world\n, fd); close(fd); return 0; }运行后 log.txt 是空文件 原因重定向之后 stdout 变成全缓冲字符串不足以填满缓冲区没有 fflush在进程退出之前我们就close(fd)关闭文件缓冲区数据还留在用户内存没有落地到文件。 修复fflush(stdout);在 close 之前强制刷新缓冲区。区分用户缓冲区 VS 内核缓冲区C 库 FILE 里面的缓冲区用户态缓冲区由 libc 提供printf/fwrite使用系统调用read/write没有用户缓冲区。调用 write 之后数据进入内核缓冲区操作系统维护由操作系统决定什么时候刷到磁盘硬件可以调用fsync/fdatasync强制把内核缓冲区落地磁盘。五、FILE 结构体 和 文件描述符 fdfdint 类型的小整数内核层面标识打开文件系统调用 open/read/write/close 使用。FILE*C 标准库的结构体指针用户层库函数封装fopen/fprintf/fwrite 使用。FILE结构体内部内部成员_fileno保存封装的文件描述符 fd一整套缓冲区指针_IO_write_base、_IO_write_ptr、_IO_write_end等维护用户缓冲区。经典 fork 缓冲区面试题#include stdio.h #include unistd.h int main() { printf(hello printf\n); fwrite(hello fwrite\n, 13,1,stdout); write(1,hello write\n,12); fork(); return 0; }直接运行输出到显示器三行各输出一次。stdout 是行缓冲遇到\nfork 之前缓冲区已经刷新完毕。使用重定向./a.out file输出到普通文件printf、fwrite输出两次write只输出一次。原因重定向到文件stdout 变成全缓冲。\n不会触发刷新printf、fwrite 的数据还留在 C 库的用户缓冲区。fork()创建子进程发生写时拷贝。父进程用户空间缓冲区数据拷贝一份给子进程。父子进程各自退出都会刷新自己的用户缓冲区所以 printf/fwrite 打印两遍。write是系统调用没有用户缓冲区数据直接交给内核fork 不会拷贝这份数据只会输出一次。总结用户缓冲区是 C 库提供存在用户空间fd 属于内核。库函数 用户缓冲区逻辑 封装系统调用。六、系统调用与库函数的关系open / close / read / write / dup2系统调用操作系统内核提供接口用户态切换内核态完成文件操作。fopen / fclose / fread / fwrite / printfC 标准库函数运行在用户空间。调用链路应用程序 → C 库函数处理用户缓冲区 → 系统调用 → 内核 → 硬件驱动库函数不是替代系统调用是在系统调用之上封装缓冲区逻辑给程序员更友好的接口。我们甚至可以自己模拟简易 C 库封装 fd、自己实现缓冲区模拟fopen/fwrite/fflush。本篇小结dup2(oldfd, newfd)拷贝文件描述符指针实现重定向子进程中使用exec 程序替换不影响 fd 表。shell 重定向解析识别 子进程做 dup2 重定向cd/export 等属于内建命令不能 fork。Linux 一切皆文件依靠struct file和file_operations函数指针表统一 read/write 系统调用底层设备驱动各自实现。缓冲区分为全缓冲、行缓冲、无缓冲重定向会改变 stdout 缓冲模式缓冲区刷新时机。fd 是内核文件描述符FILE 结构体是 C 库封装内部包含 fd 用户缓冲区fork 会拷贝用户缓冲区造成重复输出。库函数封装系统调用用户缓冲区属于 libcwrite 系统调用没有用户缓冲区。