目录

1.基本概念

1.1.Linux 的时钟信息 - timespec

1.2.Linux 获取时间 - clock_gettime()

2.QEMU 定时器

2.1.时钟类型

2.2.基本数据类型

2.2.1.定时器链表节点 - QEMUTimer

2.2.2.链表头节点 - QEMUClock

2.2.3.定时器链表 - QEMUTimerList

2.2.4.定时器链表集合 - QEMUTimerListGroup

3.初始化流程

3.1.创建定时器链表

3.1.1.创建全局定时器链表集合 - timerlistgroup_init()

3.1.2.创建各个类型的定时器链表 - timerlist_new()

3.2.创建定时器 - timer_new()

3.2.1.创建定时器 - timer_new_full()

3.2.2.初始化定时器将其添加至链表 - timer_init_full()

3.3.设置超时时间 - timer_mod()

4.获取时间 - qemu_clock_get_ns()


1.基本概念

  • 频率与时间的关系如下:

    • 晶振频率为 20MHz,则 20*1000*1000 个晶振脉冲为 1s;

  • 不同单位的时间换算方式如下:

    • 1s(秒)= 1000ms(毫秒)

    • 1ms(毫秒)= 1000μs(微秒)

    • 1μs(微秒)= 1000ns(纳秒)

    • 1ns(纳秒)= 1000ps(皮秒)

    • 1s = 1*1000*1000*1000ns

1.1.Linux 的时钟信息 - timespec

Linux 保存时钟信息的结构体为 timespec,保存秒和纳秒:

struct timespec {
    time_t   tv_sec;        /* seconds */
    long     tv_nsec;       /* nanoseconds */
};

1.2.Linux 获取时间 - clock_gettime()

通过 clock_gettime() 获取系统的时钟信息:

int clock_gettime(clockid_t clk_id, struct timespec *tp);

传入参数的含义如下:

  • clk_id 指定时钟类型;

  • tp 指向 timespec 结构体,用于存储获取的时间值;

clock_gettime() 支持多种时钟类型,每种类型都有不同的用途和特性:

  • CLOCK_REALTIME:系统实时时间,从 Epoch(1970-01-01 00:00:00 UTC)开始计时,可以更改;

  • CLOCK_MONOTONIC:系统运行时间,从系统启动时开始计时,不受系统时间调整的影响;

  • CLOCK_PROCESS_CPUTIME_ID:当前进程的 CPU 时间;

  • CLOCK_THREAD_CPUTIME_ID:当前线程的 CPU 时间;

  • CLOCK_MONOTONIC_RAW:类似于 CLOCK_MONOTONIC,但不受 NTP 调整的影响;

  • CLOCK_REALTIME_COARSE:CLOCK_REALTIME 的低精度版本,速度更快;

  • CLOCK_MONOTONIC_COARSE:CLOCK_MONOTONIC 的低精度版本,速度更快;

  • CLOCK_BOOTTIME:类似于 CLOCK_MONOTONIC,但包括系统挂起的时间;

  • CLOCK_TAI:国际原子时,不受闰秒影响;

2.QEMU 定时器

2.1.时钟类型

  • QEMU_CLOCK_REALTIME:实时时钟,即使虚拟机停止也会继续运行,不受宿主机时钟变化影响;

  • QEMU_CLOCK_VIRTUAL:虚拟机时钟,仅在虚拟机运行时运行,虚拟机停止时停止;

  • QEMU_CLOCK_HOST:实时时钟,受宿主机时钟影响(使用 gettimeofday() 接口),虚拟机停止也继续运行,受 NTP 网络时钟、宿主机时钟修改等因素影响

  • QEMU_CLOCK_VIRTUAL_RT:虚拟机时钟,icount 模式使用纳秒计数;

// include/qemu/timer.h
typedef enum {
    QEMU_CLOCK_REALTIME = 0,
    QEMU_CLOCK_VIRTUAL = 1,
    QEMU_CLOCK_HOST = 2,
    QEMU_CLOCK_VIRTUAL_RT = 3,
    QEMU_CLOCK_MAX
} QEMUClockType;

2.2.基本数据类型

2.2.1.定时器链表节点 - QEMUTimer

QEMU 定时器节点按链表形式组织:

每个链表节点表示一个定时器:

// include/qemu/timer.h
struct QEMUTimer {
    int64_t expire_time;        /* in nanoseconds */
    QEMUTimerList *timer_list;
    QEMUTimerCB *cb; // 回调函数,到达expire_time后触发
    void *opaque;
    QEMUTimer *next;
    int attributes;
    int scale;
};
  • expire_time 为定时器的超时时间(定时时长),单位为纳秒;

  • *cb 为定时器的超时回调函数,当定时器运行时间达到 expire_time 时触发定时器超时,并调用该回调函数;

2.2.2.链表头节点 - QEMUClock

定时器链表头结点定义时钟类型:RealTimer、Virtual、Host、Virtual_RT

QEMUClock 使用 QLIST_HEAD() 宏定义链表头结点,timerlists 保存了该类型定时器链表的头结点

// util/qemu-timer.c
typedef struct QEMUClock {
    QLIST_HEAD(, QEMUTimerList) timerlists; // 定时器列表头结点

    QEMUClockType type; // 时钟类型
    bool enabled;
} QEMUClock;

定义 qemu_clocks 数组保存链表头结点,即每种类型的时钟都有专门的链表,而不是将各种类型的时钟放在一起

// util/qemu-timer.c
static QEMUClock qemu_clocks[QEMU_CLOCK_MAX];

2.2.3.定时器链表 - QEMUTimerList

每种类型的定时器均有一个专门的链表,以 REALTIME 类型的定时器链表为例:

// util/qemu-timer.c
struct QEMUTimerList {
    QEMUClock *clock;
    QemuMutex active_timers_lock;
    QEMUTimer *active_timers;
    QLIST_ENTRY(QEMUTimerList) list;
    QEMUTimerListNotifyCB *notify_cb;
    void *notify_opaque;

    /* lightweight method to mark the end of timerlist's running */
    QemuEvent timers_done_ev;
};

2.2.4.定时器链表集合 - QEMUTimerListGroup

保存四种类型的定时器链表,main_loop_tlg 为全局变量

// include/qemu/timer.h
struct QEMUTimerListGroup {
    QEMUTimerList *tl[QEMU_CLOCK_MAX];
};
-------------------------------------------

// include/qemu/timer.h
extern QEMUTimerListGroup main_loop_tlg;

3.初始化流程

QEMU 定时器创建分为三个部分:

  • 创建定时器链表;

  • 创建对应类型的定时器;

  • 设置超时时间;

3.1.创建定时器链表

3.1.1.创建全局定时器链表集合 - timerlistgroup_init()

定时器链表集合 tlg 是 AioContext 的成员之一:

// include/block/aio.h
struct AioContext {
    GSource source;
    ...
    /* TimerLists for calling timers - one per clock type.  Has its own
     * locking.
     */
    QEMUTimerListGroup tlg;

虚拟机启动时执行 AIO 初始化,创建 AIO Context 时创建定时器链表集合 main_loop_tlg:

|--> aio_context_new()
    |--> ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext));
    |--> timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
-------------------------------------------------------------------------

// util/qemu-timer.c
void timerlistgroup_init(QEMUTimerListGroup *tlg,
                         QEMUTimerListNotifyCB *cb, void *opaque)
{
    QEMUClockType type;
    for (type = 0; type < QEMU_CLOCK_MAX; type++) {
        tlg->tl[type] = timerlist_new(type, cb, opaque);
    }
}

3.1.2.创建各个类型的定时器链表 - timerlist_new()

timerlist_new() 按照给定的类型,创建对应的定时器链表

// util/qemu-timer.c
QEMUTimerList *timerlist_new(QEMUClockType type,
                             QEMUTimerListNotifyCB *cb,
                             void *opaque)
{
    QEMUTimerList *timer_list;
    QEMUClock *clock = qemu_clock_ptr(type);

    timer_list = g_new0(QEMUTimerList, 1);
    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;
}

3.2.创建定时器 - timer_new()

timer_new() 函数接受 4 个参数:

  • type:时钟类型,QEMU_CLOCK_REALTIME、QEMU_CLOCK_VIRTUAL、...;

  • scale:时间单位,纳秒 SCALE_NS、微秒 SCALE_US,毫秒 SCALE_MS;

  • *cb:回调函数;

  • *opaque:回调函数所使用的参数;

// include/qemu/timer.h
static inline QEMUTimer *timer_new(QEMUClockType type, int scale,
                                   QEMUTimerCB *cb, void *opaque)
{
    return timer_new_full(NULL, type, scale, 0, cb, opaque);
}

除此以外,还可以使用时间单位对应的接口创建定时器,这些接口均调用 timer_new()

// include/qemu/timer.h
static inline QEMUTimer *timer_new_ns(QEMUClockType type, QEMUTimerCB *cb,
                                      void *opaque)
{
    return timer_new(type, SCALE_NS, cb, opaque);
}
-------------------------------------------------------------------------

// include/qemu/timer.h
static inline QEMUTimer *timer_new_us(QEMUClockType type, QEMUTimerCB *cb,
                                      void *opaque)
{
    return timer_new(type, SCALE_US, cb, opaque);
}
-------------------------------------------------------------------------

// include/qemu/timer.h
static inline QEMUTimer *timer_new_ms(QEMUClockType type, QEMUTimerCB *cb,
                                      void *opaque)
{
    return timer_new(type, SCALE_MS, cb, opaque);
}

3.2.1.创建定时器 - timer_new_full()

timer_new() 调用 timer_new_full() 完成定时器创建,并在创建后立即进行初始化:

// include/qemu/timer.h
static inline QEMUTimer *timer_new_full(QEMUTimerListGroup *timer_list_group,
                                        QEMUClockType type,
                                        int scale, int attributes,
                                        QEMUTimerCB *cb, void *opaque)
{
    QEMUTimer *ts = g_new0(QEMUTimer, 1);
    timer_init_full(ts, timer_list_group, type, scale, attributes, cb, opaque);
    return ts;
}

3.2.2.初始化定时器将其添加至链表 - timer_init_full()

timer_init_full() 将该定时器添加至对应类型的链表,并设置该定时器的回调函数、定时精度等信息:

// util/qemu-timer.c
void timer_init_full(QEMUTimer *ts,
                     QEMUTimerListGroup *timer_list_group, QEMUClockType type,
                     int scale, int attributes,
                     QEMUTimerCB *cb, void *opaque)
{
    if (!timer_list_group) {
        timer_list_group = &main_loop_tlg;
    }
    ts->timer_list = timer_list_group->tl[type];
    ts->cb = cb;
    ts->opaque = opaque;
    ts->scale = scale;
    ts->attributes = attributes;
    ts->expire_time = -1;
}

3.3.设置超时时间 - timer_mod()

定时器创建后,调用 timer_mod() 设置超时时长,需要注意的是,在传入超时时长 expire_time 时,需要加上当前的系统时间,即 qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + time

// util/qemu-timer.c
void timer_mod(QEMUTimer *ts, int64_t expire_time)
{
    timer_mod_ns(ts, expire_time * ts->scale);
}
----------------------------------------------------------

// util/qemu-timer.c
void timer_mod_ns(QEMUTimer *ts, int64_t expire_time)
{
    QEMUTimerList *timer_list = ts->timer_list;
    bool rearm;

    qemu_mutex_lock(&timer_list->active_timers_lock);
    timer_del_locked(timer_list, ts);
    rearm = timer_mod_ns_locked(timer_list, ts, expire_time);
    qemu_mutex_unlock(&timer_list->active_timers_lock);

    if (rearm) {
        timerlist_rearm(timer_list);
    }
}
----------------------------------------------------------

// util/qemu-timer.c
static bool timer_mod_ns_locked(QEMUTimerList *timer_list,
                                QEMUTimer *ts, int64_t expire_time)
{
    QEMUTimer **pt, *t;

    /* add the timer in the sorted list */
    pt = &timer_list->active_timers;
    for (;;) {
        t = *pt;
        if (!timer_expired_ns(t, expire_time)) {
            break;
        }
        pt = &t->next;
    }
    ts->expire_time = MAX(expire_time, 0);
    ts->next = *pt;
    qatomic_set(pt, ts);

    return pt == &timer_list->active_timers;
}

4.获取时间 - qemu_clock_get_ns()

该接口最终调用 Linux 获取时钟的接口 clock_gettime(),时间单位为纳秒 ns

// util/qemu-timer.c
int64_t qemu_clock_get_ns(QEMUClockType type)
{
    switch (type) {
    case QEMU_CLOCK_REALTIME:
        return get_clock();
    default:
    case QEMU_CLOCK_VIRTUAL:
        return cpus_get_virtual_clock();
    case QEMU_CLOCK_HOST:
        return REPLAY_CLOCK(REPLAY_CLOCK_HOST, get_clock_realtime());
    case QEMU_CLOCK_VIRTUAL_RT:
        return REPLAY_CLOCK(REPLAY_CLOCK_VIRTUAL_RT, cpu_get_clock());
    }
}
----------------------------------------------------------

// system/cpus.c
int64_t cpus_get_virtual_clock(void)
{
    if (cpus_accel && cpus_accel->get_virtual_clock) {
        return cpus_accel->get_virtual_clock();
    }
    return cpu_get_clock();
}
----------------------------------------------------------

// system/cpu-timers.c
int64_t cpu_get_clock(void)
{
    int64_t ti;
    unsigned start;

    do {
        start = seqlock_read_begin(&timers_state.vm_clock_seqlock);
        ti = cpu_get_clock_locked();
    } while (seqlock_read_retry(&timers_state.vm_clock_seqlock, start));

    return ti;
}
----------------------------------------------------------

// system/cpu-timers.c
int64_t cpu_get_clock_locked(void)
{
    int64_t time;

    time = timers_state.cpu_clock_offset;
    if (timers_state.cpu_ticks_enabled) {
        time += get_clock();
    }

    return time;
}
----------------------------------------------------------

// include/qemu/timer.h
static inline int64_t get_clock(void)
{
    if (use_rt_clock) {
        struct timespec ts;
        clock_gettime(CLOCK_MONOTONIC, &ts); // Linux 获取时间的接口
        return ts.tv_sec * 1000000000LL + ts.tv_nsec;
    } else {
        return get_clock_realtime();
    }
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐