精华内容
下载资源
问答
  • 三星NVME驱动

    2018-10-05 21:43:18
    三星NVME驱动,适用于win10 64位.三星NVME驱动,适用于win10 64位.三星NVME驱动,适用于win10 64位.
  • 三星Nvme驱动

    2020-04-04 03:40:18
    三星Nvme驱动,海力士固态硬盘可用,修改硬件ID,shutdown /r /o /f /t 00,关闭驱动数字签证可强行安装,win7,win8可在安装在其他硬盘之后使用Ghost备份到Nvme硬盘上!
  • NVMe驱动详解系列 第一部: NVMe驱动初始化与注销 作者:perftrace@gmail.com 1 NVMe驱动详解之一源码和编译 本系列主要针对linux系统中自带的NVMe驱动,进行详细的分析和学习,从而掌握NVMe以及PCI...

     

     

     

     

     

     

    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驱动代码为主了。

     

    展开全文
  • NVMe驱动详解系列——第一部分:NVMe驱动初始化和注销 pdf版本下载:https://pan.baidu.com/s/1wtJKcn3ml1o3fb21VEUIOQ

    NVMe驱动详解系列——第一部分:NVMe驱动初始化和注销 

    pdf版本下载:

    https://pan.baidu.com/s/1wtJKcn3ml1o3fb21VEUIOQ

    展开全文
  • 三星NVme驱动

    2018-02-06 23:34:21
    三星NVME驱动,好多都失效了,这个可以用在SM951上,亲测可用
  • 三星PM951 NVME 驱动

    2019-03-06 00:49:06
    亲测 好用!!!三星PM951 NVME 驱动
  • z77xud3h包含nvme驱动

    2018-12-30 22:55:47
    技嘉主板的nvme驱动bioss!!!
  • 三文件nvme驱动,添加至bios可让老主板支持nvme硬盘。此为三文件版,里面含压缩和解压缩共6个文件。
  • 三星 Samsung 固态 nvme驱动程序,nvme_3.1, 三星固态检测程序,三星魔术师5.3,
  • NVMe驱动详解系列,第一部:NVMe驱动初始化与注销。 本系列主要针对linux系统中自带的NVMe驱动,进行详细的分析和学习,从而掌握NVMe以及PCI相关知识。文中所使用的源码是linux4.17.2。 需要提醒的是,阅读本系列文章...
  • U盘启动加载NVME驱动

    2019-12-06 17:06:09
    老主板不能刷BIOS方案,用U盘启动加载NVME驱动,再从启动项安装WIN10 -------
  • 三星SM951 NVME驱动

    2016-06-30 23:40:04
    可用于win10 64位的三星SM951 NVME驱动程序
  • 虚拟机通用nvme驱动x64,无签名!
  • esxi6.7封装nvme驱动

    千次阅读 2020-10-26 15:05:03
    1,下载最新Exsi-6.7.0U3b版本 ... 2,下载旧 Exsi-6.7.0版本 ... 3,解压 Exsi-6.7.0版本ZIP压缩包,找到旧版本...4,解压 最新Exsi-6.7.0U3b版本ZIP压缩包,找到NVME驱动目录,选中文件nvme驱文件、按F2、Ctrl+C复制名称、回

    1,下载最新Exsi-6.7.0U3b版本

    https://blog.csdn.net/dnpao/article/details/109289560
    

    2,下载旧 Exsi-6.7.0版本

    https://blog.csdn.net/dnpao/article/details/109289623
    

    3,解压 Exsi-6.7.0版本ZIP压缩包,找到旧版本NVME驱动
    在这里插入图片描述
    4,解压 最新Exsi-6.7.0U3b版本ZIP压缩包,找到NVME驱动目录,选中文件nvme驱文件、按F2、Ctrl+C复制名称、回到Exsi-6.7.0目录把旧版本NVME名称修改为新版本名称
    5,修改完后复制出来,放到与要封装的目录,我这里是放到与ZIP同级的 2 文件夹下

    在这里插入图片描述
    6. 进行封装,(注:前提是你已经安装了,VMware PowerCLI,如果没安装按我的教程进行安装! )

    # VMware PowerCLI 安装教程,如果安装请忽略!
    https://blog.csdn.net/dnpao/article/details/109287216
    
     \ESXi-Customizer-PS-v2.6.0.ps1 -izip .\ESXi670-201912001.zip -pkgDir .\2\
    

    在这里插入图片描述

    展开全文
  • VxWorks6.8操作系统下NVMe驱动设计
  • 使用u盘重装win7遇到“缺少所需的CD/DVD驱动程序”需要向Win7系统镜像中注入USB3.0和NVMe驱动的驱动注入工具
  • 随着近年来USB3设备的大量普及,NVME固态硬盘(SSD)的逐步兴起,对此二者驱动问题的解决成了当务之急。USB3驱动如果无法正确安装,将导致在进系统后相关...所以当前,USB3和NVME驱动已经成了阻碍装机效率的两大问题。
  • 采用原版64位win7镜像,集成USB3.0驱动、NVME驱动。ISO镜像格式,刻成光盘或者制作启动优盘安装,多机测试正常。
  • check_smart:监视插件,以使用SMART检查硬盘驱动器,固态驱动器和NVMe驱动
  • 主要解决办法本文以【教程】如何给Win7安装镜像注入usb3.0和nvme驱动方法为主,然后根据OCZ RD400 NVMe 驱动 (支持东芝 XG3 NVMe) 更新所提供的硬盘型号信息,做了以下两个工作: 将适用东芝XG3固态硬盘的NVMe驱动...

    ##主要解决办法

    本文以【教程】如何给Win7安装镜像注入usb3.0和nvme驱动方法为主,然后根据OCZ RD400 NVMe 驱动 (支持东芝 XG3 NVMe) 更新所提供的硬盘型号信息,做了以下两个工作:

    1. 将适用东芝XG3固态硬盘的NVMe驱动解压后的文件夹x64)添加到【教程】如何给Win7安装镜像注入usb3.0和nvme驱动中drivers文件夹中,以获得对该硬盘的支持1
    2. 将提取出来的部署工具直接放在C:\Windows\System32目录下,以避免安装windows AIK2

    ##适用的特定配置系统

    1.ThinkPad T470;
    2.6代CPU,甚至可能只适用6600U;
    3.东芝XG3型号固态硬盘,甚至可能只适用THNSF5512GPUK;某些型号的Intel和SAMSUNG固态硬盘也被支持(drivers文件夹包含有驱动);

    ##所需驱动文件下载地址

    1.【教程】如何给Win7安装镜像注入usb3.0和nvme驱动提供的下载文件驱动补丁
    2.提取出来的部署工具
    3.适用东芝XG3固态硬盘的NVMe驱动

    ##可直接下载安装的Win7镜像
    也可以下载作者最终做出来的Win7_sp1_x64镜像来直接进行安装和使用(该镜像以MSDN上的cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso镜像为基础)

    ##参考文献


    1. OCZ RD400 NVMe 驱动 (支持东芝 XG3 NVMe) 更新; ↩︎

    2. 【教程】如何给Win7安装镜像注入usb3.0和nvme驱动; ↩︎

    展开全文
  • 缘起:朋友电脑系统崩了,开机无法引导,估计是系统坏...安装Win7时很麻烦,但是如果你是直接安装Win10的就不用这么麻烦了,因为Win10镜像是自带NVME驱动的,而Win7的镜像没有这个驱动。 开始动手搞: 1.我的PE ...
  • systemd服务,以便在Proxmox启动之前将NVMe驱动器重新绑定到vfio-pci驱动程序。 我创建了该脚本,因为nvme驱动器已内置到Proxmox内核中,但我没有想修改基本系统,因为人偶控制了我的主机节点生命周期。 档案文件 ...
  • NVMe驱动中断绑定

    2016-02-25 14:35:31
    NVMe驱动中断绑定 1. 要想理清绑定先把irqbalance关闭 2. Kernel 和NVMe 驱动都可以和中断绑定相关 a. CentOS 6.6 之前的版本,NVMe 设备默认是绑定在最近的NUMA node的的第一个core,. 如果有多个强悍的PCIe...
  • NVMe驱动解析-前言

    千次阅读 2016-10-09 15:40:48
    本系列文章主要以Linux内核 3.10的nvme驱动为参考,从源码方面解析nvme协议。
  • win7安装镜像注入USB3.0,NVMe驱动

    万次阅读 2019-04-05 10:06:48
    win7安装镜像注入USB3.0,NVMe驱动 现在的新款主板和笔记本因为原生自带了USB3.0和NVMe,在安装WIN7的时候会出现进入安装界面后不识别USB设备且在硬盘列表中无法读取M.2类型的固态硬盘信息。导致这个现象的原因就是...
  • 联想发布的一款为WINDOWS7 64位系统ISO镜像添加注入USB3.0和联想Nvme驱动的工具,用于解决安装WIN7系统后键盘和鼠标不能使用,以及不能识别U盘的问题。本工具虽然写着联想开发,但是其他镜像仍然可以使用。注意:在...
  • 自制Windows 7 sp1 64位 旗舰版 集成usb3.0及nvme驱动纯净版(独家)
  • ThinkPad-T470安装Win7添加USB3.0和NVMe驱动的问题-附件资源

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,222
精华内容 1,688
关键字:

nvme驱动