大话存储系列23——存储系统内部IO 下

5、层与层之间的调度员:IO Manager

IO Manager 或称IO Scheduer。每个操作系统都会有这样一个角色,它专门负责上层程序的IO请求,然后将IO请求下发到对应的模块和设备驱动中执行,然后将结果通知给上层程序,当某个程序师徒访问某个文件的时候,它其实并没有和文件系统打交道,而只是在与IO Manager打交道,而只是在与IO Manager 打交道。


1、某时刻,图中的“Subsystem”这里就是指某个应用程序,向OS(System Service)发起了对某个文件对象,或者某个设备的open操作,欲打开这个文件或者设备对其进行进一步的操作。

2、IO Manger只是一个调度和代理模块,它本身并不知道上层所请求的这个对象(文件或者设备等)到底存放在哪里,是哪一类的对象,文件还是设备等。所以IO Manger 需要问一个明白人,他就是Object Manager,IOM向OM发起查询请求,同时,IO Manger也会向Security 模块来查询当前应用程序是否具有访问目标对象的权限。假设本例中应用程序open的是比如:D:\Melonhead 文件。

3、本例对象就是D分区文件系统下的一个文件:“D:\Melonhead”,Object Manager 会将这一信息返回给IO Manager(IOM),IOM得到这一信息后首先检查对应的分区或者卷是否已经被挂载、如果尚未挂载,则IOM暂挂上层的Open()操作,转而首先让文件系统挂载这个卷。Windows中存在多种文件系统模块,比如:FAT32,NTFS,CDFS等,IO会依次轮询地尝试让每个文件系统模块来挂载这个卷,直到某个文件系统成功识别了这个卷上的对应文件系统信息,并将其挂载为止。FS挂载之后,IOM继续执行Open()请求。

4、IOM为这个请求分配对应的内存空间,然后向对应的下层驱动(这里就是文件系统驱动咯,也就是文件系统模块本身)发送一个IRP(IO Request Packet:IO请求包),IRP中包含了上层所请求的所有信息以及完成这个请求所要涉及的底层所有驱动链(文件系统驱动、磁盘设备驱动等)的对应信息。

5、IOM将生成的IRP首先发送给驱动链的顶层第一个驱动,这里就是文件系统本身。文件系统收到IRP之后,会读取IRP中给自己的信息,提取触操作对象和操作内容,然后开始操作,如果应用调用时未指定DIO模式,则文件系统当然首先要查询是否请求的对象数据已经位于Cache中(本例中应用请求Open()一个文件,所以这里的目标数据就是指这个文件的Metadata,FS要读入这个文件的Metadata后才可以响应上层Open()操作),如果目标数据恰好位于Cache中,那么文件系统直接完成这个IRP请求,将携带有IO完成标志的IRP返回给IOM宣告完成,IOM从而也返回给应用程序宣告完成。如果Cache未命中,则FS需要从底层的磁盘来读取目标数据,这就需要FS这个IRP中对应的底层设备驱动信息中填入底层设备所要执行的动作,比如读取某某LBA段的内容等,然后将组装好的新IRP通过条用IOM提供的功能API来将它发送至位于FS下层的设备驱动处。

6、设备驱动处收到IRP之后,与文件系统做相同的动作,也是首先从IRP中找出给自己的信息,提取出操作对象和内容,然后操控物理设备执行对应的扇区读写。

7、当驱动链中所有驱动都完成各自的任务之后,底层驱动将带有完成状态的IRP返回给IOM。

8、IOM收到这个IRP之后,检查器状态标志,如果是成功完成,则将会通知上层应用程序打开成功,并且返回给应用程序一个针对这个文件对象的File Handle。

9、IOM 将这个IRP清除。

10、IOM返回一个File Handle给程序,以后这个程序需要使用这个handle来对这个文件进行其他操作,比如写、读、删等等,其对应的IO请求执行过程会依照上述步骤进行。


可以看到,在OS内核中,每个IO请求都是以IRP为载体来传递的。与外部网络上的数据包相同,只不过IRP一直在内存中传递(因为各个层的驱动都在内存中运行),而不是在线缆上。下面我们看看一个IRP包的结构:

IOM在发送IRP给驱动链顶层驱动之前,一定要知道完成上层IO需要的全部底层角色,如FS、Volume、Device Driver等,这些角色按照发生作用的先后顺序依次排列,形成一个自上而下的驱动链。

IRP在被IOM初次组装时,会被IOM填充入一个或者多个IOSL(IO Stack Location)。驱动链中有几层驱动,IRP中就有几层IOSL,每层驱动都对应自身的IOSL。IOSL中包含了本层驱动应该做的事的描述。比如操作目标、何种操作。。。。


被IOM组装好的IRP初次被发送到驱动链顶层(本例中是FS)驱动模块时,只有顶层的IOSL被IOM填入,其他层次为空,因为此时只有IOM知道应用程序要做什么,而且IOM也不知道FS下层的磁盘驱动要操作哪些扇区。






6、底层设备驱动层

IO请求从应用程序发起,经历了文件系统、卷卷管理系统、现在这个IO请求要经历它离开主机操作系统前的最后一关:设备驱动程序,我们以Windows系统的驱动层次架构为例,说明操作系统是如何使用底层硬件设备的。

总线控制器驱动:一个世纪的物理设备,一般是以某种总线来连接到计算机主板上的,比如PCIX,PCIE总线,主板上的导线接入对应的总线控制器芯片(一般是北桥,或者南桥,或者CPU集成的),操作系统就是从总线控制器芯片来获取所有外部设备信息的,任何发向或者收到自外部设备的数据和控制信息流,都经过这个总线控制器,总线控制器只负责将数据流转发到对应总线的对应地址上的设备,但是数据流本身的流动是由设备DMA部分直接负责的。所以,操作系统如果想看到一个设备,必须通过这个设备所在的总线控制器。总线控制器驱动位于操作系统最底层,它可以从总线控制芯片来获取当前总线上的任何动静以及任何已经介入的设备信息,也就是说这个驱动是用来驱动总线控制器的。任何新接入的设备都会在硬件上被总线控制器感受到,从而也就被总线驱动程序感受到。

然而,总线控制器驱动只能感知到总线上新接入了一个设备,却不知道这是什么设备,该如何使用。想要使用这个设备,就得再加载一层驱动,也就是这个设备自身的驱动。

至此,我们数一下这个驱动链里的驱动数目:主机总线驱动——SCSI控制器驱动——SCSI Trager 设备驱动(如硬盘设备)——LUN设备驱动。


Windows 下的IO设备驱动类型:
Windows系统将IO设备的驱动程序按照层次分为了两大类,即上层的Class Driver和下层的Port Driver。Class Driver一般就是鼠标啊、键盘啊、磁盘啊等各种大类型的驱动;Port Driver则是专门用来驱动各种外部总线控制器的,比如USB、IDE、SCSI等。

IO在驱动层次之间的流动:


上层将IRP发向下层的Storage Class Driver,Class Driver将IRP转换为SRB(SCSI Request Block),SRB中包含了CDB(command Description Block)以及其他一些内容(有人可能会问,Storage Class Driver对任何设备都是用SCSI协议集吗?底层如果是ATA硬盘怎么办?留着这个问题继续看),Port Driver接收到SRB之后,对SRB进行分析,并将SRB转换为外部总线所要求的的指令格式发送给对应的设备(举个例子:Storage Class Driver统一使用SCSI指令集来操作底层设备,如果底层设备并非SCSI的,比如ATA磁盘,那么会由位于Class Driver下层的ATA Port Driver 将SCSI协议映射翻译为ATA协议指令)。此外,Class Driver也并不关心底层设备对应的总线物理地址,底层寻址的操作由Port Driver执行。


Linux下的存储系统驱动链

如下图所示:


第一、第二层不必解释太多。第三层是系统缓存,全局Page Cache缓存区,可Prefetch也可以Write Back;第四层是Generic Block Layer,这一层是FS抽象的结束,FS将文件IO映射为Block IO 之后,就要发送到Generic Block Layer。再往下就是各个块设备的驱动层,通过块层将对应的IO请求发送给对应的块设备驱动,著名的IO Scheduler(IOM)就是运行在此层,此处调度器需要一个Queue。

再往下是一个公共服务层,即SCSI Middle Layer,这一个层的功能是专门处理SCSI上层逻辑,比如数据IO、错误处理等机制,还负责将上层的块IO映射翻译成对应的SCSI协议CDB,然后向下层的SCSI Host Controller,即SCSI控制器驱动,这里也需要有一个Queue。,到了这里,再往下就是系统总线和外部总线,适配卡等硬件了。。

下面我们用一个IO请求在Linux内核中的流动过程来演绎一下这个原理:

1、用户程序对一个VFS目录对象(比如/mnt/test.file)发起Open()操作。

2、随后进行Read()操作,内核接收到read()请求后,首先进行文件系统的处理流程。文件系统入口为generic_file_read()。入口函数判断本次IO是否为Direct IO,如果是,则调用generic_file_direct_IO()函数来进入DIO流程,如果是Buffer IO,则调用do_generic_file_read()函数进入Cache Hit流程。do_generic_file_read()函数其实只是一个外壳包装,其内部其实是do_generic_mapping_read()。

3、文件的区段会映射到Page Cache中对应的Page中,这个函数首先检查请求的数据对应的Page是否已有数据填充,如果有,则Cache Hit,直接将其中的数据返回给上层,如果未命中,则调用readpage()函数来向底层发起一个块请求,readpage()调用了mpage_readpage()。

4、mpage_readpage()又调用了do_mpage_readpage()函数来创建一个bio请求(Block IO)这个bio相当于Windows下的IRP.

5、然后接着调用mpage_bio_submit()处理这个bio请求,mpage_bio_submit()其实调用了submit_bio()函数将bio发送给下层的函数generic_make_request()。generic_make_request()函数为通用块层的入口函数,这个函数调用make_request_fn()函数将bio发送给下层,也就是Block Device Driver层的IO Scheduler的队列中。

6、make_request_fn()函数为块设备驱动层的入口函数,也就是IO Scheduler的入口函数,这个哈数包装了make_request()函数,它是实现IO Scheduler的调度功能的主要函数。(IO Scheduler下文介绍)

7、IO Scheduler通过调用request_fn()函数从而将bio下发给了SCSI控制器驱动,SCSI控制器驱动程序调用函数将这个bio转换成了SCSI指令,然后调用scsi_dispatch_cmd()将指令发送给对应的SCSI控制器硬件。













转载于:https://www.cnblogs.com/cymm/archive/2013/04/09/3390392.html

你可能感兴趣的:(大话存储系列23——存储系统内部IO 下)