从前,计算机还是奢侈品,有一位屌丝程序员叫蛋蛋,尽管他每天开发电脑软件,但是码砖的住不起房子,码代码的买不起电脑。对蛋蛋来说,买个电脑跟愚公移山一样难,可是他太喜欢电脑了,怎么办?蛋蛋终于想出了一个主意:真的买不起,还不能搞一个假的吗?蛋蛋的想法是用软件模拟一个电脑,把所有的硬件都用软件来虚拟。隔壁Cubicle的程序员智叟听说了之后,讥笑他:”蛋蛋啊,你也太傻了,微软的软件高管说成熟的工程师一年只能写4000行代码,你什么时候才能模拟出那台电脑?”蛋蛋斩钉截铁地回答:”我死了,还有我的儿子,儿子死了还有孙子,子子孙孙无穷无尽地码代码,就能开发成功!”蛋蛋想出了一个子子孙孙继承事业的好办法,他用了面向对象的技术,自己只是写了顶层的几个类,确定好框架,后来的人想要加个新的硬件进来,就一层层继承各种类,完善接口就能使用了。没想到,子孙还没接班,其他世界各地的程序员也加入进来帮忙,很快就开发成了这个著名的虚拟机——QEMU。
记得以前有位同学说他去一家牛公司面试,一道面试题就是C语言怎么开发面向对象的代码?我听了这道题觉得还是有点难度,不过没好好细想。如今看了QEMU的代码,才发现这就是C语言实现面向对象的绝佳例子啊!他如果当初看过QEMU的源码,估计能和面试官谈笑风生了。当然,本文的主要目的还是为了介绍NVMe在QEMU中的数据结构,教你面试题是副产品。代码不是阿呆自己写的,要那样我就牛了,QEMU的NVMe虚拟机代码是GIT上的一个开源项目https://github.com/nvmeqemu,谁都可以下载下来玩。
面向对象三大特点
如上图,我们知道面向对象有三大特征:
- 封装:这个好理解,就是在一个struct/class里面把各种变量,函数都打包塞进来。这个大部分人很快就能想到,很多宣称面向对象的C语言代码其实只是实现到了这一步。比如阿呆之前写的大话EXT4文件系统,Linux内核的VFS说是面向对象设计,其实就是做了个封装而已。
- 继承。继承的意思就程序员太懒了,要开发一个机器猫,想起以前搞过猫的对象,所以就让机器猫把猫这个对象包起来,这样猫对象所有的属性和接口都被继承了。再加一些机器猫独有的属性和接口,代码搞定。猫是父类,机器猫是子类,还可以继续子子孙孙,繁衍不息。这个咱C语言也有办法,就真的在struct里面包一个父类的struct不就完了,只不过纯手工,麻烦一点。
-
多态。
- 重载:就是函数名一样,参数不一样。这个比较好搞定,编译的时候编译成不一样的函数名就可以了。
- 覆盖:父类和子类同样名称和参数的函数,子类重新定义覆盖父类的。执行的时候,如果子类对象赋给父类指针,还得执行子类的新的函数,这个叫做动态绑定。不太好弄,需要包含一个表格来实时查询。
我们下面来看看NVMe的数据结构,顺便看看QEMU实现了面向对象的哪些功能。
QEMU面向谁的对象?
下图是一个PIT设备在QEMU中的结构,很明显,是个继承的关系,子类包含了一个父类的对象,继承了父亲的一切(其实应该是母亲的一切,谁叫码农是男的居多呢,parent自然翻译成了父亲。但是生物上来说,那些没有两性生殖的生物,一切基因应该是来自于母亲。),不像我们两性生物,父母各取一半。不过新版的QEMU已经有点区别了,我看代码,NVMe设备继承向上到了DeviceState就结束了。
QEMU里面的设备分为三类:
Block是Linux的块设备,比如磁盘IO之类,Device就是各种外设,比如PCIe设备,Machine是CPU等的虚拟。NVMe是一个PCI设备,所以是Device类型。描述NVMe设备的对象叫NVMEState,继承关系为:
那一个子类在QEMU中如果做到初始化?其实是要一级级初始化的,QEMU用一种递归的方法,如果子类的父类还没有初始化,那就先初始化父类,调用父类的构造函数。
NVMe设备怎么定义
尽管说NVMe设备的对象是NVMEState,但是nvme.c里面只看到了一个NVMe相关的对象定义:
那这个nvme_info是个什么东东?咱们且听下回分解。
引用
QEMU设备模拟,mnstory.net