鴻蒙內核源碼分析(時間管理篇) | Tick是操作系統的基本時間單位
本篇說清楚時間概念
讀本篇之前建議先讀鴻蒙內核源碼分析(總目錄)其他篇.
時間概念太重要了,在鴻蒙內核又是如何管理和使用時間的呢?
時間管理以系統時鐘 g_sysClock 為基礎,給應用程序提供所有和時間有關的服務。
● 用戶以秒、毫秒為單位計時.
● 操作系統以Tick為單位計時,這個認識很重要. 每秒的tick大小很大程度上決定了內核調度的次數多少.
● 當用戶需要對系統進行操作時,例如任務掛起、延時等,此時需要時間管理模塊對Tick和秒/毫秒進行轉換。
熟悉兩個概念:
● Cycle(周期):系統最小的計時單位。Cycle的時長由系統主時鐘頻率決定,系統主時鐘頻率就是每秒鐘的Cycle數。
● Tick(節拍):Tick是操作系統的基本時間單位,由用戶配置的每秒Tick數決定,可大可小.
怎么去理解他們之間的關系呢?看幾個宏定義就清楚了.
時鐘周期(振蕩周期)
在鴻蒙g_sysClock表示時鐘周期,是CPU的赫茲,也就是上面說的Cycle,這是固定不變的,由硬件晶振的頻率決定的. OsMain是內核運行的第一個C函數,首個子函數就是 osRegister,完成對g_sysClock的賦值
CPU周期也叫(機器周期)
在鴻蒙宏OS_CYCLE_PER_TICK表示機器周期,Tick由用戶根據實際情況配置. 例如:主頻為1G的CPU,其振蕩周期為: 1吉赫 (GHz 109 Hz) = 1 000 000 000 Hz 當Tick為100時,則1 000 000 000/100 = 10000000 ,即一秒內可產生1千萬個CPU周期.CPU就是用這1千萬個周期去執行指令的.
指令周期
指令周期是執行一條指令所需要的時間,一般由若干個機器周期組成。指令不同,所需的機器周期數也不同。 對于一些簡單的的單字節指令,在取指令周期中,指令取出到指令寄存器后,立即譯碼執行,不再需要其它的機器周期。 對于一些比較復雜的指令,例如轉移指令、乘法指令,則需要兩個或者兩個以上的機器周期。 通常含一個機器周期的指令稱為單周期指令,包含兩個機器周期的指令稱為雙周期指令。
Tick硬中斷函數
- LITE_OS_SEC_BSS volatile UINT64 g_tickCount[LOSCFG_KERNEL_CORE_NUM] = {0};//tick計數器,系統一旦啟動,一直在++, 為防止溢出,這是一個 UINT64 的變量
- LITE_OS_SEC_DATA_INIT UINT32 g_sysClock;//系統時鐘,是絕大部分部件工作的時鐘源,也是其他所有外設的時鐘的來源
- LITE_OS_SEC_DATA_INIT UINT32 g_tickPerSecond;//每秒Tick數,鴻蒙默認是每秒100次,即:10ms
- LITE_OS_SEC_BSS DOUBLE g_cycle2NsScale; //周期轉納秒級
- /* spinlock for task module */
- LITE_OS_SEC_BSS SPIN_LOCK_INIT(g_tickSpin); //節拍器自旋鎖
- #define TICK_LOCK(state) LOS_SpinLockSave(&g_tickSpin, &(state))
- /*
- * Description : Tick interruption handler
- *///節拍中斷處理函數 ,鴻蒙默認10ms觸發一次
- LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
- {
- UINT32 intSave;
- TICK_LOCK(intSave);
- g_tickCount[ArchCurrCpuid()]++;//當前CPU核計數器
- TICK_UNLOCK(intSave);
- #ifdef LOSCFG_KERNEL_VDSO
- OsUpdateVdsoTimeval();
- #endif
- #ifdef LOSCFG_KERNEL_TICKLESS
- OsTickIrqFlagSet(OsTicklessFlagGet());
- #endif
- #if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES)
- HalClockIrqClear(); /* diff from every platform */
- #endif
- OsTimesliceCheck();//時間片檢查
- OsTaskScan(); /* task timeout scan *///任務掃描
- #if (LOSCFG_BASE_CORE_SWTMR == YES)
- OsSwtmrScan();//定時器掃描,看是否有超時的定時器
- #endif
- }
- #ifdef __cplusplus
- #if __cplusplus
- }
解讀
● g_tickCount記錄每個CPU核tick的數組,每次硬中斷都觸發 OsTickHandler,每個CPU核單獨計數.
● OsTickHandler是內核調度的動力,其中會檢查任務時間片是否用完,定時器是否超時.主動delay的任務是否需要被喚醒,其本質是個硬中斷,在HalClockInit硬時鐘初始化時創建的,具體在硬中斷篇中會詳細講解.
● TICK_LOCK是tick操作的自旋鎖,宏原型LOS_SpinLockSave在自旋鎖篇中已詳細介紹.
功能函數
- #define OS_SYS_MS_PER_SECOND 1000 //一秒多少毫秒
- //獲取自系統啟動以來的Tick數
- LITE_OS_SEC_TEXT_MINOR UINT64 LOS_TickCountGet(VOID)
- {
- UINT32 intSave;
- UINT64 tick;
- /*
- * use core0's tick as system's timeline,
- * the tick needs to be atomic.
- */
- TICK_LOCK(intSave);
- tick = g_tickCount[0];//使用CPU core0作為系統的 tick數
- TICK_UNLOCK(intSave);
- return tick;
- }
- //每個Tick多少Cycle數
- LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID)
- {
- return g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND;
- }
- //毫秒轉換成Tick
- LITE_OS_SEC_TEXT_MINOR UINT32 LOS_MS2Tick(UINT32 millisec)
- {
- if (millisec == OS_MAX_VALUE) {
- return OS_MAX_VALUE;
- }
- return ((UINT64)millisec * LOSCFG_BASE_CORE_TICK_PER_SECOND) / OS_SYS_MS_PER_SECOND;
- }
- //Tick轉化為毫秒
- LITE_OS_SEC_TEXT_MINOR UINT32 LOS_Tick2MS(UINT32 tick)
- {
- return ((UINT64)tick * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND;
- }
說明
● 在CPU篇中講過,0號CPU核默認為主核,默認獲取自系統啟動以來的Tick數使用的是g_tickCount[0]
● 因每個CPU核的tick是獨立計數的,所以g_tickCount中各值是不一樣的.
● 系統的Tick數在關中斷的情況下不進行計數,因為OsTickHandler本質是由硬中斷觸發的,屏蔽硬中斷的情況下就不會觸發OsTickHandler,自然也就不會有g_tickCount[ArchCurrCpuid()]++的計數,所以系統Tick數不能作為準確時間使用.
● 追問下,什么情況下硬中斷會被屏蔽?
編程示例
前提條件:
● 使用每秒的Tick數LOSCFG_BASE_CORE_TICK_PER_SECOND的默認值100。
● 配好OS_SYS_CLOCK系統主時鐘頻率。
時間轉換
- VOID Example_TransformTime(VOID)
- {
- UINT32 ms;
- UINT32 tick;
- tick = LOS_MS2Tick(10000); // 10000ms轉換為tick
- dprintf("tick = %d \n",tick);
- ms = LOS_Tick2MS(100); // 100tick轉換為ms
- dprintf("ms = %d \n",ms);
- }
時間轉換結果
- tick = 1000
- ms = 1000
時間統計和時間延遲
- LITE_OS_SEC_TEXT UINT32 LOS_TaskDelay(UINT32 tick);
- VOID Example_GetTime(VOID)
- {
- UINT32 cyclePerTick;
- UINT64 tickCount;
- cyclePerTick = LOS_CyclePerTickGet();
- if(0 != cyclePerTick) {
- dprintf("LOS_CyclePerTickGet = %d \n", cyclePerTick);
- }
- tickCount = LOS_TickCountGet();
- if(0 != tickCount) {
- dprintf("LOS_TickCountGet = %d \n", (UINT32)tickCount);
- }
- LOS_TaskDelay(200);//延遲200個tick
- tickCount = LOS_TickCountGet();
- if(0 != tickCount) {
- dprintf("LOS_TickCountGet after delay = %d \n", (UINT32)tickCount);
- }
- }
時間統計和時間延遲結果
- LOS_CyclePerTickGet = 495000 //取決于CPU的頻率
- LOS_TickCountGet = 1 //實際情況不一定是1的
- LOS_TickCountGet after delay = 201 //實際情況不一定是201,但二者的差距會是200