Linux下一切皆文件? -- 理解文件标识符fd

什么是文件标识符?

最浅来看 文件标识符就是我们调用系统接口 open 打开文件时,open函数的返回值。

但要深入理解它,我们得知道:

1.文件打开的本质什么,操作系统如何管理被打开的文件?

2.打开的文件与打开它的进程如何建立联系,也就是进程如何找到要操作的文件的?

文件的打开与管理

先来回答第一个问题。我们假设一个场景:当我们用open打开当前路径一个文件名为test.txt的文件,然后对它进行读写时,当前进程是直接与磁盘交互吗?当然不可能,那样效率太低,又不能保证安全性。但是文件本来就是在磁盘上啊,那进程是怎么完成读写任务的呢?

答案就在操作系统 和内存 ! 当我们用open打开文件时,操作系统会先把磁盘上的文件加载到内存,以结构体的方式描述它,再以类似链表的结构统一管理所有被各种进程打开的文件。

我们进程对文件的读写就是对内存中描述文件的struct读写!再由操作系统通过驱动程序完成最终的写入磁盘。要清楚进程对文件的操作都是内存级的!是只限于内存范围内的!

1c5c339a58344c1799c3970b9c153dd4.png

被打开的文件

进程与被打开的文件连接

了解被打开的文件是被内核统一组织管理的之后,就来到了第二个问题。那进程是如何访问到它让操作系统开的文件struct呢?

我们要明白两点:

1.因为文件是以结构体的方式描述后被加载到内存 对文件操作就是对结构体操作 ->拿到这个结构体的指针?

2.进程与打开的文件可以是一对多的(同一个进程可以打开多个文件)->指针数组?文件描述符总是0 1 2 3...是不是很可疑 很像下标?

基于以上两点,基本上就可以推测出进程如何找到打开的文件。

那内核中究竟是怎么做的呢?

35836ee5e2c048fd967a3046f00bb64b.png

进程与文件

在每个进程PCB中都存有,专门为它管理打开文件的结构体指针,这个结构体内有一个指针数组,进程可以通过数组下标拿到文件结构体指针,从而对文件进行读写。

而这个数组下标就是文件描述符!内核是通过指针数组的方式与文件建立映射关系的。 

文件描述符的分配规则 

当打开新的文件时,会遍历指针数组,找到第一个空位置,存入文件struct的地址后返回下标

如下代码可以验证:

  #include <stdio.h>  
  #include<sys/stat.h>  
  #include <string.h>  
  #include <unistd.h>
  #include <sys/types.h>
  #include <fcntl.h>
                                                                                                                                                                   
  int main(){
    umask(0);
    int fd1 = open("test1",O_CREAT|O_TRUNC|O_WRONLY,0666);
    printf("fd1: %d\n",fd1);
    int fd2 = open("test1",O_CREAT|O_TRUNC|O_WRONLY,0666);
    printf("fd2: %d\n",fd2);
    close(fd1);
    printf("fd1: closed\n");
    int fd3 = open("test1",O_CREAT|O_TRUNC|O_WRONLY,0666);
    printf("fd3: %d\n",fd3);
    int fd4 = open("test1",O_CREAT|O_TRUNC|O_WRONLY,0666);
    printf("fd4:%d\n",fd4);
    return 0;                                                                                                                                 
  }      

d83b397656474f0d986e6360839dcbd9.png

文件标识符与上层封装

要知道 文件标识符是进程对文件操作的唯一凭证,所以常见的语言对文件的操作,如C 中struct FILE ,C++中的流对象等... 一定也是通过对文件标识符 和系统调用做封装得来的, 这是毋庸置疑的。

默认打开的文件

不难观察到上面代码中 fd1是从3开始的,说明在我们打开第一个文件之前已经有三个文件被自动打开了,他们的结构体地址分别存储在对应下标为0,1,2的位置。

它们分别是:

0 标准输入 1 标准输出 2 标准错误 

这里要引入一个"一切皆文件的概念",这个概念下文会详细讲,这里先简单理解,在Linux进程的视角下,所有对外设的输入输出 都会被当成文件来操作。

从标准输入(标识符为0的文件)读入就是从键盘读入,c语言常用的scanf就是对它做的封装

往标准输出和标准错误 写入 就是在屏幕上打印,printf,cout底层也是这么做的。

  int main(){  
    umask(0);  
    write(1,"linux so easy!\n",15);  
    return 0;  
  }  

1bddb5133e76408c892686d8fc91277f.png

运行结果往终端输出

如何理解一切皆文件

为什么往磁盘文件写入能存储在磁盘上往标准输出写入怎么就打印到屏幕上了?这是如何做到的?不是都调用的同一个write接口吗?

9cc5dd219ca64817a778d02e9ce38b6f.png

操作系统在将文件加载到内存时,

给不同的文件创建同样的结构体类型,但是里面保存不同的方法!

不同外设在上层视角下变成了相同的 文件struct,只需要以相同的方式(如调用write)进行操作!这就是“一切皆文件的理解”

重定向的理解

基于以上包括“一切皆文件”认识,重定向的本质就很好理解了。

输出重定向:把文件标识符1对应的地址覆盖为自己指定文件,进程还是向1里写入,但原本向屏幕打印的内容就被写入到指定文件了。

输入重定向:把文件标识符0对应的地址覆盖为自己指定文件,进程还是从0里读入,但原本从键盘读入就变成从指定文件里读入了。

追加重定向类似,只是打开文件时以append方式打开。

如何实现:这里介绍一个系统调用 : dup2(int oldfd , int newfd)dcb94c23786b4e5bbd3b3a0453c77829.png

 80c186b58d2744f798939a743dfa9ce3.png

意思是把newfd 数组对应的文件地址覆盖为oldfd下标处的。使得它们映射同一个文件。

总结

由此再来从头梳理一遍,分析如下代码是如何从调用open函数,文件被打开,到与进程建立连接,再到调用write写入后 关闭的:

 #include<stdio.h>                                                                                                                                                
 #include<sys/stat.h>  
 #include<sys/types.h>  
 #include<fcntl.h>  
 #include<unistd.h>  
 #include<string.h>  
  int main(){  
   umask(0);  
   int fd  =open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);  
   const char*buffer="linux so easy!!\n";  
   write(fd,buffer,strlen(buffer));  
   close(fd);  
   return 0;  
 }  

1.调用open函数打开文件

此函数为Linux的系统调用,操作系统会先在数据结构(类似于链表)中查找文件是否已经被打开,若没有,会创建结构体,把文件的部分内容和属性以及操作方法储存在结构体内,加载到内存,并与其他被打开的文件统一管理起来。

2.文件与进程建立映射关系

确保文件的结构体被加载到内存以后,操作系统会找到进程PCB中管理打开文件的struct中存储文件struct指针的数组,从头遍历空位,将打开文件的结构体指针存入空位,并返回下标(文件描述符)。

3.调用write接口

通过参入传递的文件描述符,找到对应的文件内核结构体,通过结构体内保存的写入方法,调用它完成写入。

4.调用close接口

将文件描述符对应的位置置为空,等待下一个打开的文件结构体地址填入。

进程不用考虑文件结构体的释放,那是操作系统干的,一个文件可能被多个进程打开,一个进程也可能打开多个文件,进程只用管理自己打开了的文件。而操作系统有引用计数的方式来决定是否释放内存文件结构体。

end~希望大家能有收获,欢迎留言讨论。

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值