1.文件理解预备知识
首先,当我们在磁盘创建一个空文件时,这个文件会不会占据磁盘空间呢?
答案是当然会占据磁盘空间了,因为文件是空的,仅仅指的是它的内容是空的,但是该文件要有对应的文件名,文件的大小,文件的创建时间,文件的权限等等,这些叫做文件的属性,是一个往往被人们所忽略的非常关键的一个点,所以我们都说文件=内容+属性。
1.所有对文件的操作,无非就两种操作:a.对内容做操作 b.对属性做操作
2.文件内容是数据,文件属性也是数据 ---- 所以说存储文件既是存储内容数据,也是存储属性数据 ----- 默认就是在磁盘上的文件。
3.我们要访问一个文件的时候,都是要先把这个文件先打开。
这里所说的我们指的是谁呢?是指我们用户吗?
我们平常在写我们的c语言代码来对文件进行操作的时候并不是写好了这个文件就会通过fopen函数直接打开了,而是要通过将代码运行起来编译形成可执行程序将其加载到内存中,然后变成进程进而来打开文件并对文件进行操作的,所以说这里的我们指的是进程,而并不是指的我们用户。
我们下面再来谈谈文件打开前与文件打开后的状态。
文件打开前:普通的磁盘文件 文件打开后:将文件加载到内存。我们上面说文件是存储在磁盘上的,既然要将文件加载到内存,根据冯诺依曼体系结构,那么就必然要涉及到访问磁盘设备,而访问磁盘设备只有操作系统有资格来访问,所以这一步必定是操作系统来做。
4.一个进程可以打开多个文件吗?多个进程可以打开多个文件吗?
加载到内存中被打开的文件可能会存在多个。进程:打开的文件 = 1:n
所以根据2、3、4这几点我们可以知道操作系统在运行中,可能会打开很多个文件,那么操作系统就需要对这些文件进行管理。那么如何管理呢?先描述,在组织。
一个文件要被打开,一定要先在内核中形成被打开的文件对象,说白了就是在c语言上我们需要用struct结构体来对文件进行描述,然后用链表链接起来,所以对文件的管理就转化成了对链表的增删查改。
其实说的更准确一点就是一个进程是通过操作系统来打开文件的,而操作系统不相信任何人,所以操作系统一定要给我们提供系统调用接口。
5.文件按照是否被打开分为 被打开的文件和没有被打开的文件
其中被打开的文件是存在于内存中的,而没有被打开的文件存在于磁盘中。
6.研究本次文件操作的本质:进程与被打开文件的关系
2.复习常见C语言的文件接口
我们先来看看c语言对文件操作的一些接口
打开文件接口:
写入文件接口:
关闭文件接口:
利用这些接口我们用以下代码进行测试:
运行结果:
然后我们把代码修改成如下方式来进行测试:
运行结果:
发现我们之前写入到log.txt的内容不见了,只剩下了刚刚新写入的内容。
而我们打开文件有以下几种方式来进行打开:
我们刚刚是以w的方式打开文件的,也就是写的方式:这种方式打开文件,如果文件不存在就会创建一个新的文件,如果需要打开的文件存在就会先清空文件中的内容,然后再打开文件进行写入。
用重定向的方式来写就是:
下面我们以a方式进行打开文件测试:
运行结果:
发现追加在了后面,这种a方式打开文件也是写入,只不过不会清空文件,而是在文件结尾处进行写入,也就是追加,不会清空文件内容。
同时也可以用重定向的方式对文件内容进行追加:
还有一种方式是以读的方式打开文件,也就是r方式。
3.认识系统调用接口,操作文件
我们学习的c语言打开文件的接口,底层一定封装了系统调用接口!!!
下面我们看一下打开文件的系统调用接口的手册:
这里的open接口有两个参数的接口,也有三个接口的参数,我们下面会来进行介绍,而这个open还有一个int类型的返回值,这个返回值叫做文件描述符fd.
我们先来对open接口的第二个参数来进行理解:
这里的flags我们称之为标志位,是一个int类型的整数,它里面有32个bit位,所以理论上我们可以用这32个bit位为0为1进行分别传参,如果要传一个标志位,那我们就可以用第一个比特位来表示,如果要传两个标志位,那么我们就可以用第一第二个比特位来表示,三个标志位就用三个,十个就用是个bit位来传。所以这个参数可以同时传递多个标志位的,而我们的标志位有O_RDONLY(只读标志位),O_WRONLY(只写标志位),O_RDWR(读写标志位)等等。这些大写的标志位都是用宏来定义的。
下面我们写一段测试代码来进行理解:
然后运行:
所以我们可以通过宏的方式为接口传递多个标志位。
下面我们再对open接口进行测试:
我们先把之前创建的文件都删除了,然后重新执行这段代码:
我们发现确实形成了一个log.txt的文件,可是为什么是红色的呢?同时它的权限也很奇怪,当我们创建文件的时候,必定是要经过linux权限创建的,但是我们刚刚创建该文件的时候并没有指明该文件的权限,所以log.txt这个临时文件的权限是一个乱码式的权限。
如果我们用open接口来打开文件,如果文件不存在,那就需要在使用open接口的时候给文件传递一个权限参数:
也就是调用这个接口:
我们先把之前的log.txt这个临时文件删除了然后重新执行上述代码:
我们发现权限跟我们传进去的并不一致,我们设置的是0666,应该是显示-rw-rw-rw-的,可是发现最后一个权限w不见了。原因就在于我们创建一个文件时在我们系统中有一个默认的权限掩码umask:
默认是0002,但是如果我们就是想要设置为0666呢?
那么我们就可以通过修改umask权限掩码为0即可
然后运行之后:
发现权限变回了0666.但是一般我们都不会去修改系统的umask.
下面我们对文件进行操作:
先看一个写文件的系统调用接口:write
使用该write接口,其中fd是文件描述符,buf是字符串首地址,count是需要写入的大小。
运行之后:
下面我们再修改代码:
把写入的内容修改一次
发现在open接口中以只写的方式打开文件如果文件不存在会创建文件,如果文件存在不会清空文件,而是从文件的开始对文件内容进行覆盖式的写入,如果我们想像c语言那个接口那样先清空文件内容再写入该怎么写呢?其实很简单,只需要在标志位上再加上一个O_TRUNC这个宏即可,下面把代码进行修改:
运行结果:
c语言中还有一种追加的方式打开文件,在系统调用接口open需要传参O_APPEND标志位即可,即把代码改成:
运行结果:
我们发现文件内容是追加上去的。
4.尝试着理解文件,打通语言与系统关于文件部分的理解
所以我们通过上述的说明可以知道如下结论:
除了了解了这些open系统调用参数,想必大家也一定对我们的open的返回值fd比较好奇是什么吧,下面我们就对这个fd进行测试:
一个进程可以打开多个文件
运行之后:
文件描述符分别是3,4,5,6,同时四个文件也创建出来了。
我们发现这是一个连续的小整数逐渐递增的,我们平时遇到这种都是以数组下标最为常见,而且我们也发现怎么没有0,1,2呢?
理解文件在操作系统中的表现:
那么0,1,2去哪了呢?
我们看一下linux内核中源代码进程结构体中的files_struct*
以上都是内核源代码中跟文件有关的结构体。
进程在运行的时候默认打开标准输入流,标准输出流,标准错误流三个文件,他们对应的文件描述符分别是0,1,2 。
通过上述的说明,我们知道,操作系统访问文件,只认文件描述符!!!
那么我们前面谈到的c语言库函数中的FILE是什么东西呢?是一个C语言提供的结构体类型,既然是结构体,那么这个结构体里面必定封装了文件描述符!!!如何证明呢?
运行结果:
所以我们初步得出了一个结论,不仅仅是fopen底层封装了open系统调用接口,文件的结构体类型也进行了封装。所以操作系统只认fd.
1.操作系统为什么要把0,1,2 ,stdin,stdout,stderr打开呢?
就是为了让程序员默认进行输入输出代码编写!
2.stderr是什么呢?
3.如何理解一切皆文件?
我们的设备像显示器,键盘,网卡,声卡,扬声器等等这些都是可以输入输出的,我们用另一种说法就是读和写,如果没有读,那么读就为空,如果没有写,那么写就为空,所以我们都可以把这些设备都看成有一个共同点,那就是都有读写方法。
这种方式的实现其实就是我们c++中面向对象的多态。