鴻蒙輕內核M核源碼分析系列五-時間管理
在鴻蒙輕內核源碼分析上一篇文章中,我們剖析了中斷的源碼,簡單提到了Tick中斷。本文會繼續分析Tick和時間相關的源碼,給讀者介紹鴻蒙輕內核的時間管理模塊。
時間管理模塊以系統時鐘為基礎,可以分為2部分,一部分是SysTick中斷,為任務調度提供必要的時鐘節拍;另外一部分是,給應用程序提供所有和時間有關的服務,如時間轉換、統計功能。
系統時鐘是由定時器/計數器產生的輸出脈沖觸發中斷產生的,一般定義為整數或長整數。輸出脈沖的周期叫做一個“時鐘滴答”,也稱為時標或者Tick。Tick是操作系統的基本時間單位,由用戶配置的每秒Tick數決定。如果用戶配置每秒的Tick數目為1000,則1個Tick等于1ms的時長。另外一個計時單位是Cycle,這是系統最小的計時單位。Cycle的時長由系統主時鐘頻率決定,系統主時鐘頻率就是每秒鐘的Cycle數,對于216 MHz的CPU,1秒產生216000000個cycles。
用戶以秒、毫秒為單位計時,而操作系統以Tick為單位計時,當用戶需要對系統進行操作時,例如任務掛起、延時等,此時可以使用時間管理模塊對Tick和秒/毫秒進行轉換。
下面,我們剖析下時間管理模塊的源代碼,若涉及開發板部分,以開發板工程targets\cortex-m7_nucleo_f767zi_gcc\為例進行源碼分析。
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中定義如下:
- #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 ,謝謝。