预备知识
文件=属性+内容
1.所有对文件的操作:a、对内容做操作 b、对属性做操作
2.内容是数据,属性也是数据----存储文件,内容和数据都要被储存----默认储存在磁盘中
3.我们(进程)要访问一个文件的时候,要先把文件打开,本质就是将文件从磁盘加载到内存,涉及硬件,由操作系统来做
4.一个进程可以打开多个文件,多进程可以打开多个文件,即打开的文件可能有多个
那么操作系统如何来管理这么多的文件呢?先描述,在组织
一个文件要被打开,首先要先在内核中形成一个文件对象(struct file)
5.文件按照是否被打开分为:被打开的文件(内存中) 没有被打开的文件(磁盘中)
6.进程和被打开的文件的关系
具体的讲解参考C语言文件操作
我们知道用户是无法直接访问硬件的,而文件在磁盘上,所以我们必然需要通过操作系统来帮我们打开文件,而上面的代码中我们调用的都是C语言接口,没有用操作系统的接口,我们是如何做到的呢?答案其实很简单:C语言的文件操作接口就是对操作系统相关接口进行封装得来的,所以底层依旧是操作系统在干活。
那么操作系统的文件相关接口有哪些?
注意:O_RDONLY | O_WRONLY 和 O_RDWR 不等价!!!想要读写都有只能写O_RDWR
该函数的返回值为整形,相当于C语言接口的返回值FILE*,都是用来识别文件的,使用如下
很显然该文件的权限是乱码,那么如何创建一个文件并同时规定它的权限呢?跟mode这个参数有关,使用如下
有人可能又要问了,我们设置的权限都是rw-,为什么其他人的权限为r--,不要忘了有掩码umask,当然我们也可以在代码中改变umask的值,这里不建议这么做
那么如何向文件中写入呢?
open函数它的返回值是什么,我们还不了解,但是我们知道它一定能帮我们找到对应的文件,我们先来打印几个看看
它的返回值是一连串的整数,看着像是数组下标,还缺了前3个,具体的情况如下
所有的硬件设备都会有输入输出,在系统看来就是读写,无论你是只读,只写还是只读写。当然不同设备的读写的具体操作会有所不同,但是我们不关心,我们只要能使得不同的设备调用它自己的读写函数,从而完成特定的功能即可, 在上层来看就是该设备完成了读写任务,这么一看是不是和面向对象的多态一样,所以Linux系统中讲一切皆文件
fd的分配规则
从之前的打印结果来看,fd是按照顺序分配的,但如果我们先关掉一个文件,再打开一个文件,那么该文件的fd是按顺序呢,还是填补最小的下标呢?
现在我们可以反过来去验证一下前面三个stdin,stdout和stderr是否默认打开
总结:
1、标准输出、标准输入和标准错误是默认打开的
2、fd的分配规则是找最小的没被分配的下标进行分配
现在我们来看看下面这两段代码和运行结果
1、都没有在屏幕上打印
2、一个log.txt文件中出现了本来要在屏幕上打印的数据,另一个啥都没有
第一个现象很好解释,因为我们将标准输出关了,当然不能在屏幕上打印
第二个现象结合上面讲的,其实也不难理解,由于标准输出被关了,所以标准输出对应的1就被分配给了新打开的文件log.txt,而printf函数默认在屏幕上输出,本质就是默认在文件描述符1所对应的文件中输出,而现在log.txt就是这个stdout,所以打印语句的内容出现在了log.txt中,但是我们发现只有加上fflush函数刷新stdout的缓冲区才能实现这个效果,这个后面会具体讲(其实本质是文件缓冲区是语言范畴的,而我们调用的是系统接口关闭文件,不会把在缓冲区中的内容刷新到文件中,需要手动调用fflush)
好,理解上面的现象之后,我们会发现它跟重定向很相似,或者说重定向的底层就是这样实现的
上面的代码是输出重定向,下面同理,分别是追加重定向和输入重定向
但是这种方法实现的重定向有点太挫了,有没有高级一点的方法呢?我们理解一下上面三个实现重定向代码的底层逻辑就会发现它们都是将文件描述符0/1对应的标准输出和输入改成其他文件就行
根据这个原理,操作系统实现了一个接口dup2
dup2就是将newfd覆盖为oldfd,演示如下
接下来,我们来讲讲标准错误stderr
上面是关于标准错误的一些用法,其实从中不难看出为什么要有标准错误,因为我们可以将程序正常运行的打印信息和程序异常的打印信息分开来输入到两个文件中,方便我们观察,纠错
缓冲区,简单来说,就是给我们暂时存放数据的一块空间,我们可以把它理解成快递站,当我们要寄东西出去的时候,我们会将物品交给快递站,然后由快递站帮我们发送,一般来说,快递站会等物品数量到达一定程度才会发货,以此提高效率降低成本,缓冲区也是同理,它会等数据量到达一定程度,才会将数据刷新到内存。(1.用户不用在管这些数据,提高使用者效率 2.提高发送效率)
刷新缓冲区也分为立即刷新,按行刷新和缓冲区满了再刷新三种方式,当然在一些场景下,也需要强制刷新这一操作,比如进程退出前,我们需要将还在缓冲区的数据强制刷新到内存中。
一般对于显示器文件,行刷新
对于磁盘上的文件,全缓存(缓冲区写满了,在刷新)
我们来看下面这两段代码
当我们都在显示器上打印的时候,打印的内容一样,但是一旦将数据打印到文件log.txt中时,右边的文件中多出了三行(除了系统接口函数,C的打印语句都打印了两遍,且系统接口打印的内容到了第一行),上面两段代码的唯一不同在于右边多了一个子进程的创建语句。
为什么呢?这个现象出现的原因是什么呢?
肯定和fork有关,但是我们知道fork创建子进程,不会影响代码的执行顺序,也就是说前面四个打印语句执行完了,就不会在执行了,那为什么打印到文件的内容增加了呢???
理解如下
1.当我们直接往显示器文件打印的时候,显示器的刷新方式是行刷新,而我们的打印语句都有\n,fork之前,所有的数据都已经被刷新
2.重定向到log.txt,本质是向磁盘在文件中写入,刷新方式就变成了全缓存
3.全缓存意味着缓冲区变大,我们写入的数据不足以将缓冲区填满,所以在fork执行的时候数据依旧还在缓冲区中
4.我们现在说的缓冲区和操作系统无关,只和C语言本身有关,因为printf、fprintf、fputs底层都是调用的write,而write语句的打印只出现了一次
5.C/C++提供的缓冲区,里面一定保存的是用户的数据,属于当前在运行的进程,但是如果我们把数据交给OS,这个数据就属于OS,不在属于用户
6.当进程退出的时候,一般要进行刷新缓冲区,即便你的数据没有满足刷新条件,而刷新缓冲区就涉及对数据做修改,那么当父子进程中的一个进程结束刷新缓冲区时,就会发生写实拷贝,从而导致打印的数据变多,而write是系统接口,打印的内容直接在OS中,和C语言的缓冲区没有关系,所以write打印语句只会打印一次
那么缓冲区在哪里呢???
我们知道系统通过文件操作符来找到文件,C语言通过FILE指针来找到文件,而FILE结构体中包含了文件操作符,缓冲区也在它里面,具体如下
(该结构体会被重定义为FILE)