linux文件——重定向原理学习——重定向指令实现、虚拟文件系统

CSDN 2024-08-17 09:37:01 阅读 100

        前言:本篇讲解linux下的重定向相关内容。 在本篇中, 博主将会带着友友们一边实验, 一边探索底层原理。 通过本篇的学习, 友友们将会了解到重定向是如何实现的, 重定向的本质是什么, 重定向和进程替换之间的关系等等, 本篇内容将会丰富我们对于进程的理解。

        ps:由于本节内容涉及到文件fd,所以本节内容适合了解文件fd的友友们进行观看。 

目录

文件描述符的分配规则

重定向的本质

dup

dup2的使用——输出重定向

 dup2的使用——输入重定向

自定义shell实现重定向指令

文件的重定向和进程替换

重定向的参数

如何理解计算机下“一切皆文件”


文件描述符的分配规则

        想要知道文件描述符的分配规则, 我们需要使用一个实验来测试出来。 下面我们开始进行这个实验:

        在这个实验中, 我们会用到wrrite, open函数, 如下图为man手册:

        其中, open函数需要包含头文件sys/types.h、sys/stat.h、fcntl.h

        write函数需要包含unistd.h头文件        

        然后, 我们的代码如下:

需要用到的头文件:

下面是我们的代码:

这个程序运行后, 就是如下结果:

图中打印fd, 然后将hello linux的内容打印到log.txt文件中, 再输出log.txt的内容, 就如同上图

        上图的fd打印为3, 我们知道, 0, 1, 2对应的是stdin, stdout, stderr。所以新文件fd就到了3号fd。

        接下来, 就开始测试文件描述符的分配规则。

        从0号小标开始, 寻找最小的没有使用的数组位置,它的下标就是新文件的文件描述符。

        我们从上图可以看出, 文件描述符是3。 而0, 1, 2都被占用了。 我们就可以考虑——对于文件来说, 文件描述符都是从小到大创建的。 

        那么我们为了验证这个猜想, 就可以消除0号下标的指向。 那么0号就空出来了。 这个时候我们再创建的文件就是被映射在了0号下标处。

下面是测试代码:

然后打印出来的fd如下:

上面的结果就是说, 消除了0号fd位置的指针, 当我们再打开一个文件的时候就可以将这个文件指针放入0号位置。 

我们再关闭1号fd进行测试:

然后运行结果:

没有打印出内容的原因是因为1号是显示器文件, 关闭后就不会再向显示器中打印了。

关闭2再测试一下

然后运行结果:

清除2号指针后, 然后打开文件, 2号就会保存新打开的文件指针。 然后打开的fd就变成了2, 打印出来的是2, 同样符合我们的假设。

那么现在就可以下结论了——文件描述符对应的分配规则是什么? 从0下标开始, 寻找最小的没有使用的数组位置, 它的下标就是新文件的文件描述符。

重定向的本质

我们在上面探究文件描述符的分配规则的时候, 知道了1号文件描述符被清空后, 再新打开的文件的文件指针就会保存到1号文件描述符中。 ——这个过程起始就是重定向。

下面重新捋一下这个过程, 对于上面这个过程, 我们的进程本来有一个文件描述符表:

        然后我们将1号fd指向显示器的文件的指针收回, 然后创建新文件log.txt, 将log.txt的struct_files的地址放到1号文件的fd处。

我们看下面的具体代码:

        上面这个1号fd转化到过程, 也就是上面黄框框的代码段。 对于操作系统来说, 他知不知道fd的指向发生了变化呢? 答案是不知道!!对于操作系统来说, 他不管fd下面做了什么, 他只认fd。 所以, 如果还向1里面写东西, 那么就是本来向显示器文件里面写东西转化为向log.txt文件里面写东西。 而这个过程就是重定向

        那么我们如果想要向其他文件里面写东西, 是不是就需要将这个文件的指针覆盖到1号fd里面? ——这就是重定向。 重定向只需要将想要重定向到文件的指针覆盖到1号所在的fd里面!!

dup

上面我们讲道理重定向的底层原理。 但是整个代码很长——需要一开始关闭1号fd文件描述符指针, 然后将新打开文件的文件描述符指针放到1号文件中。 实际上, 系统就是提供了一种fd覆盖的接口——dup系列, 下面是man手册:

上面有三个dup系列函数, 常用的是dup2. 下面我们具体查看一下dup2的用法:

第一个参数名为newfd, 对应上面的1号fd, 第二个参数名是oldfd, 对应上面的新打开的文件。 也就是说将oldfd里面的内容拷贝到newfd里面。

dup2的使用——输出重定向

        dup2可以直接将数组中的一个fd覆盖到另一个数组fd。 我们dup2的第一个参数是新打开的文件fd, 第二个参数是要拷贝到的fd的位置

        如下为代码:

运行结果如下:

        我们也可以把清空写改成追加写:

运行结果如下:

 dup2的使用——输入重定向

先创建一个数组进行拷贝拷贝, 然后向显示器中读取, 如果读取, 那么打印读取的内容。

此时是向键盘中读取:

我们使用dup2, 将新打开的文件覆盖到0号fd。 就是输入重定向, 将新打开的文件的数据打印:

如图就是将新打开文件的数据打印到inbuffer。 再将inbuffer的数据打印。 我们在log.txt里面写上aaaaaaaa

下面是打印内容:

自定义shell实现重定向指令

        如何自己实现>, >>, < 指令

        要自己实现>, >>, <指令, 我们就要拿出我们之前写的自定义shell的代码了。 

        在代码中, 我们需要先新定义几个宏——NONE代表没有重定向, IN_RDIR代表输入重定向, OUT_RDIR代表输入输出重定向, APPEND_RDIR代表追加重定向。

        也要定义两个新的变量——rdirfilename指向重定向文件的首地址, rdir代表重定向的标志。

如下图宏定义:

新创建的变量:

在交互函数里面分析是否有重定向, check_rdir就是重定向判断的函数:

下图是check_rdir的实现:

然后我们再在执行普通命令的板块里面创建一个新的代码块。 也就是当id == 0的时候, 判断此时的rdir的状态, 如果是NONE才是exec, 正常加载执行逻辑。 如下是代码:

然后我们还要在每次输入指令的时候都给rdir和rdirfilename做初始化:

运行出结果之后:

 

文件的重定向和进程替换

        现在有一个问题, 就是在重定向的时候, 我们修改了fd。 然后加载了子进程, 为什么这样做是正确的呢?——要解决这个问题, 就要拿起进程的知识了, 如下图:

        在上面的图里面, PCB和文件管理, 是内核数据结构; 而虚拟地址空间, 物理内存, 页表, 是进程数据结构, 这两个是结偶关系。 而对于物理内存, 程序和代码加载替换掉物理内存, 页表重新映射物理内存。 这个过程, 在内核数据结构里, 并不关心。

        所以, 文件的重定向和进程替换之间互不影响!!!

重定向的参数

我们使用重定向, 可能遇到下图这种只有一部分数据重定向到了新文件, 但是还有一部分直接打印到了显示器的情况:

        上面描述的问题就是重定向了一部分, 但是还有一部分没有重定向, 这是因为对于重定向来说, 默认是将打印到显示器的数据重定向到文件中。

        想要将两部分——stdout、stderr两个部分的数据都进行重定向, 就需要使用参数fd, 使用方式如下: fd > 文件。

        如下图使用:

这两种方法我们要谈的是第二种方法:./newfile.exe 1 > both.log 2>&1, 这里面2>&1的意思就是说, 将1fd的内容拷贝到2fd里面去。 而1已经重定向到了both.log, 所以, 将1的内容拷贝到2fd里面去后。 本应该打印到2fd里面的内容也会被打印到文件中。

如何理解计算机下“一切皆文件”

        对于计算机来说, 所有的操作计算机的动作, 都是以进程的方式进行操作的。所有访问文件的操作, 最终都是用进程的方式访问文件的。

        计算机上所有应用的所有操作, 最终都会被系统解释成进程。 目前, 所有对文件的操作, 全部都依赖进程的操作。 

        而且, 我们知道, 对于冯诺依曼体系结构来说, 底层大部分都是外设!!!如下图:

        上面就是一个一个打开文件后创建的结构体, 下面就是底层硬件。

         对于上面图中的底层设备, 每一个外设的读写方法都是不一样的, 也就是他们的struct file是不一样的。 所以这个时候每一个struct file里面都有一个指针指向struct operation_func类型的结构体。

如下图:

那么, 未来操作系统为了进行文件操作, 就会先创建一个进程:

然后, 操作系统又专门给我们定义了系统调用:

        所以, 操作系统就实现了在上层统一使用read, write接口, 然后在下层根据文件的不同, 找到不同的write, read方法。

         所以, 一切皆文件——就是操作系统在文件层面上封装一层struct file结构体对象, 然后, 根据这个对象里面的指针找到对应文件的write, read。 而这里的write, read同样是一层封装各种各样读写方法的结构体。 而真正的各种设备的读写方法如何实现, 我们并不关心!!!

        从struct file往上, 就是用户!是给我们看到的, 我们看到的, 就是struct file。 看到的就是——一切皆文件!!!

        所以, 在linux中, 在struct file这一层, 被称作VFS——virtual file system虚拟文件系统

        以后, 当我们的进程再想实现open, write这些接口的时候, 就会先找到struct file。 然后struct file就回去找到自己里面的operation_func, 至于operation里面是什么情况, struct file并不关心。 而这, 就是多态。 这里面的一层一层的指针的包含关系, 就叫做继承!!所以, 如果未来我们想用c语言实现c++的多态, 我们怎么做呢?——其实就是在c语言的结构体里面封装一个个变量,当作事物的属性;然后再定义几个函数指针指向想要使用的事物方法。 这就形成了一个类。而c++里面的虚函数表本质上就是函数指针数组, 我们在结构体里面封装一个函数指针数组足为虚函数表。 那么就可以形成多态!!!

以上, 就是本节的全部内容, 下面是博主整理的个人笔记:



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。