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

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介:

 

 

 

 

 

 

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;

        }

        kobject_uevent(&drv->p->kobj, KOBJ_ADD);

 

        return ret;

}

函数会先检测device_driver->driver_private,开始应该是为NULL,不然就panic了。

然后判断总线和驱动都是否都定义了probe,remobe,shutdown函数,因为总线中已有这三个函数的定义,所以device_driver中并不需要了,如果出现则打印输出警告。

            接着通过driver_find函数,在需要注册总线上查找是否已经存在相同名字的驱动了,如果存在,那就停止注册。函数会调用kset_find_obj来查找, 传入的参数是要注册的驱动名字和总线结构体。如果找到则返回驱动。

struct device_driver *driver_find(const char *name, struct bus_type *bus)

{

        struct kobject *k = kset_find_obj(bus->p->drivers_kset, name);

        struct driver_private *priv;

 

        if (k) {

                /* Drop reference added by kset_find_obj() */

                kobject_put(k);

                priv = to_driver(k);

                return priv->driver;

        }

        return NULL;

}

            其中bus->p的结构体是subsys_private,其中变量driver_kset是表示和总线相关的驱动,其类型是kset, kset通过其中的list成员组成链表。

            kset_find_obj的函数如下,先获取一个自旋锁,然后在列表中遍历查找,这里用了列表遍历函数list_for_each_entry,每次获取总线中的一个代表驱动的kobject,然后通过kobject_name获得节点中项的名字(驱动名字),然后与要注册的驱动名字对比,如果相等则返回该kobject,否则返回NULL,最后释放自旋锁。其中kset_find_obj函数如下。

struct kobject *kset_find_obj(struct kset *kset, const char *name)

{

        struct kobject *k;

        struct kobject *ret = NULL;

 

        spin_lock(&kset->list_lock);

       

        list_for_each_entry(k, &kset->list, entry) {

                if (kobject_name(k) && !strcmp(kobject_name(k), name)) {

                        ret = kobject_get_unless_zero(k);

                        break;

                }

        }      

               

        spin_unlock(&kset->list_lock);

        return ret;

}

            到此是确定系统中要么已经存在同名驱动退出注册,要么是系统可以继续注册。如果可以继续注册,那么需要继续执行driver_register函数中的代码片段如下,我们在下小节中详解。

        ret = bus_add_driver(drv);

        if (ret)

                return ret;

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

        if (ret) {

                bus_remove_driver(drv);

                return ret;

        }

        kobject_uevent(&drv->p->kobj, KOBJ_ADD);

 

2.3     驱动注册下

接下去,才是真正实现将驱动设备添加到总线中的过程。该函数是bus_add_driver。

int bus_add_driver(struct device_driver *drv)

{

        struct bus_type *bus;

        struct driver_private *priv;

        int error = 0;

 

        bus = bus_get(drv->bus);

        if (!bus)

                return -EINVAL;

 

        pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

 

        priv = kzalloc(sizeof(*priv), GFP_KERNEL);

        if (!priv) {

                error = -ENOMEM;

                goto out_put_bus;

        }

        klist_init(&priv->klist_devices, NULL, NULL);

        priv->driver = drv;

        drv->p = priv;

        priv->kobj.kset = bus->p->drivers_kset;

        error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,

                                     "%s", drv->name);

        if (error)

                goto out_unregister;

 

        klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

        if (drv->bus->p->drivers_autoprobe) {

                if (driver_allows_async_probing(drv)) {

                        pr_debug("bus: '%s': probing driver %s asynchronously\n",

                                drv->bus->name, drv->name);

                        async_schedule(driver_attach_async, drv);

                } else {

                        error = driver_attach(drv);

                        if (error)

                                goto out_unregister;

                }

        }

        module_add_driver(drv->owner, drv);

 

        error = driver_create_file(drv, &driver_attr_uevent);

        if (error) {

                printk(KERN_ERR "%s: uevent attr (%s) failed\n",

                        __func__, drv->name);

        }

        error = driver_add_groups(drv, bus->drv_groups);

        if (error) {

                /* How the hell do we get out of this pickle? Give up */

                printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",

                        __func__, drv->name);

        }

 

        if (!drv->suppress_bind_attrs) {

                error = add_bind_files(drv);

                if (error) {

                        /* Ditto */

                        printk(KERN_ERR "%s: add_bind_files(%s) failed\n",

                                __func__, drv->name);

                }

        }

 

        return 0;

 

out_unregister:

        kobject_put(&priv->kobj);

        /* drv->p is freed in driver_release()  */

        drv->p = NULL;

out_put_bus:

        bus_put(bus);

        return error;

}

bus_add_driver函数的入参为device_driver,先通过bus_get获取总线,参数为bus_type->private_subsys->kset,是kset类型,最后会将对应的ojbect引用增加1。函数结束会调用bus_put来减少引用。

static struct bus_type *bus_get(struct bus_type *bus)

{

        if (bus) {

                kset_get(&bus->p->subsys);

                return bus;

        }

        return NULL;

 }

然后通过kzalloc(分配的空间都置0)分配结构体driver_private类型变量priv,并调用klist_init初始化设备列表,这里对应的将来驱动所能驱动的设备。

接着填充priv和驱动结构体drv,填充priv->driver=drv,以及drv->p=priv,相互指向。

设置priv->kobj.kset为总线的bus->p->drivers_kset

然后调用kobject_init_and_add函数,该函数初始化一个kojbect结构体,并加入到kobject架构中。其中kobject的对象就是priv->kobj,类型为driver_ktype,

static struct kobj_type driver_ktype = {

        .sysfs_ops      = &driver_sysfs_ops,

        .release        = driver_release,

};

         然后调用klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);将priv->knode_bus添加到总线的subsys_private->klist_drivers 链表中

            接着判断总线是否存在drivers_autoprobe函数,如果总线存在该函数,进一步则判断是否支持异步探测,主要是判断drv->probe_type变量,调用driver_attach_async或者driver_attach(async_schedule其实就是异步调用driver_attach_async函数),而driver_attach_async最后也是调用driver_attach。作者实测了一下,在pci总线中因为没有设置drv->probe_type为异步探测。函数driver_attach会调用bus_for_each_dev负责在总线上遍历设备,并将设备传递给函数会调用__driver_attach进行设备和驱动的匹配。

我们来看下__driver_attach函数,文件只列出该函数其他函数不再文中列出,大家可以查看源码。该函数其先调用driver_match_device,而driver_match_device则调用总线的match函数,pci总线的就是函数pci_bus_match。pci_bus_match先判断设备中的match_driver变量是否已经设备,如果设备说明已经和驱动匹配则无需匹配;否则调用pci_match_device (这个函数中会先通过override判断是否只绑定到指定驱动),先使用宏list_for_each_entry遍历驱动中动态id,显然后遍历静态id(驱动中的id_table表),如果匹配返回pci_device_id(如果设备设置了dev->override,且注册的驱动名字和设备需要的名字匹配,就算没找到也会也返回一个pci_device_id_any),这里注意的是pci_device_id 中class和classmask合计32位,实际有效的是class的16位,另外16位是为了掩盖pci_device中32位class中无效的16位。

     如果执行driver_match_device出错,并且返回错误是-EPROBE_DEFER,则需要调用driver_deferred_probe_add,来将设备通过dev->p->deferred_probe添加到deferred_probe_pending_list链表中。

     当返回pci_device_id后,如果设备没有绑定驱动, __driver_attach函数会调用driver_probe_device(调用该函数需要先获取设备锁),该函数负责将设备和驱动绑定。函数先判断设备dev->kobj.state_in_sysfs是否注册,然后调用really_probe(这里其实还会涉及linux电源管理的动作,此处为了简化问题暂时不展开)。

really_probe函数中,会设置dev->driver = drv将驱动赋值为设备,同时会调用driver_bound函数,将设备也绑定到驱动相关链表中,此外会调用总线的probe函数,如果总线没有probe函数则调用设备驱动的probe函数,当然really_probe函数中的学问还有很多,可以单独列一篇章来讲解。

static int __driver_attach(struct device *dev, void *data)

{

        struct device_driver *drv = data;

        int ret;

        /*

         * Lock device and try to bind to it. We drop the error

         * here and always return 0, because we need to keep trying

         * to bind to devices and some drivers will return an error

         * simply if it didn't support the device.

         *

         * driver_probe_device() will spit a warning if there

         * is an error.

         */

        ret = driver_match_device(drv, dev);

        if (ret == 0) {

                /* no match */

                return 0;

        } else if (ret == -EPROBE_DEFER) {

                dev_dbg(dev, "Device match requests probe deferral\n");

                driver_deferred_probe_add(dev);

        } else if (ret < 0) {

                dev_dbg(dev, "Bus failed to match device: %d", ret);

                return ret;

        } /* ret > 0 means positive match */

 

        if (dev->parent)        /* Needed for USB */

                device_lock(dev->parent);

        device_lock(dev);

        if (!dev->driver)

                driver_probe_device(drv, dev);

        device_unlock(dev);

        if (dev->parent)

                device_unlock(dev->parent);

 

        return 0;

}

然后bus_add_driver继续调用module_add_driver,该函数主要实现是sysfs_create_link在sysfs文件系统中创建相关文件。

   sysfs_create_link(&drv->p->kobj, &mk->kobj, "module");

   sysfs_create_link(mk->drivers_dir, &drv->p->kobj, driver_name);

            第一个sysfs_create_link调用在/sys/bus/pci/drivers/nvme中创建module指向/sys/module/nvme,第二个sysfs_create_link在目录/sys/module/nvme/drivers中创建pci:nvme链接,指向/sys/bus/pci/drivers/nvme驱动。

接着调用driver_create_file,通过sysfs_create_file函数,

sysfs_create_file(&drv->p->kobj, &attr->attr),在/sys/bus/pci/drivers/nvme中创建驱动的属性文件

然后调用driver_add_groups,通过 sysfs_create_groups函数创建总线中驱动的属性组。

            最后判断drv->suppress_bind_attrs是否设置,如果没有设置则调用add_bind_files创建绑定的属性文件。至此,bus_add_driver运行结束了,非常的繁杂,其函数调用链如下。对于我们掌握NMVe驱动来说是足够了,但是对于想要继续深入的同学其实还有很多细节并没有一一阐述,大家可以自己去hack。

            在driver_register函数的末尾,调用driver_add_groups,也是通过 sysfs_create_groups函数创建NVMe驱动的属性组。之前在bus_add_driver中是总线自己的属性组。

            最后调用kobject_uevent(&drv->p->kobj, KOBJ_ADD);通知用户驱动加载成功。

     总的注册流程图如下:

0cdae31a0274fab6790fa090ff3f0bde60ab5391

       高清图下载地址:

https://github.com/kernel-z/filesystem/blob/master/nvme-init-exit-0.1.png

终于,驱动注册完毕。

这片文章与其说是NVMe驱动注册,不如说是PCI设备驱动的注册。但是不管怎么样,我们终于一步一步的弄清楚了PCI驱动的注册大概流程,下面就看下NVMe驱动注销。

 

 

3     NVMe驱动详解之三PCI驱动注销

承接上篇PCI驱动注册,这篇我们来看下PCI驱动的注销。

3.1     驱动注销上

驱动的注销,其实就是模块的退出函数。

   module_exit(nvme_exit);

驱动注销函数:

static void __exit nvme_exit(void)

{

        pci_unregister_driver(&nvme_driver);

        flush_workqueue(nvme_wq);

        _nvme_check_size();

}

            pci_unregister_driver这个函数就是pci_register_driver的配对函数。注销的动作基本是注册动作的取反,把之前创建的文件分配的资源都进行回收,我们来看下其主要逻辑。

3.2     驱动注销中

pci_unregister_driver函数负责从已注册的pci驱动中注销一个pci驱动结构,同时触发驱动设备的remove函数,最后设置所驱动设备为无驱动状态。它的参数还是nvme驱动数据结构体nvme_driver,它会调用driver_unregister和pci_free_dynids。driver_unregister是和driver_register配对的函数,其参数为结构体device_driver,而pci_free_dynids负责删除驱动结构中drv->dynids.list中的节点,这些节点是动态添加的,所以在注册驱动的时候并不需要创建。

void pci_unregister_driver(struct pci_driver *drv)

{

        driver_unregister(&drv->driver);

        pci_free_dynids(drv);

}

            我们来看下driver_unregister,该函数负责从系统中移除一个驱动,主要通过两个函数driver_remove_groups和bus_remove_driver来实现。

void driver_unregister(struct device_driver *drv)

{

        if (!drv || !drv->p) {

                WARN(1, "Unexpected driver unregister!\n");

                return;

        }      

        driver_remove_groups(drv, drv->groups);

        bus_remove_driver(drv);

}

driver_remove_groups函数调用sysfs_remove_groups,负责从sysfs文件系统中删除驱动的属性组,其实就是一些文件,为了不影响我们的理解,我们这里不深入展开。

3.3     驱动注销下

            我们再来看主要的函数,就是bus_remove_driver,这个函数和bus_add_driver配对。其中调用的函数其实也和bus_add_driver中逆着来的。

void bus_remove_driver(struct device_driver *drv)

{

        if (!drv->bus)

                return;

 

        if (!drv->suppress_bind_attrs)

                remove_bind_files(drv);

        driver_remove_groups(drv, drv->bus->drv_groups);

        driver_remove_file(drv, &driver_attr_uevent);

        klist_remove(&drv->p->knode_bus);

        pr_debug("bus: '%s': remove driver %s\n", drv->bus->name, drv->name);

        driver_detach(drv);

        module_remove_driver(drv);

        kobject_put(&drv->p->kobj);

        bus_put(drv->bus);

}

            我们一个一个来看下这些函数。

remove_bind_files函数取决于驱动中的参数drv->suppress_bind_attrs,如果为0,则调用该函数,因为再加载的时候也是调用了add_bind_files函数。remove_bind_files函数会调用driver_remove_file,然后调用sysfs_remove_file将驱动的属性文件删除。

driver_remove_groups会调用sysfs_remove_groups,最后调用sysfs_remove_group将总线的组属性移除。

driver_remove_file也是调用sysfs_remove_file函数。从sysfs中删除驱动属性文件。

klist_remove函数将驱动从总线中移除。

driver_detach(drv)将驱动管理的所有设备进行取消驱动关联。这个函数相对其他函数会稍微重要一点,所以在文中将其代码列出来如下。

void driver_detach(struct device_driver *drv)

{      

        struct device_private *dev_prv;

        struct device *dev;

       

        for (;;) {

                spin_lock(&drv->p->klist_devices.k_lock);

                if (list_empty(&drv->p->klist_devices.k_list)) {

                        spin_unlock(&drv->p->klist_devices.k_lock);

                        break;

                }

                dev_prv = list_entry(drv->p->klist_devices.k_list.prev,

                                     struct device_private,

                                     knode_driver.n_node);

                dev = dev_prv->device;

                get_device(dev);

                spin_unlock(&drv->p->klist_devices.k_lock);

                device_release_driver_internal(dev, drv, dev->parent);

                put_device(dev);

        }                                        

}

driver_detach函数是一个循环,非常符合逻辑,因为要将相关所有设备进行操作。然后先通过spin_lock获取驱动所管理的设备链的自旋锁,判断链路中是否有设备,如果没有设备则退出循环,并释放自旋锁。否则如果有设备,则在列表(device_driver->p->klist_devices.k_list)中获取device_private结构体,从而获得设备结构体(设备结构体为device_private的成员变量)。然后调用get_device增加设备的引用,并释放自旋锁,然后调用device_release_driver_internal函数。在device_release_driver_internal函数中,需要先锁住设备,然后调用__device_release_driver,这个函数是最终处理场所。在__device_release_driver中会调用driver_sysfs_remove删除sysfs中设备相关属性文件及软连接,最重要的一个点是会调用总线的remove函数,如果没有则调用驱动的remove函数。__device_release_drive函数通过kobject_uevent来通知用户。

最后通过函数put_device释放设备的引用。

module_remove_driver(drv)函数调用sysfs_remove_link函数将sysfs_create_link创建的软连接移除。

kobject_put(&drv->p->kobj)减少驱动的引用,如果已经没有其他引用则释放该结构。

bus_put(drv->bus)减少总线的一次引用,对应加载时候的bug_get函数。

至此,驱动注销结束了,其总的流程图如下:

edc9df7d86bb87869fe75d278c4b402363096fc3

            (高清图下载地址:

https://github.com/kernel-z/filesystem/blob/master/nvme-init-exit-0.1.png

从以上分析来看,注销的过程和注册的过程是基本对立的过程,而且函数很多名字上也是配对的,这对我们理解有很大的帮助。虽然其中还有一些细节展开,但是对我们理解NVMe驱动注销我想已经够了的。

下一篇中,我们会对NVMe驱动所依赖的nvme-core模块进行注册和注销分析。

4     NVMe驱动详解之四nvme-core模块初始化

这个nvme-core模块是nvme模块所依赖的,我们可以在第一篇内核配置中知道,也可以在使用modinfo命令来观察发现。

    # modinfo  nvme

filename:       /lib/modules/4.17.2/kernel/drivers/nvme/host/nvme.ko

version:        1.0

license:        GPL

author:         Matthew Wilcox <willy@linux.intel.com>

srcversion:     727870919D954433442B206

alias:          pci:v0000106Bd00002003sv*sd*bc*sc*i*

alias:          pci:v0000106Bd00002001sv*sd*bc*sc*i*

alias:          pci:v*d*sv*sd*bc01sc08i02*

alias:          pci:v00001D1Dd00002807sv*sd*bc*sc*i*

alias:          pci:v00001D1Dd00001F1Fsv*sd*bc*sc*i*

alias:          pci:v0000144Dd0000A822sv*sd*bc*sc*i*

alias:          pci:v0000144Dd0000A821sv*sd*bc*sc*i*

alias:          pci:v00001C5Fd00000540sv*sd*bc*sc*i*

alias:          pci:v00001C58d00000023sv*sd*bc*sc*i*

alias:          pci:v00001C58d00000003sv*sd*bc*sc*i*

alias:          pci:v00008086d00005845sv*sd*bc*sc*i*

alias:          pci:v00008086d0000F1A5sv*sd*bc*sc*i*

alias:          pci:v00008086d00000A55sv*sd*bc*sc*i*

alias:          pci:v00008086d00000A54sv*sd*bc*sc*i*

alias:          pci:v00008086d00000A53sv*sd*bc*sc*i*

alias:          pci:v00008086d00000953sv*sd*bc*sc*i*

depends:        nvme-core

intree:         Y

name:           nvme

vermagic:       4.17.2 SMP mod_unload

parm:           use_threaded_interrupts:int

parm:           use_cmb_sqes:use controller's memory buffer for I/O SQes (bool)

parm:           max_host_mem_size_mb:Maximum Host Memory Buffer (HMB) size per controller (in MiB) (uint)

parm:           sgl_threshold:Use SGLs when average request segment size is larger or equal to this size. Use 0 to disable SGLs. (uint)

parm:           io_queue_depth:set io queue depth, should >= 2

            这意味着在加载nvme模块之前会提前加载nvme-core模块。

我们来看下nvme-core驱动的注册和注销:

module_init(nvme_core_init);

module_exit(nvme_core_exit);

     这个module_init和module_exit是linux内核模块的标准方式。

4.1     nvme_core_init

nvme_core模块初始化函数如下:

int __init nvme_core_init(void)

{

        int result = -ENOMEM;

 

        nvme_wq = alloc_workqueue("nvme-wq",

                        WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);

        if (!nvme_wq)

                goto out;

 

        nvme_reset_wq = alloc_workqueue("nvme-reset-wq",

                        WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);

        if (!nvme_reset_wq)

                goto destroy_wq;

 

        nvme_delete_wq = alloc_workqueue("nvme-delete-wq",

                        WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);

        if (!nvme_delete_wq)

                goto destroy_reset_wq;

 

        result = alloc_chrdev_region(&nvme_chr_devt, 0, NVME_MINORS, "nvme");

        if (result < 0)

                goto destroy_delete_wq;

 

        nvme_class = class_create(THIS_MODULE, "nvme");

        if (IS_ERR(nvme_class)) {

                result = PTR_ERR(nvme_class);

                goto unregister_chrdev;

        }   

 

        nvme_subsys_class = class_create(THIS_MODULE, "nvme-subsystem");

        if (IS_ERR(nvme_subsys_class)) {

                result = PTR_ERR(nvme_subsys_class);

                goto destroy_class;

        }   

        return 0;

 

destroy_class:

        class_destroy(nvme_class);

unregister_chrdev:

        unregister_chrdev_region(nvme_chr_devt, NVME_MINORS);

destroy_delete_wq:

        destroy_workqueue(nvme_delete_wq);

destroy_reset_wq:

        destroy_workqueue(nvme_reset_wq);

destroy_wq:

        destroy_workqueue(nvme_wq);

out:

        return result;

}

            我们来看下这个初始化函数的过程:

            首先调用alloc_workqueue,其实是个宏,该宏会调用__alloc_workqueue_key返回workqueue_struct结构体,初始化过程会创建三个工作队列,分别是nvme-wq,nvme-reset-wq,nvme-delete-wq。其中会调用workqueue_sysfs_register,最后队列会暴露出现在/sys/bus/workqueue/devices工作队列是延迟执行中使用最多到机制,分为unbound workqueue和bound workqueue。bound workqueue就是绑定到cpu上的,挂入到此队列中的work只会在相对应的cpu上运行。unbound workqueue不绑定到特定的cpu,而且后台线程池的数量也是动态的。此处三个工作队列都是unbound模式的。对于后续驱动使用来说,只要定义一个work,然后把work加入到workqueue就可以了。相比与tasklet机制,工作队列可以在不同 CPU 上同时运行。

            然后调用alloc_chrdev_region函数(会调用__register_chrdev_region函数),分配一个字符串设备nvme的范围,主设备号值随机,次设备号从0开始,范围为NVME_MINORS,并将第一个值(设备号)赋值给nvme_chr_devt。

            接着调用class_create宏,通过调用函数__class_create创建一个class结构体。函数中会调用__class_register,会将类进行注册,为后续设备注册准备。

            class_create会调用两次,创建两个类,一个nvme和一个nvme_subsystem。会在/sys/class中创建同名的两个目录,便于后续设备注册。

            nvme-core模块初始化过程流程如下:

6dedf783bb7b891a0007105b2ff618c3aca0aad8

高清图:

https://github.com/kernel-z/filesystem/blob/master/nvme-init-exit-0.1.png

 

4.2     nvme_core_exit

我们来看下注销过程,该过程是初始化的完全逆操作。

模块nvme_core注销函数如下:

void nvme_core_exit(void)

{

        ida_destroy(&nvme_subsystems_ida);

        class_destroy(nvme_subsys_class);

        class_destroy(nvme_class);

        unregister_chrdev_region(nvme_chr_devt, NVME_MINORS);

        destroy_workqueue(nvme_delete_wq);

        destroy_workqueue(nvme_reset_wq);

        destroy_workqueue(nvme_wq);

}

163864168d0943abc48645e29e6526aa63cb0ee0

高清图:

https://github.com/kernel-z/filesystem/blob/master/nvme-init-exit-0.1.png

其中ida_destory是释放所有和IDA关联的资源。IDA结构体是一个树状结构体,是内核工作的一个机制。 

这里先介绍一下IDR,IDR机制是内核中将一个整数ID号和指针关联在一起的机制。 如果使用数组进行索引,当ID号很大时,数组索引会占据大量的存储空间,如果使用链表,在总线上设备特别多的情况下,链表的查询效率不高。而IDR机制内部采用红黑树,可以很方便的将整数和指针关联起来,并且有很高的搜索效率。

IDA只是用来分配id,并不将某数据结构和id关联起来。 例如sd设备的设备名,如sda,驱动在生成设备文件的时候会向系统申请一个ida,也就是唯一id,然后把id映射成设备文件名。  

            在nvme-core中有使用到ida,所以在最后中需要释放。

            static DEFINE_IDA(nvme_subsystems_ida);

下一篇中,我们会对NVMe驱动加载和卸载过程的输出内容分析,加强对前两篇内容的理解。

5     NVMe驱动详解之五加载分析

内核配置可以参考《Linux开发环境内核配置》,主要是针对模块调试的那些选项,否则系统不会有详细的输出。例如:配置参数CONFIG_DYNAMIC_DEBUG后才能使能dev_dbg输出函数。

对于调试环境,内核配置的参数参考和说明如下:

CONFIG_DYNAMIC_DEBUGEnable dynamic printk() support

CONFIG_DEBUG_KERNEL
: Kernel debugging

CONFIG_DEBUG_SLABDebug slab memory allocations

CONFIG_DEBUG_PAGEALLOCDebug page memory allocations

CONFIG_DEBUG_SPINLOCK: Spinlock and rw-lock debugging: basic checks

CONFIG_DEBUG_INFO:Compile the kernel with debug info

CONFIG_MAGIC_SYSRQ
: Magic SysRq key

CONFIG_DEBUG_STACKOVERFLOW: Check for stack overflows

CONFIG_DEBUG_STACK_USAGE: Stack utilization instrumentation 

CONFIG_KALLSYMS: Load all symbols for debugging/ksymoops

CONFIG_IKCONFIG: Kernel .config support 

CONFIG_IKCONFIG_PROC: Enable access to .config through /proc/config.gz

CONFIG_ACPI_DEBUG: Debug Statements

CONFIG_DEBUG_DRIVER: Driver Core verbose debug messages 

CONFIG_SCSI_CONSTANTS: Verbose SCSI error reporting (kernel size += 36K)

CONFIG_INPUT_EVBUG: Event debugging

CONFIG_PROFILING: Profiling support

CONFIG_DEBUG_KOBJECT_RELEASE(这个选项可以不开,容易死机)

如果可以使能所有配置最好。

配置完毕并重新编译内核并重启。接着我们开始分析。

使用modprobe nvme加载NVMe驱动(关于如何编译请参考本系列第一个文章,对NVMe驱动源码位置和编译进行了详细描述),会出现如下输出。

<7>[30385.621786] device: 'nvme-wq': device_add

<7>[30385.621795] bus: 'workqueue': add device nvme-wq

<7>[30385.621804] PM: Adding info for workqueue:nvme-wq

<7>[30385.624172] device: 'nvme-reset-wq': device_add

<7>[30385.624184] bus: 'workqueue': add device nvme-reset-wq

<7>[30385.624196] PM: Adding info for workqueue:nvme-reset-wq

<7>[30385.624772] device: 'nvme-delete-wq': device_add

<7>[30385.624780] bus: 'workqueue': add device nvme-delete-wq

<7>[30385.624790] PM: Adding info for workqueue:nvme-delete-wq

<7>[30385.624839] device class 'nvme': registering

<7>[30385.624864] device class 'nvme-subsystem': registering

<7>[30385.625689] bus: 'pci': add driver nvme

<7>[30385.625702] bus: 'pci': driver_probe_device: matched device 0000:00:0e.0 with driver nvme

<7>[30385.625705] bus: 'pci': really_probe: probing driver nvme with device 0000:00:0e.0

<7>[30385.625712] devices_kset: Moving 0000:00:0e.0 to end of list

<7>[30385.625862] device: 'nvme0': device_add

<7>[30385.625886] PM: Adding info for No Bus:nvme0

<6>[30385.625949] nvme nvme0: pci function 0000:00:0e.0

<7>[30385.629882] driver: 'nvme': driver_bound: bound to device '0000:00:0e.0'

<7>[30385.629913] bus: 'pci': really_probe: bound device 0000:00:0e.0 to driver nvme

<7>[30385.628129] device: 'nvme-subsys0': device_add

<7>[30385.628150] PM: Adding info for No Bus:nvme-subsys0

<7>[30385.629458] device: '259:0': device_add

<7>[30385.629479] PM: Adding info for No Bus:259:0

<7>[30385.629537] device: 'nvme0n1': device_add

<7>[30385.629556] PM: Adding info for No Bus:nvme0n1

使用modprobe –r nvme卸载NVMe驱动,出现输出如下:

<7>[78541.046026] bus: 'pci': remove driver nvme

<7>[78541.048245] device: '259:0': device_unregister

<7>[78541.048262] PM: Removing info for No Bus:259:0

<7>[78541.049034] device: '259:0': device_create_release

<7>[78541.049572] PM: Removing info for No Bus:nvme0n1

<7>[78541.091916] PM: Removing info for No Bus:nvme0

<7>[78541.092007] PM: Removing info for No Bus:nvme-subsys0

<7>[78541.092067] driver: 'nvme': driver_release

<7>[78541.098693] device class 'nvme-subsystem': unregistering

<7>[78541.098773] class 'nvme-subsystem': release.

<7>[78541.098775] class_create_release called for nvme-subsystem

<7>[78541.098778] device class 'nvme': unregistering

<7>[78541.098792] class 'nvme': release.

<7>[78541.098794] class_create_release called for nvme

<7>[78541.098799] device: 'nvme-delete-wq': device_unregister

<7>[78541.098811] bus: 'workqueue': remove device nvme-delete-wq

<7>[78541.098831] PM: Removing info for workqueue:nvme-delete-wq

<7>[78541.099292] device: 'nvme-reset-wq': device_unregister

<7>[78541.099309] bus: 'workqueue': remove device nvme-reset-wq

<7>[78541.099312] PM: Removing info for workqueue:nvme-reset-wq

<7>[78541.099531] device: 'nvme-wq': device_unregister

<7>[78541.099547] bus: 'workqueue': remove device nvme-wq

<7>[78541.099549] PM: Removing info for workqueue:nvme-wq

            这里需要注意的是并不是所有的注册注销活动都会有debug信息,所以这些并不代表驱动注册和注销的全部。很多sysfs的操作是没有这些输出的,因为可以直接在系统/sys目录下观察的。

5.1     debug注册

下面我们将每行输出关联到内核中的代码函数中:

l   nvme-core模块中alloc_workqueue宏触发,调用__alloc_workqueue_key,其又调用workqueue_sysfs_register开启对sysfs文件系统的注册活动,接着调device_register,以及device_add函数,最后在devcie_add函数中调用,pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

<7>[30385.621786] device: 'nvme-wq': device_add

l   在device_add函数中,调用bus_add_device函数,将设备添加到总线中,其中有,pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));

<7>[30385.621795] bus: 'workqueue': add device nvme-wq

l   在device_add函数中调用device_pm_add,负责将设备添加到PM的激活设备列表中。其会调用如下:pr_debug("PM: Adding info for %s:%s\n",dev->bus ? dev->bus->name : "No Bus", dev_name(dev));

 

<7>[30385.621804] PM: Adding info for workqueue:nvme-wq

l   下面三行输出同上,都是创建工作队列过程中的输出,只是参数的区别。

<7>[30385.624172] device: 'nvme-reset-wq': device_add

<7>[30385.624184] bus: 'workqueue': add device nvme-reset-wq

<7>[30385.624196] PM: Adding info for workqueue:nvme-reset-wq

l   下面三行输出同上,都是创建工作队列过程中的输出,只是参数的区别。

<7>[30385.624772] device: 'nvme-delete-wq': device_add

<7>[30385.624780] bus: 'workqueue': add device nvme-delete-wq

<7>[30385.624790] PM: Adding info for workqueue:nvme-delete-wq

l   nvme-core模块中,class_create调用__class_register,在注册nvme类时候会调用,pr_debug("device class '%s': registering\n", cls->name);

 

<7>[30385.624839] device class 'nvme': registering

l   class_create调用__class_register,在注册nvme-subsystem类时候会调用pr_debug("device class '%s': registering\n", cls->name);

 

<7>[30385.624864] device class 'nvme-subsystem': registering

l   nvme模块中,bus_add_river函数中有如下pr_debug函数,pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

    <7>[30385.625689] bus: 'pci': add driver nvme

l   nvme模块中,driver_probe_device函数中有如下pr_debug函数,pr_debug("bus: '%s': %s: matched device %s with driver %s\n",drv->bus->name, __func__, dev_name(dev), drv->name);

<7>[30385.625702] bus: 'pci': driver_probe_device: matched device 0000:00:0e.0 with driver nvme

l   在函数driver_probe_device中调用really_probe,pr_debug("bus: '%s': %s: probing driver %s with device %s\n",drv->bus->name, __func__, drv->name, dev_name(dev));

<7>[30385.625705] bus: 'pci': really_probe: probing driver nvme with device 0000:00:0e.0

l   really_probe函数会调用devices_kset_move_last将设备移动到devices_kset的末尾。pr_debug("devices_kset: Moving %s to end of list\n", dev_name(dev));

<7>[30385.625712] devices_kset: Moving 0000:00:0e.0 to end of list

,

l   nvme_probe函数会调用nvme_init_ctrl在函数nvme_init_ctrl(初始化NMVe控制结构体)中调用,cdev_device_add调用device_add,函数中有如下:pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

<7>[30385.625862] device: 'nvme0': device_add

l   在上行的上下文中,调用device_pm_add,pr_debug("PM: Adding info for %s:%s\n",dev->bus ? dev->bus->name : "No Bus", dev_name(dev));

<7>[30385.625886] PM: Adding info for No Bus:nvme0

l   在驱动代码的nvme_probe函数中,dev_info(dev->ctrl.device, "pci function %s\n", dev_name(&pdev->dev));

<6>[30385.625949] nvme nvme0: pci function 0000:00:0e.0

l driver_bound函数中当设备与驱动绑定结束后,pr_debug("driver: '%s': %s: bound to device '%s'\n", dev->driver->name, __func__, dev_name(dev));

<7>[30385.629882] driver: 'nvme': driver_bound: bound to device '0000:00:0e.0'

l   really_probe函数中当设备与驱动绑定结束后,pr_debug("bus: '%s': %s: bound device %s to driver %s\n",drv->bus->name, __func__, dev_name(dev), drv->name);

<7>[30385.629913] bus: 'pci': really_probe: bound device 0000:00:0e.0 to driver nvme

l   wake_up_new_task->nvme_reset_work->nvme_init_identify->__init_waitqueue_head->device_add。

<7>[30385.628129] device: 'nvme-subsys0': device_add

l   由device_add调用device_pm_add

<7>[30385.628150] PM: Adding info for No Bus:nvme-subsys0

l   下面这个输出的调用栈如下,通过nvme_scan_work来扫到设备。

<4>[    8.362018]  device_add+0x165/0x610

<4>[    8.362021]  device_create_groups_vargs+0xd3/0x100

<4>[    8.362024]  device_create_vargs+0x17/0x20

<4>[    8.362028]  bdi_register_va.part.16+0x23/0x180

<4>[    8.362030]  bdi_register+0x6c/0x80

<4>[    8.362033]  bdi_register_owner+0x2c/0x60

<4>[    8.362036]  __device_add_disk+0x285/0x4a0

<4>[    8.362038]  device_add_disk+0xe/0x10

<4>[    8.362041]  nvme_validate_ns+0x489/0x7d0 [nvme_core]

<4>[    8.362044]  ? blk_mq_free_request+0xfd/0x130

<4>[    8.362047]  ? __nvme_submit_sync_cmd+0x90/0xe0 [nvme_core]

<4>[    8.362049]  nvme_scan_work+0x230/0x300 [nvme_core]

<7>[30385.629458] device: '259:0': device_add

l   由device_add调用device_pm_add

<7>[30385.629479] PM: Adding info for No Bus:259:0

l   这个也是扫描设备得到的,调用堆栈同259:0设备的添加。

<7>[30385.629537] device: 'nvme0n1': device_add

由device_add调用device_pm_add

<7>[30385.629556] PM: Adding info for No Bus:nvme0n1

            这小节中,需要结合第二篇和第三篇中的注册流程图,能结合代码将是最好的了。

5.2     debug注销

l   nvme模块中pci_unregister_driver函数调用,driver_unregister,调用bus_remove_driver,pr_debug("bus: '%s': remove driver %s\n", drv->bus->name, drv->name);

<7>[78541.046026] bus: 'pci': remove driver nvme

l   driver_detach调用nvme_remove回调函数, 而nvme_remove之后的函数如下:nvme_remove->nvme_remove_namespace->nvme_ns_remove->del_gendisk->bdi_unregister->device_unregister,在device_unregister函数中,pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

<7>[78541.048245] device: '259:0': device_unregister

l   下面一条输出的函数调用栈如下:nvme_remove->nvme_remove_namespace->nvme_ns_remove->del_gendisk->bdi_unregister->device_unregister->device_pm_remove,在device_pm_remove函数中,pr_debug("PM: Removing info for %s:%s\n",dev->bus ? dev->bus->name : "No Bus", dev_name(dev));

<7>[78541.048262] PM: Removing info for No Bus:259:0

l   下面这个也是release函数,由kobject_delayed_cleanup函数调用。当device结构体引用为0后,会依次调用device结构中定义的release函数,或device_type中定义的release函数,或device所属的class中所定义的release函数,最后会吧device_private结构释放掉。在device_create->device_create_vargs->device_create_groups_vargs函数中会调用dev->release = device_create_release,所以最后会调用device_create_release。device_create_release函数,pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

 

<7>[78541.049034] device: '259:0': device_create_release

l   nvme_remove->nvme_remove_namespace->nvme_ns_remove->del_gendisk->device_del->device_pm_remove

<7>[78541.049572] PM: Removing info for No Bus:nvme0n1

l   nvme_remove->nvme_uninit_ctrl->cdev_device_del->device_del->device_pm_remove

<7>[78541.091916] PM: Removing info for No Bus:nvme0

l   下面这个输出,先触发device_release,因为在代码中设置了ctrl->device->release = nvme_free_ctrl,所以调用nvme_free_ctrl函数。而在nvme_free_ctrl中会间接调用nvme_destroy_subsystem。逻辑如下:nvme_free_ctrl-> nvme_put_subsystem->nvme_destroy_subsystem-> device_del-> device_pm_remove

 

<7>[78541.092007] PM: Removing info for No Bus:nvme-subsys0

l   下面这条记录,是由驱动对象引用减少为0后,内核中的工作队列延时执行的,其调用如下:

<4>[  243.031657]  driver_release+0x4b/0x70

<4>[  243.031660]  kobject_delayed_cleanup+0x6a/0x180

<4>[  243.031665]  process_one_work+0x13b/0x350

<4>[  243.031669]  worker_thread+0x45/0x3c0

<4>[  243.031672]  kthread+0xfd/0x130

<4>[  243.031674]  ? process_one_work+0x350/0x350

<4>[  243.031677]  ? kthread_bind+0x10/0x10

<4>[  243.031681]  ret_from_fork+0x35/0x40

 

<7>[78541.092067] driver: 'nvme': driver_release

l   nvme-core模块中nvme_exit函数中,函数class_destroy调用class_unregister,其中如下:pr_debug("device class '%s': unregistering\n", cls->name);

<7>[78541.098693] device class 'nvme-subsystem': unregistering

l   这个在class_unregister函数下,调用kset_unregister后,类的引用减少最后调用release函数释放。调用的函数栈如下:class_unregister-> kset_unregister-> kobject_put-> kobject_cleanup-> class_release。

<7>[78541.098773] class 'nvme-subsystem': release.

l   调用的函数栈如下:class_unregister-> kset_unregister-> kobject_put-> kobject_cleanup-> class_release-> class_create_release。在class_release函数中会调用class->class_release(class)来实现跳转(而在class_create中会设定cls->class_release = class_create_release)。class_create_release函数中有如下:pr_debug("%s called for %s\n", __func__, cls->name);

<7>[78541.098775] class_create_release called for nvme-subsystem

l   nvme-core模块中nvme_exit函数中,函数class_destroy调用class_unregister,其中如下:pr_debug("device class '%s': unregistering\n", cls->name);

<7>[78541.098778] device class 'nvme': unregistering

l   调用的函数栈如下:class_unregister-> kset_unregister-> kobject_put-> kobject_cleanup-> class_release

<7>[78541.098792] class 'nvme': release.

l   调用的函数栈如下:class_unregister-> kset_unregister-> kobject_put-> kobject_cleanup-> class_release-> class_create_release。在class_release函数中会调用class->class_release(class)来实现跳转(而在class_create中会设定cls->class_release = class_create_release)。class_create_release函数中有如下:pr_debug("%s called for %s\n", __func__, cls->name);

<7>[78541.098794] class_create_release called for nvme

 

l   最后是注销之前nvme-core注销过程中创建的三个队列。位于nvme-core模块中。destroy_workqueue函数调用device_unregister,其中有如下输出,输出的设备名字为nvme-delete-wq,其中有:pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

<7>[78541.098799] device: 'nvme-delete-wq': device_unregister

 

l   上行中的device_unregister会调用device_del, device_del函数调用bus_remove_device,bus_remove_device函数中pr_debug("bus: '%s': remove device %s\n",

                 dev->bus->name, dev_name(dev));

<7>[78541.098811] bus: 'workqueue': remove device nvme-delete-wq

l   device_del函数调用device_pm_remove,有输入下输出。

<7>[78541.098831] PM: Removing info for workqueue:nvme-delete-wq

l   destroy_workqueue函数调用device_unregister,在函数device_unregister中有如下输出,输出的设备名字为nvme-reset-wq,其中有pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

<7>[78541.099292] device: 'nvme-reset-wq': device_unregister

l   上行中的device_unregister会调用device_del, device_del函数调用bus_remove_device,bus_remove_device函数中

<7>[78541.099309] bus: 'workqueue': remove device nvme-reset-wq

l   device_del函数调用device_pm_remove,有输入下输出。

<7>[78541.099312] PM: Removing info for workqueue:nvme-reset-wq

l   在函数device_unregister中有如下输出,输出的设备名字为nvme-wq,其中有pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

 

<7>[78541.099531] device: 'nvme-wq': device_unregister

l   上行中的device_unregister会调用device_del, device_del函数调用bus_remove_device,bus_remove_device函数中

<7>[78541.099547] bus: 'workqueue': remove device nvme-wq

l   device_del函数调用device_pm_remove,有输入下输出。

<7>[78541.099549] PM: Removing info for workqueue:nvme-wq

     如果看到有些晕的话,可以看下面的极其简化说明版。

5.3     简化版

5.3.1 注册

nvme-core注册队列,创建2个类:

<7>[30385.621786] device: 'nvme-wq': device_add

<7>[30385.621795] bus: 'workqueue': add device nvme-wq

<7>[30385.621804] PM: Adding info for workqueue:nvme-wq

<7>[30385.624172] device: 'nvme-reset-wq': device_add

<7>[30385.624184] bus: 'workqueue': add device nvme-reset-wq

<7>[30385.624196] PM: Adding info for workqueue:nvme-reset-wq

<7>[30385.624772] device: 'nvme-delete-wq': device_add

<7>[30385.624780] bus: 'workqueue': add device nvme-delete-wq

<7>[30385.624790] PM: Adding info for workqueue:nvme-delete-wq

<7>[30385.624839] device class 'nvme': registering

<7>[30385.624864] device class 'nvme-subsystem': registering

#nvme模块添加nvme驱动,并探测pci中nvme设备。

<7>[30385.625689] bus: 'pci': add driver nvme

<7>[30385.625702] bus: 'pci': driver_probe_device: matched device 0000:00:0e.0 with driver nvme

<7>[30385.625705] bus: 'pci': really_probe: probing driver nvme with device 0000:00:0e.0

<7>[30385.625712] devices_kset: Moving 0000:00:0e.0 to end of list

<7>[30385.625862] device: 'nvme0': device_add

<7>[30385.625886] PM: Adding info for No Bus:nvme0

<6>[30385.625949] nvme nvme0: pci function 0000:00:0e.0

<7>[30385.629882] driver: 'nvme': driver_bound: bound to device '0000:00:0e.0'

<7>[30385.629913] bus: 'pci': really_probe: bound device 0000:00:0e.0 to driver nvme

<7>[30385.628129] device: 'nvme-subsys0': device_add

<7>[30385.628150] PM: Adding info for No Bus:nvme-subsys0

<7>[30385.629458] device: '259:0': device_add

<7>[30385.629479] PM: Adding info for No Bus:259:0

<7>[30385.629537] device: 'nvme0n1': device_add

<7>[30385.629556] PM: Adding info for No Bus:nvme0n1

5.3.2 注销

#以下释放nvme设备,并注销NVMe驱动

<7>[78541.046026] bus: 'pci': remove driver nvme

<7>[78541.048245] device: '259:0': device_unregister

<7>[78541.048262] PM: Removing info for No Bus:259:0

<7>[78541.049034] device: '259:0': device_create_release

<7>[78541.049572] PM: Removing info for No Bus:nvme0n1

<7>[78541.091916] PM: Removing info for No Bus:nvme0

<7>[78541.092007] PM: Removing info for No Bus:nvme-subsys0

<7>[78541.092067] driver: 'nvme': driver_release

#以下在nvme-core模块中,通过nvme_exit->class_destroy->class_unregister,注销之前加载时候的两个类。

<7>[78541.098693] device class 'nvme-subsystem': unregistering

<7>[78541.098773] class 'nvme-subsystem': release.

<7>[78541.098775] class_create_release called for nvme-subsystem

<7>[78541.098778] device class 'nvme': unregistering

<7>[78541.098792] class 'nvme': release.

<7>[78541.098794] class_create_release called for nvme

#以下在nvme-core模块中,通过nvme_exit->destroy_workqueue->workqueue_sysfs_unregister->device_unregister->device_del来注销之前创建的三个工作队列完成。

<7>[78541.098799] device: 'nvme-delete-wq': device_unregister

<7>[78541.098811] bus: 'workqueue': remove device nvme-delete-wq

<7>[78541.098831] PM: Removing info for workqueue:nvme-delete-wq

<7>[78541.099292] device: 'nvme-reset-wq': device_unregister

<7>[78541.099309] bus: 'workqueue': remove device nvme-reset-wq

<7>[78541.099312] PM: Removing info for workqueue:nvme-reset-wq

<7>[78541.099531] device: 'nvme-wq': device_unregister

<7>[78541.099547] bus: 'workqueue': remove device nvme-wq

<7>[78541.099549] PM: Removing info for workqueue:nvme-wq

5.4     小结

这小节需要结合第二篇和第三篇中的注销流程图,如果能结合源代码将是最好的了。否则的话,收获会是很有效。

            其实,内核在每个关键阶段都是会有输出的,所以在打开内核相关debug配置选项后对于开发者是非常有帮助。

本节虽然还没涉及到nvme驱动的代码,但是通过实际加载分析输出,使得我们对第二篇中所讲述的内容有了更好的理解,当出现实际驱动加载问题时候可以快速定位到函数位置。

            以上几篇涉及的代码其实都是内核中已经存在的,例如pci总线,设备模型等,和NVMe驱动本身并且没有多大关系。从下部开始将主要以NVMe驱动代码为主了。

 

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
高通平台开发系列讲解(USB篇)USB端口的说明及切换方法
高通平台开发系列讲解(USB篇)USB端口的说明及切换方法
513 0
高通平台开发系列讲解(USB篇)USB端口的说明及切换方法
西门子S7-1200有什么功能特点?应用范围有哪些?CPU型号及模块类型有哪些?
S7-1200是西门子公司新推出的一款面向离散自动化系统和独立自动化系统的低端PLC。S7-1200采用了模块化设计,具备强大的工艺功能,适用于多种场合,可以满足不同的自动化需求。
西门子S7-1200有什么功能特点?应用范围有哪些?CPU型号及模块类型有哪些?
|
3月前
|
芯片 SoC
【驱动】【设备树】一个简单的系统级芯片(SoC)的设备树节点
【驱动】【设备树】一个简单的系统级芯片(SoC)的设备树节点
47 0
|
6月前
|
传感器 芯片
PCF8574芯片介绍及驱动方法
PCF8574芯片介绍及驱动方法
371 0
|
6月前
|
测试技术
【ZYNQ】ZYNQ7000 私有定时器及其驱动应用示例
【ZYNQ】ZYNQ7000 私有定时器及其驱动应用示例
142 0
|
Ubuntu 调度
usb摄像头驱动-core层usb设备的注册
usb摄像头驱动-core层usb设备的注册
100 0
|
存储 Linux 开发者
【Linux学习笔记】设备驱动模型详解——总线、设备、驱动和类
设备驱动是计算机系统中的重要组成部分,它们允许操作系统与硬件交互。设备驱动模型是一种通用的抽象框架,用于描述操作系统如何管理硬件设备。这里我们将介绍设备驱动模型中的四个关键概念:总线、设备、驱动和类。
|
Linux 开发工具 git
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十)LED模板驱动程序的改造:总线设备驱动模型
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十)LED模板驱动程序的改造:总线设备驱动模型
229 1
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十)LED模板驱动程序的改造:总线设备驱动模型
|
存储 异构计算 内存技术