Hongmeng kernel source code analysis (scheduling mechanism)

Note: Based on core open source obscurity analysis, the official source [ kernel_liteos_a ] official documents [ docs ] Reference Document [ Huawei LiteOS ]
author: Hong Meng core enthusiasts, will continue to study obscurity kernel, update blog, so stay tuned. The content only represents personal views, errors are welcome, everyone is welcome to correct and improve. All articles in this series enter to view the  source code analysis of Hongmeng system (general catalog)


This article analyzes the source code of the task scheduling mechanism in detail: ../kernel/base/sched/sched_sq/los_sched.c

table of Contents

It is recommended to read first

Why do you have to learn so many concepts to learn one thing?

State transition diagram of processes and threads

Who will trigger the scheduling work?

What does the source code tell you about the scheduling process?

Please understand the kernel's most beautiful function OsGetTopTask()




It is recommended to read first

It is recommended to read other articles in this series before reading, and enter the source code analysis of Hongmeng system (general catalog)

In order to understand the task scheduling mechanism of this article.

Why do you have to learn so many concepts to learn one thing?

In Hongmeng's kernel, Task and thread can be understood as one thing in a broad sense, but there will definitely be differences in a narrow sense. The difference lies in the different management systems. Task is a concept at the scheduling level, and thread is a concept at the process level. For example, the first function in the main() function, OsSetMainTask(); is to set the startup task, but nothing has started yet, and the Kprocess process has not been created. How can there be threads that everyone understands in the general sense. The follow-up in the narrow sense is explained by Hongmeng Kernel Source Code Analysis (Startup Process). I don't know if you have this kind of experience. In the process of learning something, you have to come into contact with many new concepts, especially in the ecology of Java/android. There are many concept thieves, and many students are trapped in the concept and can't come out. The question is why are so many concepts needed?

Take an example to understand:

If you go to Shenzhen for an interview, the boss asks where are you from? You would say that you are from Jiangxi, Hunan... but not Zhang Quandan from the second group of Zhangjiacun, so who would dare to ask you. But if you participate in a fellow township meeting and someone asks you the same question, you will not say that you are from the Northeast Nata, but you will say Zhang Quandan from the second group of Zhangjiacun. do you understand? Zhang Quandan is still the same Zhang Quandan, but because the scene has changed, your statement must be changed accordingly, otherwise you will not be able to chat happily.

That program design comes from life and belongs to life. Everyone's understanding of programs is to use scenes in life to compare and understand concepts better. What do you say?

At the kernel scheduling level, let's say task, task is the unit of kernel scheduling, and scheduling revolves around it.

State transition diagram of processes and threads

Let's first look at which channels the task is generated from:

There are many channels, which may be a command of the shell, or created by the kernel, and more of a thread created by everyone who writes the application program.

The content of the scheduling is already there, so how can they be scheduled in an orderly manner? Answer: There are 32 process and thread ready queues, each with 32. Why are there 32? The Hongmeng system source code analysis (general catalog) article has detailed instructions, please go through it yourself.

This schematic diagram of process state migration must be understood. Please refer to the official document for thread state migration. It will not be listed one by one, and it will take up too much space.

Note that both processes and threads have only ready queues (blocked thread pendlist is a linked list, and the kernel does not use queues to describe it), but because ready means that the work is ready and waiting for the CPU to execute. There are three situations will be added to the ready queue

 

  • Init→Ready:

    When a process is created or fork, it enters the Init state after getting the process control block and is in the process initialization stage. When the process initialization is completed, the process is inserted into the scheduling queue, and the process enters the ready state.

  • Pend→Ready / Pend→Running:

    When any thread in the blocked process returns to the ready state, the process is added to the ready queue and synchronously changes to the ready state. If a process switch occurs at this time, the process state changes from the ready state to the running state.

  • Running→Ready:

    There are two situations in which the process changes from the running state to the ready state:

  • After a process with a higher priority is created or restored, process scheduling occurs. At this moment, the highest priority process in the ready list becomes the running state, and the originally running process changes from the running state to the ready state.
  • If the scheduling strategy of the process is SCHED_RR, and another process with the same priority is in the ready state, after the time slice of the process is exhausted, the process turns from the running state to the ready state, and another process with the same priority changes from the ready state The state changes to the running state.

Who will trigger the scheduling work?

The ready queue allows the task to be in place, and the progress status flows continuously during its life cycle. What makes the scheduling work and how is it triggered?

The trigger methods I can think of are the following four:

  • Tick ​​(clock management), similar to JAVA's timing task, triggers when the time is up. The system timer is the most important part of the kernel time mechanism. It provides a periodic trigger interrupt mechanism, that is, the system timer automatically triggers the clock interrupt at the frequency of HZ (clock tick rate). When a clock interrupt occurs, the kernel handles it through the clock interrupt handler OsTickHandler. By default, the Hongmeng kernel is triggered once every 10ms and executes the following interrupt functions:
/*
 * 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 *///*kyf 任务扫描,发起调度

#if (LOSCFG_BASE_CORE_SWTMR == YES)
    OsSwtmrScan();
#endif
}

Scan the task and call the task scheduler

  • The second is the interruption caused by various hardware and software interrupts, how to USB plug and unplug, keyboard, mouse and other peripherals.
  • The third is the active interruption of the program, such as the need to apply for other resources during operation, and actively give up control
  • The last one is preemptive scheduling initiated after creating a new task
  • Where will apply for scheduling? Look at a picture.

Here is the OsCopyProcess() in the figure below, which is the main function of the fork process. It can be seen that a scheduling is applied for immediately after the fork.

LITE_OS_SEC_TEXT INT32 LOS_Fork(UINT32 flags, const CHAR *name, const TSK_ENTRY_FUNC entry, UINT32 stackSize)
{
    UINT32 cloneFlag = CLONE_PARENT | CLONE_THREAD | CLONE_VFORK | CLONE_FILES;

    if (flags & (~cloneFlag)) {
        PRINT_WARN("Clone dont support some flags!\n");
    }

    flags |= CLONE_FILES;
    return OsCopyProcess(cloneFlag & flags, name, (UINTPTR)entry, stackSize);
}

STATIC INT32 OsCopyProcess(UINT32 flags, const CHAR *name, UINTPTR sp, UINT32 size)
{
    UINT32 intSave, ret, processID;
    LosProcessCB *run = OsCurrProcessGet();

    LosProcessCB *child = OsGetFreePCB();
    if (child == NULL) {
        return -LOS_EAGAIN;
    }
    processID = child->processID;

    ret = OsForkInitPCB(flags, child, name, sp, size);
    if (ret != LOS_OK) {
        goto ERROR_INIT;
    }

    ret = OsCopyProcessResources(flags, child, run);
    if (ret != LOS_OK) {
        goto ERROR_TASK;
    }

    ret = OsChildSetProcessGroupAndSched(child, run);
    if (ret != LOS_OK) {
        goto ERROR_TASK;
    }

    LOS_MpSchedule(OS_MP_CPU_ALL);
    if (OS_SCHEDULER_ACTIVE) {
        LOS_Schedule();//*kyf 申请调度
    }

    return processID;

ERROR_TASK:
    SCHEDULER_LOCK(intSave);
    (VOID)OsTaskDeleteUnsafe(OS_TCB_FROM_TID(child->threadGroupID), OS_PRO_EXIT_OK, intSave);
ERROR_INIT:
    OsDeInitPCB(child);
    return -ret;
}

It turns out that creating a process is so simple, it really is COPY! 

What does the source code tell you about the scheduling process?

The above is the information that needs to be known in advance. Next, go directly to the source code to see the scheduling process. The file has three functions, mainly this:

VOID OsSchedResched(VOID)
{
    LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//*kyf 调度过程要上锁
    newTask = OsGetTopTask(); //*kyf 获取最高优先级任务
    OsSchedSwitchProcess(runProcess, newProcess);//*kyf 切换运行的进程
    (VOID)OsTaskSwitchCheck(runTask, newTask);
    OsCurrTaskSet((VOID*)newTask);//*kyf 设置当前任务
    if (OsProcessIsUserMode(newProcess)) {
        OsCurrUserTaskSet(newTask->userArea);//*kyf 运行空间
    }
    /* do the task context switch */
    OsTaskSchedule(newTask, runTask); //*kyf 切换任务上下文
}

The function is a bit long. The author left the most important lines. Just look at these lines. The process is as follows:

  1.  The scheduling process requires a spin lock, and no interrupts are allowed. Yes, it is said that nothing can be interrupted, otherwise the consequences will be too serious. This is the operation of the kernel switching processes and threads.
  2. Find the highest priority task in the ready queue
  3. Switch process, that is, the process to which the task/thread belongs is the current process
  4. Set it as current task
  5. User mode needs to set the running space, because the space of each process is different
  6. The most important thing is to switch the task context. The parameters are the new and old tasks, one to save the scene and the other to restore the scene.

What is task context? Look at other articles of Hongmeng System Source Code Analysis (General Catalog) , there are special introductions. What I want to explain here is that at the CPU level, it only recognizes the task context! No code can be seen here, because it is related to the CPU, and different CPUs need to adapt to different assembly codes, so these assembly codes will not appear in a general project. Please pay attention to the follow-up Hongmeng kernel source code analysis (assembly instructions).

Please understand the kernel's most beautiful function OsGetTopTask()

Finally, leave an assignment. After reading what the author thinks is the most beautiful function of the kernel, you will understand what the ready queue is about.

LITE_OS_SEC_TEXT_MINOR LosTaskCB *OsGetTopTask(VOID)
{
    UINT32 priority, processPriority;
    UINT32 bitmap;
    UINT32 processBitmap;
    LosTaskCB *newTask = NULL;
#if (LOSCFG_KERNEL_SMP == YES)
    UINT32 cpuid = ArchCurrCpuid();
#endif
    LosProcessCB *processCB = NULL;
    processBitmap = g_priQueueBitmap;
    while (processBitmap) {
        processPriority = CLZ(processBitmap);
        LOS_DL_LIST_FOR_EACH_ENTRY(processCB, &g_priQueueList[processPriority], LosProcessCB, pendList) {
            bitmap = processCB->threadScheduleMap;
            while (bitmap) {
                priority = CLZ(bitmap);
                LOS_DL_LIST_FOR_EACH_ENTRY(newTask, &processCB->threadPriQueueList[priority], LosTaskCB, pendList) {
#if (LOSCFG_KERNEL_SMP == YES)
                    if (newTask->cpuAffiMask & (1U << cpuid)) {
#endif
                        newTask->taskStatus &= ~OS_TASK_STATUS_READY;
                        OsPriQueueDequeue(processCB->threadPriQueueList,
                                          &processCB->threadScheduleMap,
                                          &newTask->pendList);
                        OsDequeEmptySchedMap(processCB);
                        goto OUT;
#if (LOSCFG_KERNEL_SMP == YES)
                    }
#endif
                }
                bitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - priority - 1));
            }
        }
        processBitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - processPriority - 1));
    }

OUT:
    return newTask;
}

#ifdef __cplusplus
#if __cplusplus
}

Let's write so much for this article. Although the Hongmeng kernel source code has few files, the relationship is extremely complicated. It is hard and happy to disassemble the source code. It is even more painful and happy to write a document and share it with everyone. I like it. Just like it. Thank you for your support!

Guess you like

Origin blog.csdn.net/kuangyufei/article/details/108705968