一: 概要
在后端模拟出balloon设备后,gustos在启动时会扫描到此设备,遵循linux设备模型调用设备的初始化工作。Virtio-balloon属于 virtio体系,很多工作的细节需要再分析virtio的工作流程,本章暂且只分析balloon的行为,涉及virtio的部分插桩分析向后再补充分析。
balloon执行流程如下:
二:驱动创建
2.1 驱动注册
Linux设备驱动模型中,各驱动可以按总线类别进行划分,且每个总线类别下可以挂载“驱动”和“设备”两类对象。内核就维护了这样一张“总线”到“驱动和设备”的总表,每当一个新驱动加进内核时,内核会扫描该驱动所挂载总线上的所有设备,并通过比对驱动中的id_table字段和设备配置空间中的Device ID,如果相同则代表该驱动可以为该设备服务,那内核就会针对该设备调用总线的probe函数(如果总线没有probe函数,再调用驱动的probe函数)。
另外一种情况是往总线上插入一个新设备,内核同样会扫描总线上的所有驱动,看哪个驱动匹配该设备,如果匹配也对该设备调用总线的probe函数(如果总线没有probe函数,再调用驱动的probe函数)。
Linux内核中前端代码主要包括driver/virtio目录下相关文件及driver/virtio_balloon.c,最终生成的内核模块有virtio.ko,virtio_ring.ko,virtio_pci.ko和virtio_balloon.ko。
由于virtio-balloon-pci设备是virtio-pci设备,而virtio-pci设备又是pci设备,所以virtio-pci设备的驱动会注册到pci总线上面,因此,整个初始化过程如下:
(1)内核会首先找到virito-pci.ko这个驱动模块,并依次加载virtio.ko,virtio-ring.ko和virtio_pci.ko (virtio_pci.ko依赖前两个模块)执行其模块初始化函数,其中,virtio.ko模块会在系统中注册一种新的总线类型virtio总线,virtio_pci的初始化函数会调用其注册的virtio_pci_probe函数;
(2)virtio_pci_probe注册一个virtio设备(register_virtio_device);
(3)内核再次为这个virtio设备搜索驱动模块,最终找到virtio_balloon.ko并加载调用其模块初始化函数;
(4)virtio_balloon初始化函数在virtio总线上添加了virtio_balloon驱动并调用了总线的probe函数(总线的probe函数优先级高于总线上设备的probe函数)即virtio_dev_probe;
(5)virtio_dev_probe调用virtballoon_probe完成最后的初始化任务。
我们最终需要关注的是virtballoon_probe这个函数是怎么被调用到的,linux设备初始化开始到调用到virtballoon_probe的过程简化如下,仅供参考:
驱动可执行的动作包含在virtio_balloon_driver定义的结构体中。先来看下这个结构体的内容,文件位置driver/virtio/virtio_balloon.c。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
可以看到,注册的 driver中注册了feature属性,driver的名称和owner,驱动加载的probe卸载的remove,感知变化的config_changed,这三个函数做了主要的工作。 先来看下加载做了什么工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
|
可以看到,这里的主要工作有:
1. 通过init_waitqueue_head初始化了两个工作队列用来接收QEMU发来的notify
2. 通过init_vqs初始化了3个 virt_queue用来和qemu发送balloon进行inflate/deflate的page地址信息以及callback回调
3. 启动内核线程执行vballoon,执行balloon的具体操作
2.2 vballoon如何运作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
主要涉及到的处理:
1. 添加等待队列,等待config_change被唤醒,即QEMU有执行balloon操作
2. 计算需要申请或者释放的空间,即diff值
3. 如果需要申请或者释放空间,则调用fill_balloon或者leak_balloon进行操作
4. 更新balloon实际占用的空间,记录到actual变量中,并通知给QEMU
计算diff值的操作如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
2.3 balloon充气过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
基本流程可以总结为:从gust空间申请页面放入balloon的链表中,并做标记使该内存内核不可用,填充设备的pfn数组,然后通过ivq通知设备侧进行处理。
2.4 leak_balloon过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
leak_balloon的过程和fill_balloon刚好相反,它会释放存放在balloon的page链表中的page项归还给gust,同理,这部分 内存会被qemu从host申请回来留给gustos备用,此时host主机的可用内存就减少了。