蝶恋花 【宋】欧阳修
庭院深深深几许?杨柳堆烟,帘幕无重数。玉勒雕鞍游冶处,楼高不见章台路。
雨横风狂三月暮,门掩黄昏,无计留春住。泪眼问花花不语,乱红飞过秋千去。
看完前面一篇,我相信你的心情就跟欧阳修这首《蝶恋花》中说的一样,感到:”庭院深深深几许?杨柳堆烟,帘幕无重数。”杨柳依依,一重又一重堵在前面,像一层层帘幕一样,我们翻了这么多层代码,根本看不到NVMe初始化在哪里( ▼-▼ )
再探DeviceInfo
读者君,还记得NVMe设备的注册函数吗?我们再来过一遍,
首先有一个static类型的全局变量nvme_info,是个PCIDeviceInfo,内容为
接着,通过一连串我们上文中发现的初始化流程,下面这个函数在main函数中通过调用module_call_init(MODULE_INIT_DEVICE)而被执行。所以我们再来一级级深入剖析下面这个函数:
PCIDeviceInfo对象nvme_info通过pci_qdev_register注册,而PCIDeviceInfo继承了DeviceInfo类,
所以类似,函数pci_qdev_register就是把这个pci设备的父对象info->qdev通过qdev_register函数注册。
如下,nvme_info的父对象DeviceInfo添加到了全局变量device_info_list当中,这个是所有device设备的链表。后面就好办了,我们只要查找这个全局变量调用的代码,就能找到初始化的地方了。
离真相只有几步
功夫不负有心人,阿呆终于搞清楚了来龙去脉,下面就是揭晓奇迹的时刻。上文中我们看到main函数调用了
但是,没有继续留意后面的代码,其实再看几十行,就有一段有点不太直接的代码。
这段代码就是device设备初始化的地方,人世间很多事都是这样,往往我们在离真相很近的时候提前放弃了,等到花了大工夫搞明白之后,才追悔莫及。文科生就不像我们理科生这么纠结,他们能用文学化悲痛为美好:众里寻他千百度,蓦然回首,那人却在灯火阑珊处。
那么问题来了,qemu_opts_foreach和qemu_find_opts都是来干嘛的?这里我们需要来看一条QEMU启动的命令:
qemu-system-x86_64 –enable-kvm -cpu host -smp cores=4,threads=2,sockets=4 -m 16384 -k en-us -hda /pps/guohongwei/vm_test/ubuntu.img -monitor stdio -device nvme
这里启动了QEMU,并且配置了各种外设,使用了KVM内核,CPU和内存配置,硬盘镜像,最后是device设备,加载了nvme设备。上面的两个函数qemu_opts_foreach(qemu_find_opts(“device”), …)就是从参数列表中找到device设备,并且遍历。
这里遍历了注册的Device设备,通过device_init_func一个个初始化。我们来看初始化流程。
qdev_device_add函数首先通过qdev_find_info查询,从我们前面看到的DeviceInfo注册的链表device_info_list中查到要初始化的device。我们翻到开头nvme_info的初始值就知道,nvme_info把里面DeviceInfo的name初始化为”nvme”,所以qdev_find_info就可以找到”-device nvme”对应的DeviceInfo。
得到DeviceInfo之后,创建DeviceState对象qdev。
首先来看qdev_create_from_info,创建了DeviceState,真的吗?真的仅仅是创建了一个DeviceState对象吗?注意这里mallocz的大小是info->size,而开头的时候,DeviceInfo的size变量我们给的是sizeof(NVMEState)!也就是说,其实这里创建的是一个NVMEState对象!并且给props赋初始值。那这些props是怎么来的?
请再回开头看看nvme_info的初始化,尾巴上有下面一段,通过Property结构体,给定了初始值。通过这种方式给NVMEState对象的变量赋初值。num_namespace和ns_size都是NVMEState类的成员。
create之后,再来看后面的qdev_init函数,其实就是调用了DeviceInfo里面的init函数。
请往前翻pci_qdev_register函数内容,就知道DeviceInfo里的init函数是pci_qdev_init。所以,我们又来看它里面做了什么。一进来,就通过container_of宏得到DeviceInfo的子类PCIDeviceInfo。为什么,因为我们在PCIDeviceInfo里面有一个对象是DeviceInfo qdev,知道了二者的偏移关系,就可以qdev的内存地址减去偏移量得到PCIDeviceInfo的内存地址,就是对象的指针。container_of这个宏就是把这堆复杂的计算弄成了一个宏。在linux编程中还是很常见的,尤其是已知struct内部变量地址,要计算struct地址的时候。
还有,DeviceState指针直接转成了PCIDevice指针,为什么可以这样?来看看PCIDevice的定义:第一个变量就是DeviceState,很巧妙吧,这样就能做到子类到父类指针的轻松切换。为了面向对象,QEMU真是煞费苦心啊。
接着,又调用了info->init函数,不过请注意,这里的info已经变成了PCIDeviceInfo,所以此init非彼init了。
我们再回过头看nvme_info的初始化,就知道init函数是pci_nvme_init。看到这里,憋了很久的阿呆忍不住大吼一声:”终于轮到NVME的初始化了!”
是啊,看了三篇代码,理清了QEMU留下的一个个路标,才轮到NVMe出场,不容易啊!下期我们就来看看NVMe的初始化,不过请先复习蛋蛋的《蛋蛋读NVME之X》系列文章。
本文以一首北宋词开头,再以一首南宋词结尾,来表达阿呆此刻的心情。
青玉案·元夕 【宋】辛弃疾
东风夜放花千树。更吹落、星如雨。宝马雕车香满路。凤箫声动,玉壶光转,一夜鱼龙舞。
蛾儿雪柳黄金缕。笑语盈盈暗香去。众里寻他千百度。蓦然回首,那人却在,灯火阑珊处。
引用