鸿蒙内核源码分析(tick 时钟管理篇)

提示:本文基于开源鸿蒙内核分析,官方源码【kernel_liteos_a】官方文档【docs】参考文档【Huawei LiteOS
本文作者:鸿蒙内核发烧友,将持续研究鸿蒙内核,更新博文,敬请关注。内容仅代表个人观点,错误之处,欢迎大家指正完善。本系列全部文章进入 查看 鸿蒙系统源码分析(总目录)


目录

多久Tick一次?

在哪里设置tick的回调函数?

OsTaskScan()

除了tick 会触发调度,还有哪些情况会触发调度?


时钟管理模块很简单,但却有内核最重要的代码段 OsTickHandler(),这是干嘛的,可以理解为 JAVA的定时任务,但这是系统内核的定时器。因鸿蒙目前开放的是 轻量级的内核 lite os (LOS),所以tick的频率不会太高

详见代码: los_tick.c     los_timeslice.c

多久Tick一次?

/**
 * @ingroup los_config
 * Number of Ticks in one second
 */
#ifndef LOSCFG_BASE_CORE_TICK_PER_SECOND
#define LOSCFG_BASE_CORE_TICK_PER_SECOND 100 //*kfy 默认每秒100次触发,当然这是可以改的
#endif

每秒100 tick ,即每秒100次调用时钟中断处理程序, 时间片单位为10ms

一次任务分配多少时间给进程执行呢?答案是 2个时间片,即 20ms

/**
 * @ingroup los_config
 * Longest execution time of tasks with the same priorities
 */
#ifndef LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT
#define LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT 2
#endif

/**
 * @ingroup los_process
 * Hold the time slice process
 */
#define OS_PROCESS_SCHED_RR_INTERVAL     LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT

用完需要可以再分配,但一次就是只给2个时间片,详细代码: OsSchedResched(VOID)

VOID OsSchedResched(VOID)
{
    LosTaskCB *runTask = NULL;
    LosTaskCB *newTask = NULL;
    LosProcessCB *runProcess = NULL;
    LosProcessCB *newProcess = NULL;

    LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));

    if (!OsPreemptableInSched()) {
        return;
    }

    runTask = OsCurrTaskGet();
    newTask = OsGetTopTask();

    /* always be able to get one task */
    LOS_ASSERT(newTask != NULL);

    if (runTask == newTask) {
        return;
    }

    runTask->taskStatus &= ~OS_TASK_STATUS_RUNNING;
    newTask->taskStatus |= OS_TASK_STATUS_RUNNING;

    runProcess = OS_PCB_FROM_PID(runTask->processID);
    newProcess = OS_PCB_FROM_PID(newTask->processID);

    OsSchedSwitchProcess(runProcess, newProcess);

#if (LOSCFG_KERNEL_SMP == YES)
    /* mask new running task's owner processor */
    runTask->currCpu = OS_TASK_INVALID_CPUID;
    newTask->currCpu = ArchCurrCpuid();
#endif

    (VOID)OsTaskSwitchCheck(runTask, newTask);

#if (LOSCFG_KERNEL_SCHED_STATISTICS == YES)
    OsSchedStatistics(runTask, newTask);
#endif

    if ((newTask->timeSlice == 0) && (newTask->policy == LOS_SCHED_RR)) {
        newTask->timeSlice = LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT; //只给两个时间片
    }

    OsCurrTaskSet((VOID*)newTask);

    if (OsProcessIsUserMode(newProcess)) {
        OsCurrUserTaskSet(newTask->userArea);
    }

    PRINT_TRACE("cpu%d run process name: (%s) pid: %d status: %x threadMap: %x task name: (%s) tid: %d status: %x ->\n"
                "     new process name: (%s) pid: %d status: %x threadMap: %x task name: (%s) tid: %d status: %x!\n",
                ArchCurrCpuid(),
                runProcess->processName, runProcess->processID, runProcess->processStatus,
                runProcess->threadScheduleMap, runTask->taskName,  runTask->taskID, runTask->taskStatus,
                newProcess->processName, newProcess->processID, newProcess->processStatus,
                newProcess->threadScheduleMap, newTask->taskName, newTask->taskID, newTask->taskStatus);

    /* do the task context switch */
    OsTaskSchedule(newTask, runTask);
}

在哪里设置tick的回调函数?

从main中可以看到tick 的初始化和中断服务程序的注册

// 中断处理函数
VOID OsTickEntry(VOID)
{
    OsTickHandler(); //最最关键函数

    /* clear private timer */
    g_privateTimer->intStatus = 0x01;
}


// 由 main 函数调用,注册中断处理函数 OsTickEntry
VOID HalClockInit(VOID)
{
    UINT32 ret;

    ret =  LOS_HwiCreate(PRVTIMER_INT_NUM, 0xa0, 0, OsTickEntry, NULL);
    if (ret != LOS_OK) {
        PRINT_ERR("%s, %d create tick irq failed, ret:0x%x\n", __FUNCTION__, __LINE__, ret);
    }
}

OsTickHandler 是tick 的中断处理程序,其中完成了时间片的检查和任务的扫描。

/*
 * Description : Tick interruption handler
 */
LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
{
    UINT32 intSave;

    TICK_LOCK(intSave);
    g_tickCount[ArchCurrCpuid()]++;
    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
}
LITE_OS_SEC_TEXT VOID OsTimesliceCheck(VOID)
{
    LosTaskCB *runTask = NULL;
    LosProcessCB *runProcess = OsCurrProcessGet();
    if (runProcess->policy != LOS_SCHED_RR) {
        goto SCHED_TASK;
    }

    if (runProcess->timeSlice != 0) {
        runProcess->timeSlice--;//进程时间片减少一次
        if (runProcess->timeSlice == 0) {
            LOS_Schedule();//进程时间片用完,发起调度
        }
    }

SCHED_TASK:
    runTask = OsCurrTaskGet();
    if (runTask->policy != LOS_SCHED_RR) {
        return;
    }

    if (runTask->timeSlice != 0) {
        runTask->timeSlice--;//对应任务时间片也减少一次
        if (runTask->timeSlice == 0) {
            LOS_Schedule();
        }
    }
}

OsTaskScan()

OsTaskScan()不断查task的状态,有任务就去执行,毫不夸张的可以说是 进程有序执行的源动力之所在!

LITE_OS_SEC_TEXT VOID OsTaskScan(VOID)
{
    SortLinkList *sortList = NULL;
    LosTaskCB *taskCB = NULL;
    BOOL needSchedule = FALSE;
    UINT16 tempStatus;
    LOS_DL_LIST *listObject = NULL;
    SortLinkAttribute *taskSortLink = NULL;

    taskSortLink = &OsPercpuGet()->taskSortLink;
    taskSortLink->cursor = (taskSortLink->cursor + 1) & OS_TSK_SORTLINK_MASK;
    listObject = taskSortLink->sortLink + taskSortLink->cursor;

    /*
     * When task is pended with timeout, the task block is on the timeout sortlink
     * (per cpu) and ipc(mutex,sem and etc.)'s block at the same time, it can be waken
     * up by either timeout or corresponding ipc it's waiting.
     *
     * Now synchronize sortlink preocedure is used, therefore the whole task scan needs
     * to be protected, preventing another core from doing sortlink deletion at same time.
     */
    LOS_SpinLock(&g_taskSpin);

    if (LOS_ListEmpty(listObject)) {
        LOS_SpinUnlock(&g_taskSpin);
        return;
    }
    sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
    ROLLNUM_DEC(sortList->idxRollNum);

    while (ROLLNUM(sortList->idxRollNum) == 0) {
        LOS_ListDelete(&sortList->sortLinkNode);
        taskCB = LOS_DL_LIST_ENTRY(sortList, LosTaskCB, sortList);
        taskCB->taskStatus &= ~OS_TASK_STATUS_PEND_TIME;
        tempStatus = taskCB->taskStatus;
        if (tempStatus & OS_TASK_STATUS_PEND) {
            taskCB->taskStatus &= ~OS_TASK_STATUS_PEND;
#if (LOSCFG_KERNEL_LITEIPC == YES)
            taskCB->ipcStatus &= ~IPC_THREAD_STATUS_PEND;
#endif
            taskCB->taskStatus |= OS_TASK_STATUS_TIMEOUT;
            LOS_ListDelete(&taskCB->pendList);
            taskCB->taskSem = NULL;
            taskCB->taskMux = NULL;
        } else {
            taskCB->taskStatus &= ~OS_TASK_STATUS_DELAY;
        }

        if (!(tempStatus & OS_TASK_STATUS_SUSPEND)) {
            OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB, OS_PROCESS_STATUS_PEND);
            needSchedule = TRUE;
        }

        if (LOS_ListEmpty(listObject)) {
            break;
        }

        sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
    }

    LOS_SpinUnlock(&g_taskSpin);

    if (needSchedule != FALSE) {
        LOS_MpSchedule(OS_MP_CPU_ALL);
        LOS_Schedule();
    }
}

除了tick 会触发调度,还有哪些情况会触发调度?

想好了请在评论区里留言 ,本系列更多文章进入 鸿蒙系统源码分析(总目录) 查看

猜你喜欢

转载自blog.csdn.net/kuangyufei/article/details/108603468