在云主机环境涉及到时间处理,虚拟机vmexit,内部时间中断模拟处理,电路板通常有一些定时操作设备,hyperson里如何实现,怎么保证时间可靠?
代码:qemu-5.0
1、qemu时钟(time.h)
此处时间被分为四类,
真实时间,即使vm停了也会运行,用于不改变虚拟机状态的内容
虚拟时间,vm停时间停,用于vm运行期
宿主机时间,主机时间源设备,虚拟机挂起时也会运行,反应系统时间改变(比如NTP)
虚拟运行时时间,虚拟机运行时,在icount模式下计时,在vcpu睡眠时增加虚拟时间
typedef enum {
QEMU_CLOCK_REALTIME = 0, //
QEMU_CLOCK_VIRTUAL = 1,
QEMU_CLOCK_HOST = 2,
QEMU_CLOCK_VIRTUAL_RT = 3,
QEMU_CLOCK_MAX
} QEMUClockType;
int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); //获取当前时间
- 定时器
qemu提供ms、ns级别定时器,timer_xxx函数进行创建、删除、重置、改变,可以把定时器加到不同的时钟上。
eg,创建定时器只在vcpu运行时计时,当经过duration毫秒时执行回调
QEMUTimer *user_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, user_timeout_cb, obj);
int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
timer_mod(timer, now + duration);
static void user_timeout_cb(void *opaque)
{
obj_t *obj = (obj_t*)opaque;
...
}
- 定时器设备
设备的两部分IO内存,IRQ中断需要实现,qemu用内部时钟模型在硬件上表现
CPIOM tick timer示例
typedef struct cpiom_clock
{
QEMUTimer *qemu_timer;
uint32_t *trigger;
int64_t restart;
double duration;
} cpiom_clock_t;
typedef struct cpiom_timer_device_state
{
/*< private >*/
SysBusDevice parent_obj;
/*< public >*/
MemoryRegion iomem;
cpiom_timer_reg_t reg;
qemu_irq irq;
/* internal clock management */
cpiom_clock_t tick;
} cpiom_timer_state_t;
cpiom定时器包含一个IO内存区域iomem、底层设备寄存器reg、一个时钟tick
真正的CPIOM计时器复杂一些,我们仅展示一个计时器实现。
static void cpiom_timer_init(Object *obj)
{
cpiom_timer_state_t *tm = CPIOM_TIMERS(obj);
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
memory_region_init_io(&tm->iomem, obj, &cpiom_timer_reg_ops, tm,
CPIOM_TIMERS_NAME"-reg", CPIOM_MMAP_TIMERS_SIZE);
sysbus_init_mmio(dev, tm->iomem);
sysbus_init_irq(dev, &tm->irq);
tm->tick.qemu_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tick_expired, tm);
tm->tick.trigger = &tm->reg.base.tick;
...
}
设备初始化,由于cpiom_timer_reg_ops 有回调,任何tm->iomem的访问都会更新tm->reg。同时创建一个纳秒定时器调用tick_expired
访问CPIO计时器
我们设置offset 0x0c is a R/W 32 bits 作为TIME_COUNTER 寄存器,当值为0,触发irq
实现一个驱动程序通过写寄存器设置定时,
static const MemoryRegionOps cpiom_timer_reg_ops = {
.read = cpiom_timer_reg_read,
.write = cpiom_timer_reg_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void cpiom_timer_reg_write(void *opaque, hwaddr addr, uint64_t data, unsigned size)
{
....
cpiom_timer_state_t *tm = (cpiom_timer_state_t*)opaque;
if (addr == 0x0c)
write_counter(tm, data);
....
}
static void write_counter(cpiom_timer_state_t *tm, uint32_t new)
{
if (!timer_is_active(tm))
return;
if (new == 0)
tick_expired((void*)tm);
else
clock_setup(tm, &tm->tick, new);
}
static void tick_expired(void *opaque)
{
cpiom_timer_state_t *tm = (cpiom_timer_state_t*)opaque;
qemu_irq_raise(tm->irq);
}
当写设备时,判断计数到期,到期就触发irq,没到就更新计数
时间膨胀,更新计数:
static void clock_setup(cpiom_timer_state_t *tm, cpiom_clock_t *clk, uint32_t count)
{
clk->duration = nsperiod * count;
clk->restart = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
uint64_t expire = clk->restart + (int64_t)floor(clk->duration);
timer_mod(clk->qemu_timer, expire /* +/- speed factor */);
}
nsperiod = (1/TIMER_FREQ_MHZ) * 1000 * scale;
经过的时间
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
count = (now - clk->restart)/nsperiod;
clk->restart = now;
每当驱动读寄存器,vm必须放映经过的时间。