目录
1.文件描述符 fd
1.1文件打开的返回值fd(重点)
1.2.如何理解Linux下的一切皆文件
1.3.文件fd的分配原则 && 输出重定向
1.4.dup2()函数
2.缓冲区
2.1. 概念
2.2. 存在的原因
2.3. 类型(刷新方案)
2.4. 存放的位置
1.文件描述符 fd
1.1文件打开的返回值fd(重点)
我们可以看到,这三大数据流是来自于C标准库的,类型又是一个我们从来没有听说过的FILE类型。我们可以预料就是,FILE类型应该是C语言库函数内自己封装的一个函数结构体。
我们看到这个数组的下标模式,也大概对文件的管理形式有了一定的猜测。
- 首先是对所有的文件,都有一个描述,即结构体对文件进行描述。
- 然后,有一个数组存储了这些文件结构体的地址对他们进行管理。
- 最后,task_struct 内部有一个指针指向该数组 。
就是如下图这种管理模式
1.2.如何理解Linux下的一切皆文件
其实上一切皆文件,就是封装的结果,利用形式上完全一模一样的结构体对各种硬件进行描述,让其截然不同的操作封装硬件提供的接口上(读写。。操作,每一个硬件都封装了这样的函数)通过,函数指针指向接口,其中的返回值,函数指针名等都一致,只是函数的底层实现不一样罢了。这样底层的不一致就被屏蔽了,在我们使用者看来就是一切皆文件了,也就是一切皆struct file
1.3.文件fd的分配原则 && 输出重定向
fd分配规则:最小的没有使用的数组下标会分配给刚刚打开的文件。
看到这里,是不是很奇怪,明明printf()是向显示器写入的为什么会对log.txt写入呢?
这数组的前三个在操作系统打开的时候,加载进来了,而printf()函数,默认就是向该数组中fd为1的文件执行写入操作,我们把fd==1的文件关了,重新加载了一个文件进去,按照分配规则,那么那个文件就该被分配为fd==1.
1.4.dup2()函数
-
参数:oldfd:要被复制的文件描述符;newfd:目标文件的描述符。
-
返回值:成功,返回新的文件描述符(即:newfd)。出错时,返回 -1,并设置 errno以指示错误。
功能:就是将新的文件描述符变成旧的文件描述符的一份拷贝拷贝。
int main()79 {80 int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);81 82 dup2(fd,1);83 printf("hello Linux\n");84 85 close(fd); 86 87 }
2.缓冲区
2.1. 概念
本质是一块内存区域,用于暂时存放数据,以便更高效地处理输入、输出操作。
- 此处的缓冲区(如:进度条中的缓冲区等),不是内存中的缓冲区,它是语言层面的缓冲区,即:C语言自带的缓冲区,由C语言标准库提供。
缓冲区也会为格式化输入、输出操作提高场所。
- printf函数工作原理:它会将其他类型的数据(如整数、浮点数等)转换为字符数据(即字符串),转化后的数据会被写入到FILE结构体维护的缓冲区中,根据条件刷新缓冲区。
- scanf函数工作原理:scanf会从输入流中读取字符数据,将读取的数据转化为相应的格式化数据,格式化的数据会被存放到FILE结构体维护的缓冲区中,最终被存放到变量中。
2.2. 存在的原因
提高使用者的效率
- 减少了C接口的使用时间,从而减少了用户的等待时间,提高了使用者的效率:调用C接口时,只要将数据交给了缓冲区,就可立即返回,无需等待实际的写入操作完成,意味这用户可以更快地继续执行其他任务。
提高计算机整体的拷贝效率。
- 调用系统调用接口,都是有成本的,有时间和空间的开销。
- 减少调用系统调用的次数,提高了计算机整体的拷贝效率:缓冲区可以聚集大量数据,直到缓冲区满了,再调用一次系统调用进行实际的数据写入,即:进行一次拷贝。
2.3. 类型(刷新方案)
一、无缓冲、无刷新
无缓冲:无刷新,意味着数据不会暂存在缓冲区中,而是立即被写入到目标设备中。
适用场景:需要立即看到结果、实时性要求很高的场景,如:实时系统、设备驱动程序。
优点:保证了数据的即时可见性。
缺点:性能下降,频繁的使用系统调用会增加开销。
二、全缓冲、全刷新
全缓冲:全刷新,缓冲区满了或者关闭文件时,缓冲区的数据才会被刷新到目的设备中。
适用场景:文件的读写操作,尤其是大文件。
优点:减少了系统调用的次数,提高了性能。
缺点:可能会丢失数据,如:在缓冲区的数据未被刷新前,发生崩溃,则这部分的数据就会丢失。
三、行缓冲、行刷新
行缓冲:行刷新,意味着遇到换行符\n,缓冲区的数据就会被立即刷新到目的设备中。
适用场景:标准输入输出(显示器)。
注:当调用c语言接口fflush(),进行强制刷新; 进程退出时,或文件关闭时,自动刷新
2.4. 存放的位置
- 缓冲区存放在FILE结构体中,即:缓冲区是被FILE结构来维护的。
- 每个通过标准C库函数打开的文件,都拥有自己的缓冲区。
fwrite等标准库函数,会先将数据拷贝到缓冲区中,然后根据一定的条件,调用系统调用接口进行刷新。
文件操作的系统调用接口,其实是个拷贝函数,它将数据从语言层的缓冲区拷贝到内存的缓冲区。
int main()80 {81 87 88 const char * s1="hello write\n";89 90 write(1,s1,strlen(s1));91 92 const char * s2="hello fprintf\n";93 fprintf(stdout,"%s",s2);94 95 const char * s3="hello fwrite\n";96 fwrite(s3 ,strlen(s3),1,stdout);97 98 fork();99 return 0;
}
现象1解释:write()为系统调用接口,直接将数据写入到内核中;fprintf、fwrite为库函数,先将数据写入到缓冲区中,因为它们都是向显示器进行写入,而写入显示器是行刷新(遇到换行符\n,进行刷新),所以fork创建子进程前缓冲区中的数据全部被刷新到内核中了。
刷新到内核的数据,不属于进程的数据;存放在缓冲区中的数据,属于进程的数据。
现象2解释:重定向到普通文件时,数据刷新缓冲区的方式,由行缓存变为全缓冲,C语言接口自带缓冲区,所以它会将数据写入到缓冲区中,就不会立即刷新。fork创建子进程,父子共享缓冲区的数据,但是进程退出后,统一进行刷新。刷新缓冲区,是清空缓冲区,是修改数据的一种方式,所以父子进程的数据会发生写时拷贝,父子进程分别刷新各自的缓冲区,随即产生两份数据。write是系统调用接口,直接将数据写入到内核中,不存在所谓的缓冲区。
- 一般C库函数写入文件时,是全缓冲; 写入到显示器时,是行缓冲。
- 重定向到普通文件时,数据刷新缓冲区的方式,由行缓存变为全缓冲。
- 刷新缓冲区,是清空缓冲区,是修改数据的一种方式