文件、fd)
一、认识文件1. 文件是什么我们知道文件 文件内容 文件属性。如果我们创建一个新的文件里面什么内容都不写但它还是占用一定的存储空间的因为一旦创建了一个文件它的属性就需要被保存起来这是需要消耗存储空间的。未来对文件操作要么修改文件内容要么文件属性。文件属性比如文件权限。2. 如何访问文件在这里插入图片描述如果要访问一个文件一定要知道这个文件它在什么地方所以c语言里我们在用fopen函数打开文件的时候需要传递路径也就是fopen的第一个参数。那么有没有想过是由谁打开的文件–由当前的进程打开一个进程可以打开多个文件。但是有的时候我们不需要传递路径只传递文件名称也能够打开文件这是因为cwd进程当前工作路径的存在。如果不传递路径OS会在进程的cwd工作下面找。谁找文件OS。谁打开文件当前进程。怎么找根据路径或进程cwd。为什么是由进程打开的因为一串fopen代码编译好之后是没有打开文件的而只有运行起来之后这个文件才会被打开。在这里插入图片描述/proc里面存储的是一个个的进程目录用 ls /proc/13721test的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的类型的整数奇怪吗为什么是一个整数它是如何做到整合这些功能的接下来看一段代码可以更好的理解。代码语言javascriptAI代码解释6 #define ONE (10) //0000 0001 7 #define TWO (11) //0000 0010 8 #define THREE (12)//0000 0100 9 #define FOUR (13)//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使用的逻辑也是这样的先用|组合起来然后用进行识别。----用单个整数传递多个布尔状态。代码语言javascriptAI代码解释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以及这个文件描述符究竟是什么东西之后会讲到。在这里插入图片描述如果我们要用系统提供的接口close函数关闭打开的文件它要求传递的参数就是文件描述符。使用系统提供的文件接口免不了要使用这个文件描述符。1.2 认识文件描述符fd之前通过open接口讲到文件描述符就是一个整数。但这个整数为什么为从3开始这是因为一个进程一旦跑起来就默认会打开三个文件分别是标准输入流文件----对应键盘标准输出流和标准错误流文件----对应显示器它们的文件描述符分别是012。在这里插入图片描述2. 而且也能够看到stdin、stdout和stderr它们的类型都是文件指针文件指针类型其实也就是对fd的一种封装即FILE是一种结构体它里面有存储fd也就是每个文件对应的文件描述符。FILE还存储了缓冲区等属性。我们可以通过以下代码进行验证使用了read和write这两个系统提供的接口在这里插入图片描述在这里插入图片描述代码语言javascriptAI代码解释10 char buf[1024]; 11 read(0, buf, sizeof(buf)); 12 write(1, buf, strlen(buf));在这里插入图片描述第一个hello world\n是我从键盘里面输入的一串字符这是read函数从0文件也就是键盘文件里面读取数据到buf数组里面。第二个hello world\n是write函数将buf里面的数据写入到1文件也就是显示器文件里面,所以我们才能看到这个hello world。在这里插入图片描述在前面讲到Linux系统里面存在多个文件。那么OS一定要对这些文件做管理先描述再组织每个文件的属性都会被收集然后储存再struct file结构体里面再形成特殊的链表方便OS进行管理。而每一个进程都能打开一系列的文件OS最好进行分类管理也就是文件要跟打开它的进程所对应上。如图每一个进程task_struct里面都存有一张用来管理当前进程打开的文件的表file这张表是struct file*类型的一个指针数组里面的元素是一个个的struct file类型指针分别指向对应的加载进内存的文件。既然是数组那么就一定有对应的下标而这些下标也就是fd即文件描述符的本质是数组下标。再来讲一讲这个缓冲区如果我们想读取一个文件里面的内容文件会把这部分内容放进缓冲区然后等时机一到刷新到内存里文件内容的对应位置同样要修改磁盘里面文件的内容也是要先把修改后的内容放进缓冲区等待被刷新进入磁盘文件。—目前可以这样理解。1.3 为什么语言要对系统调用进行封装拿c语言举例系统提供的fdc语言封装为FILEopen封装为fopenclose封装为fclosewrite封装为fwrite。为什么c语言要这样做----为了有更好的跨平台性。