Linux下一種高效多定時器實現

Linux下一種高效多定時器實現

做者:LouisozZlinux

日期:2018.08.29編程

運行環境說明

因爲在 Linux 系統下一個進程只能設置一個時鐘定時器,因此當應用須要有多個定時器來共同管理程序運行時,就須要自行實現多定時器管理。數據結構

本文就是基於這種需求,在實際編碼工做的基礎上總結而出,但願跟你們共享,也歡迎你們討論指正。多線程

多定時器原理

在一個進程只能有一個定時器的前提條件下,想要實現多定時器,就得用到那個進程能利用的惟一的定時器,這個定時器是由操做系統提供的,經過系統提供的接口來設置,經常使用的有 alarm() 和 setitimer(),不論用什麼,後文統一稱做系統定時接口,這兩個接口的區別在不少博客裏都有,不怎麼清楚的能夠自行搜索,這裏就再也不贅述(我比較懶,打字多對腎很差)。經過它們產生的定時信號做爲基準時間,來管理實現多定時器。函數

舉個栗子,糖炒板栗。利用系統定時接口設置了基準定時器,基準定時器每秒產生一個 SIGALRM 信號(系統時鐘的超時時間到了以後會向進程發送信號以通知定時超時,alarm() 和 setitimer() 都是向進程發送 SIGALRM 信號,關於 Linux ‘信號’ 的內容,能夠參考 《UNIX環境高級編程》),產生兩個 SIGALRM 信號的時間間隔,就是多定時器的基準時間。固然,上述的基準時間是一秒,若是你是每隔 50ms 產生一個 SIGALRM 信號,那麼多定時器的基準時間就是 50ms 。當有了基準時間以後,就能夠對它進行管理,能夠設置多個定時任務,現有兩個定時任務,Timer1_Task , Timer2_Task, 其中 Timer1_Task 的定時時長爲 10 個基準時間,Timer2_Task 爲 15 個基準時間,則每產生 10 個 SIGALRM 信號,就表示 Timer1_Task 定時器超時到達,執行一次 Timer1_Task 的超時任務,每產生 15 個 SIGALRM 信號,則執行一次 Timer2_Task 超時任務,當產生的 SIGALRM 信號個數是 30 (10 和 15 的最小公倍數),則 Timer1_Task 和 Timer2_Task 的超時任務都要被執行。ui

好了,原理講完了,下面就是本文的重點了。this

————————————— 說重點專用閹割線 —————————————編碼

————————————— 說重點專用分割線 —————————————spa

高效多定時器

數據結構

由一個全局鏈表 g_pTimeoutCheckListHead 來管理超時任務。鏈表的每一個節點是一個 tMultiTimer 結構體:操作系統

typedef void TimeoutCallBack(void*); //回調函數格式 typedef struct tMultiTimer { uint8_t nTimerID; //設置宏定義 uint32_t nInterval; //定時時長 uint32_t nTimeStamp; //時間戳 bool bIsSingleUse; //是否單次使用 bool bIsOverflow; //用於解決計數溢出問題 TimeoutCallBack *pTimeoutCallbackfunction; //回調函數 void* pTimeoutCallbackParameter; //回調函數參數 struct tMultiTimer* pNextTimer; //雙向鏈表後驅指針 struct tMultiTimer* pPreTimer; //雙向鏈表前驅指針 struct tMultiTimer* pNextHandle; //二維鏈表相同超時Timer節點 }tMultiTimer; tMultiTimer* g_pTimeoutCheckListHead; //管理多定時器的全局鏈表 bool g_bIs_g_nAbsoluteTimeOverFlow; //基準時間計次器溢出標誌位 uint32_t g_nAbsoluteTime; //基準時間計次器
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

(各個成員變量的意義在後文會逐一介紹,客官莫急)

這個是一個二維雙向鏈表,第一維根據時間戳,即絕對時間,按照前後順序鏈接每個 tMultiTimer 節點,當有多個超時任務的超時時刻是相同的時候,只有一個節點位於第一維,其他接在上一個相同超時時刻 tMultiTimer 節點的 pNextHandle 上,圖示以下:
這裏寫圖片描述

多定時管理流程

超時檢測與運行

首先須要調用系統定時接口,設置進程的定時器,產生 SIGALRM 信號,每一次 SIGALRM 到來時,全局的基準時間計次器 g_nAbsoluteTime 自加,因爲 g_nAbsoluteTime 是無符號類型,當其溢出時,是回到 0 ,每次溢出就把 g_bIs_g_nAbsoluteTimeOverFlow 取反。

每一個  tMultiTimer 節點都有 :

    uint32_t nInterval;             //定時時長
    uint32_t nTimeStamp;        //時間戳
  • 1
  • 2
  • 3
  • 4

其中 nTimeStamp 這個值,是由 nInterval + g_nAbsoluteTime 計算而來,在把這個節點加入到全局鏈表的時刻計算的 ,這個和做爲超時的絕對時間保存在結構體中,當計算的和溢出時,bIsOverflow 取反。經過這兩個溢出標誌位,能夠用來解決溢出以後判斷是否超時的問題,具體以下:

每一次基準時間超時,就檢查鏈表的第一個節點的超時時間 nTimeStamp 是否小於全局絕對時間 g_nAbsoluteTime ,若是 g_bIs_g_nAbsoluteTimeOverFlow 與 bIsOverflow 不相等,則鏈表第一個節點的超時時間必定未到達,由於 bIsOverflow 的取反操做必定是先於 g_bIs_g_nAbsoluteTimeOverFlow ,若是同樣則比較數值大小(初始化的時候兩個溢出標誌位是同樣的)。當全局絕對時間大於等於第一個節點的時間戳,則把該節點及其 pNextHandle 指向的第二維鏈表取下,並更新 g_pTimeoutCheckListHead,而後依次執行所取下鏈表的回調函數。執行完以後(或者以前,根據實際狀況定),判斷 bIsSingleUse 成員變量,若是爲 true 則表示是單次的計數器,僅執行一次,執行完回調以後則定時任務完成。若是是 false ,怎表示是定時任務,則從新執行一次添加超時任務。(添加超時任務看下一節)

添加超時任務

添加超時任務(添加一個 tMultiTimer 節點到全局鏈表 g_pTimeoutCheckListHead 中)的時候,指定超時時長,即間隔多少個基準時間,賦值給這個任務的成員變量 nInterval ,而後計算

nTimeStamp = nInterval  + g_nAbsoluteTime; 
    if(nTimeStamp < g_nAbsoluteTime) bIsOverflow = ~bIsOverflow;
  • 1
  • 2
  • 3

接着搜索 g_pTimeoutCheckListHead ,若是有相同時間戳,則添加到其 pNextHandle 指向位置,若是沒有相同時間戳節點,找到比要插入的節點時間戳大的節點,而後把當前節點插入到其前方。

對於鏈表中已經有相同 ID 的 tMultiTimer 節點的狀況,再次添加則表示更新該定時任務,取消以前的定時任務從新插入到鏈表中。

取消超時任務

直接把對應 ID 的 tMultiTimer 節點從 g_pTimeoutCheckListHead 鏈表中摘掉便可。

效率分析

g_pTimeoutCheckListHead 鏈表中的 tMultiTimer 節點數量,是總共設置的超時任務數量,假設爲 n,添加一個超時任務(節點)的最壞狀況是遍歷 n 個節點。

檢查是否有任務超時所用的時間是常數時間,只檢查第一個節點。

對於定時任務的再次插入問題,若是定時任務間隔時間越短,其反覆被插入的次數越多,可是因爲定時時間短,因此在鏈表中的插入位置也就越靠前,將快速找到插入點;若是定時任務間隔時間越長,越可能遍歷整個鏈表在末尾插入,可是因爲間隔時間長,重複插入的頻率則很低。

與一種簡單的定時器實現相比較:

if(g_nAbsoluteTime % 4) { Timer_Task_1(); } if(g_nAbsoluteTime % 17) { Timer_Task_2(); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

這種簡單實現來講,

1:每次添加、取消一個定時任務都須要修改定時器源碼,複用性不高。

2:每次檢查是否有任務超時須要遍歷 n 個定時任務。

關於多線程下的一些坑

我在實際項目中使用的環境是多線程的,有四個,我把多定時器管理放在了單獨的一個線程裏。因爲系統定時器接口產生的信號是發送給進程的,因此全部的線程都共享這個鬧鐘信號。一開始我是這麼想的,定時器的默認動做是殺死進程,那麼給每一個線程添加信號捕捉函數,這樣的話鬧鐘信號到了以後不論是那個線程接管了,都能到我指定的處理函數去,但是實際狀況並不是如此,進程仍然會被殺死。

後面我用了線程信號屏蔽,把非定時器線程都設置了信號屏蔽字,即鬧鐘信號不被別的線程可見,這樣才能正常運行,至於第一種方法爲什麼不行,如今我尚未找到緣由,仍是對 Linux 的信號機制不熟,之後看有時間的話把這裏搞懂吧。

main.c

//配置信號集 sigset_t g_sigset_mask; sigemptyset(&g_sigset_mask); sigaddset(&g_sigset_mask,SIGALRM);
  • 1
  • 2
  • 3
  • 4

other_thread.c

sigset_t old_sig_mask;
    if(err = pthread_sigmask(SIG_SETMASK,&g_sigset_mask,&old_sig_mask) != 0) { // pthread_sigmask 設置信號屏蔽 return ; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

mulitimer_thread

void* MultiTimer_thread(void *parameter) { int err,signo; struct itimerval new_time_value,old_time_value; new_time_value.it_interval.tv_sec = 0; new_time_value.it_interval.tv_usec = 1000; new_time_value.it_value.tv_sec = 0; new_time_value.it_value.tv_usec = 1; setitimer(ITIMER_REAL, &new_time_value,NULL); for(;;) { err = sigwait(&g_sigset_mask,&signo);//信號捕捉 if(err != 0) { return ; } if(signo == SIGALRM) { SYSTimeoutHandler(signo); } } return ((void*)0); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

multiTimer.c

#include "multiTimer.h" /** * @function 把一個定時任務添加到定時檢測鏈表中 * @parameter 一個定時器對象,能夠由全局變量 g_aSPPMultiTimer 經過 TIMER_ID 映射獲得 */ static void AddTimerToCheckList(tMultiTimer* pTimer) { tMultiTimer* pEarliestTimer = NULL; tMultiTimer* pEarliestTimer_pre = NULL; CDebugAssert(pTimer->nInterval != 0); pTimer->nTimeStamp = g_nAbsoluteTime + pTimer->nInterval; if(pTimer->nTimeStamp < g_nAbsoluteTime) pTimer->bIsOverflow = !(pTimer->bIsOverflow); if(g_pTimeoutCheckListHead == NULL) { g_pTimeoutCheckListHead = pTimer; g_pTimeoutCheckListHead->pNextTimer = NULL; g_pTimeoutCheckListHead->pPreTimer = NULL; g_pTimeoutCheckListHead->pNextHandle = NULL; return; } else { pEarliestTimer = g_pTimeoutCheckListHead; while(pEarliestTimer != NULL) { //若是超時時間小於新加的timer則直接跳過; if((pEarliestTimer->bIsOverflow != pTimer->bIsOverflow) || (pEarliestTimer->nTimeStamp < pTimer->nTimeStamp)) { pEarliestTimer_pre = pEarliestTimer; pEarliestTimer = pEarliestTimer->pNextTimer; } else { if(pEarliestTimer->nTimeStamp == pTimer->nTimeStamp) //超時時刻相等,直接添加到相同時刻處理列表的列表頭 { pTimer->pNextHandle = pEarliestTimer->pNextHandle; pEarliestTimer->pNextHandle = pTimer; return; } else //找到了超時時刻大於新加入timer的第一個節點 { if(pEarliestTimer->pPreTimer == NULL) //新加入的是最先到達超時時刻的,添加到鏈表頭 { pEarliestTimer->pPreTimer = pTimer; pTimer->pNextTimer = pEarliestTimer; pTimer->pPreTimer = NULL; pTimer->pNextHandle = NULL; g_pTimeoutCheckListHead = pTimer; return; } else //中間節點 { pEarliestTimer->pPreTimer->pNextTimer = pTimer; pTimer->pNextTimer = pEarliestTimer; pTimer->pPreTimer = pEarliestTimer->pPreTimer; pEarliestTimer->pPreTimer = pTimer; pTimer->pNextHandle = NULL; return; } } } } if(pEarliestTimer == NULL) //新加入的timer超時時間是最晚的那個 { pEarliestTimer_pre->pNextTimer = pTimer; pTimer->pPreTimer = pEarliestTimer_pre; pTimer->pNextTimer = NULL; pTimer->pNextHandle = NULL; } return; } } /** * @function 設置一個定時任務,指定超時間隔與回調函數,當超時到來,自動執行回調 * @parameter1 TIMER_ID * @parameter2 超時間隔時間 * @parameter3 是不是一次性定時任務 * @parameter4 回調函數,注意,回調函數的函數形式 void function(void*); * @parameter5 void* 回調函數的參數,建議用結構體強轉成 void*,在回調函數中再強轉回來 * @return 錯誤碼 */ uint8_t SetTimer(uint8_t nTimerID,uint32_t nInterval,bool bIsSingleUse,TimeoutCallBack* pCallBackFunction,void* pCallBackParameter) { printf("\nset timer %d\n",nTimerID); tMultiTimer* pChoosedTimer = NULL; pChoosedTimer = g_aSPPMultiTimer[nTimerID]; pChoosedTimer->nInterval = nInterval; pChoosedTimer->bIsSingleUse = bIsSingleUse; pChoosedTimer->pTimeoutCallbackfunction = pCallBackFunction; pChoosedTimer->pTimeoutCallbackParameter = pCallBackParameter; //若是超時任務鏈表中已經有這個任務了,先取消,而後再設置,即重置超時任務 if(pChoosedTimer->pNextTimer != NULL || pChoosedTimer->pPreTimer != NULL) CancelTimerTask(nTimerID,CANCEL_MODE_IMMEDIATELY); AddTimerToCheckList(pChoosedTimer); return 0; } /** * @function 取消超時檢測鏈表中的指定超時任務 * @parameter1 要取消的超時任務的ID * @parameter2 模式選擇,是當即取消,仍是下次執行後取消 * @return 錯誤碼 */ uint8_t CancelTimerTask(uint8_t nTimerID,uint8_t nCancelMode) { printf("\ncancle timer %d\n",nTimerID); tMultiTimer* pEarliestTimer = NULL; tMultiTimer* pHandleTimer = NULL; tMultiTimer* pHandleTimer_pre = NULL; tMultiTimer* pChoosedTimer = NULL; pEarliestTimer = g_pTimeoutCheckListHead; pChoosedTimer = g_aSPPMultiTimer[nTimerID]; if(nCancelMode == CANCEL_MODE_IMMEDIATELY) { while(pEarliestTimer != NULL) { pHandleTimer = pEarliestTimer; pHandleTimer_pre = NULL; while(pHandleTimer != NULL) { if(pHandleTimer->nTimerID == nTimerID) { if(pHandleTimer_pre == NULL) { if(pHandleTimer->pNextHandle != NULL) { pEarliestTimer = pHandleTimer->pNextHandle; pEarliestTimer->pPreTimer = pHandleTimer->pPreTimer; if(pHandleTimer->pPreTimer != NULL) pHandleTimer->pPreTimer->pNextTimer = pEarliestTimer; pEarliestTimer->pNextTimer = pHandleTimer->pNextTimer; if(pHandleTimer->pNextTimer != NULL) pHandleTimer->pNextTimer->pPreTimer = pEarliestTimer; pHandleTimer->pNextTimer = NULL; pHandleTimer->pPreTimer = NULL; pHandleTimer->pNextHandle = NULL; } else { if(pEarliestTimer->pPreTimer == NULL) { g_pTimeoutCheckListHead = pEarliestTimer->pNextTimer; g_pTimeoutCheckListHead->pPreTimer = NULL; pEarliestTimer->pNextTimer = NULL; } else if(pEarliestTimer->pNextTimer == NULL) { pEarliestTimer->pPreTimer->pNextTimer = NULL; pEarliestTimer->pPreTimer = NULL; } else { pEarliestTimer->pPreTimer->pNextTimer = pEarliestTimer->pNextTimer; pEarliestTimer->pNextTimer->pPreTimer = pEarliestTimer->pPreTimer; pEarliestTimer->pPreTimer = NULL; pEarliestTimer->pNextTimer = NULL; } } } else { pHandleTimer_pre->pNextHandle = pHandleTimer->pNextHandle; pHandleTimer->pNextHandle = NULL; } return 0; } else { pHandleTimer_pre = pHandleTimer; pHandleTimer = pHandleTimer_pre->pNextHandle; } } pEarliestTimer = pEarliestTimer->pNextTimer; } #ifdef DEBUG_PRINTF printf("\nThere is no this timer task!\n"); #endif return 2; //出錯,超時檢測鏈表中沒有這個超時任務 } else if(nCancelMode == CANCEL_MODE_AFTER_NEXT_TIMEOUT) { pChoosedTimer->bIsSingleUse = true; return 0; } else { return 1; //出錯,模式錯誤,不認識該模式 } } /** * @function 定時器處理函數,用於檢測是否有定時任務超時,若是有則調用該定時任務的回調函數,並更新超時檢測鏈表 * 更新動做:若是超時的那個定時任務不是一次性的,則將新的節點加入到檢測超時鏈表中,不然直接刪掉該節點; * @parameter * @return */ void SYSTimeoutHandler(int signo) { //printf("\nenter SYSTimeoutHandler\n"); if(signo != SIGALRM) return; tMultiTimer* pEarliestTimer = NULL; tMultiTimer* pWaitingToHandle = NULL; tMultiTimer* pEarliestTimerPreHandle = NULL; if(g_pTimeoutCheckListHead != NULL) { if((g_pTimeoutCheckListHead->nTimeStamp <= g_nAbsoluteTime) && (g_pTimeoutCheckListHead->bIsOverflow == g_bIs_g_nAbsoluteTimeOverFlow)) { pWaitingToHandle = g_pTimeoutCheckListHead; g_pTimeoutCheckListHead = g_pTimeoutCheckListHead->pNextTimer; if(g_pTimeoutCheckListHead != NULL) g_pTimeoutCheckListHead->pPreTimer = NULL; pWaitingToHandle->pNextTimer = NULL; pEarliestTimer = pWaitingToHandle; while(pEarliestTimer != NULL) { pEarliestTimerPreHandle = pEarliestTimer; pEarliestTimer = pEarliestTimer->pNextHandle; pEarliestTimerPreHandle->pNextHandle = NULL; pEarliestTimerPreHandle->pNextTimer = NULL; pEarliestTimerPreHandle->pPreTimer = NULL; pEarliestTimerPreHandle->pTimeoutCallbackfunction(pEarliestTimerPreHandle->pTimeoutCallbackParameter); if(!(pEarliestTimerPreHandle->bIsSingleUse)) AddTimerToCheckList(pEarliestTimerPreHandle); } } } g_nAbsoluteTime++; if(g_nAbsoluteTime == 0) g_bIs_g_nAbsoluteTimeOverFlow = !g_bIs_g_nAbsoluteTimeOverFlow; return ; } void CancleAllTimerTask() { tMultiTimer* pEarliestTimer = NULL; tMultiTimer* pHandleTimer = NULL; while(g_pTimeoutCheckListHead != NULL) { pEarliestTimer = g_pTimeoutCheckListHead; g_pTimeoutCheckListHead = g_pTimeoutCheckListHead->pNextTimer; while(pEarliestTimer != NULL) { pHandleTimer = pEarliestTimer; pEarliestTimer = pEarliestTimer->pNextHandle; pHandleTimer->pNextHandle = NULL; pHandleTimer->pNextTimer = NULL; pHandleTimer->pPreTimer = NULL; pHandleTimer->bIsOverflow = false; } } g_bIs_g_nAbsoluteTimeOverFlow = false; g_nAbsoluteTime = 0; return; } void MultiTimerInit() { g_pTimeoutCheckListHead = NULL; g_bIs_g_nAbsoluteTimeOverFlow = false; g_nAbsoluteTime = 0; for(uint8_t index = 0; index < MAX_TIMER_UPPER_LIMIT; index++) { g_aSPPMultiTimer[index] = (tMultiTimer*)CMALLOC(sizeof(tMultiTimer)); g_aSPPMultiTimer[index]->nTimerID = g_aTimerID[index]; g_aSPPMultiTimer[index]->nInterval = g_aDefaultTimeout[index]; g_aSPPMultiTimer[index]->nTimeStamp = 0; g_aSPPMultiTimer[index]->bIsSingleUse = true; g_aSPPMultiTimer[index]->bIsOverflow = false; g_aSPPMultiTimer[index]->pTimeoutCallbackfunction = NULL; g_aSPPMultiTimer[index]->pTimeoutCallbackParameter = NULL; g_aSPPMultiTimer[index]->pNextTimer = NULL; g_aSPPMultiTimer[index]->pPreTimer = NULL; g_aSPPMultiTimer[index]->pNextHandle = NULL; } /* 若是預先規定了一些定時器,這個時候能夠初始化除時間戳之外的其餘值 */ //開啓應答超時任務 //OPEN_MULTITIMER_MANGMENT(); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294

multiTimer.h

#ifndef __MULTITIMER_H__ #define __MULTITIMER_H__ #define MAX_TIMER_UPPER_LIMIT 6 #define TIMER_0 0 #define TIMER_1 1 //timer ID #define TIMER_2 2 #define TIMER_3 3 #define TIMER_4 4 #define TIMER_5 5 #define CANCEL_MODE_IMMEDIATELY 0xf9 #define CANCEL_MODE_AFTER_NEXT_TIMEOUT 0x9f typedef void TimeoutCallBack(void*); //======================================================== // timer結構定義 //======================================================== typedef struct tMultiTimer { uint8_t nTimerID; // uint32_t nInterval; //定時時長 uint32_t nTimeStamp; //時間戳 bool bIsSingleUse; //是否單次使用 bool bIsOverflow; //用於解決計數溢出問題 TimeoutCallBack *pTimeoutCallbackfunction; void* pTimeoutCallbackParameter; //雙向鏈表指針 struct tMultiTimer* pNextTimer; struct tMultiTimer* pPreTimer; //相同時間戳的下一個處理函數 這裏可能會有隱藏的 bug,若是基礎時間中斷比較快,那麼可能在處理多個同一時間節點的 //回調函數的時候被下一次的中斷打斷,這裏會引發時序錯誤, //解決方案有三種, //一是能夠人爲避免,不設置有公約數的定時時間,這樣的話同一個時刻有多個定時任務的狀況就小不少; //二是回調函數儘可能少作事,快速退出定時處理函數; //三是另開一個線程,這個線程僅把回調函數放到一個隊列中,另外一個線程持續從隊列中取回調函數執行,這個是沒有問題的方案,可是須要支持多線程或者多任務,而且須要注意加鎖 struct tMultiTimer* pNextHandle; }tMultiTimer; //======================================================== // 實現多定時任務的相關變量 //======================================================== tMultiTimer* g_pTimeoutCheckListHead; bool g_bIs_g_nAbsoluteTimeOverFlow; uint32_t g_nAbsoluteTime; //======================================================== // 外部接口 //======================================================== void MultiTimerInit(); uint8_t SetTimer(uint8_t nTimerID,uint32_t nInterval,bool bIsSingleUse,TimeoutCallBack* pCallBackFunction,void* pCallBackParameter); uint8_t CancelTimerTask(uint8_t nTimerID,uint8_t nCancelMode); void CancleAllTimerTask(); void SYSTimeoutHandler(int signo); #endif
相關文章
相關標籤/搜索