目录
- 一、文件描述符
- 二、文件流指针
- 三、缓冲区
之前讲解过了IO库函数和IO接口,库函数是对系统调用接口的封装,也就是说实际上在库函数内部是通过调用系统调用接口来完成最终功能的。
库函数通过文件流指针操作文件,系统调用接口通过文件描述符操作文件。实际上文件流指针就是系统将文件描述符封装后得来的,本文来简单聊一聊它们。
一、文件描述符
文件描述符:
使用系统调用接口open打开一个文件,如果打开成功会返回一个非负整数,这个数字就是这个文件的文件描述符,就是一个文件的操作句柄。
操作句柄:
用杯子来举例,我们要喝水时,端着杯子的把手,借助这个把手来操作杯子,这个把手就是杯子的操作句柄。在文件操作中,我们通过文件描述符来操作对应的文件,这个文件描述符就是文件的操作句柄。
为什么可以通过文件描述符操作文件?
文件是什么?文件就是一块磁盘空间。给文件写入数据,其实就是写入到了磁盘的指定位置。但磁盘中不止存储了这个文件,还有一块空间用来存储这个文件的描述信息,这个描述信息就是文件的属性。这就类似于进程pcb,pcb就是对动态运行的程序的描述。
如图:硬盘中存储了文件和文件的描述信息。
在程序的运行中,我们可能会有多个文件被使用,所以我们要把这些文件管理起来,让我们可以高效的访问。
因此在Linux的进程pcb中还有一个用来存储文件相关内容的结构体 files_struct,这个结构体中有一个指针数组 fd_arr[],这个数组中存储的就是每个文件的描述信息 struct_file 的地址,而这个地址的下标就是文件描述符。
通过文件描述符控制文件其实就是找到files_struct后,找到fd_arr数组,然后根据的文件描述符(也就是数组下标),找到数组中的对应的地址,然后就可以找到文件的描述信息。
二、文件流指针
系统调用接口通过文件描述符来操作文件,而库函数则是通过文件流指针来操作对应文件。
库函数和系统调用接口的关系
库函数其实是对系统调用接口的封装,系统调用接口加上一点其他内容就构成了库函数。因此文件流指针其实就是对文件描述符的封装,它的内部也是使用文件描述符来操作文件的。
文件流指针就是一个 struct FILE* 类型的变量,而struct FILE是stdio.h头文件中的结构体,我们来看一下头文件中对于FILE的描述。
如图:头文件中可以看到,FILE其实是_IO_FILE的别名,所以FILE其实就是_IO_FILE,FILE* 指针就是 _IO_FILE* 指针。
接下来看一下 _IO_FILE 结构体的部分内容:269行的 int _fileno 就是文件描述符,结构体把文件描述符封装了起来。
三、缓冲区
IO库函数有缓冲区,而系统调用IO接口没有缓冲区,因为缓冲区封装在库函数中的 _IO_FILE 结构体中。
这幅图的252到263行其实就是缓冲区,用读缓冲区举个例子。缓冲区是在用户空间中申请的一块空间,252行的指针指向这块空间的当前读写位置,253行的指针指向这块缓冲区空间的结束地址,254行的指针指向这块空间的开始地址。
当读取数据时,就会从当前读写位置开始,一直读取到空间结束。写入文件时,从当前读写位置开始写,当写入到结束位置,代表着缓冲区写满了,需要刷新了。这样就用指针实现了缓冲区。