1、文件描述符的引入
1.1 系统调用接口的引入
??不管是学习语言还是学习操作系统,IO流是我们学习过程中不可获取的一个阶段,在这一部分我们会学习打开文件、读写文件等操作,在C语言中我们打开文件调用的是C语言的库函数接口,像fopen打开,fclose关闭,fputs写入,fgets读取,这些都是在C中我们对文件进行操作的一些库函数,但是如果不允许使用库函数接口时我们应该怎么办??? ??这时候我们就应该使用系统调用相关接口,我们首先要明确一个概念,C语言接口和操作系统接口是上下级的关系,任何一个语言,不管是C、C++、java、Python都有自己打开文件关闭文件读写文件的库函数,但是这些库函数的使用都是在Linux和Windows系统下进行的,所以任何语言的接口和系统接口是一种上下级的关系。 ??在系统调用接口中,我们打开文件使用open、关闭文件close、写入write、读取read。那这些接口和C中库函数接口有什么联系呢?我们可以这样理解:C中调用得这些库函数底层一定封装了系统调用接口,可以认为fopen底层调用open,fclose底层调用close,fread底层调用read,fwrite底层调用write。我们在windows中打开文件,windows底层也有一套自己的windows相关的api系统接口,当我们在windows使用C的库函数时,C调用的就是windows下的系统接口。这样在语言层面上就实现了跨平台性。
1.2 文件描述符
??我们查看关于C语言中库函数和系统调用相关接口的使用手册。 ??图中我们给出了C和系统调用的相关接口的使用手册,我们发现C中库函数的类型为FILE*,系统调用接口的类型为int。 ??FILE*是文件指针,在C中打开一个文件,打开成功后会返回一个文件指针,该指针指向文件内容的起始地址,文件指针是C语言级别的概念;int fd本质是new file descriptor-文件描述符,文件描述符是系统级别的概念。
2、文件描述符
2.1 演示文件描述符
??光说不练假把式,我们写一份代码来实质性的感受一下文件描述符的概念。
图1 代码
|
图2 运行结果
| ??通过上图中,我们就可以观察到了fd的值,但是fd的值为-1,-1意思是这个文件不让我们创建不让我们打开,这是因为单纯的O_WRONLY是没有创建功能的,所以如果你想打开一个文件写入,并且文件不存在想创建的话需要扩加一个选项O_CREAT。
图1 代码
|
图2 运行结果
| ??其实OS系统的open接口的O_WRONLY+O_CREAT组合起来就是C语言接口中的w方式,因为他们是上下级的关系,所以fopen一定调用了open,即C语言中的w方式底层同时给open函数传入了两个参数:O_WRONLY+O_CREAT。
2.2 文件描述符的返回值
??刚才我们打开一个文件fd为3,那么我们创建多个是什么情况呢?
图1 代码
|
图2 运行结果
| ??我们发现返回值从3开始依次递增,有很强的规律性;而-1代表打开文件失败,那么012在哪呢?又代表什么?
??相信各位读者应该都听过一个概念,C语言程序会默认打开3个输入输出流,其中这三个输入输出流对应的名为stdin,stdout,stderr,文件类型为FILE*,而FILE*是C语言的概念,底层对应的文件描述符,其中stdin对应0,stdout对应1,stderr对应2,换言之012被默认已经打开了,再打开时就是从3开始打开了,所谓的文件描述符,本质其实就是数组下标。
2.3 文件描述符底层原理(重点)
??一个进程是可以可以打开多个文件的,无非就是多调用几次open,而我们的计算机中是同时存在大量进程的,而这些进程可能会打开各种各样的文件,所以系统中在任何时刻都可能存在大量已经打开的文件,操作系统的功能之一就是文件管理,就是要对这些打开的文件进行管理。 ??而我们都知道,所谓管理就是先描述再管理,底层中描述文件的数据结构叫做struce file,一个文件对应一个struct file,大量的文件就有大量的struct file,我们只需将这些数据结构用双链表连接起来,所以对文件的管理就变成了对双链表的增删改查。而我们现在要做的,这些已经被打开的文件那些文件属于某个特定的进程,就需要建立进程和文件的对应关系。 ??每一个进程都有一个task_strut,这个task_struct会指向一个struct files_struct结构体,这个结构体里会有一个指针数组struct file* fd_array[32],而这个指针数组就是文件描述符对应的数组。 ??既然是数组就有下标,下标从0开始依次递增,task_struct结构里会有一个指针变量指向这个struct files_struct结构体,我们这个指针数组中的每个数据都是一个指针变量。默认的3个文件+我们上面的例子中自己打开的log.txt,总共有4个文件描述符打开,这四个打开的文件描述符都对应一个struct file的结构体,结构体里有描述该文件属性的相关信息,而这些struct file文件结构体之间,是通过双链表的形式链接起来的。 ??对于输入输出错误,将下标012分配给他们,自己打开的文件从3开始依次分配,当我们将下标和struct file结构体的指向关系表明清楚以后,open函数返回时,就会将下标数字直接返回给调用方,至此在应用层我们就拿到了文件描述符,至此我们就完成了文件和进程的对应关系。所以,所谓的文件描述符实际就是数组的下标。
2.3 文件描述符修饰规则
??文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。(其实就是将数组从上到下扫描,找没有被使用的)
3、总结
??通过上面的学习,我们知道了文件描述符就是一个小整数,Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.;0,1,2对应的物理设备一般是:键盘,显示器,显示器。 ??而每当我们打开一个新的文件时,系统就会将文件描述符对应的指针数组从上而下进行扫描,找到没有被使用的作为该文件的文件描述符。
|