虚拟化场景时间管理

发布时间 2023-11-28 11:16:00作者: yuanzhiyuan
 在云主机环境涉及到时间处理,虚拟机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); //获取当前时间

 

2、  定时器

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;
...
}

 

3、  定时器设备

设备的两部分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必须放映经过的时间。