目录
理解“文件”
狭义理解
- ⽂件在磁盘⾥
- 磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的
- 磁盘是外设(即是输出设备也是输⼊设备)
- 磁盘上的⽂件本质是对⽂件的所有操作,都是对外设的输⼊和输出简称IO
广义理解
- Linux下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘……这些都是抽象化的过程)(后⾯会讲如何去 理解)
⽂件操作的归类认知
- 对于0KB的空⽂件是占⽤磁盘空间的
- ⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件=属性(元数据)+内容)
- 所有的⽂件操作本质是⽂件内容操作和⽂件属性操作
系统⻆度
- 对⽂件的操作本质是进程对⽂件的操作
- 磁盘的管理者是操作系统
- ⽂件的读写本质不是通过C语⾔/C++的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽ 是通过⽂件相关的系统调⽤接⼝来实现的
像fclose,fopen等文件IO函数是C语言库函数,它们的底层封装了系统调用!
对文件的操作,比如打开,修改,关闭,其实本质就是进程对文件的操作。
回顾C语言接口
毫无疑问接下来讲到的C语言接口都是语言级的函数,它们的底层封装了系统调用!
文件写操作
理解:
w操作是如果log.txt在进程的当前路径没有,就创建,有则写入。
fwrite函数循环将msg中的数据写入到log.txt中。
那打开的文件是在哪里呢?系统怎么知道是打开这个文件呢?
我们之前说过进程有个动态的路径(存放着正在跑的进程),读写这个文件的进程的文件的里面有个当前进程的工作目录。
- cwd:指向当前进程运⾏⽬录的⼀个符号链接。
- exe:指向启动当前进程的可执⾏⽂件(完整路径)的符号链接。
打开⽂件,本质是进程打开,所以,进程知道⾃⼰在哪⾥,即便⽂件不带路径,进程也知道。由此OS 就能知道要创建的⽂件放在哪⾥。
文件读操作
理解:
feof函数是用于判断文件读取操作是否因到达文件末尾而终止,如果是返回真,否则返回假。
循环在指定文件流中读取到buf中,直到将文件里面的数据全部读取完毕。
总结
文件打开方式:
下面我们来看看系统IO.
系统文件IO
打开⽂件的⽅式不仅仅是fopen,ifstream等流式,语⾔层的⽅案,其实系统才是打开⽂件最底层的⽅案。
不过在学习系统文件操作之前,我们需要学习系统传递标志位的方式来代表读写操作。
⼀种传递标志位的⽅法
先看代码:
理解:
用一个数字代表某一个读写操作,准确来说是用一个比特位来表示,只有这样,当两个数之间按位或运算时,它们的按位或结果就会整合它们两个数的读写操作,当按位与时,就会消除它们两个的读写操作。得到结果后,只需要检测它们的比特位上的数字即可!
文件写操作
理解:
文件读操作
open返回值
在认识返回值之前,先来认识⼀下两个概念:库函数和系统调用。
系统调⽤接⼝和库函数的关系,⼀⽬了然。
所以,可以认为, f# 系列的函数,都是对系统调⽤的封装,⽅便⼆次开发。
文件描述符
我们先看代码:
当我们执行myfile程序,程序等待我们输入,当输入qwert时,又会打印两次到显示器上。
解释:
- Linux进程默认情况下会有3个缺省打开的⽂件描述符,分别是标准输⼊0,标准输出1,标准错 误2.
- 0,1,2对应的物理设备⼀般是:键盘,显⽰器,显⽰器
直到现在我们发现fopen和open函数的返回值不同,为什么呢?
fopen是file*(C语言一系列的文件操作函数也是file*),而open却是整数(一系列的系统调用也是整数),其实进程中有个files_struct结构体叫文件描述符表,表中有一个fd_array数组,这个数组的0,1,2下标分别对应stdin,stdout,stderror,而只要我们新打开文件,新开的文件就会被存入数组其他空间的文件流。
这样一个进程只需要管理文件描述表即可管理打开的多个文件!
代码底层:
⽂件描述符就是从0开始的⼩整数。当我们打开⽂件时,操作系统在内存中要创建相应的 数据结构来描述⽬标⽂件。于是就有了file结构体。表⽰⼀个已经打开的⽂件对象。⽽进程执⾏open系 统调⽤,所以必须让进程和⽂件关联起来。每个进程都有⼀个指针*files,指向⼀张表files_struct,该表 最重要的部分就是包含⼀个指针数组,每个元素都是⼀个指向打开⽂件的指针!所以,本质上,⽂件 描述符就是该数组的下标。所以,只要拿着⽂件描述符,就可以找到对应的⽂件。
文件描述符的分配规则
代码:
标准输入,输出,错误分别是数组下标的0,1,2。而新开的文件下标是3.
代码:
关闭了标准输入,新开的文件的文件描述符是0。
得出结论:⽂件描述符的分配规则是,在files_struct数组当中,找到 当前没有被使⽤的最⼩的⼀个下标,作为新的⽂件描述符。
重定向
我们关闭文件描述符1看看效果:
我们发现原来打印显示器上的内容,打印到了文件上。
这种现象叫输出重定向。
为什么会这样?
因为我们关闭了1,按照文件描述符的分配规则,新开的文件对应的文件描述符就会分配给1,1中的file*对应的就是log.txt文件,而printf默认是给1打印的,所以打印给了log.txt文件。
这种重定向,我们也可以调用函数实现。
dup2的系统调用
其核心作用是将一个已打开的文件描述符复制到另一个指定的文件描述符编号。
oldfd复制给newfd。
代码:
理解:
上述是在代码中实现重定向操作,但也可以这样写。
看代码:
理解:
printf往stdout(标准输出)打印,fprintf往stderr(标准错误)打印,但两者都是显示器,./myfile >log.txt 的意思是将log.txt重定向到标准输出,和./myfile 1>log.txt的意思一样。也就是这个原因原本打印到标准输出的信息打印到了log.txt文件,从而显示器上的信息只有打印到标准错误的信息。
我们可以将打印到标准输出内容和标准错误的内容分开到不同文件:
理解:
标准输出重定向到了log.txt,而标准错误重定向到了log.error,所以显示器上无信息,而log.txt和log.error文件分别可以看到往标准输出和标准错误打印的信息。
我们也可以将标准输出和标准错误信息打印到同一文件:
理解:
标准输出重定向到了log.txt,然后再标准输出(此时是log.txt文件)重定向到标准错误,所以不管是往标准输出打印还是往标准错误打印,都是往log.txt文件打印!(这里&看成取地址符号)
好了,我们下期见!