孫彥森,劉金棟,王喜龍,劉雨霞,張 靜
(濰柴動力股份有限公司,山東濰坊 261061)
嵌入式軟件開發(fā)過程中會用到大量的定時操作,往往通過操作系統(tǒng)提供的原生定時服務進行。例如,Linux操作系統(tǒng)提供了alarm、settimer 等系統(tǒng)調用用作定時處理[1-2],這些定時器都建立在信號觸發(fā)和處理的基礎上,有定時器個數(shù)和信號的限制,同時多個操作系統(tǒng)間的代碼移植性較差,在實際應用中存在一定的局限性。因此,需要一種可以擺脫信號處理的束縛,且能夠在多個操作系統(tǒng)間進行代碼移植的定時器。
近年來,許多國內外機構和學者對定時器進行了相關研究。在不同的應用領域,定時器有著不同的應用場景,發(fā)揮著不同的作用。在電信應用領域,電子科技大學的周鵬[3]、李群[4]針對電信級Linux 強實時性的要求,設計并實現(xiàn)了新型高精度定時器,從Linux 內核的角度提高了定時精度;中國科學技術大學喻詩祥[5]設計了一種基于多核平臺的用戶態(tài)定時器,提出了一種共享內存策略,在保證定時器系統(tǒng)定時精度及誤差的情況下,可減少系統(tǒng)性能損耗。在核聚變研究領域,東華大學的代路偉[6]結合EAST托卡馬克中央定時系統(tǒng)的時間參數(shù)等因素,在分析積分器設備所需的功能基礎上,搭建了基于ARM處理器的硬件平臺,并實現(xiàn)了長時間高精度定時器。在雷達研究領域,南京電子技術研究所的柯小路等[7]以現(xiàn)場可編程門陣列為平臺,提出一種通用軟件化定時設計方法,可提升設計和調試效率;針對雷達定時控制,也有眾多科研人員進行了相關定時研究并取得了一定成果[8-11]。在汽車電子領域,結合車載操作系統(tǒng),定時器主要應用于車載智能終端開發(fā)[12-14]、車載信息娛樂系統(tǒng)開發(fā)[15]、車載網(wǎng)絡實時管理等方面[16-18],具有廣泛的應用。
針對汽車電子領域的車載智能終端,利用軟件模塊化設計思想,本文設計了一種通用定時器,可以作為嵌入式軟件應用設計中一個模塊,為系統(tǒng)中其他軟件應用模塊提供定時功能。本方案為一種相對定時器方案,其優(yōu)點是可動態(tài)設定定時器數(shù)量和精度、支持在多個操作系統(tǒng)間進行移植、支持同時給其他多個模塊提供定時功能。
定時器模塊提供一個相對定時器,當其他模塊需要使用定時器時,會調用設置接口設置定時器。定時器掃描模塊會先查詢是否有可用的定時器,如果有,則為該模塊分配一個定時器。當定時器到時時,定時器掃描模塊會以消息的形式發(fā)送給使用該定時器的模塊,通知定時器已經(jīng)到時。定時器到時后,該定時器就會被刪除,及時釋放所占用的定時器資源,進而分配給其他模塊調用。軟件定時器服務方式如圖1 所示。
圖1 軟件定時器服務方式
定時器模塊通過維護一個時鐘滴答數(shù)組實現(xiàn)定時。
1.2.1 時鐘滴答數(shù)組
定時器模塊維護一個時鐘滴答數(shù)組,設置數(shù)組大小為TIMER_MAX_TMCB_NUM,可靈活配置,本文取值30 000;數(shù)組中每一個組員代表一個時間段TIMER_SLEEP,可靈活配置,本文取值10 ms。數(shù)組中各個成員的位置按序為0、1、2、…、TIMER_MAX_TMCB_NUM-1,本文對應0~29 999。設置一個游標wScanPos 指向數(shù)組中的某個組員,代表當前的時間,每走過10 ms,游標會向前移動一個數(shù)組成員。例如,要設定一個30 ms 定時,游標需要前移3 個組員時,定時器才會超時,此時定時器模塊會向需要定時的模塊發(fā)送消息,告知已經(jīng)到時。定時器就是掛載到該時刻的數(shù)組成員下,當游標移動到該數(shù)組成員時,就代表了該定時器到時。
如圖2 所示,設定一個30 ms 的定時器。黃色代表當前游標的位置7,藍色代表30 ms到時時的游標的位置9,即定時器需掛載到位置9 處。注意,當前游標所在的位置7 也需包含在內。這是因為游標開始時指向位置7,位置7 所代表的10 ms時間段并沒有走過。
定時器模塊每隔10 ms 移動一下游標,然后判斷該游標下是否有定時器;如果有定時器,判斷定時器是否已經(jīng)到時;如果到時,就向使用該定時器的模塊發(fā)送消息,通知該定時器已經(jīng)到時。因為該游標所指的位置處可能不只一個定時器,定時器模塊會將該游標下的到時的定時器通知使用這些定時器的模塊。如圖2 所示,位置9 處同時掛載了定時器1 和定時器2 兩個定時器。
圖2 時鐘滴答數(shù)組
由于定時器要掛載在時鐘滴答數(shù)組成員下,有可能某個數(shù)組成員下有多個定時器。為解決這個問題,采用雙向鏈表的方式將定時器串聯(lián)起來,每次申請定時器時,都會將這個新定時器放到鏈表頭,然后重新組合鏈表。每個定時器都有兩個變量wPreNode 和wNextNode,分別表示在雙向鏈表中前一個定時器和后一個定時器。時鐘滴答數(shù)組成員就指向這個雙向鏈表的表頭。
對于大于10 ms×30 000 的定時時間,數(shù)組會回到開頭循環(huán)使用。在設置定時器時,需預先確定定時器在10 ms時鐘滴答數(shù)組中到時的位置,定時時間大于10 ms×30 000 的,需要計算該定時器在10 ms數(shù)組上經(jīng)過多少次循環(huán)才會到時。例如,設置一個定時器a,定時時間為10 ×60 000 ms,當前游標的位置在10 ms 數(shù)組的200位置,那么計算定時器a的過程如下:
dwDly =10 ×60 000/10
dwTime =200 +dwDly -1
dwTimerCounter =(dwDly-1)/30 000
wPos =dwTime%30 000
式中:dwDly為游標移動次數(shù),計算值為60 000;dwTime為定時器在滴答數(shù)組中對應的位置,計算值為60 199;dwTimerCounter為游標循環(huán)次數(shù),計算值為1;wPos為定時器到時對應的游標位置,計算值為199。
由此可知,定時器a 在10 ms 數(shù)組中對應掛載位置為199,游標需要第二次移動到位置199,定時器才到時。
1.2.2 定時器節(jié)點存儲區(qū)
定時器節(jié)點存儲區(qū)里面存儲著定時器,包括空閑的定時器和正在使用的定時器,該存儲區(qū)就是一個定時器的數(shù)組。定時器節(jié)點存儲區(qū)示意圖如圖3 所示,這些定時器的編號按序為0、1、2、…、(TIMER_MAX_ TIMERS-1)。其中,TIMER_ MAX_ TIMERS 代表定時器的最大數(shù)量。
圖3 定時器節(jié)點存儲區(qū)
1.2.3 定時器空閑節(jié)點表
定時器空閑節(jié)點表用來記錄空閑的定時器在定時器共享內存中的位置,它是一個數(shù)組,大小為(TIMER_MAX_ TIMERS +1)??臻e節(jié)點表有個兩個游標wFreeHead、wFreeTail,分別指示空閑節(jié)點表中的開始位置和空閑節(jié)點表結束位置加1。
定時器模塊剛啟動時,沒有定時器被申請,空閑節(jié)點表中記錄的空閑定時器就是定時器共享內存的中的所有定時器??臻e節(jié)點表數(shù)組中按序記錄著空閑定時器在定時器共享內存中的位置0、1、2、…、(TIMER_MAX_TIMERS-1)。wFreeHead指向空閑節(jié)點表的數(shù)組成員0,wFreeTail指向空閑節(jié)點表的數(shù)組成員TIMER_ MAX_TIMERS。當其他模塊申請設置定時器時,定時器模塊將空閑節(jié)點表中第wFreeHead 個數(shù)組成員所指向的定時器分配給該模塊,然后wFreeHead加1;當其他模塊釋放定時器時,空閑節(jié)點的第wFreeTail個成員記錄該釋放的定時器的序號(即該定時器在定時器共享內存中的位置),然后wFreeTail加1。
如圖4 所示,假設定時器0、1 被申請占用,wFreeHead將移到位置3,即虛線處。當wFreeHead 或wFreeTail大于TIMER_ MAX_ TIMERS 時,會將其置0,重新回到起始位置,即這兩個游標在空閑節(jié)點表上是循環(huán)移動的。
圖4 定時器空閑節(jié)點表
定時器軟件整體上就是一個掃描時鐘滴答數(shù)組的過程。該時鐘滴答數(shù)組各個成員下掛載著定時器,游標在時鐘滴答數(shù)組中移動,當游標移動到這個數(shù)組的某個成員時,查詢該成員下是否有定時器、定時器是否到時,如果到時,就發(fā)送消息給使用該定時器的模塊,告知它使用的定時器已經(jīng)到時,同時釋放該定時器,修改空閑節(jié)點表;否則,游標移動到下一個數(shù)組成員。當某個時鐘滴答數(shù)組成員下有多個定時器時,會逐一判斷這些定時器是否到時。
游標每隔一段時間TIMER_SLEEP 向前移動一次,TIMER_SLEEP時間大小可靈活設置的,通過延時函數(shù)來實現(xiàn)。例如,在Linux系統(tǒng)中可以通過usleep()函數(shù)實現(xiàn)。假設游標每走一步的時間為10 ms,則TIMER_SLEEP為10,函數(shù)設置為usleep(10 ×1 000)。如果設置一個50 ms的定時器,則需要移動5 次定時器才會到時。定時器掃描處理流程如圖5 所示。
圖5 定時器掃描處理流程
在定時器模塊中申請一個定時器,若有可分配的定時器模塊,則根據(jù)定時器定時的時間,確定定時器在時鐘滴答數(shù)組中的掛載位置。若無,則告知無法設置定時器。
申請一個定時器時,首先需查詢定時器空閑節(jié)點表,是否有空閑的定時器。若游標wFreeHead 等于游標wFreeTail,則表示沒有空閑的定時器,無法申請一個定時器;否則,將空閑節(jié)點表成員wFreeHead 所指的定時器分配給該模塊,并在該定時器上標記已被使用,同時游標wFreeHead 加1。然后,根據(jù)定時器的定時時間dwTime和當前時鐘滴答數(shù)組的游標wScanPos,計算定時器到時時游標所在的位置,并將該定時器掛載在那個時鐘數(shù)組成員下,重新組合該時鐘滴答數(shù)組成員下的定時器鏈表。
計算定時器掛載位置的計算公式如下:
式中:dwTime 為要設定的定時時長;TIMER_SLEEP 為延時時長(休眠間隔);dwDly為游標移動次數(shù)。
式中:wScanPos 為定時器在滴答數(shù)組中當前位置;dwTime為定時器到時時在滴答數(shù)組中對應的計算位置。
式中:TIMER_MAX為時鐘滴答數(shù)組的大?。籨wTimerCounter為游標循環(huán)次數(shù)。
式中:wPos為定時器掛載在時鐘滴答數(shù)組的實際位置。
由此可知,游標第(dwTimerCounter +1)次經(jīng)過wPos位置時,該定時器到時。設置定時器的流程如圖6所示。
圖6 定時器設置處理流程
刪除正在使用的定時器,并釋放定時器資源,該定時器資源就可以分配給其他模塊使用。
當刪除一個定時器時,在該定時器上標記為已空閑,同時將空閑節(jié)點表成員wFreeTail 指向該定時器,并將游標wFreeTail加1,然后在該成員下的定時器鏈表中刪除該定時器,重新組合鏈表。刪除定時器的流程如圖7所示。
圖7 定時器刪除處理流程
在Linux操作系統(tǒng)無其他任務運行的情況下,設置不同的定時時長和延時時長(休眠間隔),并對比分析定時誤差,發(fā)現(xiàn)誤差均在50 ms 以內,滿足一般要求下的定時誤差要求。需要說明的是,在不同的操作系統(tǒng)、不同的負載運行情況下,測試結果可能不同。定時器精度測試結果如表1 所示。在定時時長為1 000 ms,延時時長分別為10、20、30 ms時,定時誤差均在40 ms 以內;在定時時長為2 000 ms,延時時長分別為20、30、50 ms 時,定時誤差均在50 ms以內;在定時時長為3 000 ms,延時時長分別為20、30、50 ms時,定時誤差均在40 ms以內。
可根據(jù)具體應用場景對定時時長的要求,結合系統(tǒng)處理能力以及負載情況,通過提前測試確認合適的延時時長,使得定時誤差最小,確保定時器的性能達到最佳。
基于汽車電子領域的車載智能終端,針對一般秒級應用場景,應用軟件模塊化設計思想,本文提出了一種通用定時器方案,并進行了編碼實現(xiàn)和精度測試。定時器模塊作為嵌入式軟件應用設計中的一個單獨模塊,可以為系統(tǒng)中其他軟件應用模塊提供定時功能。
本方案為一種相對定時器方案,可以動態(tài)設定定時器數(shù)量和精度,支持在多個操作系統(tǒng)間進行移植,支持同時給其他多個模塊提供定時功能。經(jīng)過測試和分析,所設計的定時器滿足一般應用場景下的定時器誤差要求。同時,可以根據(jù)系統(tǒng)處理能力以及負載情況,對定時精度進行優(yōu)化和調整,使得定時器模塊達到最佳性能。