承接上文
https://blog.csdn.net/yongbaoii/article/details/123789460
BH
那么我们现在就理解了BH在整个qemu中的地位。
说白了就是可以通过bh,能让qemu的poll机制产生回调。
这种中断并不是在qemu中被提出的,是在linux里面就有的。
在一些linux的书籍文献中这样介绍:
中断句柄只是中断处理的第一部分,因为下面一些限制,必须要有另外一部分来完整的处理中断:
1.中断句柄是异步运行的,只要发生中断就会进入中断句柄,而中断的发生是异步的,也就是说随时可以发生。因此中断句柄可能会中断一些正在执行的重要代码,包括另外一个中断句柄。
2.中断句柄运行时需要关中断,最好情况是禁止了当前的中断级(未设置SA_INTERRUPT),最坏的情况是所有的中断被中断(设置SA_INTERRUPT)。当然,关中断只是运行中断句柄时必要的步骤,适当的时候还是需要打开的。同样这也决定了中断句柄要尽快结束。
3.中断句柄一般都是时间紧迫的任务,因为他们处理的是硬件。
4.中断句柄不运行在进程上下文,因此不能被堵塞。
因此,中断处理分成了两部分,第一部分就是中断句柄(top halves),一旦发生中断就必须执行;第二部分叫做bottom halves,用来处理剩下的那些不那么紧急的工作,目的很简单,就是让中断句柄尽可能快的返回。
那么我们再来详细看一下qemu中的BH
BH 数据结构
struct QEMUBH {
AioContext *ctx; // 下半部所在的context
QEMUBHFunc *cb; // 下半部要执行的函数
void *opaque; // 函数参数
QEMUBH *next; // 下一个要执行的下半部
bool scheduled; // 使能bh,是否被调度,true:下一次dispatch会触发cb; false:下一次dispatch不会触发cb
bool idle;
bool deleted; // 标记是否将bh删除
};
BH数据结构的最后三个属性用于控制事件循环线程对BH的操作行为。
为什么要有这三个属性?因为bh的注册和执行是异步地,因此需要有一种方法提供给注册者用来通知执行者,注册者期望bh被怎样执行。
scheduled用来通知执行者,期望在下一次dispatch时bh被调度到;idle用于通知执行者bh不用作progress计数;deleted通知执行者bh被调度一次之后就删除。
qemu主线程有默认的事件循环qemu_aio_context,bh挂载到主线程的事件循环有接口qemu_bh_new可以直接调用
/* Functions to operate on the main QEMU AioContext. */
QEMUBH *qemu_bh_new(QEMUBHFunc *cb, void *opaque)
{
return aio_bh_new(qemu_aio_context, cb, opaque);
}
bh还有一种用法是只执行一次,然后就删除bh,这个通过aio_bh_schedule_oneshot
接口可以实现
而普通bh执行方式是调用qemu_bh_schedule
,虽然也只调度一次但不会被删除,下一次调度不需要重新注册
事件循环在poll到fd准备好之后,会调度qemu定制的回调函数aio_ctx_dispatch
,其主要功能是执行fd对应的回调函数,但在此之前会先检查context上是否挂载有bh,如果有先执行bh。
/* 注册到AioContext的回调函数,当fd准备好之后,事件循环机制会调度这个回调 */
static void
aio_dispatch(AioContext *ctx)
{
qemu_lockcnt_inc(&ctx->list_lock);
aio_bh_poll(ctx);
aio_dispatch_handlers(ctx);
qemu_lockcnt_dec(&ctx->list_lock);
}
static gboolean
aio_ctx_dispatch(GSource *source,
GSourceFunc callback,
gpointer user_data)
{
#ifdef DEBUG
g_print("%s\n", __FUNCTION__);
#endif
AioContext *ctx = (AioContext *) source;
aio_dispatch(ctx);
return TRUE;
}
qemu时钟系统
qemu支持四种时钟
QEMU_CLOCK_REALTIME 不受虚拟系统的影响,随时间流逝而累加计数
QEMU_CLOCK_VIRTUAL 虚拟时钟,记录虚拟系统的时间滴答
QEMU_CLOCK_HOST 这个类似墙上时钟,修改宿主机系统时间会改变这个时间
QEMU_CLOCK_VIRTUAL_RT 在非icount模式下和QEMU_CLOCK_VIRTUAL,在icount模式下于QEMU_CLOCK_VIRTUAL不同的是在虚拟cpu休眠的时候该值也会累加
四个时钟使用类型作为索引放在qemu_clocks数组中,描述时钟的数据结构为QEMUClock。
QEMUClock数据结构如下
typedef struct QEMUClock {
/* We rely on BQL to protect the timerlists */
QLIST_HEAD(, QEMUTimerList) timerlists;
NotifierList reset_notifiers;
int64_t last;
QEMUClockType type;
bool enabled;
} QEMUClock;
QEMUTimerList用于存放定时器链表
timerlist_new函数用于初始化时钟的定时器链表
QEMUTimerList *timerlist_new(QEMUClockType type,
QEMUTimerListNotifyCB *cb,
void *opaque)
{
QEMUTimerList *timer_list;
QEMUClock *clock = qemu_clock_ptr(type);
timer_list = g_malloc0(sizeof(QEMUTimerList));
qemu_event_init(&timer_list->timers_done_ev, true);
timer_list->clock = clock;
timer_list->notify_cb = cb;
timer_list->notify_opaque = opaque;
qemu_mutex_init(&timer_list->active_timers_lock);
QLIST_INSERT_HEAD(&clock->timerlists, timer_list, list);
return timer_list;
}
这个函数其实就是把一个定时器回调函数注册到了这个时钟的timer队列里面
主线程的定时器不当可以从定时器结构QEMUClock.timer_list 中索引,还可以从main_loop_tlg中索引。
再介绍一个timer_init_full函数
这是源码
void timer_init_full(QEMUTimer *ts,
QEMUTimerListGroup *timer_list_group, QEMUClockType type,
int scale, int attributes,
QEMUTimerCB *cb, void *opaque);
/**
* timer_init:
* @ts: the timer to be initialised
* @type: the clock to associate with the timer
* @scale: the scale value for the timer
* @cb: the callback to call when the timer expires
* @opaque: the opaque pointer to pass to the callback
*
* Initialize a timer with the given scale on the default timer list
* associated with the clock.
* See timer_init_full for details.
*/
看注释就能明白
这个函数对QEMUTimer结构体进行了设置。
设置它所在了timerlist
设置时间
设置回调函数等等。