一、本章重点
1、进程和打开文件的关系
2、简单复习c语言文件操作
3、介绍系统调用:open、clos、write、read
4、理解文件描述符
5、文件描述符分配规则
6、理解stdin、stdout、stderr与fd的关系
7、理解linux下一切皆文件
8、理解重定向的本质
9、理解stdin和stdout的区别
10、理解缓冲区
01 进程和打开文件的关系
1、需要明确一点的是打开文件是由进程打开的,那么进程可以打开多个文件吗?当然可以,打开了多个文件,那么就需要将它们管理起来,操作系统是怎么管理这些打开文件的呢?先创建struct file用来描述打开的文件,然后将多个struct file用指针链接起来。
那么struct file里面包含了什么呢?
1、部分磁盘文件的属性(权限、路径、最近一次修改时间等)
2、read方法、write方法。
........
关于struct file的疑惑后面会解答。
02 简单复习c语言文件操作
1、fwrite
2、fread
3、fprintf
03 介绍系统调用接口:open 、close、write、read
1、open、close、write
可以看到有两个open系统调用,它们是重载关系。
ssize_t 是 typedef int ssize_t
其中O_WRONLY:代表只写,O_CREATE:代表没有该文件就创建,O_TRUNC:如果写之前文件有内容就清空文件所有内容。
0666代表创建文件的默认权限,但为什么创建出来的demo.txt权限是664?
答案:因为umask为0002,最终权限等于664 & (~002),一般我们创建文件的时候,都会让umask等于0,不用系统的默认权限,因为有的操作系统的默认权限可能不是0002,因此使用umask(0)这个函数调用,可以让我们的程序更完美。
我们可以将O_WRONLY、O_CREATE、O_TRUNC看做c语言 “w” 的打开方,
O_WRONLY、O_CREATE、O_APPEND。看做c语言fopen中 “a” 的打开方式。
2、open、close、read
04 文件描述符
1、什么是文件描述符。
open系统调用返回的就是一个文件描述符,我们调用write和read也需要文件描述符。
进程的task_struct中有一个指向struct files_struct结构体的指针,struct files_struct中有一个数组array_fd[ ],该数组存储了该进程打开文件的struct file结构体,所谓的文件描述符,其实是数组下标,进程可以通过数组下标,找到打开文件的结构体,struct file里有文件的读写方法,就能够将进程的数据写到打开文件对于的磁盘文件,或者从磁盘文件读入到进程的数据中。
1、简单介绍:一个磁盘文件被打开,操作系统做了什么?
创建一个struct file内存文件结构,然后将它的地址放入进程的文件描述符表中,最后返回数组下标给用户。
05 文件描述符的分配规则
1、文件描述符的分配规则:优先分配最小的、未分配的数组下标。
为什么这个进程的文件描述符是从3开始的,如果是数组下标的话,不是应该从0开始吗?
答案:0 1 2被分配给键盘、显示器、显示器了。一个进程默认会打开3个流,stdin、stdout、stderr,分别对于0 1 2.
如果关闭0,那么按照规则来说,fd应该是0。
06 理解stdin、stdout、stderr与fd的关系
stdin是FILE* 类型,那么FILE是什么呢?它是一个结构体。
我们都知道可以这样向屏幕打印字符
也可以这样向屏幕打印。
我们也知道c语言接口和系统接口的关系,c语言库函数是对系统接口的一层封装。
为什么c语言接口要对系统接口进行一层封装呢?
①、系统接口不好用,对使用者要求高一点。
②、对系统接口进行封装,可以使得c语言函数在大多数操作系统都能够运行,移植性好。
现在我们大概可以猜测出FILE里面包含了fd,如何证明?
07 理解linux下一切皆文件
驱动层:由于不同厂商制作的硬件可能有差异,操作硬件的方法可能不同,所以在操作系统和硬件之间加了一个驱动层,驱动层提供方法给操作系统,这样不管什么硬件,只要安装了相关驱动,一般而言操作系统都能够使用该硬件。
struct file是打开文件的数据结构,进程可以通过fd找到struct file,然后通过struct file里面的方法可以对硬件或者普通文件进行读写,但对每个硬件或者普通文件的读写方法肯定是不同的,因此就需要别人提供方法的地址给它,struct file就不用管它要调用哪种方法了,而是直接使用read和write。这种调用同一个函数,通过传的函数地址不同,展示的功能不同,不就是多态吗?那么struct file就能够以一种统一的视角看待硬件和普通文件了,可以把键盘、显示器、网卡、磁盘都看做文件。
08 理解重定向的本质
1、先复习一下从定向
①、输出从定向
②、输入从定向
③、追加从定向
2、看看下面的这种现象
如何理解这种现象?
可以把printf(“%s”,"谢谢你\n")与fprintf(stdout,“%s”,"谢谢你\n")等价,又因为stdout包含了1,本来1是指向显示器的,关闭1后打开log.txt,根据文件描述符的分配规则,1指向的是log.txt,可是stdout可不管你是不是显示器,我只负责向文件描述符为1的打开文件写数据。
自然printf输入的数据到了log.txt磁盘文件中,这就是重定向的底层原理。
2、那么只能通过关闭0、1文件描述符,打开新的文件来实现重定向吗?我们还可以使用dup2()来实现重定向。
注意的是:它是将oldfd拷贝到newfd,不要记反了。
09 stdin和stdout的区别
1、现象一
似乎并没有什么区别,只要一个stdout不就好了吗?
一个打印到屏幕上,一个输出到log.txt文件里,如何解释这种现象?
解释:
因为重定向的缘故,1文件描述符不再指向显示器,而是指向log.txt,因此标准输入会打印到log.txt中,但2文件描述符并未重定向,依然指向的是屏幕,因此标准错误打印到屏幕上。从这里我们也知道了,>重定向的只是1文件描述符。
2、现象二
10 缓冲区
1、一个令人疑惑的现象
为啥无法打印出hello world?
解释:printf是c语言接口,默认向stdout流中输出数据,也就是向屏幕输出数据,但由于有c语言缓冲区的因素,该缓冲区刷新策略依靠内存文件指向的对象,如果指向屏幕,那么采取行刷新,如果是普通文件,则采取全刷新。close(1),然后再打开log.txt,导致刷新对象由显示器变为普通文件,因此printf(“hello world”)的内容会保存在用户缓冲区(c语言缓冲区),最后因为你又关闭了1文件描述符,导致用户缓冲区无法通过1描述符将数据刷新到log.txt中。
如果最后没有关闭close(1),在进程结束的时候,操作系统会自动将进程用户缓冲区的数据刷新到文件对象中。
2、关于close()和dup2的理解
dup2(fd,1)是将fd的内容拷贝到1中,假设fd是3,那么1和3都是指向新打开的文件的,也就是说你既可以通过1传入数据,也可以3来传入数据。
close(fd),相当于将arr_fds[ fd ]置为0,并对指向的内存文件中的引用计数--,如果该计数为0,则需要释放该内存文件。
对于dup2(fd,1),如果不需要fd和1同时指向log.txt文件,则可以关闭fd文件描述符。这个时候不能关闭1文件描述符,如果关闭,相当于dup2()调用和没调用是一样的。
3、综合题
解释:write直接输出到屏幕文件,而其他则输出到c语言提供的缓冲区中没有打印出来,由于进程的独立性,每个进程都有自己独立的缓冲区,同时子进程会继承父进程的缓冲区数据,最后在两个进程终止时,操作系统会将进程缓冲区的数据刷新到对应的文件中。
4、模拟实现一个perror