>> Linux基础IO系列文章
1. Linux文件操作系统接口的学习使用
一、前言
在上一篇博客中,我们初步学习了Linux文件操作的系统接口,不难发现的是,这些系统函数都与文件描述符密切相关:open函数返回值是一个文件描述符,write函数、read函数、close函数都是对于 文件描述符 进行操作。
这让我们不禁思考,文件描述符到底是什么东西呢? 先上结论,文件描述符本质就是数组下标
。此时的你肯定会有不少困惑,且看下文我为大家细细展开。
二、Linux标准文件描述符
[问题一]:为什么为什么打开文件时,fd总是从3开始? 0,1,2分别是什么?
直接上结论:fd = 0~2的标准文件是默认被打开的,分别表示为
- 0:标准输入 → 键盘
- 1:标准输出 → 显示器
- 2:标准错误 → 显示器
口说无凭,我们用代码来验证上面的结论:
- 从标准输入中直接读取数据
- 向标准输入中写入数据
[总结一]:
- fd = 0~2的文件分别对应着标准输入、标准输入和标准错误
- 标准文件是默认被打开的,因此 fd 总是会从3开始增加(除非手动关闭标准文件)
三、文件描述符与FILE结构体的关系
FILE
是C语言定义的文件结构体,里面包含了各种文件信息。可以肯定的一点是,FILE结构体内一定封装了fd
。为什么?来看接下来的思路分析:
1.使用系统接口的必然性
文件存储在磁盘上,属于外设。谁有权限访问外设呢?只有操作系统。因为操作系统对上要提供稳定的服务,对下要管理好各种软硬件资源。
如果文件操作能绕开操作系统,那么操作系统怎么知道某个文件到底有没有被创建,有没有被销毁呢,还怎么给你提供稳定的服务呢?基于上述简单的认识,我们不难理解,要想访问硬件资源,就必须通过操作系统。
而操作系统出于安全性和减少使用成本的角度考虑,是不相信任何人的。就像银行一样,不会将金库直接向大众开放,而是只会有几个业务窗口为大家提供服务。操作系统也是这样,操作系统提供的窗口就是系统接口。
至此通过我们的逻辑推演,我们已经可以得出以下的结论:要想访问外设就必须使用操作系统提供的系统接口。所以C语言的各种文件操作函数本质就是对系统接口的封装
2.FILE结构体封装fd的必然性
C语言的文件操作都是系统统接口的封装,而系统接口的使用只认fd
,因此FILE结构体中必然会封装fd
验证的方法也很简单直接:
四、进程与文件的映射关系
在理解两者的关系之前,我们首先来回答几个问题,让大家有基础的认识:
[问题二]:打开的本质是什么?
答:本质是将文件加载到内存。为什么要加载到内存?这是由冯诺依曼体系决定的
[问题三]:打开文件、访问文件、关闭文件,都是谁在操作?
答:这些操作都是我们调用函数完成的,难道说是我们操作的吗?哈哈这样想就单纯了。当我们编译生成可执行文件的时候,有进行文件操作吗?答案显然是没有。我们的程序文件只有在运行起来的时候才会执行相应的代码,然后才会执行相应的文件操作。所以说这些操作本质都会由进程完成的。所以文件操作本质上是进程和打开的文件的联系。
[问题四]:OS如何管理大量的文件呢?
答:当文件被打开时,文件是存在于内存中的,因此内存中当然会存在大量的文件。操作系统要不要管理这些文件呢?答案是肯定的。如何管理呢?显然是是先描述后组织的设计思想:
一个文件被打开,在内核中就要创建对应的内核数据结构struct file(先描述),然后通过链表的方式将各个文件组织起来(后组织)。伪代码形式如下:struct file { // 文件内容和属性成员变量 struct file* next; struct file* prev; }
有了上述的基本认识之后,我们基于Linux 内核源码,再来具体谈谈进程与文件的映射关系:
进程控制块
task_struct
中有一个类型为files_struct
的文件指针,files _struct
:
在files_struct
有一个成员类型为file*
的指针数组,数组中的每个指针变量就对应着被该进程打开的文件。所以 fd 本质上就是 fd_array 数组的下标 。file*
结构体表示文件的各种基本信息。
在用这样一张图来为大家梳理思路:
[问题五]:文件描述符的分配规则
答:从头遍历fd_arr数组,找到一个最小的没有被使用的下标,分配新的文件。如果我们手动将 fd = 1 的文件关闭,那么新创建文件的 fd 就等于1。