ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Linux重定向原理详解:从文件描述符分配到dup2实现底层逻辑

Linux重定向原理详解:从文件描述符分配到dup2实现底层逻辑 Linux重定向原理详解从文件描述符分配到dup2实现底层逻辑摘要本文深入剖析Linux重定向的底层原理从文件描述符的“最小可用”分配规则入手通过代码实战验证重定向的诞生过程详解dup2系统调用的工作机制并揭示Shell中、、等重定向符号的背后实现。最后从struct file和file_operations函数指针表的角度解读“一切皆文件”设计哲学如何让重定向能力覆盖所有设备类型。适合Linux开发者、运维工程师及对系统编程感兴趣的读者。一、引言从文件描述符到重定向本文是Linux系统编程系列的第四篇。在阅读本文之前强烈建议先理解文件描述符和基础IO的相关概念。可点击跳转至上一篇基础IO认知二、文件描述符分配规则遍历fd_array的“最小可用”原则在上一篇博客中我们提到进程通过文件描述符来操作文件。每个进程在内核中都有一个task_struct其中的files指针指向一个files_struct结构体该结构体维护着一张数组——即fd_array。每一个打开的文件都在数组中占据一个位置下标就是文件描述符。当一个进程要打开新文件时内核会为该文件分配一个文件描述符。分配规则非常简单从fd_array的下标0开始遍历整个数组找到第一个未被使用的空位将其下标作为新的文件描述符返回。这个机制意味着文件描述符总是最小可用的整数如果你关闭了某个文件描述符下一次open就极有可能复用这个编号2.1 实战验证关闭0号描述符后open会得到什么#includestdio.h#includeunistd.h#includefcntl.hintmain(){close(0);// 关闭标准输入intfdopen(test.txt,O_RDONLY);printf(new fd %d\n,fd);close(fd);return0;}编译运行控制台输出new fd 0因为我们主动关闭了0执行open时内核从0开始寻找可用空位发现0号位置刚好空出来于是返回0——验证了最小可用规则。三、重定向的诞生当printf不再打印到屏幕文件描述符分配的这一特性直接为“重定向”埋下了伏笔。3.1 输出重定向close(1) open 的巧妙实验#includestdio.h#includeunistd.h#includefcntl.h#includesys/types.h#includesys/stat.hintmain(){close(1);// 关闭标准输出intfdopen(output.txt,O_WRONLY|O_CREAT|O_TRUNC,0644);// 此刻 fd 一定是 1printf(Hello, this goes to file!\n);fflush(stdout);// 强制刷新缓冲区确保数据写入close(fd);return0;}编译执行后屏幕上什么也没有打印。打开output.txt里面写着Hello, this goes to file!这就是输出重定向原本写入stdout文件描述符1的内容因为1号描述符此时指向的是一个普通文件printf的输出就自然落入了该文件中。我们没有修改printf的任何代码只是改变了文件描述符1所指向的内核对象。3.2 输入重定向同理#includestdio.h#includeunistd.h#includefcntl.hintmain(){close(0);// 关闭标准输入intfdopen(input.txt,O_RDONLY);// fd 必定为 0charbuf[128];fgets(buf,sizeof(buf),stdin);// 实际从 input.txt 读取printf(read from file: %s,buf);close(fd);return0;}如果input.txt中有一行I am redirected input程序就会输出read from file: I am redirected input重定向的核心改变文件描述符表的内容从而改变进程默认数据流的方向。四、重定向的本质dup2与fd_array指针拷贝上面用closeopen实现重定向的方式存在局限——必须依赖内核分配的最小可用规则且原始的1号描述符被“丢弃”后难以恢复。真正标准的重定向本质上是让fd_array中的某个位置例如1直接拷贝另一个位置的“文件对象指针”使两个文件描述符指向同一个打开文件。4.1 标准接口dup2Linux提供了dup2系统调用来实现这个拷贝intdup2(intoldfd,intnewfd);dup2会做几件事如果newfd已经打开先关闭它将newfd指向oldfd所指向的struct file即fd_array[newfd] fd_array[oldfd]增加struct file的引用计数此后读写newfd和oldfd将操作同一个文件共享偏移量。4.2 用dup2实现重定向#includestdio.h#includeunistd.h#includefcntl.hintmain(){intfdopen(dup2_output.txt,O_WRONLY|O_CREAT|O_TRUNC,0644);if(fd0){perror(open);return1;}dup2(fd,1);// 让 1 指向 fd 所指向的文件close(fd);// 关闭 fd但 1 仍然持有该文件对象printf(Hello via dup2!\n);fflush(stdout);return0;}运行后dup2_output.txt中写入了Hello via dup2!。这种方式比先close(1)再open更加优雅、可控。五、Shell重定向符号的底层实现fork open dup2理解了dup2就很容易明白Linux命令行中重定向符号的底层实现。5.1 输出重定向以command file为例Shell首先fork()出一个子进程在子进程中执行command之前先open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644)得到文件描述符fd调用dup2(fd, 1)把标准输出重定向到file关闭fd最后exec执行command命令内部的任何printf或write(1, ...)都会写入file。5.2 追加重定向与输入重定向同理区别在于打开文件时加上O_APPEND标志输入重定向open(input.txt, O_RDONLY)然后dup2(fd, 0)5.3 错误重定向2与212本质是dup2(fd, 2)command 21则是dup2(1, 2)把标准错误重定向到标准输出当前所指向的地方所有重定向符号本质上都是Shell在用户态用opendup2close这套系统调用来实现的。六、“一切皆文件”重定向能力覆盖所有设备的根本原因前面反复提到struct file它是内核中表示“已打开文件”的对象。每当我们open一个东西——无论是普通文本文件、管道、socket还是硬件设备如/dev/sda——内核都会为它创建一个struct file实例。在这个结构体中有一个至关重要的成员指向struct file_operations的指针f_op。6.1 file_operations函数指针表struct file_operations是一张函数指针表定义了对这个“文件”进行操作的各种方法structfile_operations{ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);int(*open)(structinode*,structfile*);int(*release)(structinode*,structfile*);// ... 还有很多其他操作};正是这些函数指针将“文件”这个抽象概念与千差万别的硬件联系在了一起。6.2 硬盘设备示例以硬盘设备/dev/sda为例进程调用open(/dev/sda, O_RDONLY)时内核创建struct file对象内核将f_op指针设置为硬盘驱动程序提供的file_operations结构体read指向驱动中读取硬盘的代码write指向驱动中写入硬盘的代码open返回文件描述符fd与装配好函数指针的struct file关联此后程序执行read(fd, buf, 1024)时内核通过fd找到struct file调用file-f_op-read——实际执行的是硬盘驱动程序中的代码。“一切皆文件”的本质Linux通过struct filefile_operations函数表为所有可操作的对象提供了一套完全统一的接口。回到重定向的主题正因如此重定向的能力才如此强大。当我们改变一个文件描述符所指向的struct file时底层的read/write函数指针也随之改变——数据会自动流向完全不同的目的地。这正是UNIX/Linux设计中最优雅的地方。七、总结文件描述符按“最小可用”原则分配由fd_array从0遍历找到空位通过close(1)open可以“碰巧”实现重定向揭示了数据流的本质重定向的本质是fd_array中文件对象指针的拷贝dup2是这一机制的正式APIShell重定向符号、、背后都是forkopendup2的组合“一切皆文件”的核心在于struct file内嵌的函数指针表让硬件和普通文件拥有一致的操作接口这也是重定向能够灵活作用于所有设备类型的根本原因希望这篇博客能帮助你彻底看透重定向的内部机制。如果觉得有收获欢迎点赞、收藏、评论我们下一篇再见
返回列表