一、认识文件
1. 文件是什么
- 我们知道:文件 = 文件内容 + 文件属性。如果我们创建一个新的文件,里面什么内容都不写,但它还是占用一定的存储空间的,因为一旦创建了一个文件,它的属性就需要被保存起来,这是需要消耗存储空间的。
- 未来对文件操作,要么修改文件内容,要么文件属性。文件属性比如文件权限。
2. 如何访问文件

在这里插入图片描述
- 如果要访问一个文件,一定要知道这个文件它在什么地方,所以c语言里我们在用fopen函数打开文件的时候需要传递路径也就是fopen的第一个参数。那么有没有想过,是由谁打开的文件?--由当前的进程打开,一个进程可以打开多个文件。但是有的时候我们不需要传递路径只传递文件名称也能够打开文件,这是因为cwd(进程当前工作路径)的存在。如果不传递路径,OS会在进程的cwd工作下面找。
- 谁找文件?OS。谁打开文件?当前进程。怎么找?根据路径或进程cwd。
- 为什么是由进程打开的?因为一串fopen代码编译好之后,是没有打开文件的,而只有运行起来之后这个文件才会被打开。

在这里插入图片描述
- /proc里面存储的是一个个的进程目录,用 ls /proc/13721(test的pid)列出这个 test 进程的属性,可以看到有一个属性叫cwd,它存储着进程当前的工作路径,test就运行在这个路径下面。
3. 打开文件的本质
- 文件本来是存储在磁盘里面的,一个进程要打开文件,那么就一定要把文件加载进内存里面。文件的本质也就是内容+属性,它会根据进程的需要懒加载进内存里面。进程对文件操作,本质就是通过cpu访问内存中的文件。
4. 根据所在位置可以分为两种文件
- Linux系统里面就也存在着大量的文件,毕竟Linux系统里面一切皆文件,如果当前我们不使用ls这个可执行文件,那么ls这个文件就不会加载内存里面。也就是说,对于Linux,文件从位置上大致可以分为被打开加载进内存的文件和没有被打开依然储存在磁盘里面的文件。现在只研究在内存里也就是被进程打开的文件。
5. 管理文件
- Linux系统里面可以同时存在多个进程,而一个进程又可以打开多个文件,所以Linux系统里面可以存在多个文件。文件这么多,要不要进行管理,那当然是要的。怎么管理?先描述再组织。跟PCB一样,把文件的一些需要管理属性塞进结构体里面,然后构建特殊的链表进行管理。
6. 打开文件的方式

在这里插入图片描述
- r:只读模式。它会从文件的开头进行读取。
- r+:可读可写。 它从文件开头直接写入,覆盖原来的内容。
- w:只写模式。它会从文件的开头进行写入。如果文件不存在,就创建这个文件,如果这个文件存在了,就清空这个文件的内容再从文件开头进行写入。
- w+:可读可写。与r+不同的是,如果这个文件不存在,就创建这个文件,存在了就清空这个文件的内容然后再从文件开头进行写入。
- a:追加写入。它会从文件的结尾进行写入,保留文件原来内容。如果文件不存在,就创建这个文件。
- a+:追加可读可写。它从文件的结尾进行写入,保留文件原来内容。如果文件不存在,就创建这个文件。读取需手动调整指针到开头,写入自动回末尾。
- 这6个模式一般用到的就是那三个基本模式r、w和a。读写模式一般不会用到。
二、从系统角度重识文件
1. 认识open接口

在这里插入图片描述
- open是系统提供的打开文件的接口,fopen是c语言里面对这个系统接口的封装。使用它们的时候要包含3个头文件如图。
- 第一个参数:pathname,文件的路径,这个就不多说了。
- 第二个参数:flags,这个对应fopen里的模式。对应有O_RDONLY----只读打开、O_WRONLY----只写打开、O_RDWR----可读可写打开、O_CREAT----文件不存在则创建、O_TRUNC----文件存在则清空内容和O_APPEND----追加写入这些宏。可以看到,flags是一个int的类型的整数,奇怪吗?为什么是一个整数,它是如何做到整合这些功能的?接下来看一段代码可以更好的理解。
代码语言:javascript
AI代码解释
6 #define ONE (1<<0) //0000 0001
7 #define TWO (1<<1) //0000 0010
8 #define THREE (1<<2)//0000 0100
9 #define FOUR (1<<3)//0000 1000
10
11 void Print(int flag)
12 {
13 if (flag & ONE)
14 printf("one\n");
15 if (flag & TWO)
16 printf("two\n");
17 if (flag & THREE)
18 printf("three\n");
19 if (flag & FOUR)
20 printf("four\n");
21 printf("\n");
22 }
23
24 int main()
25 {
26 Print(ONE|TWO);
27 Print(ONE|THREE);
28 Print(ONE|TWO|THREE|FOUR);
29 return 0;
30 }

在这里插入图片描述
- 看这段代码,我定义了4个宏,这四个宏从ONE到FOUR分别是1即0000 0001左移0位、1位、2位和3位之后得到的数。然后我定义了一个Print函数,这个Print函数会通过&(按位与)来识别flag这个数里面与ONE/TWO/THREE/FOUR对应的二进制数位里面有没有1。有1的话就打印出来。用的时候各个宏之间用|组合起来形成最终要传递的flag整数。
- 而open函数它的这个flag使用的逻辑也是这样的,先用|组合起来,然后用&进行识别。----用单个整数传递多个布尔状态。
代码语言:javascript
AI代码解释
8 int fp = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
9 printf("fp:%d\n", fp);

在这里插入图片描述
- 打开已经存在的文件使用两个参数的open函数就足够了,但如果要打开一个不存在的文件即需要创建它时需要对应的权限也就是flag要有O_CREAT,然后还要传递所创建文件的对应三种用户权限也就是第三个参数,mode。

在这里插入图片描述
- open函数的返回值:它是一个整数,即文件描述符,现在可以理解为是相对于打开此文件的进程专属文件编号,每一个文件加载到内存里面的文件对于当前进程都有一个编号,打开失败返回值会是-1。可以看到log.txt对应的文件编号是3。至于为什么会是3以及这个文件描述符究竟是什么东西,之后会讲到。