上回,我们学习了Linux NVMe驱动的架构以及nvme_core_init的相关内容(),本文我们主要学习一下Linux NVMe驱动初始化过程中都做了哪些事情。

打开Pci.c找到初始化入口module_init(nvme_init)

static int __init nvme_init(void)

{

int result;

        //1, 创建全局工作队列

nvme_workq = alloc_workqueue("nvme", WQ_UNBOUND | WQ_MEM_RECLAIM, 0);

if (!nvme_workq)

return -ENOMEM;

        //2, 注册NVMe驱动

result = pci_register_driver(&nvme_driver); 

if (result)

destroy_workqueue(nvme_workq);

return result;

}

从上面code来看,初始化的过程很简单,只进行了两步,code结构也太简单了吧。如果你对这点认真,那你就输了。不要被假象给蒙蔽咯,我们一步一步的拆开学习一下。

第一步:创建全局工作队列

创建Workqueue时,实际调用的函数是__alloc_workqueue_key(),这部分已经涉及到Linux底层的知识点,我们不展开详细学习了,还是主要针对nvme相关的内容具体解析。在这里只是结合__alloc_workqueue_key()函数中最开始的一段代码,学习一下Kernel的两种线程:

if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)

flags |= WQ_UNBOUND;

在kernel中,有两种线程池,一种是线程池是per cpu的,也就是说,系统中有多少个cpu,就会创建多少个线程池,cpu x上的线程池创建的worker线程也只会运行在cpu x上。另外一种是unbound thread pool,该线程池创建的worker线程可以调度到任意的cpu上去。由于cache locality的原因,per cpu的线程池的性能会好一些,但是对power saving有一些影响。设计往往如此,workqueue需要在performance和power saving之间平衡,想要更好的性能,那么最好让一个cpu上的worker thread来处理work,这样的话,cache命中率会比较高,性能会更好。但是,从电源管理的角度来看,最好的策略是让idle状态的cpu尽可能的保持idle,而不是反复idle,working,idle again

我们来一个例子辅助理解上面的内容。在t1时刻,work被调度到CPU A上执行,t2时刻work执行完毕,CPU A进入idle,t3时刻有一个新的work需要处理,这时候调度work到那个CPU会好些呢?是处于working状态的CPU B还是处于idle状态的CPU A呢?如果调度到CPU B上运行,那么,由于之前处理过work,其cache内容新鲜,处理起work当然是得心应手,速度很快,但是,这需要将CPU A从idle状态中唤醒。选择CPU B呢就不存在将CPU 从idle状态唤醒,从而获取power saving方面的好处。

了解了上面的基础内容之后,我们再来检视per cpu thread pool和unbound thread pool。当workqueue收到一个要处理的work,如果该workqueue是unbound类型的话,那么该work由unbound thread pool处理并把调度该work去哪一个CPU执行这样的策略交给系统的调度器模块来完成,对于scheduler而言,它会考虑CPU core的idle状态,从而尽可能的让CPU保持在idle状态,从而节省了功耗。因此,如果一个workqueue有WQ_UNBOUND这样的flag,则说明该workqueue上挂入的work处理是考虑到power saving的。如果workqueue没有WQ_UNBOUND flag,则说明该workqueue是per cpu的,这时候,调度哪一个CPU core运行worker thread来处理work已经不是scheduler可以控制的了,这样,也就间接影响了功耗。

这块涉及的详细内容,有兴趣的可以翻阅Linux相关书籍。我们接下来进入本文的重点。

第二步:注册NVME驱动

在初始化过程中,调用kernel提供的pci_register_driver()函数将nvme_driver注册到PCI Bus。问题来了,PCI Bus是怎么将nvme driver匹配到对应的NVMe设备的呢?

系统启动时,BIOS会枚举整个PCI Bus, 之后将扫描到的设备通过ACPI tables传给操作系统。当操作系统加载时,PCI Bus驱动则会根据此信息读取各个PCI设备的Header Config空间,从class code寄存器获得一个特征值。class code就是PCI bus用来选择哪个驱动加载设备的唯一根据。NVMe Spec定义NVMe设备的Class code=0x010802h, 如下图。

根据code来看,nvme driver会将class code写入nvme_id_table,

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_id_table的内容如下,

static const struct pci_device_id nvme_id_table[] = {

{ PCI_VDEVICE(INTEL, 0x0953),

.driver_data = NVME_QUIRK_STRIPE_SIZE |

NVME_QUIRK_DISCARD_ZEROES, },

{ PCI_VDEVICE(INTEL, 0x0a53),

.driver_data = NVME_QUIRK_STRIPE_SIZE |

NVME_QUIRK_DISCARD_ZEROES, },

{ PCI_VDEVICE(INTEL, 0x0a54),

.driver_data = NVME_QUIRK_STRIPE_SIZE |

NVME_QUIRK_DISCARD_ZEROES, },

{ PCI_VDEVICE(INTEL, 0x5845), /* Qemu emulated controller */

.driver_data = NVME_QUIRK_IDENTIFY_CNS, },

{ PCI_DEVICE(0x1c58, 0x0003), /* HGST adapter */

.driver_data = NVME_QUIRK_DELAY_BEFORE_CHK_RDY, },

{ PCI_DEVICE(0x1c5f, 0x0540), /* Memblaze Pblaze4 adapter */

.driver_data = NVME_QUIRK_DELAY_BEFORE_CHK_RDY, },

{ PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) },

{ PCI_DEVICE(PCI_VENDOR_ID_APPLE, 0x2001) },

{ 0, }

};

咦,在上面的nvme_id_table里面怎么没有看到0x010802h呢?原来是最新的linux code将PCI_CLASS_STORAGE_EXPRESS定义放在了pci_ids.h里面了。

#define PCI_CLASS_STORAGE_EXPRESS 0x010802

pci_register_driver()函数将nvme_driver注册到PCI Bus之后,PCI Bus就明白了这个驱动是给NVMe设备(Class code=0x010802h)用的。

到这里,只是找到PCI Bus上面驱动与NVMe设备的对应关系。nvme_init执行完毕,返回后,nvme驱动就啥事不做了,直到pci总线枚举出了这个nvme设备,就开始调用nvme_probe()函数开始干活咯。再请出nvme_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_probe()的风采吗?请听下回分解~

 

GitHub 加速计划 / nv / nvm
78.06 K
7.82 K
下载
nvm-sh/nvm: 是一个 Node.js 版本管理器,用于在不同的 Node.js 版本之间进行切换。它可以帮助开发者轻松管理多个 Node.js 版本,方便进行开发和测试。特点包括轻量级、易于使用、支持跨平台等。
最近提交(Master分支:2 个月前 )
9c9ff4ba Moved issue template into ISSUE_TEMPLATE folder 9 天前
51ea809d - 9 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐