色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

rt-thread優化系列(三)軟定時器的定時漂移問題分析

出出 ? 來源:出出 ? 作者:出出 ? 2022-06-23 09:35 ? 次閱讀

定時器

所謂軟定時器,是由一個線程運行維護的定時器列表。由線程調用定時器回調函數。
相對硬定時器,是由中斷(SysTick)維護的定時器列表,并在中斷中調用定時器回調函數。

另外,還有一種*硬件定時器*,這個和單片機里的定時器是一個概念,由外設定時器實現定時。和 rt-thread 提供的硬定時器是兩個不同概念。

對硬定時器回調函數有嚴格的執行時間要求,而且不能調用任何在中斷中不能調用的函數。總之不能有任何不能在中斷中執行的操作。

那么,軟定時器呢,要求需要這么嚴格嗎?

比如有個處理執行時間 10ms,比如在定時器中斷函數里發送個等待 10ms 的消息...

無論是硬定時器還是軟定時器,它們各自有一個定時器列表。列表中的定時器根據定時時間長短排序,定時時間短的在前。掃描這個列表中所有定時器,直到結尾或者出現第一個定時時間未到的定時器節點。當判斷出定時時間到的時候,調用定時器回調函數。
假如,某次掃描這個列表中多于一個定時器到達定時時間,也就是需要執行兩個以上的定時器回調函數。并且前一個掃描到的定時器的回調函數執行時間比較長,出現上面設想的某一種使用情況。這時候會出現什么效果?

因為硬定時器的種種硬性要求,以下討論只針對軟定時器。

從對 `rt_soft_timer_check` 的幾個疑問講起

先擺出官方的 rt_soft_timer_check 函數,實現。這個函數是來掃描定時器列表中到達定時時間的定時器,并調用定時器回調函數的。

```
void rt_soft_timer_check(void)
{
   rt_tick_t current_tick;
   struct rt_timer *t;
   register rt_base_t level;
   rt_list_t list;
   rt_list_init(&list);

   RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check enter\n"));

   /* disable interrupt */
   level = rt_hw_interrupt_disable();

   while (!rt_list_isempty(&rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
   {
       t = rt_list_entry(rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
                           struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);

       current_tick = rt_tick_get();

       /*
        * It supposes that the new tick shall less than the half duration of
        * tick max.
        */
       if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
       {
           RT_OBJECT_HOOK_CALL(rt_timer_enter_hook, (t));

           /* remove timer from timer list firstly */
           _rt_timer_remove(t);
           if (!(t->parent.flag & RT_TIMER_FLAG_PERIODIC))
           {
               t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
           }
           /* add timer to temporary list  */
           rt_list_insert_after(&list, &(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));

           soft_timer_status = RT_SOFT_TIMER_BUSY;
           /* enable interrupt */
           rt_hw_interrupt_enable(level);

           /* call timeout function */
           t->timeout_func(t->parameter);

           RT_OBJECT_HOOK_CALL(rt_timer_exit_hook, (t));
           RT_DEBUG_LOG(RT_DEBUG_TIMER, ("current tick: %d\n", current_tick));

           /* disable interrupt */
           level = rt_hw_interrupt_disable();

           soft_timer_status = RT_SOFT_TIMER_IDLE;
           /* Check whether the timer object is detached or started again */
           if (rt_list_isempty(&list))
           {
               continue;
           }
           rt_list_remove(&(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
           if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
               (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
           {
               /* start it */
               t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
               rt_timer_start(t);
           }
       }
       else break; /* not check anymore */
   }
   /* enable interrupt */
   rt_hw_interrupt_enable(level);

   RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check leave\n"));
}

疑問一 `rt_list_t list` 這個臨時中間變量的作用是什么?

  • 進入 `rt_soft_timer_check` 函數后,先初始化 list 變量。
  • 掃描定時器列表,當有到時的定時器,把這個定時器從軟定時器列表 `rt_soft_timer_list` 移除,插入到這個 list 臨時存放。
  • 開中斷,調用定時器回調函數,關中斷
  • 把 list 中存放的定時器移除
  • 判斷定時器的周期定時器還是一次性定時器
  • 繼續掃描定時器列表

list 是個局部變量,僅僅起臨時存放當前這個定時器的作用。看似是一種穩妥的做法。
但是,這樣做的初衷是什么?
為什么這個定時器一定要放到某個列表里?
如果從軟定時器列表 `rt_soft_timer_list` 移除后,不插入任何列表會有什么影響?
因為退出 `rt_soft_timer_check` 函數后,list 列表不復存在了,應該不是退出 `rt_soft_timer_check` 函數后的需求,那么插入 list 和 從 list 取出之間有哪些情況需要我們注意,需要用一個臨時列表將軟定時器暫存?

定時器回調函數里可能發生哪些操作?

  • 修改定時器設置
  • 停止,重啟定時器
  • 刪除定時器
  • 發生中斷,在中斷里執行上述三種操作

修改定時器設置,可能只涉及到定時器的定時時間間隔和定時周期特性。這兩個參數設置需要定時器必須在某個列表中嗎?
停止,重啟定時器,必然導致修改定時器所在列表指針,這里就涉及到雙向列表的操作了。

> 簡短介紹一下雙向列表,

  • rt-thread 使用的雙向列表,每一個節點有一個 prev 和 一個 next 指針,分別指向雙向列表中的前一個節點和后一個節點。
  • 一個空列表 l 僅有一個不含數據的節點,此節點的 prev next 指針均指向它自己。
  • 任何一個帶數據的列表節點必須進行初始化,使得它的 prev next 分別指向它自己,這一點和空列表 l 完全雷同!換句話說,***任何一個雙向列表節點均有作為鏈表的潛質***。從操作上講,你可以定義兩個定時器,然后這兩個定時器之間構建一個含有兩個節點的雙向列表,當然,這種做法沒有多少實用意義。
  • 從鏈表中移除的節點,**必須**使得它的 prev next 指針指向它自己。
  • **無論一個節點是否在某個雙向鏈表中,或者僅僅是一個獨立節點,對它進行刪除操作,效果是完全一樣的!**
  • 更多的操作詳見 rtservice.c 文件中相關函數,`rt_list_init, rt_list_insert_after, rt_list_insert_before, rt_list_remove` 等等。

停止定時器會把當前定時器從定時器列表刪除,無論這個定時器有沒有在某個定時器列表中,或者只是一個獨立的定時器節點,刪除操作的結果都是一樣的,使用 list 這個臨時列表可能不能保護它不被刪除。
重啟定時器會把它先從前一個列表中刪除,然后插入軟定時器列表 `rt_soft_timer_list` 。list 這個臨時列表也阻止不了重啟定時器操作。

**至此,可以看出,`rt_list_t list` 這個臨時列表無任何存在意義**

疑問二 `soft_timer_status` 指示的是什么狀態?

這是一個全局靜態變量,它的使用也很簡單,只在四個地方使用了,上面的源碼函數里有兩處,其它兩個地方分別是初始化聲明

/* soft timer status */
static rt_uint8_t soft_timer_status = RT_SOFT_TIMER_IDLE;

和——以下摘自 `rt_timer_start` 函數

#ifdef RT_USING_TIMER_SOFT
   if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
   {
       /* check whether timer thread is ready */
       if ((soft_timer_status == RT_SOFT_TIMER_IDLE) &&
          ((timer_thread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND))
       {
           /* resume timer thread to check soft timer */
           rt_thread_resume(&timer_thread);
           rt_schedule();
       }
   }
#endif /* RT_USING_TIMER_SOFT */

將這四個地方聯系起來看,意思好像是調用定時器回調函數前修改軟定時器為 busy 狀態,返回回調函數后恢復為 idle 狀態,而如果是在定時器回調函數里調用 `rt_timer_start` ,可以達到不進行任務調度的目的。好像是起了雙保險作用,真是這樣嗎?

我們分析一下上面這段 `rt_timer_start` 函數片段。
首先判斷定時器是不是軟定時器,只有軟定時器啟動時才有進行任務調度的可能。
其次,判斷 `soft_timer_status` 是否空閑,以及軟定時器線程是否***掛起態***。
以上仨條件均滿足,進行任務調度。

我們重點關注“其次”,一個定時器線程調用的定時器回調函數,這個線程會是掛起態嗎?答案是肯定不是。它在運行著,肯定是 `RT_THREAD_RUNNING` 的。那么這個 `soft_timer_status` “雙保險”了嗎?

疑問三 開篇提到的假想

開篇提到了一種假想,假想軟定時器回調函數占用 cpu 時間有點兒長,會產生什么影響,引起什么后果。
討論這個問題仍然離不開 `rt_soft_timer_check` 函數工作原理,我們再梳理一下 `rt_soft_timer_check` 函數的操作。(以下分析忽略 list 以及 soft_timer_status 相關操作)

  • 關中斷
  • 掃描列表是否有節點
  • 取出第一個節點
  • ***獲取當前系統 tick***
  • 檢查定時器是否定時時間到,如果到時
  • 先從軟定時器列表 `rt_soft_timer_list` 移除定時器。非周期定時器,取消激活態
  • 開中斷
  • 執行定時器回調函數。(這里可能存在長時間操作)
  • 關中斷
  • 對于周期性定時器,重啟定時器;非周期定時器,前面已經做了取消激活態操作。
  • 繼續掃描列表,取出第一個節點,***獲取當前系統 tick*** ,檢查定時器是否定時時間到。。。

假設 RT_TICK_PER_SECOND = 1000,有兩個周期性定時器 t0 t1 ,定時間隔不同,同時啟動,各自的定時器回調函數執行時間 t0 500us,t1 5 ms。
經過一段時間以后,總是可能會出現定時間隔公倍數時刻 Tn ,它們倆同時達到定時時間。
如果 t1 先被處理,那么 t1 重啟的時候系統 tick 已經是 Tn + 5;t0 的重啟時間有 50% 的可能是 Tn + 5, 50%的可能是 Tn + 6。
如果 t0 先被處理,t0 的重啟時間有 50% 的可能是 Tn, 50%的可能是 Tn + 1;t1 重啟時間是 Tn + 5 或 Tn + 6。

即便不考慮 t0 不考慮它對外的影響,也不考慮它受到的影響,僅僅分析 t1 自己對自己的影響,也可以看出來,隨著時間的推移,它的定時間隔不是初始設定的 Inv ,而是 Inv + 5。

優化后的 `rt_soft_timer_check` 流程

  • ***獲取當前系統 tick***
  • 關中斷
  • 掃描列表是否有節點
  • 取出第一個節點
  • 檢查定時器是否定時時間到,如果到時
  • 先從軟定時器列表 `rt_soft_timer_list` 移除定時器。
  • 對于周期性定時器,***重啟定時器***;非周期定時器,取消激活態。
  • 開中斷
  • 執行定時器回調函數。(這里可能存在長時間操作)
  • 關中斷
  • 繼續掃描列表,取出第一個節點,檢查定時器是否定時時間到。。。

其中,優化后的重啟定時器不能使用原來的接口。需要使用如下原型函數接口

static rt_err_t _rt_timer_start(rt_timer_t timer, rt_tick_t current_tick)

第二個參數是進入 `rt_soft_timer_check` 函數,第一次關中斷前獲取的當前系統 `tick` 值,無論下面掃描出多少個到達時間的定時器,啟動時間都是同一個 `tick` 值。
而且無論其中某個定時器回調函數執行時間有多長,或者多個回調函數累積執行時間有多長,它們的啟動時間都是相同的!

注:由此,引起的另一個弊端的,期間某個定時器定時時間到了,但是會被判定為未到,下次調用 `rt_soft_timer_check` 時才會被處理。

附測試程序

static void led1_timeout(void *parameter)
{
   rt_tick_t current_tick;
   int pin = rt_pin_read(LED1_PIN); 
   rt_pin_write(LED1_PIN, !pin);
   current_tick = rt_tick_get();
   rt_hw_us_delay(50000);
}
void led_tick_thread(void *parameter)
{
   rt_timer_t led1_timer;
   led1_timer = rt_timer_create("ledtim1", led1_timeout,
                               RT_NULL,  1000,
                               RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
   if (led1_timer != RT_NULL) {
       rt_timer_start(led1_timer);
   }
   while (1) {
       rt_pin_write(LED0_PIN, PIN_HIGH);
       rt_thread_mdelay(500);
       rt_pin_write(LED0_PIN, PIN_LOW);
       rt_thread_mdelay(500);
   }
}

作為對比,兩個 led 一個用軟定時器控制亮滅頻率,一個用 mdelay 延時控制亮滅頻率。
如果 timeout 沒有延遲,兩個燈一直是同步的;有延遲后,過一段時間兩個燈亮滅變不同步了。

總結

肯定有很多人反對說,定時器回調函數不要有長時間操作。發消息,信號,郵箱...交給其它線程操作云云。
軟定時器本身就是一個線程,通過某種技術手段,在這個線程中可以完成的工作,一定要使用消息機制交給另外一下線程完成嗎?

如何抉擇,請君深思

本優化系列所有提到的更改已經提交到 gitee ,歡迎大家測試
https://gitee.com/thewon/rt_thread_repo

相關文章:
rt-thread 優化系列(0) SysTick 優化分析
rt-thread 優化系列(一) 之 過多關中斷
rt-thread 優化系列(二) 之 同步和消息關中斷分析

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 定時器
    +關注

    關注

    23

    文章

    3246

    瀏覽量

    114740
  • 函數
    +關注

    關注

    3

    文章

    4329

    瀏覽量

    62576
  • RT-Thread
    +關注

    關注

    31

    文章

    1285

    瀏覽量

    40094
收藏 人收藏

    評論

    相關推薦

    rt-thread 驅動篇(八)hwtimer 重載算法優化

    區別于 rt-thread 內核實現的兩種定時器,這種定時器依賴芯片內置的定時器外設,依靠穩定高速的晶振實現精確定時,可以實現
    的頭像 發表于 06-23 10:10 ?2998次閱讀
    <b class='flag-5'>rt-thread</b> 驅動篇(八)hwtimer 重載算法<b class='flag-5'>優化</b>

    RT-Thread定時器工作機制以及定時器的管理方式

    當不再需要動態定時器時,可以將其刪除,執行如下函數之后系統會把這個定時器rt_timer_list 鏈表中刪除,然后釋放相應的定時器控制塊占有的內存:
    的頭像 發表于 02-15 10:36 ?1.2w次閱讀
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>定時器</b>工作機制以及<b class='flag-5'>定時器</b>的管理方式

    RT-Thread記錄(四、RTT時鐘節拍和軟件定時器

    RT-Thread第4課,聽聽 RT-Thread 的心跳,再學習一下基于心跳的軟件定時器使用。
    的頭像 發表于 06-20 11:50 ?7764次閱讀
    <b class='flag-5'>RT-Thread</b>記錄(四、RTT時鐘節拍和軟件<b class='flag-5'>定時器</b>)

    【每日一練】RT-Thread Nano-ADC(時鐘管理之軟件定時器)-3軟件定時器內核代碼分析(第十七節學習視頻)

    :全局變量rt_tick自加 B:當前線程執行剩余時間減1C:當前線程執行完畢時間片會引起一次調度D:檢查定時器(hard模式;鏈表)打卡規則詳見:【每日一練】RT-Thread Nano入門全套
    發表于 06-02 10:32

    【每日一練】課程實踐-基于RT-Thread內核定時器的電壓采集

    是基于對何老師課程的理解,做一個使用內核的定時器來周期獲取電壓的采樣值,以這個實踐來鞏固這段時間學習到的RT-Thread的知識,借這篇文章分享課程感悟,幫助更多想學習RT-Thread的小伙伴。二
    發表于 06-24 11:21

    RT-Thread提供了怎樣的定時器操作函數以及如何使用它們呢

    rt_tick 的值表示了系統從啟動到現在共經過的時鐘節拍個數。定時器工作機制RT-Thread 提供的定時器基于系統的節拍,提供了基于節拍整數倍的
    發表于 04-02 11:41

    請問下rt-thread是否有更高定時器對象的實現

    查看rt-thread的源碼和相關文檔,已經了解到了rt-thread定時器的實現原理,如文檔介紹,該定時器的精度并不很高,請問下,r-thread
    發表于 05-09 11:40

    【原創精選】RT-Thread征文精選技術文章合集

    優化系列(零) SysTick 優化分析rt-thread 優化系列(一) 之 過多關中斷
    發表于 07-26 14:56

    RT-Thread軟件定時器怎么獲取定時器的狀態?

    rt-thread 軟件定時器怎么獲取定時器的狀態 比如獲取當前定時器是運行狀態,還是停止狀態 ,自己加flag的方法除外哈
    發表于 01-31 15:19

    請教各位rt-thread軟件定時器大家一般怎么用 ?

    請教各位,rt-thread軟件定時器大家一般怎么用 ?按文檔說明 定時器是在一個單獨的任務里運行,不能在定時器里做會導致延時的操作,所以
    發表于 04-20 16:14

    RT-thread初學

    RT-thread初學線程動態創建線程靜態創建線程鉤子函數定時器獲取系統時間動態創建定時器靜態創建定時器信號量靜態創建與動態創建信號量信號量實例注意線程動態創建線程/*任務回調函數*/
    發表于 12-31 19:45 ?8次下載
    <b class='flag-5'>RT-thread</b>初學

    詳細剖析Linux和RTOS(RT-Thread)的時鐘和定時器的使用

    Linux發燒友1.RTOS篇1.1RT-Thread簡介1.2時鐘管理1.2.1時鐘節拍1.3獲取系統節拍1.4定時器分類1.5定時器源碼分析1.6
    發表于 01-17 09:31 ?4次下載
    詳細剖析Linux和RTOS(<b class='flag-5'>RT-Thread</b>)的時鐘和<b class='flag-5'>定時器</b>的使用

    RT-Thread學習筆記 -- 時鐘管理

    RT-Thread學習筆記 – 時鐘管理軟件硬件定時器區別1.定時器分為硬件定時器和軟件定時器。區別:硬件
    發表于 01-17 09:53 ?1次下載
    <b class='flag-5'>RT-Thread</b>學習筆記 -- 時鐘管理

    RT-Thread驅動篇:hwtimer重載值算法

    區別于 rt-thread 內核實現的兩種定時器,這種定時器依賴芯片內置的定時器外設,依靠穩定高速的晶振實現精確定時,可以實現
    的頭像 發表于 04-01 10:06 ?1771次閱讀

    LPC55S69對接RT-Thread PWM設備框架

    在使用 RT-Thread 的 bsp pwm 的時候,注意到 lpc55sxx 系列只對接了通用定時器2中的通道1作為 PWM 輸出。但其實 LPC55S69 具備非常多的 PWM 資源。于是根據
    的頭像 發表于 10-11 15:02 ?727次閱讀
    主站蜘蛛池模板: 亚洲欧美日本中文子不卡| 麻豆狠色伊人亚洲综合网站| 俄罗斯12x13x处| 99人精品福利在线观看| 3dbdsm变态videos高清| 制服的微热| 在线伦理电影网| 宅男午夜大片又黄又爽大片| 一二三四在线高清中文版免费观看电影 | 超碰免费碰免费视频| 99日韩精品| 99蜜桃在线观看免费视频网站| 538prom精品视频我们不只是| 在线电影一区二区| 岳扒开让我添| 《乳色吐息》无删减版在线观看 | 少男同志freedeos| 呻吟翘臀后进爆白浆| 20岁中国男同志china1069| 久久婷婷国产五月综合色啪最新 | 青青青青青青草| 欧美午夜精品A片一区二区HD| 女人被躁到高潮嗷嗷叫免费| 啪啪漫画无遮挡全彩h同人| 全黄h全肉短篇禁乱np| 色狠狠一区二区| 午夜免费体验30分| 亚洲国产AV精品卡一卡二| 亚洲午夜精品AV无码少妇 | 内射少妇36P九色| 日本韩国欧美一区| 试看做受120秒免费午夜剧场| 偷偷鲁青春草原视频| 亚洲国产精品线在线观看| 亚洲一区综合图区| 最新亚洲中文字幕在线观看| 9国产露脸精品国产麻豆| 大乳牛奶女在线观看| 国产午夜小视频| 久久精品久久精品| 免费网站在线观看国产v片|