fopen干了啥
书接上文,下面这些代码都是系统函数调用,不能即用C库函数,又用系统函数。
举个例子。fopen
打开文件返回的是FILE*
类型,不能直接将其传入到write
。因为 write
接收的文件描述符是int
类型。事实上,FILE*
类型内部封装了一个文件描述符,文件描述符就是文件描述符指针数组的下标,通过该文件描述符就可以找到对应文件,从而进行读写。更改数组的指向,就可以实现重定向。
其中,fopen
首先申请struct File
结构体空间,然后返回其地址,也就是 struct File*
,然后调用系统调用函数open
得到文件描述符,将文件描述符填充到结构体中。
缓冲区
运行下面这段代码的效果,看上去是程序先休眠后打印。但实际上,程序是先打印,后睡眠,因为打印未显示出来,所以从运行效果上看,就是先睡眠后打印。
造成这种结果的原因是数据先被缓存起来了。
缓冲策略
- 无缓冲
- 行缓冲: 只有当内存行缓冲区打满或者遇到
\n
,系统才会把缓冲区内的数据刷新。 - 全缓冲:对磁盘文件写入时采用的策略。需要将缓冲区写满,才能写到磁盘文件中。
之所以要有缓冲区,这是由计算机体系决定的。外设由于是机械设备,它的输入输出速度跟不上中央处理器的速度,因此就需要在输入输出时先将数据缓存到内存中,再进行后续处理。
父子进程与缓冲区的关联
运行上面代码,将结果显示到显示器上,得到的结果为三个函数以此的打印结果。
当运行时将打印结果重定向,结果如下。
造成这两种结果的原因是:当我们往显示器上打印,缓冲策略采用的是行刷新。因为往显示器打印字符串的函数调用都带有\n
,所以在fork
之前,程序已经打印并刷新,缓冲区内没有数据,不存在写时拷贝。
当重定向到一个磁盘文件时,缓冲策略变成全缓冲。此时\n
对全缓冲没有影响,字符串只打印不刷新,字符串存在缓冲区中,当执行fork
时,进程结束要刷新修改缓冲区内的数据,因为父子进程要保持独立性,所以在子进程也会拷贝一份缓冲区的数据,即写时拷贝。
因此调用C库函数就分别打印了两次。而系统调用write
是没有缓冲区的,只打印一次。那么,这个缓冲区就是C语言提供的,若是系统提供的,那么write
也应该有缓冲区,打印两次,两者构成矛盾。
需要注意的是内存分为用户空间和系统空间,语言本身提供的缓冲区在用户空间中,为了将此缓冲区数据写入磁盘文件中,还需经过系统缓冲区,系统缓冲区将数据最后写到磁盘文件。
系统调用:文件重定向
这里使用dup2
系统调用函数,使得1号文件描述符的指向和fd的指向一致,也就是文件描述符指针数组下标不变,内容变了,结果就是两者都指向同一个struct file
,由此实现了重定向。可以看到往1号文件描述符写数据,数据写进了文本内。
进一步理解:Linux 一切皆文件
一切皆文件也可以认为是一切皆struct file
。Linux设备都会被描述成这种结构。这样通过read
和write
函数调用就可以实现对设备的输入和输出。不同的设备有不同的输入和输出方式,因此这时就可以采用函数指针实现多态,让不同设备有不同的读写方法。