【Linux系统编程】如何理解“Linux下一切皆文件!”
文章目录1. Linux下一切皆文件2. 深入理解切入点——谈谈设备看看内核源码VFS1. Linux下一切皆文件⾸先在windows中是文件的东西它们在linux中也是文件其次⼀些在windows中不是文件的东西比如磁盘、显示器、键盘这样硬件设备也被抽象成了文件你可以使用访问文件的方法访问它们获得信息甚至将来我们要学习网络编程中的socket套接字这样的东西使用的接口跟文件接口也是⼀致的。这样做最明显的好处是开发者仅需要使用⼀套 API 和开发工具即可调取 Linux 系统中绝大部分的资源。举个简单的例子Linux 中几乎所有读读文件读系统状态读PIPE的操作都可以用read 函数来进行几乎所有更改更改文件更改系统参数写 PIPE的操作都可以⽤ write 函数来进行。那我们改如何理解呢2. 深入理解在之前的学习中我们其实也经常提到Linux下一切皆文件包括前几篇文章我们讲基础IO文件描述符、重定向这些东西。我们提到一个进程启动会默认打开标准输入、标准输出、标准错误这三个流。我们也知道它们底层对应的是键盘、显示器这些东西是文件吗现在我们知道这三个东西其实就是三个文件指针嘛FILE*FILE内部封装了文件描述符Linux平台上它们三个分别占用。我们使用诸如read、write这些文件IO的接口也可以访问它们。所以从这里也能看出Linux下它们也被抽象成了文件。这也在一定程度上证实了Linux下一切皆文件这一说法。但要真正理解这一结论其实不是很好搞会比较抽象一点。那我们这里要怎么讲呢切入点——谈谈设备我们可以从这样一个点进行切入我们之前的文章中有给大家介绍过这样一张图——计算机体系结构操作系统在中间起到一个承上启下的作用对下管理软硬件资源对上给用户提供良好的服务虽然操作系统给我们提供服务但是他不相信任何人。为了保证自己的安全在开发角度操作系统对外会表现为一个整体但是会暴露自己的部分接口供上层开发使用这部分由操作系统提供的接口叫做系统调用。所以我们前面学习的各种诸如进程、文件相关的各种操作都是通过各种系统调用来完成的即使用语言级别的函数底层也必定封装了系统调用。这都是我们之前讲过的内容。那么问题来了。既然操作系统要对各种软硬件资源进行管理那我们的计算机中有各种各样的硬件资源比如磁盘、网卡、键盘、显示器…那操作系统要不要将他们管理起来呢当然如何管理先描述、再组织我们可以来模拟一下这个过程比如我们使用一个struct device结构体来描述各种硬件比如我们列举这样几个当然在内核中就是一个个结构体来描述它们。再组织然后我们就可以使用一个双链表把它们都链接起来那未来我们的进程可能需要和各种设备进行各种各样的交互比如我们以读写IO为例那各种不同的设备它们的读写方法也大概率甚至说一定是不同的比如键盘读方法可以读取从键盘输入的数据但是键盘好像并不需要写方法啊。所以键盘的写方法就实现成一个空就行了函数体里面啥也不用写。但是如果是磁盘的话就有读也有写。所以这些计算机外设有的设备只读有的设备只写有的又读又写。不同设备的各种操作的访问方式一定是不同的要不然为什么不同的设备有不同的驱动呢下一个问题我们访问这些设备的时候本质是谁在访问其实还是我们的进程代表了用户在访问进程执行了某些特定的代码底层可能通过某些系统调用然后访问到某种设备完成相关的操作。那么内核中描述一个进程——task_structLinux下一切皆文件所以进程访问这些设备也要通过访问文件的方式进行。另外我们知道一个进程启动默认打开标准输入、标准输出、标准错误这三个文件它们就对应键盘、显示器、显示器这三个设备。那我们就以键盘、显示器设备为例来讲解那打开的这些文件它们底层可能就对应的是一些设备。因为不同设备的各种操作方法是不同的以读写为例那操作系统为了以一种统一的方式进行管理和访问会在file结构体中提供类似这样的函数指针尽管底层对应的设备可能是不同的但是站在struct file这一层无所谓我们看到的接口都是统一的一模一样的。这样我们打开了一个文件之后它底层对应的是什么设备就把struct file中的函数指针指向其特定的操作方法那这样的话站在进程的视角不管它要读写哪些设备都无需关心底层这些设备的各种方法有多大的不同只需调用struct file中的对应方法即可。所以这就是一切皆文件是站在进程的视角看待它可以访问的“一切”——都是文件所以按照上面说的在struct file中存在了一组方法集当然这组方法集不是具体的函数实现而是一组函数指针当对应的文件被打开时这些函数指针就会指向具体设备的操作方法。看看内核源码那如何证明打开内核源码我们能够找到首先这是之前讲过的然后在struct file的定义中我们能找到这样一个字段一个结构体指针——const struct file_operations *f_op;那我们转到这个结构体的定义这里面放的不就是各种文件操作的函数指针嘛这里的函数指针指向的是“针对特定文件类型/设备类型”所实现的读写例程。 对于磁盘文件它指向文件系统模块负责缓存和日志对于键盘/串口它指向字符设备驱动负责直接操作硬件寄存器。但它们都通过 struct file 作为桥梁完美地实现了“一切皆文件”的统一接口。思考那讲完上面的逻辑链大家有没有感觉到些许熟悉同样的函数指针在最终调用的时候可以调到不同的特定的方法。这种感觉好像有点像我们之前C里面学习的多态当然面向对象的编程语言都有多态啊多态表现出的结果是怎样的继承关系的不同类对象去调用同一个函数产生不同的行为其实是底层调到了不同的函数。C多态的原理是怎样的简单回顾一下之前的C文章中有讲其实就是通过对象里的虚函数指针去找到其对应的虚函数表那子类对象的虚指针就指向子类的虚函数表虚表其实是一个函数指针数组父类对象的虚指针就指向父类的虚函数表那这样它们就能调到不同的虚函数进而实现多态不同对象去完成同一行为时展现出不同的形态。所以可以认为上面讲的逻辑就是C语言实现的多态因为Linux主要是由C语言写的VFS最后这一部分我们可以把它称为虚拟文件系统。没有这套机制操作系统就无法做到用 open 打开一切用 read/write 操作一切。当进程调用 read(fd, …) 系统调用时VFS 根本不关心 fd 背后是什么真实的一个文件还是对应一个硬件对应的操作方法有多么不同它只需要通过 fd 找到 struct file然后直接调用 file-f_op-read(…) 即可。底层的差异被 file_operations 完美屏蔽了。一切都可以看成是“文件”虚拟文件系统VFS的本质就是在底层硬件驱动和上层用户进程之间搭建一层抽象的“中间层”。回想我们之前提到过的一句话
