NVMe驱动详解系列_第一部:NVMe驱动初始化与注销

本文详细解析Linux NVMe驱动的初始化与注销过程,包括NVMe驱动的源码位置、编译方法,以及PCI驱动的注册、注销流程。通过对NVMe驱动注册过程的分析,展示了PCI驱动注册中的关键函数和步骤,为理解NVMe驱动的工作原理提供了基础。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

 

 

 

 

NVMe驱动详解系列

第一部: NVMe驱动初始化与注销

作者:perftrace@gmail.com

 

 

 

 

 

 

 

 

 

 

 

 

 

1     NVMe驱动详解之一源码和编译

本系列主要针对linux系统中自带的NVMe驱动,进行详细的分析和学习,从而掌握NVMe以及PCI相关知识。文中所使用的源码是linux4.17.2。

需要提醒的是,阅读本系列文章需要一些linux内核模块、pci总线、内核数据结构以及设备驱动模型相关知识,当然作者会尽全力将内容写得简单易懂,使得读者不需要太深奥的知识。大家可以先尝试看如果阅读遇到问题再回去补充知识也可以的,或者直接邮件给我perftrace@gmail.com。

            我们直接进入正题。

开篇我们先来看下源码的位置和编译方法。

1.1     模块源码

            NVMe相关的代码位于内核源码树的drivers/nvme中,我们可以看到主要有两个文件夹一个是host,一个targets.

     其中targets是用于实现将本系统中的nvme设备作为磁盘导出,供其他服务器或者系统使用的功能。而文件夹host是实现NVMe磁盘供本系统自己使用,不会对外提供,这意味着外部系统不能通过网络或者光纤来访问我们的NVMe磁盘。如果配置NVMe target还需要工具nvmetcli工具:

     http://git.infradead.org/users/hch/nvmetcli.git

     我们这个系列主要针对host,关于target将来有机会再做进一步分析。

            所以后续所有文件都是位于drviers/nvme/host中。

1.2     模块诞生

先来看下drviers/nvme/host目录中的Makefile,具体如下。我们发现根据内核中的参数配置,最多会有5个模块。

# SPDX-License-Identifier: GPL-2.0

 

ccflags-y                               += -I$(src)

 

obj-$(CONFIG_NVME_CORE)                 += nvme-core.o

obj-$(CONFIG_BLK_DEV_NVME)              += nvme.o

obj-$(CONFIG_NVME_FABRICS)              += nvme-fabrics.o

obj-$(CONFIG_NVME_RDMA)                 += nvme-rdma.o

obj-$(CONFIG_NVME_FC)                   += nvme-fc.o

 

nvme-core-y                             := core.o

nvme-core-$(CONFIG_TRACING)             += trace.o

nvme-core-$(CONFIG_NVME_MULTIPATH)      += multipath.o

nvme-core-$(CONFIG_NVM)                 += lightnvm.o

nvme-core-$(CONFIG_FAULT_INJECTION_DEBUG_FS)    += fault_inject.o

 

nvme-y                                  += pci.o

nvme-fabrics-y                          += fabrics.o

nvme-rdma-y                             += rdma.o

nvme-fc-y                               += fc.o

            其中ccflags-y是编译标记,会被正常的cc调用,指定了$(CC)编译时候的选项,这里只是将内核源码的头文件包含进去。 其中$(src)是指向内核根目录中Makefile所在的目录,包含模块需要的一些头文件。当然,这里其实我们可以不用纠结或者理睬它。

我们看下决定模块是否编译的5个配置参数:

l   NVME_CORE:这是一个被动的选项。该选项在BLK_DEV_NVME, NVME_RDMA, NVME_FC使能时候会自动选上,是nvme核心基础。对应的代码是core.c,产生的模块是nvme-core.ko。另外。这里需要注意的是,如果使能了配置:TRACING,NVME_MULTIPATH,NVM,FAULT_INJECTION_DEBUG_FS,那么模块nvme-core.ko会合入trace.c,multipath.c,lightnvm.c和fault_inject.c文件,这些是NVMe驱动的特点可选择是否开启。

l   BLK_DEV_NVME:这个选项开启后会自动选上NVME_CORE,同时自身依赖pci和block.这个产生nvme.ko驱动用于直接将ssd链接到pci或者pcie.对应的代码是nvme.c和pci.c,产生的模块是nvme.ko.

l   CONFIG_NVME_FABRICS:是这个被动选项。被NVME_RDMA和NVME_FC选择(当然,还有一些其他条件需要满足)。主要用于支持FC协议。

l   CONFIG_NVME_RDMA:这个驱动使得NVMe over Fabric可以通过RDMA传输(该选项还依赖于CONFIG_INFINIBAND)。该选项会自动使能NVME_CORE和NVME_FABRICS,SG_POOL

l   CONFIG_NVME_FC:这个驱动使得NVMe over Fabric可以在FC传输。该选项会自动使能NVME_CORE和NVME_FABRICS,SG_POOL

 

模块

依赖

源码文件

nvme-core.ko

-

nvme-core.c, trace.c, multipath.c, lightnvm.c, fault_inject.c

nvme.ko

nvme-core

pci.c

nvme-fabirc.ko

-

fabrics.c

nvme-rdma.ko

nvme-core,nvme-fabirc,sp_pool

rdma.c

nvme-fc.ko

nvme-core,nvme-fabirc,sp_pool

fc.c

            配置完毕后,可以在内核代码根目录中执行make命令产生驱动。

#make M=drivers/nvme/host

            编译后会产生所配置的驱动模块,我们本系列只覆盖nvme.ko这个驱动模板,当然另一个nvme-core.ko必须的。编译后可以通过make modules_install来安装。

            然后可以通过modprobe nvme来加载驱动。

2     NVMe驱动详解之二PCI驱动注册

我们现在使用的NVMe设备都是基于PCI的,所以最后设备需要连接到内核中的pci总线上才能够使用。这也是为什么在上篇配置nvme.ko时会需要pci.c文件,在Makefile中有如下这一行的:

nvme-y    += pci.o

本篇主要针对pci.c文件的一部分内容进行分析(其实本系列涉及的代码内容就是在pci.c和nvme-core.c两个文件中)。

2.1     驱动注册上

我们先来看下驱动的注册和注销,其实就是模块的初始化和退出函数,如下。

   module_init(nvme_init);

   module_exit(nvme_exit);

可知模块注册函数是nvme_init,非常简单,就是一个pci_register_driver函数。

static int __init nvme_init(void)

{

        return pci_register_driver(&nvme_driver);

}

            注册了nvme_driver驱动,参数为结构体nvme_driver,该结构体类型是pci_driver。

static struct pci_driver nvme_driver = {

        .name           = "nvme",

        .id_table       = nvme_id_table,

        .probe          = nvme_probe,

        .remove         = nvme_remove,

        .shutdown       = nvme_shutdown,

        .driver         = {

                .pm     = &nvme_dev_pm_ops,

        },

        .sriov_configure = nvme_pci_sriov_configure,

        .err_handler    = &nvme_err_handler,

};

            我们可以从结构体nvme_driver中得知,驱动的名字是nvme;初始化函数是nvme_probe,该函数负责在驱动加载时候探测总线上的硬件设备;设备与驱动的关联表为nvme_id_table,通过这个表内核可以知道哪些设备是通过这个驱动来工作的;probe函数,该函数用来初始化设备;remove函数,当前驱动从内核移除时候被调用;shutdown函数用于关闭设备;错误处理句柄nvme_err_handler;以及nvme的sriov操作函数。

            而pci_register_driver是个宏,其实是__pci_register_driver函数,该函数会通过调用driver_register将要注册的驱动结构体放到系统中设备驱动链表中,将其串成了一串。这里要注意的是pci_driver中包含了device_driver,而我们的驱动nvme_driver就是pci_driver类型。

struct pci_driver {

          struct list_head        node;  

      ……

        struct device_driver    driver;

        struct pci_dynids       dynids;

};

            我们来具体看下这个__pci_register_driver,函数会参数也就是将驱动代码中nvme_driver结构体值赋值给nvme_driver中device_driver这个通用结构体。

int __pci_register_driver(struct pci_driver *drv, struct module *owner,

                          const char *mod_name)

{

        /* initialize common driver fields */

        drv->driver.name = drv->name;//赋值为”nvme”

        drv->driver.bus = &pci_bus_type;//设置为pci_bus_type,是个结构体

        drv->driver.owner = owner;//驱动的拥有者

        drv->driver.mod_name = mod_name;//device_driver中的名字,为系统中的KBUILD_NAME

        drv->driver.groups = drv->groups;//驱动代码中并未赋值

                                          

        spin_lock_init(&drv->dynids.lock);//获取自旋锁

        INIT_LIST_HEAD(&drv->dynids.list);//初始化设备驱动中的节点元素,用于在链表中串起来

 

        /* register with core */

        return driver_register(&drv->driver);//调用driver_register注册驱动。

}

            参数中基本都是比较直白的,唯独pci_bus_type是由充满玄机的,故而把它列出来如下。结构图中定义了很多和总线相关的函数,这些函数其实是由pci总线驱动提供的,位于drivers/pci/pci-driver.c文件,这些内容我们在后续会有说明,这里先让它们露个脸和大家打个照面。

struct bus_type pci_bus_type = {

        .name           = "pci",

        .match          = pci_bus_match,

        .uevent         = pci_uevent,

        .probe          = pci_device_probe,

        .remove         = pci_device_remove,   

        .shutdown       = pci_device_shutdown,

        .dev_groups     = pci_dev_groups,

        .bus_groups     = pci_bus_groups,

        .drv_groups     = pci_drv_groups,

        .pm             = PCI_PM_OPS_PTR,

        .num_vf         = pci_bus_num_vf,

        .force_dma      = true,

};

总之呢,这个pci_register_driver函数主要作用就是传递驱动相关参数,并调用driver_register。接下我们看下driver_register.

2.2     驱动注册中

继续驱动注册,上面讲到driver_register函数。该函数实现驱动注册到总线,参数就是一个需要注册的device_driver。

int driver_register(struct device_driver *drv)

{      

        int ret;

        struct device_driver *other;

 

        BUG_ON(!drv->bus->p);//检测device_driver->driver_private,开始应该是为NULL

 

        if ((drv->bus->probe && drv->probe) ||

            (drv->bus->remove && drv->remove) ||

            (drv->bus->shutdown && drv->shutdown))

                printk(KERN_WARNING "Driver '%s' needs updating - please use "

                        "bus_type methods\n", drv->name);

 

        other = driver_find(drv->name, drv->bus);

        if (other) {

                printk(KERN_ERR "Error: Driver '%s' is already registered, "

                        "aborting...\n", drv->name);

                return -EBUSY;

        }

 

        ret = bus_add_driver(drv);

        if (ret)

                return ret;

        ret = driver_add_groups(drv, drv->groups);

        if (ret) {

                bus_remove_driver(drv);

                return ret;

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值