文件描述符fd 和 缓冲区

举报
绝活蛋炒饭 发表于 2024/12/15 19:22:50 2024/12/15
【摘要】 本文介绍的文件描述符如何让进程打开文件时如何管理文件,以及C语言在语言层面给用户提供的缓冲区

 1.文件描述符 fd


1.1文件打开的返回值fd(重点)

编辑

我们可以看到,这三大数据流是来自于C标准库的,类型又是一个我们从来没有听说过的FILE类型。我们可以预料就是,FILE类型应该是C语言库函数内自己封装的一个函数结构体。 

编辑

编辑  

我们看到这个数组的下标模式,也大概对文件的管理形式有了一定的猜测。

  1. 首先是对所有的文件,都有一个描述,即结构体对文件进行描述。
  2. 然后,有一个数组存储了这些文件结构体的地址对他们进行管理。
  3. 最后,task_struct 内部有一个指针指向该数组 。

就是如下图这种管理模式

 编辑


1.2.如何理解Linux下的一切皆文件

其实上一切皆文件,就是封装的结果,利用形式上完全一模一样的结构体对各种硬件进行描述,让其截然不同的操作封装硬件提供的接口上(读写。。操作,每一个硬件都封装了这样的函数)通过,函数指针指向接口,其中的返回值,函数指针名等都一致,只是函数的底层实现不一样罢了。这样底层的不一致就被屏蔽了,在我们使用者看来就是一切皆文件了,也就是一切皆struct file

编辑

编辑


1.3.文件fd的分配原则 && 输出重定向

 fd分配规则:最小的没有使用的数组下标会分配给刚刚打开的文件。

编辑

编辑

看到这里,是不是很奇怪,明明printf()是向显示器写入的为什么会对log.txt写入呢? 

编辑

这数组的前三个在操作系统打开的时候,加载进来了,而printf()函数,默认就是向该数组中fd为1的文件执行写入操作,我们把fd==1的文件关了,重新加载了一个文件进去,按照分配规则,那么那个文件就该被分配为fd==1. 


1.4.dup2()函数


编辑

  1. 参数:oldfd:要被复制的文件描述符;newfd:目标文件的描述符。

  2. 返回值:成功,返回新的文件描述符(即: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. 存放的位置

  1. 缓冲区存放在FILE结构体中,即:缓冲区是被FILE结构来维护的。
  2. 每个通过标准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是系统调用接口,直接将数据写入到内核中,不存在所谓的缓冲区。

  1. 一般C库函数写入文件时,是全缓冲; 写入到显示器时,是行缓冲。
  2. 重定向到普通文件时,数据刷新缓冲区的方式,由行缓存变为全缓冲。
  3. 刷新缓冲区,是清空缓冲区,是修改数据的一种方式

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。