目录
前言
一、文件描述符的分配规则
二、重定向
三、系统中的重定向接口
1、dup2()介绍
2、dup2()使用
1)输出重定向和追加重定向
2)输入重定向
四、文件缓冲区
1、定义
2、缓冲区刷新的条件
1)文件缓冲区存在的意义
2)刷新策略
3)一个重定向问题
五、命令行重定向的用法
前言
上一篇讲到了文件描述符,我们知道每次自己新创建的文件描述符都是默认从3开始的,因为stdin、stdout和stderr分别对应的0、1、2是默认打开的。所以后面是从3开始的。接下来我们分别关闭0、1、2的文件再创建新的文件看看有什么影响
一、文件描述符的分配规则
我们先关闭0号文件描述符所对应的文件,即关闭了stdin
可以看到新打开的文件的文件描述符变成了1。这说明文件描述符的分配规则是,从头遍历这个fd_array数组,如果看到哪一个文件描述符没有被使用,就将这个文件描述符分配给新打开的这个文件。
二、重定向
我们已经了解了文件描述符的分配规则,再来看一个现象,我们知道系统默认打开的三个文件分别是标准输入、标准输出和标准错误,分别对应着0、1、2文件描述符。1号文件描述符对应的是C语言中的stdout指针,其又被称为标准输出流,使用fprintf()可以向标准输出流写入内容,从而可以将写入的內容打印到显示器上,如果关闭了1号文件描述符,那么就不能向显示器输出信息了。
为什么此时屏幕没有输出呢?fprintf输出的数据去哪里了呢?
答:因为我们一开始就将1号文件描述符文件关闭了,此时再新打开一个文件时,系统就会将1号文件描述符分配给它,fprintf()是向stdout输出的,但是从fprintf的角度看,fprintf函数是C 语言函数,它并不知道操作系统已经将stdout关闭了,它只知道文件描述符1,所以,它会继续向文件描述符为1的对应文件写入,而我们知道文件描述符1已经分配给了新打开的文件,因此,此时fprintf函数会向新打开的文件写入,但是我们打开myfile.txt文件的时候会发现里面啥都没有。
这又是为什么呢?这就涉及到了刷新缓冲区的问题了。 我们先简单理解为输出的时候没有刷新缓冲区,此时没有办法输出到外设,我们此时可以强制刷新缓冲区。
上面所演示的就是所谓的输出重定向。将本来打印到标准输出流的信息,却打印到了刚刚打开的文件中。
如图所示:
在我们没有关闭默认的文件的情况下,打开新文件
当我们关闭标准输出时,再打开新文件:
此时 再向stdout流中写入数据,就不是在向标准输出写入数据了,而是在向打开的指定文件中写入数据。这就是一种重定向。
三、系统中的重定向接口
1、dup2()介绍
上面我们已经知道了通过关闭文件来实现重定向,但是这个未免也太过于麻烦,系统中已经给出了重定向的接口,我们先来介绍一个:dup2()
可以看到,dup2()接口的两个参数分别是两个文件描述符,并且newfd是oldfd的一份拷贝(实际上是将oldfd文件描述符里面的內容拷贝一份到newfd),dup2既然是一个重定向接口,那么其操作的就是两个文件,将一个文件重定向为另一个文件。
那么newfd和oldfd哪一个是重定向的文件,哪一个是被重定向的文件呢?实际上我们可以这么理解,在将oldfd文件描述符里面的內容拷贝一份到newfd,oldfd文件描述符保存的什么呢?保存的指向其所对应文件的指针,我们将oldfd的文件指针拷贝了一份给newfd,即覆盖后,newfd就不会找到原来它所对应的文件了,而是同样指向了oldfd所指向的文件,于是newfd和oldfd同时描述原来只由oldfd描述的文件。
2、dup2()使用
1)输出重定向和追加重定向
stdout
是一个FILE *
类型的流,它是C标准I/O库的一部分,而_fileno
是这个结构中的一个成员,它指向实际的文件描述符。然而,一旦stdout
流被关闭(例如通过fclose(stdout)
或重定向),stdout
流的状态就不再有效 。
可以看到成功将字符串重定向输出到指定文件夹中。
下面演示一下追加重定向
只需要将
2)输入重定向
类似输出重定向,来实现一个输出重定向
fgets()本来是从输入流中读取字符,但是我们用了重定向的接口,所以fgets()就从我们新打开的文件中获取字符。
四、文件缓冲区
我们到现在为止对文件缓冲区还没有一个深刻的认识,下面就来介绍一下Linux系统的文件缓冲区。
1、定义
文件缓冲区是一块内存空间,是存放进程向操作系统内核写入的数据的。
1、可以看到的现象是先打印了后面的,再打印的是前边的,这是为什么呢?
这种现象就和我们今天说的文件缓冲区有关,我们可以得出结论,系统调用接口write是不存在缓冲区的,所以调用write是直接向标准输出写数据,会直接在屏幕中打印出来。而printf()如果不刷新缓冲区的话,就不会打印信息出来,且C语言中的printf()底层一定是调用了write(),所以说它是在满足一定的条件下才会调用write()接口,这个一定条件就是缓冲区的刷新条件。
2、文件缓冲区在哪里呢?
文件缓冲区是在printf()内部使用的,write()接口并没有文件缓冲区,文件缓冲区本身就是由语言本身提供的,与操作系统是无关的。我们都知道C语言的FILE是一个结构体,里面封装了好多与文件相关的属性,其中就包括了文件缓冲区。我们在使用C语言的文件接口的时候,每次新打开一个文件都会返回一个FILE*,文件缓冲区就在这个FILE结构体中描述着,也就是说,C语言每个打开的文件都会有着自己的独立的文件缓冲区。
3、也就是说,我们只要使用底层调用了write()接口的C语言接口,例如printf,fprintf,fputs等,都会使用到C语言提供的文件缓冲区。只有在缓冲区被刷新时,才会真正调用write()接口向文件中写入数据。
2、缓冲区刷新的条件
如果我们考虑在刷新前就关闭了输出流,那么会发生什么?
如果没有关闭stdout,那么在当前进程退出的时候,缓冲区将会被刷新,这三个语句将会被打印在显示器上,但是我们在进程退出前关闭了stdout文件,所以就不会将这些语句从缓冲区刷新到显示器文件中了,即不会再打印出来了。我们不关闭显示器文件的运行结果如下:
所以缓冲区刷新的条件究竟是什么呢?首先我们来探讨一下缓冲区存在的意义是什么?
1)文件缓冲区存在的意义
- 我们知道进程有一种状态称为阻塞状态,即当前进程需要与其他硬件进行交互的时候(获取硬件资源),如果当前硬件资源被占用,那么它就会进入阻塞状态,等待硬件分配资源,我们通过程序向显示器文件中打印数据,也是一种与显示器资源交互,获取显示器资源的行为,如果当前显示器资源被占用,如果进程只为了打印一个语句而在这里一直处于阻塞状态,太过于浪费资源。有了缓冲区之后,就可以将数据先存储到缓冲区中,这样进程就可以去干点其他的事。
- 第二点就是文件缓区是用来减少I/O次数的,有了它之后我们可以减轻操作系统的负担,试想如果没有文件缓冲区,我们循环无休止地向显示器文件打印,则操作系统要不断进行I/O,这样会加重操作系统的负担。
2)刷新策略
有了上面的了解之后,我们就再来讨论什么样地刷新策略才能更好地适配操作系统呢。
- 无缓冲,立即刷新,每次存储在缓冲区地內容都会被立即写入系统的内核数据,并刷新缓冲区。
- 行刷新,即遇到‘\n'刷新,例如我们使用的printf()接口向显示器文件中写入数据的一般都采用的是行刷新策略,当输出的內容结尾处有'\n’时,会将'\n'及之前的数据都打印出来。
- 全刷新,即缓冲区满了再刷新。该策略一般在向块设备对应的文件如磁盘文件中写入数据时采用。
- 当进程退出的时候,文件缓冲区会自动刷新
- 使用fflush()函数强制刷新文件缓冲区。
注意事项:
- 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲的。
- printf、fwrite库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式就由行缓冲变成了全缓冲。
- 我们这里所提到的缓冲区都只是用户级缓冲区,其实OS也会提供相关内核级缓冲区,我们不做讨论。
3)一个重定向问题
我们运行以下代码
结果没有任何问题,因为fork()后创建的子进程只能从fork()之后开始执行
但是当我们将输出重定向到其他文件的时候就会出现如下结果
所以这是为什么呢?
输出重定向到了一个文件中. 那么文件缓冲区的刷新策略就改变了, 上面介绍过向文件中写入数据, 文件缓冲区的刷新策略是全刷新. 所以在执行前三个语句时, 会将三句话都存储到文件缓冲区内且不刷新. 而执行系统接口wirte()是没有缓冲区的, 所以会率先写入到文件中.
之后, 进程会创建子进程. 我们知道, 子进程和父进程在不修改数据时是共享一份代码和数据的. 而无论父子进程谁要修改数据, 就会发生写时拷贝. 子进程被创建时, 很明显父进程的文件缓冲区还没有被刷新. 那么也就是说子进程创建出来时, 是与父进程共享同一份文件缓冲区的。 那么接下来, 无论是子进程先终止, 还是父进程先终止, 都需要清除共享的文件缓冲区. 而fork()父子进程修改数据的机制是, 只要修改就会发生写时拷贝, 所以在进程要清除文件缓冲区时, 另一个进程会先拷贝一份 . 拷贝完成之后, 先终止的进程就会刷新文件缓冲区, 将缓冲区内的数据写入到文件中, 然后另一个进程终止, 将拷贝的文件缓冲区也刷新掉, 将相同的数据写入到文件中。
五、命令行重定向的用法
我们知道stdout和stderr都对应的是显示器,但是一个是标准输出,一个是标准错误,接下来我们向这两个中分别写入些数据查看结果。
接下来我们将输出结果使用符号重定向到其他文件中
这表示stdout、stderr虽然都是代表的显示器,但是并不是受到同一个控制的。默认的输出重定向是修改的标准输出,对标准错误不起作用,那么我们该如何控制标准错误呢?
1> 表示的是输出重定向, 2>表示的是错误重定向。所以在命令行中,重定向的完整正确用法是这样的——fd > 文件
0 > 是输入重定向 1 >是输出重定向 2 >是错误重定向 >>代表的是追加重定向
我们还可以同时输出重定向和错误重定向
这样的用法可以分离程序的运行日志,可以将运行错误的日志分离出来以便分析。
将输出、错误重定向同时重定向到同一个文件