摘要:Huawei LiteOS的時間管理模塊以系統時鐘爲基礎,分爲2部分,一部分是SysTick中斷,爲任務調度提供必要的時鐘節拍;另外一部分是,給應用程序提供所有和時間有關的服務,如時間轉換、統計、延遲功能。
本文分享自華爲雲社區《LiteOS內核源碼分析系列四 LiteOS內核源碼分析--時間管理》,原文作者:zhushy 。
Huawei LiteOS的時間管理模塊以系統時鐘爲基礎,可以分爲2部分,一部分是SysTick中斷,爲任務調度提供必要的時鐘節拍;另外一部分是,給應用程序提供所有和時間有關的服務,如時間轉換、統計、延遲功能。
系統時鐘是由定時器/計數器產生的輸出脈衝觸發中斷產生的,一般定義爲整數或長整數。輸出脈衝的週期叫做一個“時鐘滴答”,也稱爲時標或者Tick。Tick是操作系統的基本時間單位,由用戶配置的每秒Tick數決定。如果用戶配置每秒的Tick數目爲1000,則1個Tick等於1ms的時長。另外一個計時單位是Cycle,這是系統最小的計時單位。Cycle的時長由系統主時鐘頻率決定,系統主時鐘頻率就是每秒鐘的Cycle數,對於216 MHz的CPU,1秒產生216000000個cycles。
用戶以秒、毫秒爲單位計時,而操作系統以Tick爲單位計時,當用戶需要對系統進行操作時,例如任務掛起、延時等,此時可以使用時間管理模塊對Tick和秒/毫秒進行轉換。
文中所涉及的源代碼, 均可以在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爲例進行源碼分析。
1、時間管理初始化和啓動。
我們先看下時間管理模塊的相關配置,然後再剖析如何初始化,如何啓動。
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也表示系統時鐘。
文件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
}
2、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
到自己賬戶下,如下圖,謝謝。