鸿蒙内核源码分析(时钟任务篇).md 12.5 KB
Newer Older
1 2
!import[\zzz\mdmerge\head.md]

3
调度算法让CPU在不同的进程和任务之间切换穿梭,但问题是
4

5
## **调度算法的驱动力在哪里? 谁负责推着调度算法走?**
6

7
有多股力量在推动,但最大的推力应该是:系统时钟. 
8

9
我们整个学生阶段有个东西很重要,就是校园铃声. 它控制着上课,下课,吃饭,睡觉的节奏.没有它学校的管理就乱套了,老师拖课想拖多久就多久,那可不行,下课铃声一响就是在告诉老师时间到了,该停止了让学生HAPPY去了.
10

11
应用开发人员对定时器不会陌生,电商平台的每晚10点准时的秒杀活动没有定时任务是不可能实现的,那内核也一定要提供这样的功能被封装给应用层使用,那这个功能在内核层的表述就是系统时钟,它发出来的节奏叫tick(节拍).
12

13
对应张大爷的故事:系统时钟就是场馆的那个大钟,很准时, 每10分响一次,一次就是一个Tick(节拍)
14

15
### **鸿蒙内核的节拍频率是怎样的**
16 17 18 19 20 21 22 23
```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
24 25
```
每秒100个tick,时间单位为10毫秒, 即每秒调用时钟中断处理程序100次.
26

27 28 29 30 31 32 33 34 35 36 37 38 39 40
### **时钟中断处理程序是怎样的?**
```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
}
41
```
42
它主要干了三件事情
43

44 45 46 47 48 49 50 51 52 53 54 55 56
### **第一:检查当前任务的时间片,任务执行一次分配多少时间呢?答案是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;//进程不是抢占式调度直接去检查任务的时间片
    }
57

58 59 60 61 62 63
    if (runProcess->timeSlice != 0) {//进程还有时间片吗?
        runProcess->timeSlice--;//进程时间片减少一次
        if (runProcess->timeSlice == 0) {//没有时间片了
            LOS_Schedule();//进程时间片用完,发起调度
        }
    }
64

65 66 67 68 69
SCHED_TASK:
    runTask = OsCurrTaskGet();//获取当前任务
    if (runTask->policy != LOS_SCHED_RR) {//任务调度算法是否是抢占式
        return;//任务不是抢占式调度直接结束检查
    }
70

71 72 73 74 75 76 77 78 79
    if (runTask->timeSlice != 0) {//任务还有时间片吗?
        runTask->timeSlice--;//任务时间片也减少一次
        if (runTask->timeSlice == 0) {//没有时间片了
            LOS_Schedule();//任务时间片用完,发起调度
        }
    }
}
```
### **第二:扫描任务,主要是检查被阻塞的任务是否可以被重新调度**
80
```cpp
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
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;
120
#endif
121 122 123 124 125 126 127
            taskCB->taskStatus |= OS_TASK_STATUS_TIMEOUT;
            LOS_ListDelete(&taskCB->pendList);
            taskCB->taskSem = NULL;
            taskCB->taskMux = NULL;
        } else {
            taskCB->taskStatus &= ~OS_TASK_STATUS_DELAY;
        }
128

129 130 131 132 133 134 135 136
        if (!(tempStatus & OS_TASK_STATUS_SUSPEND)) {
            OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB, OS_PROCESS_STATUS_PEND);
            needSchedule = TRUE;
        }

        if (LOS_ListEmpty(listObject)) {
            break;
        }
137

138 139 140 141
        sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
    }

    LOS_SpinUnlock(&g_taskSpin);
142

143 144 145 146 147
    if (needSchedule != FALSE) {//需要调度
        LOS_MpSchedule(OS_MP_CPU_ALL);//核间通讯,给所有CPU发送调度信号
        LOS_Schedule();//开始调度
    }
}
148
```
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
### **第三:定时器扫描,看是否有超时的定时器**
```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);
            }
        }
193

194 195 196 197 198 199 200 201 202 203 204 205 206 207
        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);
        }
208

209 210 211 212 213 214 215 216 217 218 219
        if (LOS_ListEmpty(listObject)) {
            break;
        }

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

    LOS_SpinUnlock(&g_swtmrSpin);
}
```
### **最后看调度算法的实现**
220
```cpp
221
//调度算法的实现
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
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 */
284
    OsTaskSchedule(newTask, runTask);//切换任务上下文,注意OsTaskSchedule是一个汇编函数 见于 los_dispatch.s
285 286
}
```
287
最后的 OsTaskSchedule 是由汇编实现的,后续会详细讲解各个汇编文件,除了tick 会触发调度,还有哪些情况会触发调度?
288 289

!import[\zzz\mdmerge\foot.md] 
290
!export[\zzz\md\docs\guide\鸿蒙内核源码分析(时钟任务篇).md]