【基础IO⑧】:进程与文件之间的联系(文件描述符fd)
- 一.前言探讨[进程与文件关系]
- 二.C语言文件操作
- 三.系统文件调用
- 1.open/write
- 四.文件描述符fd
一.前言探讨[进程与文件关系]
我们首先了解一些基本的认识:
1.文件包括文件内容和文件属性
2.文件分为打开文件和没有打开文件。
3.打开的文件是谁打开的呢?又是如何打开的呢?这是我们本篇的重点处理问题。
4.没有打开的文件在哪里放着呢?
对于打开的文件我们重点想了解是谁打开的,并且是如何打开的。对于没有打开的文件我们重点想了解这些没有被打开的文件在哪里!
1.打开的文件的操作肯定是我们写代码才能操作,所以也就是我们的程序即进程!是进程打开文件。
2.对于没有打开的文件肯定很多,那这些文件是如何被分门别类的放置好的呢?这是我们要探讨的问题,因为我们想对这些未打开的文件快速的进行增删查改。
进程打开了文件,本质上就是要研究进程和文件之间的关系。
要想访问一个文件,首先需要将文件加载到内存里,并且一个进程可以打开多个文件,所以操作系统内部肯定存在很多被打开的文件!那么这些被打开的文件操作系统也需要对他们进行管理。通过先描述再组织的方式进行管理---->每一个被打开的文件操作系统都会给它创建一个文件对象,这个对象包含文件的很多属性。
所以综上:我们要想探讨一个被打开的文件,本质上是探讨进程与文件之间的关系即------->进程PCB与文件对象之间的关系。
二.C语言文件操作
C语言的常见文件操作就是fopen fwrite,fread fclose等打开文件写入文件输出文件等操作。
对于fopen(参数1,参数2)的使用很简单,就是打开某个路径下的文件,以什么样的方式打开.
所以如果不指定文件在哪个路径下,系统会默认将当前进程的路径添加上去,也就是在当前进程目录下创建。
所以如果我们将进程的当前目录修改,就可以将文件新建到别的地方了。
1.‘w’就是以写的方式打开。不过要注意w在写入之前,都会对文件进行清空操作。比如像重定向里的,打开文件但不往里面写,就会直接清空文件,说明重定向操作里面打开文件的方式就是w。
2.‘a’也是写入操作,只不过在打开文件时不会清空文件,而是直接追加在内容末尾。
3.这里还有一个问题:就是当使用fwrite时,后面计算字符串的大小需不需要+1,因为\0的原因就很困扰,为什么有时加1有时不加1呢?
首先我们要明白,字符串是C语言规定出来的,它的后面必须要有\0。但文件就不认识C语言,文件只需字符的内容就可以了,所以当我们往文件里写入时字符串时,都不需要加+1,直接计算字符串的长度即可。
还有一个知识:那就是C语言下,会默认给我们打开3个标准输入输出流:
三.系统文件调用
文件其实是在磁盘上的,磁盘又是属于硬件设备。操作系统的构成,用户层在最上面,硬件在最下面,用户要想访问硬件必须要通过操作系统,而操作系统又因为安全性只会给你提供系统调用接口来访问。
所以所有的库函数,只要要访问硬件资源,就一定要封装系统调用接口。
1.open/write
有两个open接口,这两个的区别在于第三个参数有和没有。
对于已经存在的文件,肯定有它自己的权限设置,这样才可以被访问,所以当一个文件被创建时,必须要设置这个文件的权限属性。
而第三个参数就是用来设置权限的。所以第一个open是用来打开已经存在的文件,而第二个open是用来打开创建一个没有存在的文件的,并设置这个文件的权限。我们主要用三个参数的open调用接口。
open系统调用接口的参数第一个就是文件路径,第二个参数是打开的方式,第三个参数是用过给这个文件设计权限的。返回值是int类型
open是系统调用接口,它有自己的打开文件的方式,它是采用比特位方式的标志位传递参数从而采用不同的打开方式。
O_WRONLY表示只写形式
O_CREAT表示如果文件不存在就创建
O_TRYNC表示打开文件之前先清空文件
O_APPEND表示追加,不清空。
所以C库函数fopen里封装着open系统调用函数,"w"方式就是O_WRONLY, O_CREAT, O_TRYNC三种方式组合,"a"方式就是
O_WRONLY, O_CREAT, O_APPEND三种方式组合。
fopen封装着open这个我们应该能理解,不过你没有注意到open的返回值是int类型,而fopen的返回值确实FILE类型,int类型和FILE类型之间有什么关系呢?
四.文件描述符fd
我们知道只要打开一个文件,操作系统就要给这个打开的文件创建个文件对象struct file。这个对象描述着文件许多属性。文件对象之间用链表连起来。这样操作系统对于文件的管理,就转换为对链表的增删查改了。
我们前面也说过一个进程可以打开多个文件,所以操作系统内部肯定有许多被打开的文件,那么进程是怎么知道它打开的是哪个文件呢?
其实进程PCB里存在一个指针对象,叫做struct file_struct对象。
它指向的是一个结构体对象,这个结构体里面存着一个数组struct file arr[],这个数组叫做文件描述符表,里面存的都是文件对象的地址。当进程要打开一个文件是,操作系统首先会为这个文件创建struct file对象。然后就会将这个对象地址存放在文件描述符表里。当又打开一个文件,就依次将文件对象的地址放进数组里面,最后返回该数组的下标给进程。最后调用open打开创建一个文件后,就会获得一个int类型的下标。所以本质上这个int类型的返回值就是数组的下标。也就是文件描述表的下标。而进程就是通过这个下标再次找到被打开的文件的。
所以这个下标也叫做文件描述符。本质上就是文件对象在文件描述表里位置。
所以进程是通过文件描述符fd来找到被打开的文件的。这也是为什么open返回值为什么是int类型了。
因为进程可以打开多个文件,所以这里采用的引用计数的方式进行关闭,每个文件对象都有一个计数器,当进程close这个文件时,计数器就减减,减完后如果计数器不为0,则说明还有其他进程在使用这个文件,则不真正关闭文件,当计数器为0时才真正的关闭文件。
open返回的是文件描述符fd。但fopen返回的确实FILE类型这是怎么回事呢?
其实在操作系统它只认识文件描述符fd,并不认识FILE。进程与文件之间本质是通过数组下标来联系的。
所以不管FILE是什么,它内部肯定封装着文件描述符fd。FILE其实一个结构体对象。后面我会详细介绍它。
所以对于文件操作来说,进行了两次封装,一次是函数层面的封装:库函数封装系统调用,一次是类型方面封装:FILE类型里面封装fd类型。
到这里我们就可以验证一个现象了:
你说C语言默认给我们打开了三个标准输入输出流,你怎么证明呢?
文件既然被打开了,操作系统就会给它创建结构体对象struct file。并且会将它的地址放入文件描述符表里,一开始文件描述符表里一个都没有,现在这三个放进去,应该分别对应的数组下标是 0 1 2.所以我们可以观察他们的文件描述符是什么。