2020年4月13日 下午12:19
在讲解这个过程的同时,也可以一并理解一下几个问题:
- 为什么需要文件描述符?
- 文件描述符fd与inode有关系吗,有怎样的关系?
- linux内核是如何封装各种抽象文件的读写接口的,能做到所谓的“一切皆文件”?
- 不同的抽象文件,他们之间的读写操作在内核中有什么区别?也就是linux内核它封装了什么?(等同于上一个问题)
- 你怎样验证你说的是正确的?有没有可以证明的方式?
讲解的角度、方法:
我们这里采用自顶向下的角度来讲解linux中是如何完成文件的读写。所谓自顶向下其实就是按着程序员代码不断展开,到达系统调用,在系统调用中就需要看linux内核的实现了,这里我们绕过内核的具体代码实现,直接找到通过系统内核系统调用之后处理的结果什么,通过这样间接的方式来验证我们的思路正确性,具体来说就是看能够在linux系统中找到系统内核调用过程中使用的文件以及对应的文件描述符,这些文件或者文件描述符其实就是系统内核调用在运行过程中留下的脚印👣,我们可以按图索骥看看内核到底干了点什么。
linux中,是如何完成文件的读写
- 根据操作系统的我们已有的知识储备,我们知道操作系统是很讲究设计的,也就是各个部门之间的分工合作,并且有上下级之间的封装调用,可以做到下级对上级来说,下级的工作是透明的。具体的在linux中的体现就是:区分用户态和内核态。从linux设计的角度来说,希望让竟可能的降低系统的使用者也就是程序员的使用难度,给他们封装出一些丰富的接口,够他们能够完成功能就可以了,程序员你就别管我操作系统是如何实现你调用的接口了。
- 正是由于来于这样一种考虑,linux设计了文件描述符:当一个文件被一个进程打开,就会创建一个文件描述符,这里的文件描述符可以理解成字节流的接口,接口这个词就能很清晰的体现了文件描述符在linux中的本质特性。那么,从用户态这个层面来看,其实并不是一切皆文件,而是一切皆文件描述符。
- 那么对于有追求的程序员来说,他不满足现状,他希望可以弄明白linux内核是如何实现文件的读写的。这是我们就需要思考一个问题,难道整个文件系统有两层吗,一层是用户态,一层是内核态,内核态就能直接操作硬盘上的文件吗?其实,我们单独拿出内核态来看,它为了实现文件的读写,其实也有所谓的层次结构,这中间也进行了精妙的设计。这里面最关键的一个设计就是inode,inode将我们操作的文件进行抽象,成为我们操作文件读写时最小的操作单元,也就是说,在内核态中其实他操作是的inode,可千万别以为我们直接操作的是赤裸裸的文件,inode就可以很要的封装了各个文件之间大小类型之间的区别,可以说inode是实现文件读写最最核心的关键,其他的类似于管理不同的抽象文件读写,其实都是在inode基础上的业务逻辑,业务逻辑可以频繁的更换,但是基础的inode这层定义逻辑是一定不能变的。这里我们就知道inode真的是特别特别重要,理解inode对理解操作系统也是一个关键。
- 认识到inode的重要性,我们接下来说说linux是如何考虑在inode的基础上完成所谓的业务逻辑,来实现不同的抽象文件的读写操作。
- inode的重要性就像是数据结构与算法中数据结构对算法的作用,inode就是这里的数据结构,在做算法题的时候,我们知道如果我们设计出了这道题需要的数据结构,那么写算法在熟练语法的基础上就是分分钟的事情
- 在inode这套数据结构之上,我们可以这样认为:从内核态的角度来看,不是所谓的一切皆文件描述符,而是一切皆inode,因为内核态中文件描述符是不存在,在从用户态到内核态时文件描述符已近展开了成为了inode。文件描述符fd和inode其实都是一个非负整数,这点他们是相同的。
- 如何证明inode是可以对抽象文件,eg:tcp socket进行抽象表示呢?在linux一切皆文件之tcp socket描述符(三) - wilson排球 - 博客园就可以找到证据。
- 要实现不同文件类型的读写,我们考虑到linux是一个各种子系统的集合 将操作系统分解,看看这个庞大的系统中包含哪些子系统,这其中就包括网络子系统,当我们需要完成基于socket、tcp socket的文件读写的时候,就需要依赖于网络子系统,把这部分工作承包给它;还有输入输出系统,当我们需要对块设备进行读写时,我们依然需要依赖于它;还有对字符流设备的读写,这里的例子有ssh连接的连接过程,其实就是一个文件读写的过程,它的本质不变,依然可以拆解为单个的文件读写过程,只不过ssh连接进行操作的过程并不是依靠单独唯一的进程、文件,它需要涉及到进行的fork,以及各个进程都会有自己对应的操作文件组。在学习的过程中,一定要明白这部分是属于业务逻辑,比如在ssh中,可以称作为client和server进行交互的逻辑。在学习的时候,容易沉溺于业务逻辑中,而忘记了文件读写的本质操作。业务逻辑会根据业务的不同随时变化,但是文件读写这样基本的操作是永远不会变的,各种复杂的业务也都是有多个文件读写操作进行组合完成的。
- 那么现在回答:单独的文件读写的本质是什么,是如何完成的?
- 一个进程A启动,那么并不是进打开一个文件,而是会打开各种各样的文件,这这些文件中有很多都是这个进行它所依赖的系统库文件,在这些依赖的系统库文件之外,才有我们真正进行读写的文件,这些文件在打开之后,会有对应的文件描述符
- 我们可以在
/proc/A/fd找到这些文件描述符的链接,这个链接就会指向我们真正操作的文件位置。其实这个位置是给内核态使用的,他拿上这个位置上的文件进行操作。在从用户态到内核态的过程中,我们就依赖于这个链接完成用户态到内核态的转换。这个位置可以是linux下文件的真实路径,这部分工作由linux文件子系统完成,另外这个位置也可以直接指向inode的编号,在socket链接中我们就以看到这个inode编号。 - 简单来说,读写文件的核心是用户态到内核态的一次转换过程,至于到达内核态之后,会根据你操作的文件类型,内核会选择不同的助手子系统来完成真正的inode读写操作。
- 对node的读写,就是读写文件的最后一步。至于要问如何完成对node的读写,我下次在写。