文件描述符
我们都知道,使用open打开一个文件后都会得到一个文件描述符,而且是一个非负正数,那这个数字是怎么来的呢?
当我们打使用open打开文件时,系统会为我们指定的文件创建一个文件描述信息结构体(struct file
),用来描述文件所在的地址及名称等信息,而在我们的进程中,还保存了一个文件信息表(struct files_struct
),文件信息表中有一个结构体数组(struct file *fd arry[]
),是用来保存文件描述信息的地址,而文件描述信息的地址所在的下标,就是文件描述符。
当我们使用文件描述符去读写文件时,会通过文件描述符去文件信息表下的结构体数组对应的下标获得到相应的地址,再通过地址找到文件描述信息,从而找到在磁盘中我们想要的那个文件
流程图如下:
所以我们每次打开一个文件,不操作了就需要close关闭并释放资源;结构体数组大小是有限的,下标也就是有限的,若不关闭文件,文件描述符用完了,那么在进程中就无法打开新的文件了。
细心的小伙伴会发现为什么每次文件标识符都会从3开始的,其实只要一个程序被运行起来,进程就会被默认打开3个文件,0-标准输入;1-标准输出; 2-标准错误
文件描述符分配规则:最小未使用—从数组0号下标开始找到未使用的下标分配给新的文件
假如我们把0号文件关闭了,那么我们再打开一个文件时,它的文件描述符就会成为0。
那假如我们关闭了1号文件描述符,会是怎样的结果呢?
我们发现并没有打印任何东西,原因是printf函数时打印数据到标准输出,也就是将数据写到1号文件描述符中,也就是当前的标准输出流中,标准输出流再将其打印;但是如果close(1)后,再打开一个新文件,那么新文件会占用1号这个位置,printf函数就不会直接打印数据,而是在刷新缓冲区之后将数据写到了新的文件中
这其实就是重定向的一个典型例子,重定向原理:将数据不再写入原本的文件,而是写入新的指定的文件中,实现方式就是替换这个文件描述符对应的文件描述信息;说到底就是文件描述符的重定向,改变描述符所指的文件,就改变了数据的流向。
在我们库函数中,也有重定向的函数–int dup2(int oldfd, int newfd)
–让newfd这个描述符也指向oldfd所指向的文件,这时候oldfd和newfd都能操作oldfd所指向的文件
这样子就能做到1个描述符能操作两个文件了
实例:实现一个带有重定向功能的shell
文件描述符与文件流指针的关系
文件描述符:是一个非负正数 —系统调用的IO接口
文件流指针:FILE结构体 —typedef struct_IO_FILE FILE --库函数IO接口的操作句柄
我们知道库函数封装了系统调用接口,底层最终还是会调用系统调用接口的,通过文件流指针进行文件操作的时候,依然还是要能够找到文件对应的文件描述符才行。
文件流指针是一个结构体,结构体中有很多成员变量,其中_fileno
为文件描述符
现在我们改变我们打开的文件的文件描述符,让该文件描述符变为1
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
FILE *fp = fopen("./test.txt", "r+");
fp->_fileno = 1;//将文件流指针中的文件描述符改成标准输出的描述符了
fwrite("Hello WhiteShirtI\n", 1, 18, fp);
fclose(fp);
return 0;
}
我们发现并没有将文件写到test.txt文件中,而是将内容打印出来
在我们想向文件写入数据时,并不会直接写入文件,而是先写入到缓冲区,刷新缓冲区的时候才会写入文件。系统调用接口是直接将数据写到文件中,系统调用接口是没有缓冲区的,只有库函数才有这个缓冲区,而这个缓冲区是在文件流指针结构体中的缓冲区–通常称为用户态缓冲区