一、重定向
1、stat() 与 read() 函数
(1)stat() 函数
path:文件路径
buf:输出型参数,用于返回文件的各种属性。
函数成功返回 0 ,失败返回 -1
(2)read() 函数
fd:文件描述符
buf:输出型参数,返回读到的文件内容。
count:要读的个数。
(3)举例
创建一个 stat 结构体对象用于返回 stat() 函数返回的文件属性,对象中的 st_size 就记录文件的大小。
2、输出重定向实现原理一
在前文我们提到过 > , >> , < 三个符号
符号 | 名称 | 作用 |
> | 输出重定向 | 把默认写入到显示器的数据写入到文件 |
>> | 追加重定向 | 把默认追加写入到显示器的数据追加写入到文件 |
< | 输入重定向 | 把默认从键盘中读到的数据显示到显示器上 改成从文件中读数据显示到显示器上。 |
我们这里以输出重定向为例,感受一下重定向是怎么操作的。
首先我们要知道分配文件描述符是在文件描述符表中遍历查找最小的,没被占用的数字作文件描述符。
所以输出重定向的一种操作:
(1)关闭一号文件(stdout 标准输出文件)
(2)打开指定文件,此时该文件的文件描述符就是1
(3)由于 printf,fprintf 函数只认文件描述符,所以在写入文件时他们就会向一号文件(本来是写入到显示器文件)现在写入到了指定文件,就达到了从显示器文件写变成写到文件中。
重定向的本质:在内核中改变文件描述符特定下标的内容,与上层无关。
3、重定向实现原理二
函数 dup2()
所以函数 dup2() 的作用就是把 newfd 文件变成 oldfd 文件的一份拷贝(不仅是文件内容,也包括指向具体文件的指针)。
所以 dup2(fd, 1); 的含义就是让文件描述符是1的文件(标准输出文件)变成指定文件的拷贝,所以原本指向显示器的指针就指向了指定文件,此时再 printf,fprintf 就是向指定文件写入。
4、标准输出 stdout 和标准错误 stderr
(1)两者区别
stdout 是对应文件描述符1,stderr对应文件描述符2,但指向的硬件都是显示器。
为什么上面的重定向只有 stdout 实现了?
其实我们一直用的重定向是不完整的,符号 > 前面如果不加文件描述符默认是 1,所以只有stdour重定向了。
所以文件描述符的不同我们就可以把他们的内容重定向到不同文件。
上图就是把 stdout 的内容写到 ok.log ,把 stderr 的内容写到 err.log
$1 表示把1号文件的内容放到2号,所以1的文件指针也给了2,2的文件指针指向了 all.log 文件进行写入。
(2)stderr 存在意义
就是区别 stdout 的正确信息,有利于我们区分正确与错误信息。
在c语言中 perror() 函数本质是向标准错误文件打印,printf() 函数本质是向标准输出文件打印。
在c++中,cout 对应 printf() ,cerr 对应 perror()
二、缓冲区
1、缓冲区好处
(1)解耦
(2)提高效率
a、提高使用者效率:使用者只要把数据写到用户级缓冲区,至于之后的数据如何刷新,怎么写入硬件通通不用关心。
b、提高刷新IO效率:不是每一次调用 printf 都要直接把数据交给内存级缓冲区,因为操作系统很忙,先存放在语言级缓冲区,等积攒大量数据之后一次性写入到内核级缓冲区,减少系统调用次数,就减少成本。
2、缓冲区刷新策略
(1)立即刷新(无缓冲)
a、语言级刷新:把语言级缓冲区的数据刷新到内核级缓冲区。
#include<stdio.h> fflush(FILE* stream)
b、内核级刷新:把内核级缓冲区的数据刷新到硬件。
#include<unistd.h> fsync(int fd)
(2)行刷新
只有显示器才有。
(3)全刷新
缓冲区写满才刷新,用于普通文件。
(4)自动刷新
进程退出自动刷新。
3、细节问题
(1)在内核级缓冲区中的数据,fork() 创建子进程时不会被拷贝,只有语言级缓冲区的数据才会被拷贝
(2)FILE 作为结构体,里面申请了许多缓冲区,所以每打开一个文件就会有对应的一个缓冲区。