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\n", 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\n", 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》。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM