关于本文的几点说明:
1.本文基于Linux4.1.12版本的内核进行介绍,其它版本的内核代码可能略有不同,但不影响理解。
2.在阅读本文之前,假如才能阅读一下本号之前关于块设备和SCSI的文章,对理解本文将很有帮助。
3.建议阅读本号之前的两篇文章(和)后阅读本文,这样理解的更透彻。
后面两篇文章我们对NVMe的原理和涉及的主要概念进行了介绍,明天我们将介绍一下NVMe的Linux驱动是怎样实现的。首先NVMe本身是一个块设备,因而NVMe的驱动也是遵守块设备的驱动构架。本文通过两部份介绍NVMe的驱动程序,一部份是操作系统怎样创建NVMe块设备,另外一部份是剖析一下NVMe的主要流程,包括读写流程和管理流程等。
创建NVMe块设备
对于Linux的块设备来说,其主要的是通过device_add_disk或则add_disk函数(前者是对后者的简单包装)来向操作系统添加一个设备实例。具体原理我们在之前的文章中早已介绍过,本文不再啰嗦了,想了解的朋友请自行翻阅一下历史文章。其基本原理就是通过调用该函数,还会创建在/dev目录下看见的类似sdX的块设备。
NVMe本身也是块设备,自然也不会跳出这个大框架。首先从硬件层面上,我们晓得任何设备必须通过某个总线与CPU向联接,NVMe则正是通过PCIe总线与CPU相连。
图1Linux内核的总线构架
其实,目前NVMe不仅可以通过PCIe总线与CPU相连外,还可以通过其它通道联接,例如FC或则IB。前者则是一种将NVMe设备从估算节点独立下来的方法,也就是此时NVMe就不再是一个卡设备,而是一个独立机箱的设备。无论何种形式相联接,其本质是一样等。
之后是操作系统软件层面的内容。硬件的连通性是基础,当硬件早已连通后,就可以在Linux内核层面发觉设备,并进行初始化了。软件层面的初始化有两种情况,一种是计算机启动的时侯,操作系统会扫描总线上的设备,并完成初始化;另外一种情况是设备在系统启动后联接的,此时须要自动触发扫描的过程。
无论是系统启动也好深度linux系统,还是自动触发扫描也好linux内核添加函数linux软件,NVMe发觉设备的核心流程是一样的。
图2设备初始化主流程
与其它块设备类似,NVMe设备初始化完成后会在/dev目录下出现一个文件。NVMe设备会出现一个形如nvmeXnY的设备文件。如图3所示,蓝色方框中的为一个NVMe块设备。
图3Linux中的NVMe块设备
核心实现剖析
前面我们简略的介绍了初始化的主流程。在里面初始化流程中须要重点关注的是nvme_alloc_ns函数的流程。该函数完成了块设备创建、基本信息填充和块设备注册到内核等工作。如图4是该函数的部份代码片断。这部份片断完成了函数表针的初始化、命令队列初始化和设备名称的初始化等工作。具体关于nvme_alloc_ns函数源代码的逻辑请自行阅读代码,本文不再赘言。
图4代码初始化
在整个初始化流程中比较关键的是对恳求队列(request_queue)中恳求处理函数表针(make_request_fn)的初始化及多队列函数集(mq_ops)的初始化。由于,这儿的函数正是NVMe区别于SCSI等类型设备数据处理流程的地方。
NVMe设备的IO流程
为了易于理解NVMe的处理流程linux内核添加函数,我们给出了传统SCSI及NVMe数据处理的对比流程。如图5所示,整个流程是从通用块层的插口(submit_bio)开始的,这个函数你们都十分清楚了。
图5NVMe数据流程
对于NVMe设备来说,在初始化的时侯初始化函数表针make_request_fn为nvme_queue_rq,该函数就是NVMe驱动程序的恳求处理插口。该函数最终会将恳求写入NVMe中的SQ队列当中,并通知控制器处理恳求。
相对于SCSI设备来说,NVMe设备的驱动还是十分简单的。关于该部份的内容本文就介绍到这儿,关于更多细节,还请你们自行阅读代码。