虚拟化场景时间管理

在云主机环境涉及到时间处理,虚拟机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); //获取当前时间

  1. 定时器

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

  1. 定时器设备

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值