Zephys OS nano内核篇:定时器 Timer

Zephyr OS 所有的学习笔记已托管到 Github,CSDN 博客里的内容只是 Github 里内容的拷贝,因此链接会有错误,请谅解。

最新的学习笔记请移步 GitHub:https://github.com/tidyjiang8/zephyr-inside

概念

nanokernel 利用系统的时钟实现了一个定时器服务。线程可以利用定时器服务来等待一段指定的时间间隔(以滴答为单位),且在等待的期间可以异步执行其它工作。一个线程可以使用多个定时器同时监控多个时间间隔。

线程在使用定时器时,可以为定时器绑定一个用户数据(使用一个指向用户数据结构体的指针)。当定时器的时间到期时,它会返回这个用户数据。

Timer 的定义

struct nano_timer {
    struct _nano_timeout timeout_data;
    void *user_data;
    void *user_data_backup;
#ifdef CONFIG_DEBUG_TRACING_KERNEL_OBJECTS
    struct nano_timer *__next;
#endif
};

不考虑用于 debug 的 __next,一共包含 3 个成员:
- timeout_data: 可以看出来,定时器内部使用了前一篇文章所讲的超时服务。
- user_data: 用户数据,定时器到期后返回给线程。
- user_data_backup: 用户数据的备份。

为什么需要备份用户数据?表面上看去,这个备份完全是多余的,但是深入分析,其实定时器同时还使用 user_data 来作为一个标志,来表示定时器当前的状态。当定时器处于工作状态(即定时器已启动,并在等待到期)时,user_data 指向一段用户自定义数据。当定时器处于非工作状态(即定时器启动前、定时器到期后或者定时器被终止后)时,user_data 指向 NULL。

Q:为什么不单独使用一个 flag 成员来表示当前的状态,那样更容易理解

Timer 的 API

Timer 服务对内核其它服务暴露了如下 API 如下:
- nano_timer_init():初始化定时器
- nano_timer_start():启动定时器。内核会根据当前的上下文调用对于的启动函数:
- nano_task_timer_start()
- nano_fiber_timer_start()
- nano_isr_timer_start()
- nano_timer_test():测试定时器是否到期。内核会根据当前的上下文调用对应的测试函数:
- nano_task_timer_test()
- nano_fiber_timer_test()
- nano_isr_timer_test()
- nano_timer_stop():强行停止定时器,将其从超时链表中删除,并将绑定的线程加入就绪队列,因此也可以将其理解为将定时器强制到期。内核会根据当前的上下文调用对于的停止函数:
- nano_task_timer_stop()
- nano_fiber_timer_stop()
- nano_isr_timer_stop()
- nano_timer_ticks_remain():判断定时器还有多久将到期,单位是滴答。

nano_timer_init

void nano_timer_init(struct nano_timer *timer, void *data)
{
    // 初始化 timeout_data
    _nano_timeout_init(&timer->timeout_data, NULL);
    timer->user_data = NULL;
    timer->user_data_backup = data;

    SYS_TRACING_OBJ_INIT(nano_timer, timer); // 用于debug
}

在启动定时器前,需要调用该函数初始化定时器结构体中的各成员。需要注意到一点,user_data 没有指向用户数据,user_data_backup 指向了用户数据。

nano_timer_start

nano_timer_start(),nano_task_timer_start(),nano_fiber_timer_start(),nano_isr_timer_start()都是函数_timer_start() 的别名:

FUNC_ALIAS(_timer_start, nano_isr_timer_start, void);
FUNC_ALIAS(_timer_start, nano_fiber_timer_start, void);
FUNC_ALIAS(_timer_start, nano_task_timer_start, void);
FUNC_ALIAS(_timer_start, nano_timer_start, void);
void _timer_start(struct nano_timer *timer, int ticks)
{
    int key = irq_lock();

    timer->user_data = timer->user_data_backup;
    _nano_timer_timeout_add(&timer->timeout_data,
                NULL, ticks);
    irq_unlock(key);
}

启动定时器,并设定定时器到期的时间间隔(以滴答为单位)。主要做了两件事:
- 将user_data 也指向用户数据。
- 将定时器结构体中的超时服务加入到内核的超时链表中。

nano_timer_test

void *nano_timer_test(struct nano_timer *timer, int32_t timeout_in_ticks)
{
    static void *(*func[3])(struct nano_timer *, int32_t) = {
        nano_isr_timer_test,
        nano_fiber_timer_test,
        nano_task_timer_test,
    };

    return func[sys_execution_context_type_get()](timer, timeout_in_ticks);
}

根据当前的上下文,调用对应的测试函数。第二个比较难以理解,它其实是一个标志 flag,用来指示该函数的具体行为:
- 如果 flag 的取值为 TICKS_NONE,表示如果待测试的定时器如果没到期,该函数直接返回。
- 如果 flag 的取值为 TICKS_UNLIMITED,表示如果待测试的定时器如果没到期,会让出 CPU 进行上下文切换或忙进行忙等待(如果当前上下文是中断,不会切换)。

nano_fiber_timer_test

void *nano_fiber_timer_test(struct nano_timer *timer, int32_t timeout_in_ticks)
{
    int key = irq_lock();
    struct _nano_timeout *t = &timer->timeout_data;
    void *user_data;

    // 根据 _nano_timer_expire_wait 的返回值判读是直接返回还是进行线程切换。
    if (_nano_timer_expire_wait(timer, timeout_in_ticks, &user_data)) {
        t->tcs = _nanokernel.current;
        _Swap(key);
        key = irq_lock();
        user_data = timer->user_data;
        timer->user_data = NULL;
    }
    irq_unlock(key);
    return user_data;
}

再看看 _nano_timer_expire_wait() 如何实现:

tatic int _nano_timer_expire_wait(struct nano_timer *timer,
                    int32_t timeout_in_ticks,
                    void **user_data_ptr)
{
    struct _nano_timeout *t = &timer->timeout_data;

    /* check if the timer has expired */
    if (t->delta_ticks_from_prev == -1) {
        // 将定时器的用户数据“取”给 user_data_ptr
        *user_data_ptr = timer->user_data;
        // 用户数据被“取”走后,将 user_data 置为 NULL
        timer->user_data = NULL;
    /* if the thread should not wait, return immediately */
    } else if (timeout_in_ticks == TICKS_NONE) {
        // 由于定时器没到期,调用函数“取”不到任何数据
        *user_data_ptr = NULL;
    } else {
        // 由于调用函数将进行上下文切换,此时不关心 user_data_ptr
        return 1;
    }
    return 0;
}

该函数先检查定时器是否已经到期了。
- 如果到期了,返回 0 ,“取”出用户数据,其调用函数不会进行线程切换。
- 如果没到期,再根据传入的标志 timeout_in_ticks 来判断具体的行为。
- 如果 timeout_in_ticks 为 TICKS_NONE,返回 0,其调用函数不会进行上下文切换。
- 如果 timeout_in_ticks 为 TICKS_UNLIMITED,返回 1,其调用函数将上下文切换。

我们现在可以回头看看为什么用户数据需要备份了。在初始化时,

nano_task_timer_test

void *nano_task_timer_test(struct nano_timer *timer, int32_t timeout_in_ticks)
{
    int key = irq_lock();
    struct _nano_timeout *t = &timer->timeout_data;
    void *user_data;

    if (_nano_timer_expire_wait(timer, timeout_in_ticks, &user_data)) {
        // 进行忙等待操作,知道定时器到期。
        while (t->delta_ticks_from_prev != -1) {
            NANO_TASK_TIMER_PEND(timer, key);
        }
        user_data = timer->user_data;
        timer->user_data = NULL;
    }
    irq_unlock(key);
    return user_data;
}

NANO_TASK_TIMER_PEND 会判断当前 task 是否是 idea task 来做相应的动作,如果是 idea task,直接进行忙等待。如果不是 idea task,将当前 task 挂起到一个定时器上。

nano_isr_timer_test

void *nano_isr_timer_test(struct nano_timer *timer, int32_t timeout_in_ticks)
{
    int key = irq_lock();
    void *user_data;

    if (_nano_timer_expire_wait(timer, timeout_in_ticks, &user_data)) {
        // 由于中断上下文的需要需要快速处于终端,所以即使 _nano_timer_expire_wait 返回 1,
        // 本函数也直接返回
        user_data = NULL;
    }
    irq_unlock(key);
    return user_data;
}

nano_timer_stop

void nano_timer_stop(struct nano_timer *timer)
{
    static void (*func[3])(struct nano_timer *) = {
        nano_isr_timer_stop,
        nano_fiber_timer_stop,
        nano_task_timer_stop,
    };

    func[sys_execution_context_type_get()](timer);
}

根据当前上下文,调用对应的定时器停止函数。

nano_fiber_timer_stop

nano_isr_timer_stop

nano_fiber_timer_stop()和nano_isr_timer_stop()都是函数 _timer_stop_non_preemptible() 的别名:

FUNC_ALIAS(_timer_stop_non_preemptible, nano_isr_timer_stop, void);
FUNC_ALIAS(_timer_stop_non_preemptible, nano_fiber_timer_stop, void);

所以我们具体来看 _timer_stop_non_preemptible() 函数:

void _timer_stop_non_preemptible(struct nano_timer *timer)
{
    struct _nano_timeout *t = &timer->timeout_data;
    struct tcs *tcs = t->tcs;
    int key = irq_lock();

    // 1. 先判断定时器所绑定的线程是否处于一个等待队列上。如果是,让其继续等待,当等待条
    // 件被满足时会有相应的程序将其加入到就绪队列中,我们不用做任何处理。
    // 2. 如果不在等待队列上,将 timeout_data 从超时链表中删除。
    // 3. 再根据定时器绑定的线程的上下文类型,将其加入到对应的就绪链表中。
    if (!t->wait_q && (_nano_timer_timeout_abort(t) == 0) &&
        tcs != NULL) {
        if (_IS_MICROKERNEL_TASK(tcs)) {
            _NANO_TIMER_TASK_READY(tcs);
        } else {
            _nano_fiber_ready(tcs);
        }
    }

    // 标志定时器处于非工作状态
    timer->user_data = NULL;
    irq_unlock(key);
}

nano_task_timer_stop

void nano_task_timer_stop(struct nano_timer *timer)
{
    struct _nano_timeout *t = &timer->timeout_data;
    struct tcs *tcs = t->tcs;
    int key = irq_lock();

    timer->user_data = NULL;

    if (!t->wait_q && (_nano_timer_timeout_abort(t) == 0) &&
        tcs != NULL) {
        if (!_IS_MICROKERNEL_TASK(tcs)) {
            _nano_fiber_ready(tcs);
            _Swap(key);
            return;
        }
        _TASK_NANO_TIMER_TASK_READY(tcs);
    }
    irq_unlock(key);
}

该函数与 _timer_stop_non_preemptible 极其相似,主要的区别是:

由于调用该函数时,当前上下文是 task,所以如果判断出定时器所绑定的线程的上下文是 fiber 或者 isr 时,就进行上下文切换。这是因为 task 的优先级比 fiber 和 isr 的优先级低。

nano_timer_ticks_remain

int32_t nano_timer_ticks_remain(struct nano_timer *timer)
{
    int key = irq_lock();
    int32_t remaining_ticks;
    struct _nano_timeout *t = &timer->timeout_data;
    sys_dlist_t *timeout_q = &_nanokernel.timeout_q;
    struct _nano_timeout *iterator;

    if (t->delta_ticks_from_prev == -1) {
        // 如果定时器已到期,直接返回 0
        remaining_ticks = 0;
    } else {
        // 如果定时器没到期,从链表头开始查找,直到找到该节点,并将各节点绑定的增量超
        // 时时间 delta_ticks_from_prev 累加
        iterator = (struct _nano_timeout *)sys_dlist_peek_head(timeout_q);
        remaining_ticks = iterator->delta_ticks_from_prev;
        while (iterator != t) {
            iterator = (struct _nano_timeout *)sys_dlist_peek_next(timeout_q, &iterator->node);
            remaining_ticks += iterator->delta_ticks_from_prev;
        }
    }

    irq_unlock(key);
    return remaining_ticks;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值