这篇文章主要解释了QEMU的定时器系统,尤其是我最近提交的新定时器系统是如何工作的。
译文来自http://blog.alex.org.uk/2013/08/24/changes-to-qemus-timer-system/ 

 

定时器做什么?

定时器(QEMU定时器)提供了一种在时间段结束后的函数回调,并且传递了一个透明指针。

QEMU中有下面三种时钟系统:

1 实时时钟,即便是VM停止了,它仍然以1000HZ精度运转

2. 虚拟时钟,只有在VM运转时工作的高精度时钟

3. 宿主机时钟,可以像实时时钟那样在VM关闭时仍然工作,同时它有可以密切随系统时钟而更改。

时钟是一次性操作,只有在时间消耗完的时候调用回调函数,如果不重新加载将不再工作。我们可以在回调函数中重新设置时钟,以便让它重复利用。

 

最新实现有什么改变?

除了对API的更改,时钟只存在于主QEMU线程中,并且只在QEMU的主循环中运行。时钟过期则是由一个依赖于不同系统的二级报警时钟触发。 QEMUNotify 在notifier FD中发起一个write操作,也逐步终止所有的poll操作。 在qemu中, poll不再过期(而不像其他那些有期限的定时器),而是使用警报定时器(POSIX信号)来停止这些系统调用。

当然这些方法也造成了如下的一系列问题:

1 定时器只能在主循环中处理。 QEMU Aiocontext 有一个在快操作中运作的内部循环,而且这个操作可能运行很长时间。而由这些块设备产生的定时器不能在这个内部循环中被保证处理,尤其是在块设备层忙碌的时候。

2. 基本上定时器是一个单线程,而现存系统对额外线程机制以及AioContexts有很好的兼容计划

3 那些 警报定时器的系统实现代码并不是很漂亮

4 调用定时器的API 也很混乱

 

为了改善以上问题,我提交了31个patch:

1 重构了几乎所有的定时器代码

2. 使用了有过期的ppoll函数,而不是那些信号或者警报定时器,也因此删除了警报定时器的实现

3 把始终代码(QEMUClock)同定时器链表(QEMUTimerList)分离

4. 引入了一个时钟链表组(QEMUTimerListGroup)来允许独立线程或者其他用户定时器可以拥有他们自己的组。

5 整理了所有的API

 

那新的定时器系统如何工作呢?

下图非常好的阐述了两种对象之间的关系

每种时钟类型只有一个QEMUclock,代表了这种特别类型的时钟源。如上所述,目前只有三种时钟源。 在以前的实现中每一个时钟都有一个定时器链表。而现在只有运行的定时器被保存在QEMUTimerList中。时钟需要跟踪所有依附于它的QEMUTimerLists。

每一个定时器用户维持了为了一个QEMUTimerListGroup。这个结构变量目前是由一个QEMUTimerList指针构成的数组组成,当然每一个时钟类型都有一个。因此,每一个QEMUTimerListGroup实际上是三个时钟类型QEMUTimerList的集合。目前有两个QEMUTimerListGroup用户。一个是全局静态的main_loop_tlg函数,代变了运行在主循环中的定时器链表。我又为AioContext增加了一个新的用户,这样块设备层就不用每次依靠退回到主循环来获取定时器。 这也同样可运用于未来的线程。 所有的子系统都可以拥有它们自己的QEMUTimerListGroup,这样也就不需要为一个pengding的定时器来调用timerlistgroup_run_timers函数了.

每一个QEMUTimerList 维护了一个上图桔黄色所示的时钟。它位于QEMUClouck的QEMUTimer链表。它包含了第一个活跃时钟的指针。这个活跃时钟不是由以前以前的qemu链表对象而是一个新的单链表定时器来维护,这个单链表按照过期时限来排序,越早过期的越排在前面。

每一个QEMUTimer对象又包含了一个指向时钟链表的指针,这样它又可以操作整个定时器链表。

本质上,AIOContext poll循环中的g_poll调用被ppoll(纳秒级别)替换。不幸的是,glib只提供了毫秒级别的精度,也就是说主循环定时器只要仍然调用glib,就只能维持在毫秒级别的精度。

API 有什么改变?

API还是有一些混乱

首先,我用timer_action 系列函数替换了之前的定时器API(qemu_mod_timer函数),action由时钟操作来替换,例如time_new, timer_mod 以及其他的操作。这个新的函数系列运用了一个枚举值来代表时钟类型,而不是以前的时钟对象指针。例如

timer = qemu_new_timer_ns (vm_clock, cb, opaque);

变成了

now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);

不使用主循环的QEMUTimerListGroup的定时器可以由timer_new_list来创建。也可以由可用的timer_init而不是malloc。 AioContext提供了帮助函数如 aio_timer_new及aio_timer_init.

第二, 我也简单的让让时钟API相对合理化,例如

now = qemu_get_clock_ns (vm_clock);

变成

now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);

第三,我增添了大量的文档,参考(include/qemu/timer.h)

如果你有一些代码需要转换到新的定时器API,可以简单的运行以下命令:

scripts/switch-timer-api [filename] ...