Abstract: HuaweiLiteOS time management module is based on the system clock and consists of two parts. One part is SysTick interrupt, which provides the necessary clock beats for task scheduling. The other part is to provide all time-related services to the application, such as time conversion, statistics, and delay functions.

LiteOS kernel source code analysis series four LiteOS kernel source code analysis — time management, original author: zhushy.

The Huawei LiteOS time management module is based on the system clock and can be divided into two parts. One part is SysTick interruption, which provides the necessary clock beats for task scheduling. The other part is to provide all time-related services to the application, such as time conversion, statistics, and delay functions.

The system clock is generated by an interrupt triggered by an output pulse generated by a timer/counter and is generally defined as an integer or long integer. The period of the output pulse is called a “clock Tick”, also known as a time scale or Tick. Tick is the basic unit of time of an operating system. It is determined by the number of ticks per second configured by users. If the number of ticks per second is set to 1000, one Tick equals 1ms. The other unit of time is Cycle, which is the smallest unit of time for the system. The length of Cycle is determined by the system master clock frequency, which is the number of cycles per second. For a 216 MHz CPU, 216000 cycles are generated in one second.

Users time the system in seconds and milliseconds, while the OPERATING system time in ticks. When users need to perform operations on the system, such as task suspension or delay, you can use the time management module to convert the ticks to seconds and milliseconds.

The source code mentioned in this article can be found on LiteOS open source site gitee.com/LiteOS/Lite… To obtain. Bit operation module source code, development documents are as follows:

  • Kernel time management source code

Source files of the time management module, including header files kernel\include\los_tick.h and private header files [kernel\base\include\los_tick_pri.h(gitee.com/LiteOS/Lite… C source code file kernel\base\los_tick. C

  • Development guide Time management module documentation

Online documentation gitee.com/LiteOS/Lite… LiteOSKernelDeveloperGuide_zh. Md# E9 B6% % % E6%97% 97% E7 A1 AE E7 B4% % % % % 90% by 86.

Below, we analyze the source code of the time management module, with one of the boards supported by LiteOS open source project STM32F769IDiscovery as an example for source analysis.

1. Time management initialization and startup.

Let’s take a look at the time management module configuration, and then analyze how to initialize, how to start.

1.1 Time Management Configurations

The time management module depends on the system clock OS_SYS_CLOCK and the number of ticks per second LOSCFG_BASE_CORE_TICK_PER_SECOND. When the system starts up, Targets \STM32F769IDISCOVERY\Src\main.c main() calls void in targets\STM32F769IDISCOVERY\Src\platform_init.c HardwareInit(void) initializes the hardware. During the initialization, void SystemClock_Config(void) is called to configure the system clock. After the system clock is configured, SystemCoreClock is set to 216000000Hz. OS_SYS_CLOCK also represents the system clock, as defined by the following two macros.

File the kernel \ include \ los_config. H:

/** * @ingroup los_config * System clock (unit: HZ) */#ifndef OS_SYS_CLOCK#define OS_SYS_CLOCK (get_bus_clk())#endif
Copy the code

File the targets \ STM32F769IDISCOVERY \ include \ hisoc \ clock h:

#define get_bus_clk() SystemCoreClock // default: 216000000
Copy the code

Another configuration item, the number of ticks per second LOSCFG_BASE_CORE_TICK_PER_SECOND, can be set by using the component configuration tool menuconfig provided by LiteOS. The configuration path is Kernel → Basic Config → Task → Tick Value Per Second, and the supported development boards also provide default values.

1.2 Time Management Initializing OsTickInit()

During system startup, call VOID OsRegister(VOID) in kernel\init\los_init.c to set the system clock and Tick configuration. The global variable g_tickPerSecond at ⑴ is assigned to LOSCFG_BASE_CORE_TICK_PER_SECOND, which also indicates how many ticks are configured per second. The macro definition at ⑵ assigns OS_SYS_CLOCK to g_sysClock, both representing the system clock. The code parsing below will cover the use of these variables.

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; # the else (1) g_tickPerSecond = LOSCFG_BASE_CORE_TICK_PER_SECOND; # endif diction 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; }Copy the code

In kernel init los_init.c, UINT32 OsTickInit(UINT32 systemClock, UINT32 tickPerSecond) is continued to be called to initialize the time configuration. This function requires two parameters, namely the system clock and the number of ticks per second. Call the HalClockInit() function further.

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;}
Copy the code

The HalClockInit() function is defined on targets\ BSP \hw\arm\timer\arm_cortex_m\systick.c using LOS_HwiCreate() to create an interrupt for M_INT_NUM. When each Tick interrupt occurs, Both call the interrupt handler OsTickHandler(), which we’ll examine later.

#define M_INT_NUM 15VOID 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}Copy the code

1.3 Time Management Module Starting OsTickStart()

Before the system starts scheduling, the function INT32 main(VOID) calls the system startup function VOID OsStart(VOID), which calls the time module startup function OsTickStart() and HalClockStart(). Let’s look at the code implementation of the function.

The global variable g_cyclesPerTick in ⑴ represents the cycle number corresponding to each Tick. (2) the function defined in arch arm cortex_m cmsis core_cm7.h initializes the system timer Systick and starts, Systick related code reads itself. (3) Call LOS_HwiEnable() to enable the Tick interrupt.

File the kernel \ base \ los_tick. C:

LITE_OS_SEC_TEXT_INIT VOID OsTickStart(VOID){ HalClockStart(); }Copy the code

File the 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; } (1) g_cyclesPerTick = OS_CYCLE_PER_TICK; 2 (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); }}Copy the code

1.4 Tick Interrupt Handler OsTickHandler()

This is the most frequently executed function in the time management module VOID OsTickHandler(VOID), which is called whenever a Tick interrupt occurs. The global array g_tickCount is updated with tick data for each core. (2) Related to tickless characteristics and subsequent series analysis. (3) the sorted list of tasks is iterated to check if there are any tasks that have timed out. (4) Check whether the timer in the timer sort list is timed out.

LITE_OS_SEC_TEXT VOID OsTickHandler(VOID){ UINT32 intSave; TICK_LOCK(intSave); (1) g_tickCount [ArchCurrCpuid ()] + +; TICK_UNLOCK(intSave); # ifdef LOSCFG_KERNEL_TICKLESS diction OsTickIrqFlagSet (OsTicklessFlagGet ()); #endif#if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES) HalClockIrqClear(); /* diff from every platform */#endif#ifdef LOSCFG_BASE_CORE_TIMESLICE OsTimesliceCheck(); # endif (3) OsTaskScan (); /* task timeout scan */#if (LOSCFG_BASE_CORE_SWTMR == YES)⑷ ⑷ */ * task timeout scan */#if (LOSCFG_BASE_CORE_SWTMR == YES)⑷ #endif}Copy the code

2, LiteOS kernel time management common operations

Huawei LiteOS time management provides the following functions, such as time conversion, time statistics, delay management, etc. We analyze the source code implementation of these interfaces.

2.1 Time conversion operation

2.1.1 Ms is converted to Tick

Function UINT32 LOS_MS2Tick(UINT32 millisederec) Converts the input parameter UINT32 millisederec into the number of ticks. OS_SYS_MS_PER_SECOND specifies 1000 milliseconds. Change the millisieverts’ time to millisieverts’ ($millisieverts, $millisieverts, $millisieverts, $millisieverts).

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);}
Copy the code

2.1.2 The Tick is converted to ms

Function UINT32 LOS_Tick2MS(UINT32 TICK) Converts the tick number into the number of milliseconds. Time conversion is also relatively simple, tick divided by LOSCFG_BASE_CORE_TICK_PER_SECOND to calculate the number of seconds, and then converted to milliseconds to calculate the resulting value.

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); }Copy the code

2.2 Time Statistics Operations

2.2.1 How many cycles of each Tick

Function UINT32 LOS_CyclePerTickGet(VOID) Calculates the cycle length of a tick. G_sysClock Indicates the cycle of the system clock in one second. LOSCFG_BASE_CORE_TICK_PER_SECOND Indicates the tick cycle in one second. Divide by the tick cycle.

LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID){ return g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND; }Copy the code

2.2.2 Obtaining the number of Ticks since system startup

UINT64 LOS_TickCountGet(VOID) calculates the number of ticks since system startup. It should be noted that no counting is carried out in the case of shutdown interruption and cannot be used as an accurate time. Global array UINT64 g_tickCount[LOSCFG_KERNEL_CORE_NUM] Records the number of ticks for each core since system startup. The data in this array is updated each time a Tick interrupt occurs in VOID OsTickHandler(VOID). We take the Tick number of the first kernel as the return result.

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; }Copy the code

2.2.3 Obtaining the Cycle number since system startup

VOID LOS_GetCpuCycle(UINT32 *highCnt, UINT32 *lowCnt) Obtains the number of cycles since system startup. This function call is defined in the HalClockGetCycles() function in the targets\ BSP \hw\arm\timer\arm_cortex_m\systick.c to get a 64-bit unsigned integer. Result The value is returned based on the unsigned value UINT32 *highCnt and UINT32 *lowCnt respectively.

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); }Copy the code

Let’s move on to the function HalClockGetCycles(). First turn off the interrupt, and then obtain the Tick number since the startup. (2) Obtain hwCycle by reading the SysTick Current Value Register.

⑷ cycle = (swTick * g_cyclesPerTick) + (g_cyclespertick-hwCycle);

If the TICK_INTR_CHECK bit of the State Register is 1, it indicates that the SYstick Interrupt is suspended. The tick does not count and needs to be adjusted by 1. (4) According to swTick, g_cyclesPerTick and hwCycle, calculate the Cycle number since the system was started.

UINT64 HalClockGetCycles(VOID){ UINT64 swTick; UINT64 cycle; UINT32 hwCycle; UINT32 intSave; intSave = LOS_IntLock(); (1) swTick = LOS_TickCountGet (); 2 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; }Copy the code

2.2.4 Obtaining the number of nanoseconds since system startup

Function UINT64 LOS_CurrNanosec(VOID) Calculates the number of nanoseconds since the system was started. HalClockGetCycles() Gets cycles since system startup. Divide g_sysClock by cycles per second to calculate the number of seconds since system startup. Gets the number of nanoseconds since the system started. The code appears twice divided by OS_SYS_NS_PER_MS to reduce the median value to avoid overflow.

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; }Copy the code

2.3 Delay Management

2.3.1 LOS_Udelay() Microsecond wait

Busy and so on in us, but can be preempted by higher priority tasks. The function VOID LOS_Udelay(UINT32 usECS) further calls the function VOID HalDelayUs(UINT32) defined in the targets\ BSP \hw\ ARM \timer\arm_cortex_m\systick.c file Usecs).

LITE_OS_SEC_TEXT_MINOR VOID LOS_Udelay(UINT32 usecs){ HalDelayUs(usecs); }Copy the code

Continue analyzing the function VOID HalDelayUs(UINT32 USECS). Microseconds are converted to nanoseconds, the current value of nanoseconds is computed, and then the while loop, which uses assembly instructions to operate null, waits for a timeout.

VOID HalDelayUs(UINT32 usecs){ UINT64 tmo = LOS_CurrNanosec() + usecs * OS_SYS_NS_PER_US; while (LOS_CurrNanosec() < tmo) { __asm__ volatile ("nop"); }}Copy the code

2.3.2 LOS_Mdelay() Ms wait

Busy and so on in ms, but can be preempted by higher priority tasks. This function converts the parameter UINT32 msecs milliseconds to subtlety, which requires consideration of overflow.

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);}
Copy the code

summary

This article led us to analyze the LiteOS time management module source code. The time management module provides necessary clock beats for task scheduling, and provides all time-related services to applications, such as time conversion, statistics, and delay functions.

Thanks for reading. If you have any questions or suggestions, please leave a comment at gitee.com/LiteOS/Lite… . To make it easier to find the LiteOS code warehouse, it is recommended to visit gitee.com/LiteOS/Lite… Pay attention to Fork into your account, as shown below, thank you.

Click to follow, the first time to learn about Huawei cloud fresh technology ~