LiteOS:剖析時間管理模塊源代碼

摘要: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到自己賬戶下,如下圖,謝謝。

 

點擊關注,第一時間瞭解華爲雲新鮮技術~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章