!import[\zzz\mdmerge\head.md] 调度算法让CPU在不同的进程和任务之间切换穿梭,但问题是 ## **调度算法的驱动力在哪里? 谁负责推着调度算法走?** 有多股力量在推动,但最大的推力应该是:系统时钟. 我们整个学生阶段有个东西很重要,就是校园铃声. 它控制着上课,下课,吃饭,睡觉的节奏.没有它学校的管理就乱套了,老师拖课想拖多久就多久,那可不行,下课铃声一响就是在告诉老师时间到了,该停止了让学生HAPPY去了. 应用开发人员对定时器不会陌生,电商平台的每晚10点准时的秒杀活动没有定时任务是不可能实现的,那内核也一定要提供这样的功能被封装给应用层使用,那这个功能在内核层的表述就是系统时钟,它发出来的节奏叫tick(节拍). 对应张大爷的故事:系统时钟就是场馆的那个大钟,很准时, 每10分响一次,一次就是一个Tick(节拍) ### **鸿蒙内核的节拍频率是怎样的** ```cpp /** * @ingroup los_config * Number of Ticks in one second */ #ifndef LOSCFG_BASE_CORE_TICK_PER_SECOND #define LOSCFG_BASE_CORE_TICK_PER_SECOND 100 //默认每秒100次触发,当然这是可以改的 #endif ``` 每秒100个tick,时间单位为10毫秒, 即每秒调用时钟中断处理程序100次. ### **时钟中断处理程序是怎样的?** ```cpp /* * Description : Tick interruption handler *///节拍中断处理函数 ,鸿蒙默认10ms触发一次 LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) { //... OsTimesliceCheck();//进程和任务的时间片检查 OsTaskScan(); /* task timeout scan *///任务扫描 #if (LOSCFG_BASE_CORE_SWTMR == YES) OsSwtmrScan();//定时器扫描,看是否有超时的定时器 #endif } ``` 它主要干了三件事情 ### **第一:检查当前任务的时间片,任务执行一次分配多少时间呢?答案是2个时间片,即 20ms.** ```cpp #ifndef LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT #define LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT 2 //2个时间片,20ms #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();//任务时间片用完,发起调度 } } } ``` ### **第二:扫描任务,主要是检查被阻塞的任务是否可以被重新调度** ```cpp 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;//只处理这个游标上的链表,因为系统对超时任务都已经规链表了. //当任务因超时而挂起时,任务块处于超时排序链接上,(每个cpu)和ipc(互斥锁、扫描电镜等)的块同时被唤醒 /*不管是超时还是相应的ipc,它都在等待。现在使用synchronize sortlink precedure,因此整个任务扫描需要保护,防止另一个核心同时删除sortlink。 * 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);//拿本次Tick对应链表的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);//核间通讯,给所有CPU发送调度信号 LOS_Schedule();//开始调度 } } ``` ### **第三:定时器扫描,看是否有超时的定时器** ```cpp /* * Description: Tick interrupt interface module of software timer * Return : LOS_OK on success or error code on failure *///OsSwtmrScan 由系统时钟中断处理函数调用 LITE_OS_SEC_TEXT VOID OsSwtmrScan(VOID)//扫描定时器,如果碰到超时的,就放入超时队列 { SortLinkList *sortList = NULL; SWTMR_CTRL_S *swtmr = NULL; SwtmrHandlerItemPtr swtmrHandler = NULL; LOS_DL_LIST *listObject = NULL; SortLinkAttribute* swtmrSortLink = &OsPercpuGet()->swtmrSortLink;//拿到当前CPU的定时器链表 swtmrSortLink->cursor = (swtmrSortLink->cursor + 1) & OS_TSK_SORTLINK_MASK; listObject = swtmrSortLink->sortLink + swtmrSortLink->cursor; //由于swtmr是在特定的sortlink中,所以需要很小心的处理它,但其他CPU Core仍然有机会处理它,比如停止计时器 /* * it needs to be carefully coped with, since the swtmr is in specific sortlink * while other cores still has the chance to process it, like stop the timer. */ LOS_SpinLock(&g_swtmrSpin); if (LOS_ListEmpty(listObject)) { LOS_SpinUnlock(&g_swtmrSpin); return; } sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode); ROLLNUM_DEC(sortList->idxRollNum); while (ROLLNUM(sortList->idxRollNum) == 0) { sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode); LOS_ListDelete(&sortList->sortLinkNode); swtmr = LOS_DL_LIST_ENTRY(sortList, SWTMR_CTRL_S, stSortList); swtmrHandler = (SwtmrHandlerItemPtr)LOS_MemboxAlloc(g_swtmrHandlerPool);//取出一个可用的软时钟处理项 if (swtmrHandler != NULL) { swtmrHandler->handler = swtmr->pfnHandler; swtmrHandler->arg = swtmr->uwArg; if (LOS_QueueWrite(OsPercpuGet()->swtmrHandlerQueue, swtmrHandler, sizeof(CHAR *), LOS_NO_WAIT)) { (VOID)LOS_MemboxFree(g_swtmrHandlerPool, swtmrHandler); } } if (swtmr->ucMode == LOS_SWTMR_MODE_ONCE) { OsSwtmrDelete(swtmr); if (swtmr->usTimerID < (OS_SWTMR_MAX_TIMERID - LOSCFG_BASE_CORE_SWTMR_LIMIT)) { swtmr->usTimerID += LOSCFG_BASE_CORE_SWTMR_LIMIT; } else { swtmr->usTimerID %= LOSCFG_BASE_CORE_SWTMR_LIMIT; } } else if (swtmr->ucMode == LOS_SWTMR_MODE_NO_SELFDELETE) { swtmr->ucState = OS_SWTMR_STATUS_CREATED; } else { swtmr->ucOverrun++; OsSwtmrStart(swtmr); } if (LOS_ListEmpty(listObject)) { break; } sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode); } LOS_SpinUnlock(&g_swtmrSpin); } ``` ### **最后看调度算法的实现** ```cpp //调度算法的实现 VOID OsSchedResched(VOID) { LosTaskCB *runTask = NULL; LosTaskCB *newTask = NULL; LosProcessCB *runProcess = NULL; LosProcessCB *newProcess = NULL; LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//必须持有任务自旋锁,自旋锁是不是进程层面去抢锁,而是CPU各自核之间去争夺锁 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);//通过进程ID索引拿到进程实体 newProcess = OS_PCB_FROM_PID(newTask->processID);//同上 OsSchedSwitchProcess(runProcess, newProcess);//切换进程,里面主要涉及进程空间的切换,也就是MMU的上下文切换. #if (LOSCFG_KERNEL_SMP == YES)//CPU多核的情况 /* mask new running task's owner processor */ runTask->currCpu = OS_TASK_INVALID_CPUID;//当前任务不占用CPU newTask->currCpu = ArchCurrCpuid();//让新任务占用CPU #endif (VOID)OsTaskSwitchCheck(runTask, newTask);//切换task的检查 #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;//给新任务时间片 默认 20ms } OsCurrTaskSet((VOID*)newTask);//设置新的task为CPU核的当前任务 if (OsProcessIsUserMode(newProcess)) {//用户模式下会怎么样? OsCurrUserTaskSet(newTask->userArea);//设置task栈空间 } 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);//切换任务上下文,注意OsTaskSchedule是一个汇编函数 见于 los_dispatch.s } ``` 最后的 OsTaskSchedule 是由汇编实现的,后续会详细讲解各个汇编文件,除了tick 会触发调度,还有哪些情况会触发调度? !import[\zzz\mdmerge\foot.md] !export[\zzz\md\docs\guide\鸿蒙内核源码分析(时钟任务篇).md]