摘要:本文帶領你們一塊兒剖析了鴻蒙輕內核的時間管理模塊的源代碼。時間管理模塊爲任務調度提供必要的時鐘節拍,會嚮應用程序提供全部和時間有關的服務,如時間轉換、統計、延遲功能。git
本文分享自華爲雲社區《鴻蒙輕內核M核源碼分析系列六 時間管理》,原文做者:zhushy 。函數
本文會繼續分析Tick
和時間相關的源碼,給讀者介紹鴻蒙輕內核的時間管理模塊。本文中所涉及的源碼,以OpenHarmony LiteOS-M
內核爲例,都可以在開源站點https://gitee.com/openharmony/kernel_liteos_m 獲取。源碼分析
時間管理模塊以系統時鐘爲基礎,能夠分爲2部分,一部分是SysTick
中斷,爲任務調度提供必要的時鐘節拍;另一部分是,給應用程序提供全部和時間有關的服務,如時間轉換、統計功能。學習
系統時鐘是由定時器/計數器產生的輸出脈衝觸發中斷產生的,通常定義爲整數或長整數。輸出脈衝的週期叫作一個「時鐘滴答」,也稱爲時標或者Tick
。Tick
是操做系統的基本時間單位,由用戶配置的每秒Tick
數決定。若是用戶配置每秒的Tick數目爲1000,則1個Tick
等於1ms的時長。另一個計時單位是Cycle
,這是系統最小的計時單位。Cycle
的時長由系統主時鐘頻率決定,系統主時鐘頻率就是每秒鐘的Cycle
數,對於216 MHz
的CPU
,1秒產生216000000個cycles
。ui
用戶以秒、毫秒爲單位計時,而操做系統以Tick
爲單位計時,當用戶須要對系統進行操做時,例如任務掛起、延時等,此時能夠使用時間管理模塊對Tick
和秒/毫秒進行轉換。url
下面,咱們剖析下時間管理模塊的源代碼,若涉及開發板部分,以開發板工程targets\cortex-m7_nucleo_f767zi_gcc\
爲例進行源碼分析。spa
1、時間管理初始化和啓動
咱們先看下時間管理模塊的相關配置,而後再剖析如何初始化,如何啓動。操作系統
1.1 時間管理相關的配置
時間管理模塊涉及3個配置項,系統時鐘OS_SYS_CLOCK
、每秒Tick
數目LOSCFG_BASE_CORE_TICK_PER_SECOND
兩個配置選項,還有宏LOSCFG_BASE_CORE_TICK_HW_TIME
。LOSCFG_BASE_CORE_TICK_HW_TIME
默認關閉,開啓時,須要提供定製函數VOID platform_tick_handler(VOID)
,在Tick中斷處理函數中執行定製操做。這些配置項在模板開發板工程目錄的文件target_config.h
中定義,如文件targets\cortex-m7_nucleo_f767zi_gcc\target_config.h
中定義以下:.net
#define OS_SYS_CLOCK 96000000 #define LOSCFG_BASE_CORE_TICK_PER_SECOND (1000UL) #define LOSCFG_BASE_CORE_TICK_HW_TIME 0
1.2 時間管理初始化和啓動
函數INT32 main(VOID)
會調用kernel\src\los_init.c
中的函數UINT32 LOS_Start(VOID)
啓動系統,該函數會調用啓動調度函數UINT32 HalStartSchedule(OS_TICK_HANDLER handler)
。源碼以下:指針
LITE_OS_SEC_TEXT_INIT UINT32 LOS_Start(VOID) { return HalStartSchedule(OsTickHandler); }
函數UINT32 HalTickStart(OS_TICK_HANDLER *handler)
定義在kernel\arch\arm\cortex-m7\gcc\los_context.c
,源碼以下。其中函數參數爲Tick
中斷處理函數OsTickHandler()
,後文會分析該tick
中斷處理函數。⑴處代碼繼續調用函數進一步調用函數HalTickStart(handler)
來設置Tick
中斷啓動。⑵處會調用匯編函數HalStartToRun
開始運行系統,後續任務調度系列再詳細分析該彙編函數。
LITE_OS_SEC_TEXT_INIT UINT32 HalStartSchedule(OS_TICK_HANDLER handler) { UINT32 ret; ⑴ ret = HalTickStart(handler); if (ret != LOS_OK) { return ret; } ⑵ HalStartToRun(); return LOS_OK; /* never return */ }
函數HalTickStart(handler)
定義在文件kernel\arch\arm\cortex-m7\gcc\los_timer.c
,源碼以下,咱們分析下函數的代碼實現。⑴處校驗下時間管理模塊的配置項的合法性。在開啓宏LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT
時,會使用系統定義的中斷。會執行⑵處的代碼,調用定義在文件kernel\arch\arm\cortex-m7\gcc\los_interrupt.c
中的函數OsSetVector()
設置中斷向量,該函數在中斷系列會詳細分析。⑶處設置全局變量g_sysClock
爲系統時鐘,g_cyclesPerTick
爲每tick
對應的cycle
數目,g_ullTickCount
初始化爲0,表示系統tick
中斷髮生次數。⑷處調用定義在targets\cortex-m7_nucleo_f767zi_gcc\Drivers\CMSIS\Include\core_cm7.h
文件中的內聯函數uint32_t SysTick_Config(uint32_t ticks)
,初始化、啓動系統定時器Systick
和中斷。
WEAK UINT32 HalTickStart(OS_TICK_HANDLER *handler) { UINT32 ret; ⑴ if ((OS_SYS_CLOCK == 0) || (LOSCFG_BASE_CORE_TICK_PER_SECOND == 0) || (LOSCFG_BASE_CORE_TICK_PER_SECOND > OS_SYS_CLOCK)) { return LOS_ERRNO_TICK_CFG_INVALID; } #if (LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT == 1) #if (OS_HWI_WITH_ARG == 1) OsSetVector(SysTick_IRQn, (HWI_PROC_FUNC)handler, NULL); #else ⑵ OsSetVector(SysTick_IRQn, (HWI_PROC_FUNC)handler); #endif #endif ⑶ g_sysClock = OS_SYS_CLOCK; g_cyclesPerTick = OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND; g_ullTickCount = 0; ⑷ ret = SysTick_Config(g_cyclesPerTick); if (ret == 1) { return LOS_ERRNO_TICK_PER_SEC_TOO_SMALL; } return LOS_OK; }
1.3 Tick中斷處理函數OsTickHandler()
文件kernel\src\los_tick.c
定義的函數VOID OsTickHandler(VOID)
,是時間管理模塊中執行最頻繁的函數,每當Tick
中斷髮生時就會調用該函數。咱們分析下該函數的源碼,⑴處若是開啓宏LOSCFG_BASE_CORE_TICK_HW_TIME
,會調用定製的tick
處理函數platform_tick_handler()
,默認不開啓。⑵處會更新全局變量g_ullTickCount
,⑶處若是開啓宏LOSCFG_BASE_CORE_TIMESLICE
,會檢查當前運行任務的時間片,在後續任務模塊會詳細分析下函數OsTimesliceCheck()
。⑷處會遍歷任務的排序鏈表,檢查是否有超時的任務。⑸處若是支持定時器特性,會檢查定時器是否超時。
源碼以下:
LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) { #if (LOSCFG_BASE_CORE_TICK_HW_TIME == 1) ⑴ platform_tick_handler(); #endif ⑵ g_ullTickCount++; #if (LOSCFG_BASE_CORE_TIMESLICE == 1) ⑶ OsTimesliceCheck(); #endif ⑷ OsTaskScan(); // task timeout scan #if (LOSCFG_BASE_CORE_SWTMR == 1) ⑸ (VOID)OsSwtmrScan(); #endif }
2、LiteOS
內核時間管理經常使用操做
時間管理提供下面幾種功能,時間轉換、時間統計等,這些函數定義在文件kernel\src\los_tick.c
,咱們剖析下這些操做的源代碼實現。
2.1 時間轉換操做
2.1.1 毫秒轉換成Tick
函數UINT32 LOS_MS2Tick(UINT32 millisec)
把輸入參數毫秒數UINT32 millisec
能夠轉化爲Tick
數目。代碼中OS_SYS_MS_PER_SECOND
,即1秒等於1000毫秒。時間轉換也比較簡單,知道一秒多少Tick
,除以OS_SYS_MS_PER_SECOND
,得出1毫秒多少Tick
,而後乘以millisec
,計算出Tick
數目的結果值並返回。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_MS2Tick(UINT32 millisec) { if (millisec == OS_NULL_INT) { return OS_NULL_INT; } return ((UINT64)millisec * LOSCFG_BASE_CORE_TICK_PER_SECOND) / OS_SYS_MS_PER_SECOND; }
2.1.2 Tick轉化爲毫秒
函數UINT32 LOS_Tick2MS(UINT32 tick)
把輸入參數Tick
數目轉換爲毫秒數。時間轉換也比較簡單,ticks
數目除以每秒多少Tick
數值LOSCFG_BASE_CORE_TICK_PER_SECOND
,計算出多少秒,而後轉換成毫秒,計算出結果值並返回。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_Tick2MS(UINT32 ticks) { return ((UINT64)ticks * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND; }
2.1.3 Cycle數目轉化爲毫秒
介紹轉換函數以前,先看下一個CpuTick
結構體,結構體比較簡單,就2個成員,分別表示一個UINT64
類型數據的高、低32位數值。
typedef struct tagCpuTick { UINT32 cntHi; /* < 一個64位數值的高32位 */ UINT32 cntLo; /* < 一個64位數值的低32位 */ } CpuTick;
繼續看轉換函數OsCpuTick2MS()
,它能夠把CpuTick
類型表示的cycle
數目轉換爲對應的毫秒數,輸出毫秒數據的高、低32位數值。看下具體的代碼,⑴處校驗參數是否爲空指針,⑵處檢查系統時鐘是否配置。⑶處把CpuTick
結構體表示的cycle
數目轉化爲UINT64
類型數據。⑷處進行數值計算,(DOUBLE)g_sysClock / OS_SYS_MS_PER_SECOND
獲得每毫秒多少個cycle
數,而後和tmpCpuTick
作除法運算,獲得cycle
數目對應的毫秒數目。⑸處把DOUBLE
類型轉換爲UINT64
類型,而後執行⑹,分別把結果數值的高、低64位賦值給*msLo
、*msHi
。
LITE_OS_SEC_TEXT_INIT UINT32 OsCpuTick2MS(CpuTick *cpuTick, UINT32 *msHi, UINT32 *msLo) { UINT64 tmpCpuTick; DOUBLE temp; ⑴ if ((cpuTick == NULL) || (msHi == NULL) || (msLo == NULL)) { return LOS_ERRNO_SYS_PTR_NULL; } ⑵ if (g_sysClock == 0) { return LOS_ERRNO_SYS_CLOCK_INVALID; } ⑶ tmpCpuTick = ((UINT64)cpuTick->cntHi << OS_SYS_MV_32_BIT) | cpuTick->cntLo; ⑷ temp = tmpCpuTick / ((DOUBLE)g_sysClock / OS_SYS_MS_PER_SECOND); tmpCpuTick = (UINT64)temp; *msLo = (UINT32)tmpCpuTick; *msHi = (UINT32)(tmpCpuTick >> OS_SYS_MV_32_BIT); return LOS_OK; }
2.1.4 Cycle數目轉化爲微秒
轉換函數OsCpuTick2US()
,它能夠把CpuTick
類型表示的cycle
數目轉換爲對應的毫秒數,輸出毫秒數據的高、低32位數值。該函數和OsCpuTick2MS()
相似,自行閱讀便可。
LITE_OS_SEC_TEXT_INIT UINT32 OsCpuTick2US(CpuTick *cpuTick, UINT32 *usHi, UINT32 *usLo) { UINT64 tmpCpuTick; DOUBLE temp; if ((cpuTick == NULL) || (usHi == NULL) || (usLo == NULL)) { return LOS_ERRNO_SYS_PTR_NULL; } if (g_sysClock == 0) { return LOS_ERRNO_SYS_CLOCK_INVALID; } tmpCpuTick = ((UINT64)cpuTick->cntHi << OS_SYS_MV_32_BIT) | cpuTick->cntLo; temp = tmpCpuTick / ((DOUBLE)g_sysClock / OS_SYS_US_PER_SECOND); tmpCpuTick = (UINT64)temp; *usLo = (UINT32)tmpCpuTick; *usHi = (UINT32)(tmpCpuTick >> OS_SYS_MV_32_BIT); return LOS_OK; }
2.2 時間統計操做
2.2.1 獲取每一個Tick等於多少Cycle數
函數UINT32 LOS_CyclePerTickGet(VOID)
計算1個tick
等於多少cycle
。g_sysClock
系統時鐘表示1秒多少cycle
,LOSCFG_BASE_CORE_TICK_PER_SECOND
一秒多少tick
,相除計算出1 tick
多少cycle
數,即g_cyclesPerTick = g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND
。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID) { return g_cyclesPerTick; }
2.2.2 獲取自系統啓動以來的Tick數
UINT64 LOS_TickCountGet(VOID)
函數計算自系統啓動以來的Tick
中斷的次數。須要注意,在關中斷的狀況下不進行計數,不能做爲準確時間使用。每次Tick
中斷髮生時,在函數VOID OsTickHandler(VOID)
中會更新g_ullTickCount
數據。
LITE_OS_SEC_TEXT_MINOR UINT64 LOS_TickCountGet(VOID) { return g_ullTickCount; }
2.2.3 獲取系統時鐘
UINT32 LOS_SysClockGet(VOID)
函數獲取配置的系統時鐘。
UINT32 LOS_SysClockGet(VOID) { return g_sysClock; }
2.2.4 獲取系統啓動以來的Cycle數
函數VOID HalGetCpuCycle(UINT32 *cntHi, UINT32 *cntLo)
定義在文件kernel\arch\arm\cortex-m7\gcc\los_timer.c
中,該函數獲取系統啓動以來的Cycle
數。返回結果按高、低32位的無符號數值UINT32 *cntHi, UINT32 *cntLo
分別返回。
咱們看下該函數的源碼。先關中斷,而後⑴處獲取啓動啓動以來的Tick
數目。⑵處經過讀取當前值寄存器SysTick Current Value Register
,獲取hwCycle
。⑶處表示中斷控制和狀態寄存器Interrupt Control and State Register
的第TICK_CHECK
位爲1時,表示掛起systick
中斷,tick
沒有計數,須要加1校準。⑷處根據swTick
、g_cyclesPerTick
和hwCycle
計算出自系統啓動以來的Cycle
數。⑸處獲取Cycle
數的高、低32位的無符號數值,而後開中斷、返回。
LITE_OS_SEC_TEXT_MINOR VOID HalGetCpuCycle(UINT32 *cntHi, UINT32 *cntLo) { UINT64 swTick; UINT64 cycle; UINT32 hwCycle; UINTPTR intSave; intSave = LOS_IntLock(); ⑴ swTick = g_ullTickCount; ⑵ hwCycle = SysTick->VAL; ⑶ if ((SCB->ICSR & TICK_CHECK) != 0) { hwCycle = SysTick->VAL; swTick++; } ⑷ cycle = (((swTick) * g_cyclesPerTick) + (g_cyclesPerTick - hwCycle)); ⑸ *cntHi = cycle >> SHIFT_32_BIT; *cntLo = cycle & CYCLE_CHECK; LOS_IntRestore(intSave); return; }
小結
本文帶領你們一塊兒剖析了鴻蒙輕內核的時間管理模塊的源代碼。時間管理模塊爲任務調度提供必要的時鐘節拍,會嚮應用程序提供全部和時間有關的服務,如時間轉換、統計、延遲功能。後續也會陸續推出更多的分享文章,敬請期待,也歡迎你們分享學習、使用鴻蒙輕內核的心得,有任何問題、建議,均可以留言給咱們: https://gitee.com/openharmony/kernel_liteos_m/issues 。爲了更容易找到鴻蒙輕內核代碼倉,建議訪問 https://gitee.com/openharmony/kernel_liteos_m ,關注Watch
、點贊Star
、並Fork
到本身帳戶下,謝謝。