摘要:Huawei LiteOS的時間管理模塊以系統時鐘爲基礎,分爲2部分,一部分是SysTick中斷,爲任務調度提供必要的時鐘節拍;另一部分是,給應用程序提供全部和時間有關的服務,如時間轉換、統計、延遲功能。
本文分享自華爲雲社區《LiteOS內核源碼分析系列四 LiteOS內核源碼分析--時間管理》,原文做者:zhushy 。git
Huawei LiteOS的時間管理模塊以系統時鐘爲基礎,能夠分爲2部分,一部分是SysTick中斷,爲任務調度提供必要的時鐘節拍;另一部分是,給應用程序提供全部和時間有關的服務,如時間轉換、統計、延遲功能。數組
系統時鐘是由定時器/計數器產生的輸出脈衝觸發中斷產生的,通常定義爲整數或長整數。輸出脈衝的週期叫作一個「時鐘滴答」,也稱爲時標或者Tick。Tick是操做系統的基本時間單位,由用戶配置的每秒Tick數決定。若是用戶配置每秒的Tick數目爲1000,則1個Tick等於1ms的時長。另一個計時單位是Cycle,這是系統最小的計時單位。Cycle的時長由系統主時鐘頻率決定,系統主時鐘頻率就是每秒鐘的Cycle數,對於216 MHz的CPU,1秒產生216000000個cycles。less
用戶以秒、毫秒爲單位計時,而操做系統以Tick爲單位計時,當用戶須要對系統進行操做時,例如任務掛起、延時等,此時可使用時間管理模塊對Tick和秒/毫秒進行轉換。ide
文中所涉及的源代碼, 都可以在LiteOS
開源站點https://gitee.com/LiteOS/LiteOS 獲取。 位操做模塊源代碼、開發文檔以下:函數
- 內核時間管理源代碼
時間管理模塊源文件,包括頭文件kernel\include\los_tick.h、 私有頭文件[kernel\base\include\los_tick_pri.h](https://gitee.com/LiteOS/LiteOS/blob/master/kernel/base/include/los_tick_pri.h、C
源代碼文件kernel\base\los_tick.c。工具
- 開發指南時間管理模塊文檔
在線文檔https://gitee.com/LiteOS/LiteOS/blob/feature/doc/Huawei_LiteOS_Kernel_Developer_Guide_zh.md#%E6%97%B6%E9%97%B4%E7%AE%A1%E7%90%86。源碼分析
下面,咱們剖析下時間管理模塊的源代碼,以LiteOS開源工程支持的板子之一STM32F769IDiscovery爲例進行源碼分析。ui
一、時間管理初始化和啓動。
咱們先看下時間管理模塊的相關配置,而後再剖析如何初始化,如何啓動。url
1.1 時間管理相關的配置
時間管理模塊依賴系統時鐘OS_SYS_CLOCK和每秒Tick數目LOSCFG_BASE_CORE_TICK_PER_SECOND兩個配置選項。在系統啓動時,targets\STM32F769IDISCOVERY\Src\main.c的main()函數調用targets\STM32F769IDISCOVERY\Src\platform_init.c文件中的void HardwareInit(void)進行硬件初始化,初始化時會調用void SystemClock_Config(void)進行系統時鐘的配置。完成系統時鐘的配置後,SystemCoreClock賦值爲216000000Hz。經過下面兩個宏定義,OS_SYS_CLOCK也表示系統時鐘。spa
文件kernel\include\los_config.h:
/** * @ingroup los_config * System clock (unit: HZ) */ #ifndef OS_SYS_CLOCK #define OS_SYS_CLOCK (get_bus_clk()) #endif
文件targets\STM32F769IDISCOVERY\include\hisoc\clock.h:
#define get_bus_clk() SystemCoreClock // default: 216000000
另一個配置項,每秒Tick數目LOSCFG_BASE_CORE_TICK_PER_SECOND,用戶能夠經過LiteOS提供的組件配置工具menuconfig進行設置,配置路徑在Kernel → Basic Config → Task → Tick Value Per Second,支持的開發板也提供了默認值。
1.2 時間管理初始化OsTickInit()
在系統啓動時,在kernel\init\los_init.c中調用VOID OsRegister(VOID)設置系統時鐘、Tick配置。⑴處全局變量g_tickPerSecond賦值爲LOSCFG_BASE_CORE_TICK_PER_SECOND,也表示每秒配置多少個Tick。⑵處的宏定義把OS_SYS_CLOCK賦值給g_sysClock,都表示系統時鐘。後文的代碼解析會涉及這些變量的使用。
LITE_OS_SEC_TEXT_INIT static VOID OsRegister(VOID) { #ifdef LOSCFG_LIB_CONFIGURABLE g_osSysClock = OS_SYS_CLOCK_CONFIG; g_tickPerSecond = LOSCFG_BASE_CORE_TICK_PER_SECOND_CONFIG; g_taskLimit = LOSCFG_BASE_CORE_TSK_LIMIT_CONFIG; g_taskMaxNum = g_taskLimit + 1; g_taskMinStkSize = LOSCFG_BASE_CORE_TSK_MIN_STACK_SIZE_CONFIG; g_taskIdleStkSize = LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE_CONFIG; g_taskDfltStkSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE_CONFIG; g_taskSwtmrStkSize = LOSCFG_BASE_CORE_TSK_SWTMR_STACK_SIZE_CONFIG; g_swtmrLimit = LOSCFG_BASE_CORE_SWTMR_LIMIT_CONFIG; g_semLimit = LOSCFG_BASE_IPC_SEM_LIMIT_CONFIG; g_muxLimit = LOSCFG_BASE_IPC_MUX_LIMIT_CONFIG; g_queueLimit = LOSCFG_BASE_IPC_QUEUE_LIMIT_CONFIG; g_timeSliceTimeOut = LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT_CONFIG; #else ⑴ g_tickPerSecond = LOSCFG_BASE_CORE_TICK_PER_SECOND; #endif ⑵ SET_SYS_CLOCK(OS_SYS_CLOCK); #ifdef LOSCFG_KERNEL_NX LOS_SET_NX_CFG(true); #else LOS_SET_NX_CFG(false); #endif LOS_SET_DL_NX_HEAP_BASE(LOS_DL_HEAP_BASE); LOS_SET_DL_NX_HEAP_SIZE(LOS_DL_HEAP_SIZE); return; }
在kernel\init\los_init.c中會繼續調用UINT32 OsTickInit(UINT32 systemClock, UINT32 tickPerSecond)來初始化時間配置。該函數須要2個參數,分別是上文配置的系統時鐘和每秒的tick數。進一步調用HalClockInit()函數。
LITE_OS_SEC_TEXT_INIT UINT32 OsTickInit(UINT32 systemClock, UINT32 tickPerSecond) { if ((systemClock == 0) || (tickPerSecond == 0) || (tickPerSecond > systemClock)) { return LOS_ERRNO_TICK_CFG_INVALID; } HalClockInit(); return LOS_OK; }
HalClockInit()函數定義在targets\bsp\hw\arm\timer\arm_cortex_m\systick.c,使用LOS_HwiCreate()爲中斷號M_INT_NUM建立一箇中斷,每個Tick中斷髮生時,都會調用中斷處理程序OsTickHandler(),這個函數後文會分析。
#define M_INT_NUM 15 VOID HalClockInit(VOID) { UINT32 ret = LOS_HwiCreate(M_INT_NUM, 0, 0, OsTickHandler, 0); if (ret != 0) { PRINTK("ret of LOS_HwiCreate = %#x\n", ret); } #if defined (LOSCFG_ARCH_ARM_CORTEX_M) && (LOSCFG_KERNEL_CPUP) TimerHwiCreate(); #endif }
1.3 時間管理模塊啓動OsTickStart()
在系統開始調度以前,函數INT32 main(VOID)會調用系統啓動函數VOID OsStart(VOID),它會調用時間模塊啓動函數OsTickStart(),進一步調用HalClockStart()。咱們分析下函數的代碼實現。
⑴處全局變量g_cyclesPerTick表示每Tick對應的cycle數目。⑵處函數定義在arch\arm\cortex_m\cmsis\core_cm7.h文件中,初始化系統定時器Systick並啓動,Systick相關的代碼自行閱讀。⑶處調用LOS_HwiEnable()函數使能Tick中斷。
文件kernel\base\los_tick.c:
LITE_OS_SEC_TEXT_INIT VOID OsTickStart(VOID) { HalClockStart(); }
文件targets\bsp\hw\arm\timer\arm_cortex_m\systick.c:
VOID HalClockStart(VOID) { if ((OS_SYS_CLOCK == 0) || (LOSCFG_BASE_CORE_TICK_PER_SECOND == 0) || (LOSCFG_BASE_CORE_TICK_PER_SECOND > OS_SYS_CLOCK)) { return; } ⑴ g_cyclesPerTick = OS_CYCLE_PER_TICK; ⑵ (VOID)SysTick_Config(OS_CYCLE_PER_TICK); ⑶ UINT32 ret = LOS_HwiEnable(M_INT_NUM); if (ret != 0) { PRINTK("LOS_HwiEnable failed. ret = %#x\n", ret); } }
1.4 Tick中斷處理函數OsTickHandler()
這是時間管理模塊中執行最頻繁的函數VOID OsTickHandler(VOID),每當Tick中斷髮生時就會調用該函數。⑴處會更新全局數組全局數組g_tickCount每一個核的tick數據。⑵和tickless特性相關,後續系列分析。⑶處會遍歷任務的排序鏈表,檢查是否有超時的任務。⑷處若是支持定時器特性,會檢查定時器排序鏈表中的定時器是否超時。
LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) { UINT32 intSave; TICK_LOCK(intSave); ⑴ g_tickCount[ArchCurrCpuid()]++; TICK_UNLOCK(intSave); #ifdef LOSCFG_KERNEL_TICKLESS ⑵ OsTickIrqFlagSet(OsTicklessFlagGet()); #endif #if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES) HalClockIrqClear(); /* diff from every platform */ #endif #ifdef LOSCFG_BASE_CORE_TIMESLICE OsTimesliceCheck(); #endif ⑶ OsTaskScan(); /* task timeout scan */ #if (LOSCFG_BASE_CORE_SWTMR == YES) ⑷ OsSwtmrScan(); #endif }
二、LiteOS內核時間管理經常使用操做
Huawei LiteOS的時間管理提供下面幾種功能,時間轉換、時間統計、延時管理等,咱們剖析下這些接口的源代碼實現。
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,計算出結果值。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_MS2Tick(UINT32 millisec) { if (millisec == UINT32_MAX) { return UINT32_MAX; } return (UINT32)(((UINT64)millisec * LOSCFG_BASE_CORE_TICK_PER_SECOND) / OS_SYS_MS_PER_SECOND); }
2.1.2 Tick轉化爲毫秒
函數UINT32 LOS_Tick2MS(UINT32 tick)把輸入參數Tick數目轉換爲毫秒數。時間轉換也比較簡單,tick除以LOSCFG_BASE_CORE_TICK_PER_SECOND,計算出多少秒,而後轉換成毫秒,計算出結果值。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_Tick2MS(UINT32 tick) { return (UINT32)(((UINT64)tick * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND); }
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數。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID) { return g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND; }
2.2.2 獲取自系統啓動以來的Tick數
UINT64 LOS_TickCountGet(VOID)函數計算自系統啓動以來的Tick數。須要注意,在關中斷的狀況下不進行計數,不能做爲準確時間使用。全局數組UINT64 g_tickCount[LOSCFG_KERNEL_CORE_NUM]記錄每個核的自系統啓動以來的Tick數,每次Tick中斷髮生時,在函數VOID OsTickHandler(VOID)中會更新這個數組的數據。咱們取第一個核的Tick數做爲返回結果。
LITE_OS_SEC_TEXT_MINOR UINT64 LOS_TickCountGet(VOID) { UINT32 intSave; UINT64 tick; TICK_LOCK(intSave); tick = g_tickCount[0]; TICK_UNLOCK(intSave); return tick; }
2.2.3 獲取自系統啓動以來的Cycle數
VOID LOS_GetCpuCycle(UINT32 *highCnt, UINT32 *lowCnt)函數獲取自系統啓動以來的Cycle數。這個函數調用定義在文件targets\bsp\hw\arm\timer\arm_cortex_m\systick.c中的HalClockGetCycles()函數獲取64位的無符號整數。返回結果按高低32位的無符號數值UINT32 *highCnt, UINT32 *lowCnt分別返回。
LITE_OS_SEC_TEXT_MINOR VOID LOS_GetCpuCycle(UINT32 *highCnt, UINT32 *lowCnt) { UINT64 cycle; if ((highCnt == NULL) || (lowCnt == NULL)) { return; } cycle = HalClockGetCycles(); /* get the high 32 bits */ *highCnt = (UINT32)(cycle >> 32); /* get the low 32 bits */ *lowCnt = (UINT32)(cycle & 0xFFFFFFFFULL); }
咱們繼續看下函數HalClockGetCycles()函數。先關中斷,而後⑴處獲取啓動啓動以來的Tick數目。⑵處經過讀取當前值寄存器SysTick Current Value Register,獲取hwCycle。
⑷ cycle = (swTick * g_cyclesPerTick) + (g_cyclesPerTick - hwCycle);
⑶處表示中斷控制和狀態寄存器Interrupt Control and State Register的第TICK_INTR_CHECK位爲1時,表示掛起systick中斷,tick沒有計數,須要加1校準。⑷處根據swTick、g_cyclesPerTick和hwCycle計算出自系統啓動以來的Cycle數。
UINT64 HalClockGetCycles(VOID) { UINT64 swTick; UINT64 cycle; UINT32 hwCycle; UINT32 intSave; intSave = LOS_IntLock(); ⑴ swTick = LOS_TickCountGet(); ⑵ hwCycle = SysTick->VAL; ⑶ if ((SCB->ICSR & TICK_INTR_CHECK) != 0) { hwCycle = SysTick->VAL; swTick++; } ⑷ cycle = (swTick * g_cyclesPerTick) + (g_cyclesPerTick - hwCycle); LOS_IntRestore(intSave); #if defined (LOSCFG_ARCH_ARM_CORTEX_M) && (LOSCFG_KERNEL_CPUP) cycle = HalClockGetCpupCycles() * TIMER_CYCLE_SWITCH; #endif return cycle; }
2.2.4 獲取自系統啓動以來的納秒數
函數UINT64 LOS_CurrNanosec(VOID)計算獲取自系統啓動以來的納秒數。HalClockGetCycles()獲取自系統啓動以來的Cycle數,除以表示每秒多少cycle的系統時鐘g_sysClock,能夠計算出自系統啓動以來的秒數,而後乘以秒和納秒的換算關係OS_SYS_NS_PER_SECOND,便可獲取自系統啓動以來的納秒數。代碼中出現2次除以OS_SYS_NS_PER_MS,來減少中間值避免數值溢出。
LITE_OS_SEC_TEXT_MINOR UINT64 LOS_CurrNanosec(VOID) { UINT64 nanos; nanos = HalClockGetCycles() * (OS_SYS_NS_PER_SECOND / OS_SYS_NS_PER_MS) / (g_sysClock / OS_SYS_NS_PER_MS); return nanos; }
2.3 延時管理
2.3.1 LOS_Udelay()微秒等待
以us爲單位的忙等,但能夠被優先級更高的任務搶佔。該函數VOID LOS_Udelay(UINT32 usecs)進一步調用targets\bsp\hw\arm\timer\arm_cortex_m\systick.c文件中定義的函數VOID HalDelayUs(UINT32 usecs)。
LITE_OS_SEC_TEXT_MINOR VOID LOS_Udelay(UINT32 usecs) { HalDelayUs(usecs); }
繼續分析下函數VOID HalDelayUs(UINT32 usecs)。微秒轉換爲納秒,計算當前的納秒數值,而後while循環,使用匯編指令空操做,等待超時。
VOID HalDelayUs(UINT32 usecs) { UINT64 tmo = LOS_CurrNanosec() + usecs * OS_SYS_NS_PER_US; while (LOS_CurrNanosec() < tmo) { __asm__ volatile ("nop"); } }
2.3.2 LOS_Mdelay()毫秒等待
以ms爲單位的忙等,但能夠被優先級更高的任務搶佔。該函數把參數UINT32 msecs毫秒轉換爲微妙,須要考慮數值溢出的問題。
LITE_OS_SEC_TEXT_MINOR VOID LOS_Mdelay(UINT32 msecs) { UINT32 delayUs = (UINT32_MAX / OS_SYS_US_PER_MS) * OS_SYS_US_PER_MS; while (msecs > UINT32_MAX / OS_SYS_US_PER_MS) { HalDelayUs(delayUs); msecs -= (UINT32_MAX / OS_SYS_US_PER_MS); } HalDelayUs(msecs * OS_SYS_US_PER_MS); }
小結
本文帶領你們一塊兒剖析了LiteOS時間管理模塊的源代碼。時間管理模塊爲任務調度提供必要的時鐘節拍,會嚮應用程序提供全部和時間有關的服務,如時間轉換、統計、延遲功能。
感謝閱讀,若有任何問題、建議, 均可以留言給咱們: https://gitee.com/LiteOS/LiteOS/issues 。爲了更容易找到LiteOS
代碼倉,建議訪問 https://gitee.com/LiteOS/LiteOS ,關注Watch
、點贊Star
、並Fork
到本身帳戶下,以下圖,謝謝。