Abstract: This article led everyone to analyze the source code of the time management module of the Hongmeng Light Kernel. The time management module provides the necessary clock beats for task scheduling, and provides all time-related services to the application, such as time conversion, statistics, and delay functions.

This article is shared from the Huawei Cloud Community " Light Kernel M Core Source Code Analysis Series Six Time Management ", the original author: zhushy.

This article will continue to analyze the Tick and time-related source code, and introduce the time management module of Hongmeng Light Kernel to readers. The source code involved in this article, taking the OpenHarmony LiteOS-M kernel as an example, can be obtained from the open source site https://gitee.com/openharmony/kernel_liteos_m .

The time management module is based on the system clock and can be divided into two parts. One part is the 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 and statistical 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 a long integer. The period of the output pulse is called a "clock tick", also known as a time stamp or tick. Tick is the basic time unit of the operating system, determined by the number of ticks per second configured by the user. If the user configures the number of ticks per second as 1000, then 1 tick is equal to the duration of 1ms. Another timing unit is Cycle, which is the smallest timing unit of the system. The duration of cycle is determined by the frequency of the system's main clock. The frequency of the system's main clock is the number of cycles per second. For a 216 MHz CPU, 216 million cycles are generated in 1 second.

The user counts in seconds and milliseconds, while the operating system uses Tick as the unit. When the user needs to perform operations on the system, such as task suspension, delay, etc., the time management module can be used to perform Tick and seconds/milliseconds. Conversion.

Next, we analyze the source code of the time management module. If the development board is involved, take the development board project targets\cortex-m7_nucleo_f767zi_gcc\ as an example for source code analysis.

1. Time management initialization and startup

We first look at the relevant configuration of the time management module, and then analyze how to initialize and how to start.

1.1 Time management related configuration

The time management module involves three configuration items, the system clock OS_SYS_CLOCK, the number of ticks per second LOSCFG_BASE_CORE_TICK_PER_SECOND two configuration options, and the macro LOSCFG_BASE_CORE_TICK_HW_TIME. LOSCFG_BASE_CORE_TICK_HW_TIME is turned off by default. When it is turned on, you need to provide a custom function VOID platform_tick_handler(VOID) to perform custom operations in the Tick interrupt handler. These configuration items are defined in the file target_config.h in the project directory of the template development board. For example, the file targets\cortex-m7_nucleo_f767zi_gcc\target_config.h is defined as follows:


#define OS_SYS_CLOCK                                        96000000
#define LOSCFG_BASE_CORE_TICK_PER_SECOND                    (1000UL)
#define LOSCFG_BASE_CORE_TICK_HW_TIME                       0

1.2 Time management initialization and startup

The function INT32 main(VOID) will call the function UINT32 LOS_Start(VOID) in kernel\src\los_init.c to start the system, and this function will call the startup scheduling function UINT32 HalStartSchedule(OS_TICK_HANDLER handler). The source code is as follows:

LITE_OS_SEC_TEXT_INIT UINT32 LOS_Start(VOID)
{
    return HalStartSchedule(OsTickHandler);
}

The function UINT32 HalTickStart(OS_TICK_HANDLER *handler) is defined in kernel\arch\arm\cortex-m7\gcc\los_context.c, the source code is as follows. The function parameter is the Tick interrupt handling function OsTickHandler(), which will be analyzed later. ⑴ The code continues to call the function and further calls the function HalTickStart (handler) to set the Tick interrupt start. (2) The assembly function HalStartToRun will be called to start running the system, and the subsequent task scheduling series will analyze the assembly function in detail.

LITE_OS_SEC_TEXT_INIT UINT32 HalStartSchedule(OS_TICK_HANDLER handler)
{
    UINT32 ret;
⑴  ret = HalTickStart(handler);
    if (ret != LOS_OK) {
        return ret;
    }
⑵  HalStartToRun();
    return LOS_OK; /* never return */
}

The function HalTickStart(handler) is defined in the file kernel\arch\arm\cortex-m7\gcc\los_timer.c, the source code is as follows, we analyze the code implementation of the function. (1) Verify the legality of the configuration items of the time management module. When the macro LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT is turned on, the system-defined interrupt will be used. The code at ⑵ will be executed, and the function OsSetVector() defined in the file kernel\arch\arm\cortex-m7\gcc\los_interrupt.c will be called to set the interrupt vector. This function will be analyzed in detail in the interrupt series. (3) The global variable g_sysClock is set as the system clock, g_cyclesPerTick is the number of cycles corresponding to each tick, and g_ullTickCount is initialized to 0, indicating the number of system tick interrupts. ⑷ Call the inline function uint32_t SysTick_Config(uint32_t ticks) defined in the files targets\cortex-m7_nucleo_f767zi_gcc\Drivers\CMSIS\Include\core_cm7.h to initialize and start the system timer Systick and interrupt.

WEAK UINT32 HalTickStart(OS_TICK_HANDLER *handler)
{
    UINT32 ret;

⑴  if ((OS_SYS_CLOCK == 0) ||
        (LOSCFG_BASE_CORE_TICK_PER_SECOND == 0) ||
        (LOSCFG_BASE_CORE_TICK_PER_SECOND > OS_SYS_CLOCK)) {
        return LOS_ERRNO_TICK_CFG_INVALID;
    }

#if (LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT == 1)
#if (OS_HWI_WITH_ARG == 1)
    OsSetVector(SysTick_IRQn, (HWI_PROC_FUNC)handler, NULL);
#else
⑵  OsSetVector(SysTick_IRQn, (HWI_PROC_FUNC)handler);
#endif
#endif

⑶  g_sysClock = OS_SYS_CLOCK;
    g_cyclesPerTick = OS_SYS_CLOCK / LOSCFG_BASE_CORE_TICK_PER_SECOND;
    g_ullTickCount = 0;

⑷  ret = SysTick_Config(g_cyclesPerTick);
    if (ret == 1) {
        return LOS_ERRNO_TICK_PER_SEC_TOO_SMALL;
    }

    return LOS_OK;
}

1.3 Tick interrupt handler function OsTickHandler()

The function VOID OsTickHandler (VOID) defined in the file kernel\src\los_tick.c is the most frequently executed function in the time management module. This function is called whenever a Tick interrupt occurs. We analyze the source code of this function. (1) If the macro LOSCFG_BASE_CORE_TICK_HW_TIME is turned on, the customized tick handling function platform_tick_handler() will be called, which is not turned on by default. (2) The global variable g_ullTickCount will be updated. (3) If the macro LOSCFG_BASE_CORE_TIMESLICE is turned on, the time slice of the currently running task will be checked, and the function OsTimesliceCheck() will be analyzed in detail in the subsequent task module. ⑷ will traverse the sorted list of tasks to check whether there are overtime tasks. ⑸ If the timer feature is supported, it will check whether the timer has expired.

The source code is as follows:

LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
{
#if (LOSCFG_BASE_CORE_TICK_HW_TIME == 1)
⑴  platform_tick_handler();
#endif

⑵  g_ullTickCount++;

#if (LOSCFG_BASE_CORE_TIMESLICE == 1)
⑶  OsTimesliceCheck();
#endif

⑷   OsTaskScan();  // task timeout scan

#if (LOSCFG_BASE_CORE_SWTMR == 1)
⑸  (VOID)OsSwtmrScan();
#endif
}

2. Common operations of LiteOS kernel time management

Time management provides the following functions, time conversion, time statistics, etc. These functions are defined in the file kernel\src\los_tick.c. We analyze the source code implementation of these operations.

2.1 Time conversion operation

2.1.1 Convert milliseconds to Tick

The function UINT32 LOS_MS2Tick(UINT32 millisec) can convert the input parameter UINT32 millisec into the number of ticks. OS_SYS_MS_PER_SECOND in the code, that is, 1 second is equal to 1000 milliseconds. Time conversion is also relatively simple, know how many ticks in a second, divide by OS_SYS_MS_PER_SECOND, get how many ticks in 1 millisecond, then multiply by millisec, calculate the result value of the number of ticks and return.

LITE_OS_SEC_TEXT_MINOR UINT32 LOS_MS2Tick(UINT32 millisec)
{
    if (millisec == OS_NULL_INT) {
        return OS_NULL_INT;
    }

    return ((UINT64)millisec * LOSCFG_BASE_CORE_TICK_PER_SECOND) / OS_SYS_MS_PER_SECOND;
}

2.1.2 Tick converted to milliseconds

The function UINT32 LOS_Tick2MS(UINT32 tick) converts the number of input parameters Tick into milliseconds. Time conversion is also relatively simple. Divide the number of ticks by the number of ticks per second LOSCFG_BASE_CORE_TICK_PER_SECOND, calculate how many seconds, and then convert it to milliseconds, calculate the result value and return it.

LITE_OS_SEC_TEXT_MINOR UINT32 LOS_Tick2MS(UINT32 ticks)
{
    return ((UINT64)ticks * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND;
}

2.1.3 The number of cycles converted to milliseconds

Before introducing the conversion function, let's look at the next CpuTick structure. The structure is relatively simple. There are only two members, which represent the high and low 32-bit values of a UINT64 type data.

typedef struct tagCpuTick {
    UINT32 cntHi; /* < 一个64位数值的高32位 */
    UINT32 cntLo; /* < 一个64位数值的低32位 */
} CpuTick;

Continue to look at the conversion function OsCpuTick2MS(), which can convert the cycle number represented by the CpuTick type to the corresponding number of milliseconds, and output the high and low 32-bit values of the millisecond data. Look at the specific code, ⑴ check whether the parameter is a null pointer, ⑵ check whether the system clock is configured. (3) Convert the cycle number represented by the CpuTick structure into UINT64 type data. ⑷ Perform numerical calculation at (DOUBLE)g_sysClock / OS_SYS_MS_PER_SECOND to get the number of cycles per millisecond, and then divide it with tmpCpuTick to get the number of milliseconds corresponding to the number of cycles. ⑸ Convert the DOUBLE type to UINT64 type, and then execute ⑹ to assign the high and low 64 bits of the result value to msLo and msHi respectively.

LITE_OS_SEC_TEXT_INIT UINT32 OsCpuTick2MS(CpuTick *cpuTick, UINT32 *msHi, UINT32 *msLo)
{
    UINT64 tmpCpuTick;
    DOUBLE temp;

⑴  if ((cpuTick == NULL) || (msHi == NULL) || (msLo == NULL)) {
        return LOS_ERRNO_SYS_PTR_NULL;
    }

⑵  if (g_sysClock == 0) {
        return LOS_ERRNO_SYS_CLOCK_INVALID;
    }
⑶  tmpCpuTick = ((UINT64)cpuTick->cntHi << OS_SYS_MV_32_BIT) | cpuTick->cntLo;
⑷  temp = tmpCpuTick / ((DOUBLE)g_sysClock / OS_SYS_MS_PER_SECOND);

    tmpCpuTick = (UINT64)temp;

    *msLo = (UINT32)tmpCpuTick;
    *msHi = (UINT32)(tmpCpuTick >> OS_SYS_MV_32_BIT);

    return LOS_OK;
}

2.1.4 The number of cycles converted to microseconds

The conversion function OsCpuTick2US(), which can convert the cycle number represented by the CpuTick type to the corresponding number of milliseconds, and output the high and low 32-bit values of the millisecond data. This function is similar to OsCpuTick2MS(), just read it yourself.

LITE_OS_SEC_TEXT_INIT UINT32 OsCpuTick2US(CpuTick *cpuTick, UINT32 *usHi, UINT32 *usLo)
{
    UINT64 tmpCpuTick;
    DOUBLE temp;

    if ((cpuTick == NULL) || (usHi == NULL) || (usLo == NULL)) {
        return LOS_ERRNO_SYS_PTR_NULL;
    }

    if (g_sysClock == 0) {
        return LOS_ERRNO_SYS_CLOCK_INVALID;
    }
    tmpCpuTick = ((UINT64)cpuTick->cntHi << OS_SYS_MV_32_BIT) | cpuTick->cntLo;
    temp = tmpCpuTick / ((DOUBLE)g_sysClock / OS_SYS_US_PER_SECOND);

    tmpCpuTick = (UINT64)temp;

    *usLo = (UINT32)tmpCpuTick;
    *usHi = (UINT32)(tmpCpuTick >> OS_SYS_MV_32_BIT);

    return LOS_OK;
}

2.2 Time statistics operation

2.2.1 Get how many cycles each tick is equal to

The function UINT32 LOS_CyclePerTickGet(VOID) calculates how many cycles one tick is equal to. The g_sysClock system clock indicates how many cycles are in 1 second, and how many ticks are LOSCFG_BASE_CORE_TICK_PER_SECOND in a second. Divide and calculate how many cycles are in 1 tick, that is, g_cyclesPerTick = g_sysClock / LOSCFG_BASE_CORE_TICK_PER_SECOND.

LITE_OS_SEC_TEXT_MINOR UINT32 LOS_CyclePerTickGet(VOID)
{
    return g_cyclesPerTick;
}

2.2.2 Get the number of ticks since the system started

The UINT64 LOS_TickCountGet(VOID) function counts the number of Tick interrupts since the system was started. It should be noted that counting is not performed when the interrupt is turned off, and it cannot be used as an accurate time. Each time a Tick interrupt occurs, the g_ullTickCount data is updated in the function VOID OsTickHandler (VOID).

LITE_OS_SEC_TEXT_MINOR UINT64 LOS_TickCountGet(VOID)
{
    return g_ullTickCount;
}

2.2.3 Get system clock

UINT32 LOS_SysClockGet(VOID) function gets the configured system clock.

UINT32 LOS_SysClockGet(VOID)
{
    return g_sysClock;
}

2.2.4 Get the number of cycles since the system started

The function VOID HalGetCpuCycle (UINT32 cntHi, UINT32 cntLo) is defined in the file kernel\arch\arm\cortex-m7\gcc\los_timer.c. This function gets the number of cycles since the system started. The returned result is returned as the high and low 32-bit unsigned values UINT32 cntHi, UINT32 cntLo respectively.

Let's look at the source code of this function. Turn off the interrupt first, and then get the number of ticks since the start-up at (1). (2) Obtain hwCycle by reading the current value register SysTick Current Value Register. (3) When the TICK_CHECK bit of the Interrupt Control and State Register Interrupt Control and State Register is 1, it means that the systick interrupt is suspended, the tick is not counted, and it needs to be calibrated by one. (4) According to swTick, g_cyclesPerTick and hwCycle, calculate the number of cycles since the system was started. ⑸ Get the high and low 32-bit unsigned value of the Cycle number, then open the interrupt and return.

LITE_OS_SEC_TEXT_MINOR VOID HalGetCpuCycle(UINT32 *cntHi, UINT32 *cntLo)
{
    UINT64 swTick;
    UINT64 cycle;
    UINT32 hwCycle;
    UINTPTR intSave;

    intSave = LOS_IntLock();

⑴  swTick = g_ullTickCount;
⑵  hwCycle = SysTick->VAL;

⑶  if ((SCB->ICSR & TICK_CHECK) != 0) {
        hwCycle = SysTick->VAL;
        swTick++;
    }

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

⑸  *cntHi = cycle >> SHIFT_32_BIT;
    *cntLo = cycle & CYCLE_CHECK;

    LOS_IntRestore(intSave);

    return;
}

summary

This article led everyone to analyze the source code of the time management module of the Hongmeng Light Kernel. The time management module provides the necessary clock beats for task scheduling, and provides all time-related services to the application, such as time conversion, statistics, and delay functions. More sharing articles will be launched in the follow-up, so stay tuned. You are also welcome to share your experience of learning and using the light kernel. If you have any questions or suggestions, you can leave a message to us: 160c2d1b97eb52 https://gitee.com/openharmony /kernel_liteos_m/issues . light kernel code warehouse easier, it is recommended to visit 160c2d1b97eb62 https://gitee.com/openharmony/kernel_liteos_m , follow Watch, like Star, and Fork to your account, thank you.

Click to follow and learn about Huawei Cloud's fresh technology for the first time~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量