本篇文章,继续和大家分享与Linux相关的知识。本次的内容主要会涉及重定向的功能,原理,模拟实现以及如何理解Linux中一切皆文件。

文件描述符分配规则

通过上一篇文章我们了解到了什么是文件描述符,文件描述符本质就是数组下标。可这些文件描述是怎么进行分配的呢?

我们可以通过设计一个简单的程序来测试了解。程序在启动的时候,会默认打开三个文件,标准输入,标准输出,标准错误,它们的文件描述符依次是0,1,2。我们分别把它们关闭。然后,打开一个文件,查看这个被打开文件的文件描述符

Linux-重定向_输入重定向

编译运行,我们可以看到被打开文件的描述符是0。内容正常写入到了log.txt文件里。

Linux-重定向_输出重定向_02

刚刚我们关闭的是描述符为0文件,我们现在改为关闭1

Linux-重定向_dup_03

编译运行,我们发现什么也没打印,文件还是正常写入了五次信息。什么也没打印,是因为我们把1位置的显示器文件关闭了,而printf底层封装了1位置的文件。

Linux-重定向_dup_04

我们这次将2位置的文件关闭

Linux-重定向_输出重定向_05

编译运行,这次被打开文件分配到文件描述符为2,log.txt文件也能正常写入

Linux-重定向_dup_06

通过刚刚测试结果的打印结果,我们可以了解到文件描述符的分配规则是从数组下标0开始,线性查找数字最小且为空的位置,作为新打开文件的文件描述符。

输出重定向(>)

刚刚我们一直都是向打开的文件里写入数据,这次我们固定向位置为1的显示器文件写入数据。然后,重复刚刚的实验,依次分别关闭0,1,2这三个文件。

关闭0位置的文件

Linux-重定向_输出重定向_07

删除log.txt文件后,编译运行。程序打印信息,文件没有内容

Linux-重定向_Linux中一切皆文件_08

关闭位置1的文件

Linux-重定向_追加重定向_09

删除log.txt后编译运行,我们发现本该打印到显示器的信息,写到了文件里

Linux-重定向_Linux中一切皆文件_10

关闭位置2的文件

Linux-重定向_dup_11

删除log.txt文件后,编译运行。这次信息也正常打印,文件里也没有内容

Linux-重定向_追加重定向_12

为什么,关闭位置1的文件,信息就不能正常打印到显示器上呢?我们来简单的分析一下。当我们将位置为1的文件关闭了,数组下标为1的位置就被空出来。此时,我们打开文件log.txt,这个位置就会被分配给log.txt。当write向数组下标为1位置的文件写入内容时,向谁写入?数组下标为1的位置指向的文件是log.txt,当然是向log.txt文件写入。所以,本该打印到显示器的内容,也就到了log.txt文件里。这个过程中,write知道要想1位置的文件写入数组,并不知道你把数组下标1位置的文件改了。这种将要写入显示器文件的数据改写到log.txt文件中的方式,就是重定向的原理

Linux-重定向_Linux中一切皆文件_13

操作系统对于重定向提供了相关的接口,我们并不需要每次重定向都close谁这么麻烦。

dup

dup有三个接口,我们这里介绍常用的dup2函数

Linux-重定向_Linux中一切皆文件_14

这个函数设计的有点奇怪,它把我们打开的log.txt的文件描述符称为oldfd,即第一个参数。把数组下标1位置的文件称之为newfd。即第二个参数。这个函数会把oldfd位置的内容拷贝到newfd位置。以我们刚刚的用例来解释,就是把数组位置3的内容拷贝到数组位置1,让数组位置1所指向的文件,由显示器文件变成log.txt文件。

Linux-重定向_输出重定向_15

下面,我来演示一下

Linux-重定向_dup_16

删除log.txt后,编译运行。原本要打印到显示器的内容就写到了log.txt中

Linux-重定向_追加重定向_17

追加重定向(>>)

刚刚我们了解了什么是重定向的实现方法,那追加重定向是怎么实现的呢?很简单,我们只需要把open函数的O_TRUNC选项改为O_APPEND选项,就是追加重定向了

Linux-重定向_追加重定向_18

编译运行,我们可以发现每运行一次,log.txt文件的内容就会增多

Linux-重定向_追加重定向_19

输入重定向(<)

输入重定向,它也就通过dup函数实现的。我们原本是从显示器文件里读取内容的,我们可以调用dup2函数,让read函数由读取显示器文件变为读取log.txt文件。

下面,我们来验证一下。这里有两点需要注意的,一是read函数的使用,它的第三个参数,是我们期望读取到的数据大小,而它的返回值是实际上读取到的数据大小。二是,C语言中规定字符串以字符'\0'结尾。所以,我们在传read的第三个函数的时候,需要对空间大小减一,给字符'\0'留个位置。

Linux-重定向_dup_20

编译运行,我们就能获取到log.txt文件中的内容了

Linux-重定向_Linux中一切皆文件_21

通过前面的那些验证,我们也就真正明白了,我们在使用输出重定向符号,追加重定向以及输入重定向时,程序大概在做什么了。

还记得我们前面文章,自定义的myshell程序吗?如果你有跟着模拟,此时,我们可以再增加这三个功能。

增加myshell的重定向功能

第一步:在原来的代码基础上,增加四个宏,NONE表示没有重定向符号,IN_RDIR表示输入重定向,OUT_RDIR表示输出重定向,APPEND_RDIR表示追加重定向。增加两个变量,rdirfilename表示重定向的文件名,rdir表示是那个重定向符号

Linux-重定向_追加重定向_22

Linux-重定向_输入重定向_23

第二步:在interact函数增加一个check_redir函数,用来检查重定向符号

Linux-重定向_Linux中一切皆文件_24

第三步:实现check_redir函数

Linux-重定向_输出重定向_25

第四步:在normalExcute函数里,加三个dup2的调用

Linux-重定向_输出重定向_26

第五步:在程序最开始的时候,初始化rdirfilename和rdir这两个变量

Linux-重定向_Linux中一切皆文件_27

做完这些,我们的模拟的myshell就具有了重定向的功能

Linux-重定向_dup_28

Linux-重定向_Linux中一切皆文件_29

这里有个问题,我们进行重定向各种关系,不会影响后面的程序替换吗?

Linux-重定向_输入重定向_30

答案是不会,你看看下图就明白了。struct file也是内核数据结构的一部分,这部分是进程管理,而进程替换是内存管理的事,它两完全不知到对方做了什么,只知道自己做了什么。

Linux-重定向_输出重定向_31

stdout和stderr

stdout和stderr这两个货,本质都是显示器文件,为什么要弄两个出来?

我们先做一个小实验:

Linux-重定向_Linux中一切皆文件_32

编译运行,我们发现信息可以正常打印,但当我们进行输出重定向的时候,只有错误的信息打印。这是什么原因?

Linux-重定向_dup_33

你想一下,是错误信息,还是正常的信息重要?当然是错误的信息啦!如果将错误信息和正常的信息打印到一起这样会很乱。所以,有了stdout和stderr两个显示器文件,将错误信息和正常信息分开来。

那我们如何使用输出重定向将打印的信息,分别向两个不同的文件呢?

使用如下的指令,

[common_108@iZf8zaj27gxmvq7veqrekfZ test]$ ./mytest 1>normal.log 2>err.log
  • 1.

即可将错误信息和正确信息分到normal.log和err.log这两个不同文件中了

Linux-重定向_追加重定向_34

也可以省略指令中的1,效果是一样的

[common_108@iZf8zaj27gxmvq7veqrekfZ test]$ ./mytest >normal.log 2>err.log
  • 1.

Linux-重定向_dup_35

如果你想就想把错误信息和正常信息打印到一块,可以是使用如下指令

[common_108@iZf8zaj27gxmvq7veqrekfZ test]$ ./mytest 1>all.log 2>&1
  • 1.

这样信息就写到了一个文件中。“2>&1"这个部分的意思是把1号文件描述符指向位置中的内容写到2号文件描述符指向的位置

Linux-重定向_dup_36

如何理解Linux中一切皆文件?

计算机大部分都是外设,每个外部设备,都有自己的操作方法接口,提供给我们使用。磁盘的方法这里用read_disk和write_disk表示,显示器用read_lly和write_lly来表示,键盘用read_keyboard和write_board表示。

Linux-重定向_输出重定向_37

每个外部设备的方法可能都不一样,但它们本质都是读与写,我们根据这一共性。可以定义一个struct operation_func结构体,这个结构体里有两个函数指针用来指向外部设备的方法。

Linux-重定向_输出重定向_38

此时,你可能还每感受到struct operation_func结构体的作用在那?我们往下看,每个设备它们都不同,但它们都会有诸多的共性,比如工作状态,设备名称,设备使用方法等等。通过提取它们的共同属性,我们就可以struct file来表示一个一个的设备。每个表示设备的结构体里又有一个f_ops这样的指针指向对应的struct operation_func结构体,这个结构体又指向对应设备的使用方法。

以图的方式呈现它们的关系就是下面这样:

Linux-重定向_Linux中一切皆文件_39

借助这个图的关系,我们可以发现,不管我们调用那个设备的都方法,都可以统一写成task_struct->files->fd_array[fd]->f_ops->read()。

Linux-重定向_输入重定向_40

这个有没有感觉到一丝熟悉感,这不就是C++中的多态吗?代码是一样的,但程序可以指针的指向动态的访问不同设备的读写方法。

我们把struct file那一层称之为虚拟文件系统,简称VFS。从这一层往上看就是一切皆文件!

Linux-重定向_追加重定向_41

好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。关于更多和Linux相关的知识,会在后面的文章更新。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。