• Zephyr的Time、Timer、sleep


    正如Linux下一样,关于时间的系统函数可以分为三类:时间值、睡眠一段时间以及延迟执行。

    在Zephyr上对应是什么样子呢?带着这个疑问,去了解一下这些函数。

    以及他们与suspend之间的关系?

    是否计入suspend时间?(计入-在到期后立即执行;不计入-需要唤醒后继续睡眠剩下时间)。

    是否具备唤醒功能?如果具备,则能将将系统从suspend唤醒。

    1 Zephyr的时间服务基础

    Zephyr的官方文档提供了详细的模块说明和API使用方法。

    Time:Kernel Clocks是系统所有基于时间服务的基础,包括Timer和Sleep。

    Zephyr提供两种时钟计数,一个是高精度的32位硬件时钟计数(hardware clock),cycle表示的长短由硬件决定;

    另一个是64位的tick计数(system clock),每个tick大小是由系统配置的,1ms~100ms不等。

    2 Zephry的Time

    我们知道Zephyr有两种clock计数:hardware clock和system clock。

    system clock是内核很多基于时间服务的基础,包括timer或者其他超时服务。

    当然,system clock是建立在hardware clock基础之上的。系统决定一个tick多长时间,然后换算成多少个hardware clock cycles。

    通过对hardware clock编程,hardware clock倒计数然后产生中断,一个中断表示一个tick。

    系统时间服务受制于tick的精度,比如10ms的tick,如果delay是25的话,会对齐到30ms。

    即使是20ms delay,很有可能时间delay达到30ms,因为当前设置的delay只有到下一次tick产生才会设置在下两个tick到期。

    系统时间相关服务建立在tick之上,tick建立在hardware clock之上,hardware clock具有最高精度,因此如果想要高精度时间测量,可以使用hardware clock。

    不同精度时间示例。

    普通精度:

    s64_t time_stamp;
    s64_t milliseconds_spent;
    
    /* capture initial time stamp */
    time_stamp = k_uptime_get();
    
    /* do work for some (extended) period of time */
    ...
    
    /* compute how long the work took (also updates the time stamp) */
    milliseconds_spent = k_uptime_delta(&time_stamp);

    高精度:

    u32_t start_time;
    u32_t stop_time;
    u32_t cycles_spent;
    u32_t nanoseconds_spent;
    
    /* capture initial time stamp */
    start_time = k_cycle_get_32();
    
    /* do work for some (short) period of time */
    ...
    
    /* capture final time stamp */
    stop_time = k_cycle_get_32();
    
    /* compute how long the work took (assumes no counter rollover) */
    cycles_spent = stop_time - start_time;
    nanoseconds_spent = SYS_CLOCK_HW_CYCLES_TO_NS(cycles_spent);

    Tick的配置通过CONFIG_SYS_CLOCK_TICKS_PER_SEC相关API位于Clocks

    3 Zephyr的Sleep

    Zephyr的时间一个应用是Thread Sleeping,线程可以通过k_sleep()来延迟一段时间处理。在这段时间内,当前线程进入睡眠。在sleep时间满足并且当前进程被调度到,才会继续运行。

    其他进程可以通过k_wakeup()提前唤醒处于k_sleep进程;如果进程不处于k_sleep中,k_wakeup则不起作用。

    问题:那么如果睡眠过程中进入suspend,k_sleep是否计入suspend时间?

    答:k_sleep是计入suspend时间的,也即suspend时间会补偿到sleep中,并且sleep到期可以唤醒suspend。

    void k_sleep(s32_t duration)
    Put the current thread to sleep.
    
    This routine puts the current thread to sleep for duration milliseconds.
    
    Return
    N/A
    Parameters
    duration: Number of milliseconds to sleep.
    
    
    void k_wakeup(k_tid_t thread)
    Wake up a sleeping thread.
    
    This routine prematurely wakes up thread from sleeping.
    
    If thread is not currently sleeping, the routine has no effect.
    
    Return
    N/A
    Parameters
    thread: ID of thread to wake.

    Busy Waiting是另一种延迟操作,k_busy_wait和k_sleep有以下不同:

    • k_busy_wait基于hardware clock进行计数,更加精确;k_sleep基于system tick。
    • k_busy_wait期间不会将CPU调度权交出去;k_sleep允许CPU调度别的线程。
    • k_busy_wait只能在非常短延时的情况下使用。
    void k_busy_wait(u32_t usec_to_wait)
    Cause the current thread to busy wait.
    
    This routine causes the current thread to execute a “do nothing” loop for usec_to_wait microseconds.
    
    Return
    N/A

      

    4 Zephyr的Timer 

    Zephyr时间另一应用是Timers,包括几个要素duration(第一次定时器)、period(第一次超时之后的周期性定时器)、expiry function(超时函数)、stop function(提前结束Timer)、status(Timer的状态)。

    Timer使用之前必须先初始化,如果period不为0,则第一次从超时后会重新起一个period的timer。timer执行过程中可以被停止或者重新触发。Timer的状态可以随时随地读取。

    Zephyr Timer还有另一种同步读取状态的功能,这个功能会将当前进程阻塞,直到timer状态非零(即timer已经发生过超时,最起码一次)或者timer被停止。如果已经非零或者被停止,则当前线程不用等待。

    Timer初始化:

    struct k_timer my_timer;
    extern void my_expiry_function(struct k_timer *timer_id);
    
    k_timer_init(&my_timer, my_expiry_function, NULL);
    
    或者:
    
    K_TIMER_DEFINE(my_timer, my_expiry_function, NULL);

    使用Timer:

    void my_work_handler(struct k_work *work)
    {
        /* do the processing that needs to be done periodically */
        ...
    }
    
    K_WORK_DEFINE(my_work, my_work_handler);
    
    void my_timer_handler(struct k_timer *dummy)
    {
        k_work_submit(&my_work);
    }
    
    K_TIMER_DEFINE(my_timer, my_timer_handler, NULL);
    
    ...
    
    /* start periodic timer that expires once every second */
    k_timer_start(&my_timer, K_SECONDS(1), K_SECONDS(1));

    获取Timer状态:

    K_TIMER_DEFINE(my_status_timer, NULL, NULL);
    
    ...
    
    /* start one shot timer that expires after 200 ms */
    k_timer_start(&my_status_timer, K_MSEC(200), 0);
    
    /* do work */
    ...
    
    /* check timer status */
    if (k_timer_status_get(&my_status_timer) > 0) {
        /* timer has expired */
    } else if (k_timer_remaining_get(&my_status_timer) == 0) {
        /* timer was stopped (by someone else) before expiring */
    } else {
        /* timer is still running */
    }

     同步获取Timer状态(还具有delay的功能):

    K_TIMER_DEFINE(my_sync_timer, NULL, NULL);
    
    ...
    
    /* do first protocol operation */
    ...
    
    /* start one shot timer that expires after 500 ms */
    k_timer_start(&my_sync_timer, K_MSEC(500), 0);
    
    /* do other work */
    ...
    
    /* ensure timer has expired (waiting for expiry, if necessary) */
    k_timer_status_sync(&my_sync_timer);
    
    /* do second protocol operation */
    ...

    停止Timer:

    k_timer_stop(&my_timer)

    5 system tick机制

    timer的中断触发,_sys_idle_elapsed_ticks在没有使能Tickless的情况下一般是1.

    .word _timer_int_handler(vector_table.S)-->
        _timer_int_handler(cortex_m_systick.c)-->
            _sys_clock_tick_announce(cortex_m_systick.c)-->
                _nano_sys_clock_tick_announce(_sys_idle_elapsed_ticks)-->
                    handle_timeouts-->遍历_timeout_q,执行超时timer的函数。
                    handle_time_slicing
    
    
    static inline void handle_timeouts(s32_t ticks)
    {
        sys_dlist_t expired;
        unsigned int key;
    
        /* init before locking interrupts */
        sys_dlist_init(&expired);
    
        key = irq_lock();
    
        struct _timeout *head =
            (struct _timeout *)sys_dlist_peek_head(&_timeout_q);---------------取_timeout_q的头
    ...
        head->delta_ticks_from_prev -= ticks;----------------------------------减去逝去tick数
    
        /*
         * Dequeue all expired timeouts from _timeout_q, relieving irq lock
         * pressure between each of them, allowing handling of higher priority
         * interrupts. We know that no new timeout will be prepended in front
         * of a timeout which delta is 0, since timeouts of 0 ticks are
         * prohibited.
         */
        sys_dnode_t *next = &head->node;
        struct _timeout *timeout = (struct _timeout *)next;
    
        _handling_timeouts = 1;
    
        while (timeout && timeout->delta_ticks_from_prev <= 0) {------------当前timeout已经超时
    
            sys_dlist_remove(next);-----------------------------------------将next(即当前timeout)从列表移除
    
            /*
             * Reverse the order that that were queued in the timeout_q:
             * timeouts expiring on the same ticks are queued in the
             * reverse order, time-wise, that they are added to shorten the
             * amount of time with interrupts locked while walking the
             * timeout_q. By reversing the order _again_ when building the
             * expired queue, they end up being processed in the same order
             * they were added, time-wise.
             */
            sys_dlist_prepend(&expired, next);------------------------------将next加入到expired列表
    
            timeout->delta_ticks_from_prev = _EXPIRED;----------------------将timeout(next)的delta_ticks_from_prev设置为_EXPIRED
    
            irq_unlock(key);
            key = irq_lock();
    
            next = sys_dlist_peek_head(&_timeout_q);------------------------重新取_timeout_q的头
            timeout = (struct _timeout *)next;
        }
    
        irq_unlock(key);
    
        _handle_expired_timeouts(&expired);--------------------------------遍历_timeout_q列表,选出超时timer到expired之后,然后在_handle_expired_timeouts中一个一个执行。
    _handling_timeouts
    = 0; }

     从handle_timeouts可知,_timeout_q上的times排列是按照超时顺序排列的。所以从头开始遍历,能按照超时顺序执行超时函数。

    那么这是如何实现的呢?就要看插入_timeout_q列表操作了。

    k_sleep-->_add_thread_timeout-->
    k_timer_init-->_time_expiration_handler-->
    k_timer_start-->
    k_delayed_work_submit_to_queue-->
            _add_timeout--------------------------------_add_timeout是操作_timeout_q的核心
    
    
    static inline void _add_timeout(struct k_thread *thread,
                    struct _timeout *timeout,
                    _wait_q_t *wait_q,
                    s32_t timeout_in_ticks)
    {
        __ASSERT(timeout_in_ticks > 0, "");
    
        timeout->delta_ticks_from_prev = timeout_in_ticks;
        timeout->thread = thread;
        timeout->wait_q = (sys_dlist_t *)wait_q;
    
        K_DEBUG("before adding timeout %p
    ", timeout);
        _dump_timeout(timeout, 0);
        _dump_timeout_q();
    
        s32_t *delta = &timeout->delta_ticks_from_prev;
        struct _timeout *in_q;
    ...
        SYS_DLIST_FOR_EACH_CONTAINER(&_timeout_q, in_q, node) {----------遍历_timeout_q列表,找出合适的位置:delta的值小于等于下一节点,大于前一节点。
            if (*delta <= in_q->delta_ticks_from_prev) {
                in_q->delta_ticks_from_prev -= *delta;
                sys_dlist_insert_before(&_timeout_q, &in_q->node,--------将新节点timeout插入到in_q之前
                            &timeout->node);
                goto inserted;
            }
    
            *delta -= in_q->delta_ticks_from_prev;-----------------------初始delta是和当前时间的差值,在寻找插入位置的过程中会逐渐递减,相对时间参考点逐渐后移。一直以前一个timer超时点为基准。
        }
    
        sys_dlist_append(&_timeout_q, &timeout->node);
    
    inserted:
        K_DEBUG("after adding timeout %p
    ", timeout);
        _dump_timeout(timeout, 0);
        _dump_timeout_q();
    
    ...
    }

    suspend对系统tick影响,通过k_tick_add将suspend时间补偿到_sys_clock_tick_count,同时更新_timeout_q

    _sys_soc_suspend-->
        k_tick_add-->
    
    
    void k_tick_add(u32_t time)
    {
        u32_t tick;
        struct _timeout *timeout;
    
        tick = _ms_to_ticks(time);
    
        _sys_clock_tick_count += tick;---------------------------系统Tick计数值
    
        timeout = (struct _timeout *)sys_dlist_peek_head(&_timeout_q);
        if (timeout)
            timeout->delta_ticks_from_prev -= tick;-------------------------????只补偿了链表头,是否有必要补偿所有节点。
    }

    补充:关于Zephyr的链表《zephyr学习笔记---双向链表dlist》。

  • 相关阅读:
    DLUTOJ 1209 字典序和r-子集
    C++ Standard-Library Random Numbers
    后缀数组模板
    UVA 1398 Meteor
    STL Iterators
    hihocoder1187 Divisors
    51nod 子序列的个数(动态规划)
    51nod 最长单增子序列(动态规划)
    51nod 更难的矩阵取数问题(动态规划)
    51nod 多重背包问题(动态规划)
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/7657571.html
Copyright © 2020-2023  润新知