文章目录
- 前言
- 什么叫文件描述符
- FILE与fd的关系
- 再次理解文件
- 为什么要有文件的方法列表呢?
- 进程和struct file的关系
- 再次理解open操作
前言
C语言的关于文件操作的各种函数实际上是对系统调用的封装。那么从进程的角度看,一个文件到底是如何被描述的呢?又是如何被组织并管理的呢?
在此之前,如果对C语言以及系统调用关于文件相关内容的知识还不太了解或许需要复习的同学,可以去看我之前的博客:
C语言关于文件概述以及文件操作
关于文件的系统调用
什么叫文件描述符
所有的I/O设备都被模型化为文件,而所有的输入输出都被当成对文件的读写操作。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单的,低级的应用接口,成为Unix I/O,着使得所有的输入输出都能以一种统一切一致的方式来执行,这也是为什么说linux下一切皆文件。
当程序通过要求内核打开相应的文件。内核会返回一个小的非负整数。这个非负整数就叫做描述符,也叫文件描述符。文件描述符是用于唯一标识文件的号码。下面我用fd表示文件标识符。
也就意味着,我们的进程实际上并不记录文件本身,而只需要记录一个为一个文件ID。所有被打开的文件的信息都被集中在一起被内核管理,内核向进程提供文件的接口。
尝试打开多个文件并观察其文件描述符:
我们发现打开的文件其ID是从3开始的,这是属于巧合吗?
其实并不是
Linux shell 创建的每一个进程开始的时候都有三个打开的文件:标准输入流(fd=0),标准输出流(fd=1),标准错误流(fd=2)。这三个文件分别对应的硬件设备是键盘、显示器、显示器。
在c语言中也会默认给我们打开三个文件
如何证明这三个文件会默认打开呢?
对于以上结果,我们可以直接在stdout文件里写入数据,说明了stdout文件确实是被默认打开了!
再证明stdout文件的描述符是1,看代码:
这里使用系统调用的write函数直接向文件描述符为1的文件写入数据。我们发现文件标识符1指的就是stdout文件。由此,证明了进程会默认打开stdout文件,并且该文件的fd=1.其余两个文件也是同样的道理。
FILE与fd的关系
FILE是C语言用来描述文件的一个结构体,在了解了文件描述符之后,我们其实也能明白FILE的底层实现了对fd的封装。
再次理解文件
以用户的视角来看,文件=属性+内容。以操作系统的视角来看,文件其实就是一个名为file
的struct
。
每当我们打开一个文件,操作系统就会生成一个struct file
记录该文件的所有信息,包括以何种方式打开,文件的标记位等。并将这个file
以双链表的形式加入到所有打开文件的集合中,这个集合统一由操作系统管理。
其中值得我们关注的是,struct file
内部会包含一个指针,指向该文件的内核级缓存区。进程就是在这个区域里面进行读写数据。该区域的存在避免了进程直接向磁盘读写数据,提高了效率(缓冲区存在的意义)。
除此之外,struct file里还含有文件的类型以及该文件的方法列表。方法列表包括了open方法,write方法等。
为什么要有文件的方法列表呢?
首先所有的硬件都有一定的存储能力。这也就意味着我们能以看待磁盘的视角来看待其他硬件。比如显示器,键盘等。我们所说的什么键盘文件其实就是用来跟键盘的存储空间打交道的。不同硬件读写数据的操作方法可能不一样。比如键盘文件就没有向其写数据的方法,我们只能从键盘读取而不能写入!
所以方法列表中的元素其实就是一个函数指针,指向对应硬件的读写方法。
虽然各个硬件中的读写方法存在差异,但是相同功能的函数名字可以一致。
这样操作系统就能以统一的视角来看待各个硬件的读写方法。
进程和struct file的关系
一个进程可以打开多个文件,对于进程来说,被打开的文件同样也算是一种资源。
为了区分一个进程拥有的“打开文件”集合。操作系统为每个进程建立了一张
struct files_struct
表,表中有一个struct file*
数组fd_array
,这个数组就指向该进程所有的打开的文件。task_struct
中的 struct files_struct* files
指针就指向这张表。
fd_array
是文件映射关系数组。fd_array
数组中的每个元素都是对应操作系统管理文件集合中的某个文件。于是我们就能通过下标访问该数组,从而在操作系统管理文件集合中找到目标文件。
非常合理的,文件描述符就是fd_array数组的下标!!!
这样我们也就明白了,文件操作的系统调用为什么是用fd来做参数的。因为我们能通过fd准确地找到目标文件。
再次理解open操作
在明白文件是如何被描述以及如何被管理之后,我们也就能更加深刻的理解系统调用open打开文件的过程:
1.创建struct file(包括fd)
2.开辟文件缓存区,加载文件中的数据(延后)
3.查进程的文件描述符表(fd_array数组)
4.将file的内存地址填入到fd_array[fd]中
5.返回fd.