鸿蒙轻内核M核源码分析系列六 任务及任务调度(2)任务模块
任务是操作系统一个重要的概念,是竞争系统资源的最小运行单元。任务可以使用或等待CPU
、使用内存空间等系统资源,并独立于其它任务运行。鸿蒙轻内核的任务模块可以给用户提供多个任务,实现任务间的切换,帮助用户管理业务程序流程。本文我们来一起学习下任务模块的源代码,所涉及的源码,以OpenHarmony LiteOS-M
内核为例,均可以在开源站点 https://gitee.com/openharmony/kernel_liteos_m 获取。
接下来,我们看下任务模块的结构体,任务初始化,任务常用操作的源代码。
1、任务模块的结构体定义
在文件kernel\include\los_task.h
定义的任务控制块结构体LosTaskCB
,源代码如下,结构体成员的解释见注释部分。
typedef struct {VOID *stackPointer; /* 任务栈指针 */UINT16 taskStatus; /* 任务状态 */UINT16 priority; /* 任务优先级 */INT32 timeSlice; /* 剩余的时间片 */UINT32 waitTimes;SortLinkList sortList; /* 任务超时排序链表节点 */UINT64 startTime;UINT32 stackSize; /* 任务栈大小 */UINT32 topOfStack; /* 栈顶指针 */UINT32 taskID; /* 任务编号Id */TSK_ENTRY_FUNC taskEntry; /* 任务入口函数 */VOID *taskSem; /* 任务持有的信号量 */VOID *taskMux; /* 导致任务阻塞的互斥锁 */UINT32 arg; /* 任务入口函数的参数 */CHAR *taskName; /* 任务名称 */LOS_DL_LIST pendList; /* 就绪队列等链表节点 */LOS_DL_LIST timerList; /* 任务超时排序链表节点 */EVENT_CB_S event;UINT32 eventMask; /* 事件掩码 */UINT32 eventMode; /* 事件模式 */VOID *msg; /* 分给给队列的内存*/INT32 errorNo;
} LosTaskCB;
另外一个比较重要的结构体是TSK_INIT_PARAM_S
,创建任务时,需要指定任务初始化的参数。源代码如下,结构体成员的解释见注释部分。
typedef struct tagTskInitParam {TSK_ENTRY_FUNC pfnTaskEntry; /** 任务入口函数 */UINT16 usTaskPrio; /** 任务参数 */UINT32 uwStackSize; /** 任务栈大小 */CHAR *pcName; /** 任务名称 */UINT32 uwResved; /** 保留 */
} TSK_INIT_PARAM_S;
2、任务模块初始化
在系统启动时,在kernel\src\los_init.c
中调用OsTaskInit()
进行任务模块初始化,还会调用OsIdleTaskCreate()
创建空闲任务。
2.1 任务模块初始化
函数OsTaskInit()
定义在kernel\src\los_task.c
,我们分析下这个函数的执行过程。
⑴处代码根据开发板配置的最大任务数g_taskMaxNum
,计算需要申请的内存大小size
,为任务控制块TCB
数组(也叫作任务池)g_taskCBArray
申请内存。为什么比最大任务数多申请一个呢?在删除任务时会使用。下文分析删除任务的源码时再详细讲解其用意。⑵处代码初始化双向链表g_losFreeTask
用作空闲的任务链表、g_taskRecyleList
可以回收的任务链表。⑶处循环初始化每一个任务,任务状态未使用OS_TASK_STATUS_UNUSED
,初始化任务Id
,并把任务挂在空闲任务链表上。
⑷处初始化全局变量LosTask g_losTask
,该全局变量维护当前运行的任务和要调度执行的任务。初始化任务池时,设置当前运行的任务为g_taskCBArray[g_taskMaxNum]
。⑸处空闲任务编号暂时设置为无效值,后续创建空闲任务时再设置空闲任务编号。
优先级队列,详细的代码实现剖析,参见之前的源码剖析文章。⑸处互斥锁死锁检测的调测特性的,后续系列文章专题进行讲解。⑹处代码初始化排序链表,详细的代码实现剖析,参见之前的源码剖析文章。⑺处如果开启了惰性栈,计算TCB
的成员变量stackFrame
在其结构体中的偏移量g_stackFrameOffLenInTcb
。
LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID)
{UINT32 size;UINT32 index;⑴ size = (g_taskMaxNum + 1) * sizeof(LosTaskCB);g_taskCBArray = (LosTaskCB *)LOS_MemAlloc(m_aucSysMem0, size);if (g_taskCBArray == NULL) {return LOS_ERRNO_TSK_NO_MEMORY;}(VOID)memset_s(g_taskCBArray, size, 0, size);⑵ LOS_ListInit(&g_losFreeTask);LOS_ListInit(&g_taskRecyleList);
⑶ for (index = 0; index <= LOSCFG_BASE_CORE_TSK_LIMIT; index++) {g_taskCBArray[index].taskStatus = OS_TASK_STATUS_UNUSED;g_taskCBArray[index].taskID = index;LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList);}// Ignore the return code when matching CSEC rule 6.6(4).
⑷ (VOID)memset_s((VOID *)(&g_losTask), sizeof(g_losTask), 0, sizeof(g_losTask));g_losTask.runTask = &g_taskCBArray[g_taskMaxNum];g_losTask.runTask->taskID = index;g_losTask.runTask->taskStatus = (OS_TASK_STATUS_UNUSED | OS_TASK_STATUS_RUNNING);g_losTask.runTask->priority = OS_TASK_PRIORITY_LOWEST + 1;⑸ g_idleTaskID = OS_INVALID;
⑹ return OsSchedInit();
}
2.2 创建空闲任务IdleCore000
除了初始化任务池,在系统启动阶段还会创建idle
空闲任务。⑴处设置任务初始化参数时,空闲任务的入口执行函数为OsIdleTask()
。⑵处调用函数把空闲任务状态设置为就绪状态。
LITE_OS_SEC_TEXT_INIT UINT32 OsIdleTaskCreate(VOID)
{UINT32 retVal;TSK_INIT_PARAM_S taskInitParam;// Ignore the return code when matching CSEC rule 6.6(4).(VOID)memset_s((VOID *)(&taskInitParam), sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
⑴ taskInitParam.pfnTaskEntry = (TSK_ENTRY_FUNC)OsIdleTask;taskInitParam.uwStackSize = LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE;taskInitParam.pcName = "IdleCore000";taskInitParam.usTaskPrio = OS_TASK_PRIORITY_LOWEST;retVal = LOS_TaskCreateOnly(&g_idleTaskID, &taskInitParam);if (retVal != LOS_OK) {return retVal;}⑵ OsSchedSetIdleTaskSchedPartam(OS_TCB_FROM_TID(g_idleTaskID));return LOS_OK;
}
我们看下空闲任务的入口执行函数为OsIdleTask()
,它调用OsRecyleFinishedTask()
回收任务栈资源,后文会分析如何回收任务资源。
LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID)
{while (1) {OsRecyleFinishedTask();HalEnterSleep(OS_SYS_DEEP_SLEEP);}
}
3、任务模块常用操作
3.1 创建和删除任务
3.1.1 创建任务
鸿蒙轻内核提供了2个创建任务的函数,有LOS_TaskCreate
、LOS_TaskCreateOnly
。LOS_TaskCreate
和LOS_TaskCreateOnly
的区别是,前者创建任务完毕就使任务进入就绪状态,并触发调度,如果就绪队列中没有更高优先级的任务,则运行该任务。后者只创建任务,设置任务状态为阻塞suspend
状态,需要开发者去调用LOS_TaskResume
使该任务进入ready状态。
函数LOS_TaskCreate
代码如下,可以看出创建任务的时候,调用⑴处的函数LOS_TaskCreateOnly()
来创建任务。创建任务后,执行⑵处的代码使任务进入ready
就绪队列,如果系统启动完成,允许任务调度,则执行⑶触发任务调度。如果新创建的任务优先级最高,则会被调度运行。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam)
{UINT32 retVal;UINTPTR intSave;LosTaskCB *taskCB = NULL;⑴ retVal = LOS_TaskCreateOnly(taskID, taskInitParam);if (retVal != LOS_OK) {return retVal;}taskCB = OS_TCB_FROM_TID(*taskID);intSave = LOS_IntLock();
#if (LOSCFG_BASE_CORE_CPUP == 1)g_cpup[taskCB->taskID].cpupID = taskCB->taskID;g_cpup[taskCB->taskID].status = taskCB->taskStatus;
#endif⑵ OsSchedTaskEnQueue(taskCB);LOS_IntRestore(intSave);⑶ if (g_taskScheduled) {LOS_Schedule();}return LOS_OK;
}
我们接着分析下如何使用函数UINT32 LOS_TaskCreateOnly()
创建任务。⑴处调用OsTaskInitParamCheck()
检测创建任务的参数的合法性。⑵处调用函数回收释放的任务。⑶处如果任务池为空,无法创建任务,返回错误码。⑷处从任务池获取一个空闲的任务控制块taskCB
,然后从空闲任务链表中删除。⑸处根据指定的任务栈大小为任务栈申请内存,⑹处判断任务栈内存申请释放成功,如果申请失败,则把任务控制块归还到空闲任务链表中,并返回错误码。⑺处调用函数初始化任务栈,更新任务控制块成员信息。详细见后面对该函数的分析。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreateOnly(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam)
{UINTPTR intSave;VOID *topOfStack = NULL;LosTaskCB *taskCB = NULL;UINT32 retVal;if (taskID == NULL) {return LOS_ERRNO_TSK_ID_INVALID;}⑴ retVal = OsTaskInitParamCheck(taskInitParam);if (retVal != LOS_OK) {return retVal;}⑵ OsRecyleFinishedTask();intSave = LOS_IntLock();
⑶ if (LOS_ListEmpty(&g_losFreeTask)) {retVal = LOS_ERRNO_TSK_TCB_UNAVAILABLE;OS_GOTO_ERREND();}⑷ taskCB = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&g_losFreeTask));LOS_ListDelete(LOS_DL_LIST_FIRST(&g_losFreeTask));LOS_IntRestore(intSave);#if (LOSCFG_EXC_HRADWARE_STACK_PROTECTION == 1)UINTPTR stackPtr = (UINTPTR)LOS_MemAllocAlign(OS_TASK_STACK_ADDR, taskInitParam->uwStackSize +OS_TASK_STACK_PROTECT_SIZE, OS_TASK_STACK_PROTECT_SIZE);topOfStack = (VOID *)(stackPtr + OS_TASK_STACK_PROTECT_SIZE);
#else
⑸ topOfStack = (VOID *)LOS_MemAllocAlign(OS_TASK_STACK_ADDR, taskInitParam->uwStackSize,LOSCFG_STACK_POINT_ALIGN_SIZE);
#endif
⑹ if (topOfStack == NULL) {intSave = LOS_IntLock();LOS_ListAdd(&g_losFreeTask, &taskCB->pendList);LOS_IntRestore(intSave);return LOS_ERRNO_TSK_NO_MEMORY;}⑺ retVal = OsNewTaskInit(taskCB, taskInitParam, topOfStack);if (retVal != LOS_OK) {return retVal;}*taskID = taskCB->taskID;OsHookCall(LOS_HOOK_TYPE_TASK_CREATE, taskCB);return retVal;LOS_ERREND:LOS_IntRestore(intSave);return retVal;
}
我们看下创建任务函数调用的函数OsRecyleFinishedTask()
,该函数在系统进入空闲时也会调用。删除运行状态的任务时,会把任务挂在双向链表里g_taskRecyleList
。任务回收函数就用来回收此类任务,实现任务资源回收。我们分析下它的代码。⑴处循环遍历回收链表,⑵从回收链表获取第一个任务taskCB
,从回收链表删除并插入到空闲任务链表里。任务栈保护在后续系列再深入分析,继续往下看代码,⑶处获取任务栈栈顶指针,接着调用内存释放函数来释放任务栈占用的内存,并设置任务栈的栈顶为空。
STATIC VOID OsRecyleFinishedTask(VOID)
{LosTaskCB *taskCB = NULL;UINTPTR intSave;UINTPTR stackPtr;intSave = LOS_IntLock();
⑴ while (!LOS_ListEmpty(&g_taskRecyleList)) {
⑵ taskCB = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&g_taskRecyleList));LOS_ListDelete(LOS_DL_LIST_FIRST(&g_taskRecyleList));LOS_ListAdd(&g_losFreeTask, &taskCB->pendList);
#if (LOSCFG_EXC_HRADWARE_STACK_PROTECTION == 1)stackPtr = taskCB->topOfStack - OS_TASK_STACK_PROTECT_SIZE;
#else
⑶ stackPtr = taskCB->topOfStack;
#endif(VOID)LOS_MemFree(OS_TASK_STACK_ADDR, (VOID *)stackPtr);taskCB->topOfStack = (UINT32)NULL;}LOS_IntRestore(intSave);
}
我们继续分析下函数OsNewTaskInit()
,⑴处调用函数初始化任务栈,上一系列已经分析过该函数,代码的其余部分用来更新任务控制块的成员信息,比如⑵处任务状态设置为阻塞状态。
LITE_OS_SEC_TEXT_INIT UINT32 OsNewTaskInit(LosTaskCB *taskCB, TSK_INIT_PARAM_S *taskInitParam, VOID *topOfStack)
{
⑴ taskCB->stackPointer = HalTskStackInit(taskCB->taskID, taskInitParam->uwStackSize, topOfStack);taskCB->arg = taskInitParam->uwArg;taskCB->topOfStack = (UINT32)(UINTPTR)topOfStack;taskCB->stackSize = taskInitParam->uwStackSize;taskCB->taskSem = NULL;taskCB->taskMux = NULL;
⑵ taskCB->taskStatus = OS_TASK_STATUS_SUSPEND;taskCB->priority = taskInitParam->usTaskPrio;taskCB->timeSlice = 0;taskCB->waitTimes = 0;taskCB->taskEntry = taskInitParam->pfnTaskEntry;taskCB->event.uwEventID = OS_NULL_INT;taskCB->eventMask = 0;taskCB->taskName = taskInitParam->pcName;taskCB->msg = NULL;SET_SORTLIST_VALUE(&taskCB->sortList, OS_SORT_LINK_INVALID_TIME);return LOS_OK;
}
3.1.2 删除任务UINT32 LOS_TaskDelete()
该函数根据传入的参数UINT32 taskId
删除任务。我们分析下删除任务的源代码,⑴处检验传入的参数,⑵处如果任务还未创建,返回错误码。⑶处如果删除的任务正在运行,又处于锁任务调度情况下,打印信息,告诉用户不推荐在锁任务调度期间进行任务删除,然后执行⑷,把全局变量赋值0来解锁任务调度。
⑸处调用函数处理任务状态,如果处于就绪状态设置为非就绪状态,并从就绪队列删除。如果处于阻塞状态,从阻塞队列中删除。如果任务处于超时等待状态,从超时排序链表中删除。⑹恢复任务控制块事件相关的成员信息。⑺如果任务正在运行,设置任务为未使用状态,接着调用函数OsRunningTaskDelete()
把任务放入回收链表,然后主动触发任务调度,稍后详细分析该函数。如果删除的任务不是出于运行状态,则执行⑻,设置任务为未使用状态,接着把任务回收到空闲任务链表里,然后获取任务栈的栈顶指针,调用内存释放函数释放任务栈的内存。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskDelete(UINT32 taskID)
{UINTPTR intSave;LosTaskCB *taskCB = OS_TCB_FROM_TID(taskID);UINTPTR stackPtr;⑴ UINT32 ret = OsCheckTaskIDValid(taskID);if (ret != LOS_OK) {return ret;}intSave = LOS_IntLock();⑵ if ((taskCB->taskStatus) & OS_TASK_STATUS_UNUSED) {LOS_IntRestore(intSave);return LOS_ERRNO_TSK_NOT_CREATED;}/* If the task is running and scheduler is locked then you can not delete it */
⑶ if (((taskCB->taskStatus) & OS_TASK_STATUS_RUNNING) && (g_losTaskLock != 0)) {PRINT_INFO("In case of task lock, task deletion is not recommended\n");
⑷ g_losTaskLock = 0;}OsHookCall(LOS_HOOK_TYPE_TASK_DELETE, taskCB);
⑸ OsSchedTaskExit(taskCB);⑹ taskCB->event.uwEventID = OS_NULL_INT;taskCB->eventMask = 0;
#if (LOSCFG_BASE_CORE_CPUP == 1)// Ignore the return code when matching CSEC rule 6.6(4).(VOID)memset_s((VOID *)&g_cpup[taskCB->taskID], sizeof(OsCpupCB), 0, sizeof(OsCpupCB));
#endifif (taskCB->taskStatus & OS_TASK_STATUS_RUNNING) {
⑺ taskCB->taskStatus = OS_TASK_STATUS_UNUSED;OsRunningTaskDelete(taskID, taskCB);LOS_IntRestore(intSave);LOS_Schedule();return LOS_OK;} else {
⑻ taskCB->taskStatus = OS_TASK_STATUS_UNUSED;LOS_ListAdd(&g_losFreeTask, &taskCB->pendList);
#if (LOSCFG_EXC_HRADWARE_STACK_PROTECTION == 1)stackPtr = taskCB->topOfStack - OS_TASK_STACK_PROTECT_SIZE;
#elsestackPtr = taskCB->topOfStack;
#endif(VOID)LOS_MemFree(OS_TASK_STACK_ADDR, (VOID *)stackPtr);taskCB->topOfStack = (UINT32)NULL;}LOS_IntRestore(intSave);return LOS_OK;
}
我们看下函数OsRunningTaskDelete()
的源码。⑴处把当前运行的任务放入待回收链表里,然后执行⑵把当前运行的任务放入任务池的最后一个位置g_taskCBArray[g_taskMaxNum]
。为什么这么操作呢?等后续分析源码的时候再来解答。
LITE_OS_SEC_TEXT_INIT STATIC_INLINE VOID OsRunningTaskDelete(UINT32 taskID, LosTaskCB *taskCB)
{
⑴ LOS_ListTailInsert(&g_taskRecyleList, &taskCB->pendList);
⑵ g_losTask.runTask = &g_taskCBArray[g_taskMaxNum];g_losTask.runTask->taskID = taskID;g_losTask.runTask->taskStatus = taskCB->taskStatus | OS_TASK_STATUS_RUNNING;g_losTask.runTask->topOfStack = taskCB->topOfStack;g_losTask.runTask->taskName = taskCB->taskName;
}
3.2 控制任务状态
3.2.1 恢复挂起的任务LOS_TaskResume()
恢复挂起的任务,使该任务进入就绪状态,和下文中的LOS_TaskSuspend()
成对使用。⑴处获取任务的TCB
,⑵处对任务状态进行判断,如果任务未创建或者非阻塞状态,则返回错误码。执行⑶设置任务状态为非挂起状态。⑶处获取任务的状态进行判断,如果任务没有创建或者不是挂起状态,则返回相应的错误码。 ⑷检查任务状态是否为OS_CHECK_TASK_BLOCK
,即(OS_TASK_STATUS_DELAY | OS_TASK_STATUS_PEND | OS_TASK_STATUS_SUSPEND)
中的一种,这几个状态影响恢复挂起的任务。如果非上述几个状态,执行⑸调用函数,把任务状态改为就绪状态,插入任务就绪队列。如果支持支持调度,则执行⑹触发调度。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskResume(UINT32 taskID)
{UINTPTR intSave;LosTaskCB *taskCB = NULL;UINT16 tempStatus;UINT32 retErr = OS_ERROR;if (taskID > LOSCFG_BASE_CORE_TSK_LIMIT) {return LOS_ERRNO_TSK_ID_INVALID;}⑴ taskCB = OS_TCB_FROM_TID(taskID);intSave = LOS_IntLock();tempStatus = taskCB->taskStatus;⑵ if (tempStatus & OS_TASK_STATUS_UNUSED) {retErr = LOS_ERRNO_TSK_NOT_CREATED;OS_GOTO_ERREND();} else if (!(tempStatus & OS_TASK_STATUS_SUSPEND)) {retErr = LOS_ERRNO_TSK_NOT_SUSPENDED;OS_GOTO_ERREND();}⑶ taskCB->taskStatus &= (~OS_TASK_STATUS_SUSPEND);
⑷ if (!(taskCB->taskStatus & OS_CHECK_TASK_BLOCK)) {
⑸ OsSchedTaskEnQueue(taskCB);if (g_taskScheduled) {LOS_IntRestore(intSave);
⑹ LOS_Schedule();return LOS_OK;}}LOS_IntRestore(intSave);return LOS_OK;LOS_ERREND:LOS_IntRestore(intSave);return retErr;
}
3.2.2 挂起指定的任务LOS_TaskSuspend()
函数用于挂起指定的任务。⑴处获取任务的TCB
,⑵处开始获取任务的状态进行判断,如果任务没有创建、任务已经挂起,返回相应的错误码。⑶处如果任务是运行状态,并且锁任务调度时,跳转到LOS_ERREND
结束挂起操作。⑷处如果任务是就绪状态,调用函数从就绪队列出队,并取消任务的就绪状态。⑸处语句设置任务状态为阻塞状态。⑹如果挂起的是当前运行的任务,则会主动触发调度。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskSuspend(UINT32 taskID)
{UINTPTR intSave;LosTaskCB *taskCB = NULL;UINT16 tempStatus;UINT32 retErr;retErr = OsCheckTaskIDValid(taskID);if (retErr != LOS_OK) {return retErr;}⑴ taskCB = OS_TCB_FROM_TID(taskID);intSave = LOS_IntLock();
⑵ tempStatus = taskCB->taskStatus;if (tempStatus & OS_TASK_STATUS_UNUSED) {retErr = LOS_ERRNO_TSK_NOT_CREATED;OS_GOTO_ERREND();}if (tempStatus & OS_TASK_STATUS_SUSPEND) {retErr = LOS_ERRNO_TSK_ALREADY_SUSPENDED;OS_GOTO_ERREND();}⑶ if ((tempStatus & OS_TASK_STATUS_RUNNING) && (g_losTaskLock != 0)) {retErr = LOS_ERRNO_TSK_SUSPEND_LOCKED;OS_GOTO_ERREND();}⑷ if (tempStatus & OS_TASK_STATUS_READY) {OsSchedTaskDeQueue(taskCB);}⑸ taskCB->taskStatus |= OS_TASK_STATUS_SUSPEND;OsHookCall(LOS_HOOK_TYPE_MOVEDTASKTOSUSPENDEDLIST, taskCB);
⑹ if (taskID == g_losTask.runTask->taskID) {LOS_IntRestore(intSave);LOS_Schedule();return LOS_OK;}LOS_IntRestore(intSave);return LOS_OK;LOS_ERREND:LOS_IntRestore(intSave);return retErr;
}
3.2.3 任务延时等待LOS_TaskDelay()
任务延时等待,释放CPU
,等待时间到期后该任务会重新进入就绪状态。⑴处代码判断系统处于中断,如果是,则返回错误码,不允许任务延时等待。⑵如果处于锁任务调度期间,则返回错误码。
⑶处如果延迟的时间为0,则执行让权操作,否则执行⑷,调用函数OsSchedDelay()
把当前任务设置为延时等待状态,然后调用LOS_Schedule()
触发调度。
LITE_OS_SEC_TEXT UINT32 LOS_TaskDelay(UINT32 tick)
{UINTPTR intSave;⑴ if (OS_INT_ACTIVE) {return LOS_ERRNO_TSK_DELAY_IN_INT;}⑵ if (g_losTaskLock != 0) {return LOS_ERRNO_TSK_DELAY_IN_LOCK;}OsHookCall(LOS_HOOK_TYPE_TASK_DELAY, tick);
⑶ if (tick == 0) {return LOS_TaskYield();} else {intSave = LOS_IntLock();
⑷ OsSchedDelay(g_losTask.runTask, tick);OsHookCall(LOS_HOOK_TYPE_MOVEDTASKTODELAYEDLIST, g_losTask.runTask);LOS_IntRestore(intSave);LOS_Schedule();}return LOS_OK;
}
另外还提供了函数LOS_Msleep()
和LOS_UDelay()
,前者以毫秒为单位进行延迟等待。后者也是以毫秒为单位进行延迟等待,但是不会触发任务调度,当前任务不会释放CPU
。
LITE_OS_SEC_TEXT_MINOR VOID LOS_Msleep(UINT32 mSecs)
{UINT32 interval;if (OS_INT_ACTIVE) {return;}if (mSecs == 0) {interval = 0;} else {interval = LOS_MS2Tick(mSecs);if (interval == 0) {interval = 1;}}(VOID)LOS_TaskDelay(interval);
}VOID LOS_UDelay(UINT64 microseconds)
{UINT64 endTime;if (microseconds == 0) {return;}endTime = (microseconds / OS_SYS_US_PER_SECOND) * OS_SYS_CLOCK +(microseconds % OS_SYS_US_PER_SECOND) * OS_SYS_CLOCK / OS_SYS_US_PER_SECOND;endTime = LOS_SysCycleGet() + endTime;while (LOS_SysCycleGet() < endTime) {}return;
}
3.2.4 任务让权LOS_TaskYield()
让权函数通过把当前任务时间片设置为0,释放CPU
占用,重新调度给其他高优先级任务执行。⑴处调用函数把当前任务时间片设置为0,然后执行⑵主动触发任务调度。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_TaskYield(VOID)
{UINTPTR intSave;intSave = LOS_IntLock();
⑴ OsSchedYield();LOS_IntRestore(intSave);
⑵ LOS_Schedule();return LOS_OK;
}
接下来看下函数OsSchedYield()
的源码。代码很简单,获取当前运行的任务,然后把其时间片设置为0,如下:
VOID OsSchedYield(VOID)
{LosTaskCB *runTask = g_losTask.runTask;runTask->timeSlice = 0;
}
3.3 控制任务调度
3.3.1 锁任务调度LOS_TaskLock()
锁任务调度LOS_TaskLock()
比较简单,把任务锁调度计数器全局变量增加1即可,代码如下。
LITE_OS_SEC_TEXT_MINOR VOID LOS_TaskLock(VOID)
{UINTPTR intSave;intSave = LOS_IntLock();g_losTaskLock++;LOS_IntRestore(intSave);
}
3.3.2 解锁任务调度LOS_TaskUnlock()
我们看看解锁任务调度函数LOS_TaskUnlock()
,⑴处如果任务锁调度计数器全局变量数值大于0,对其减1。⑵处如果任务锁调度计数器等于0,则执行⑶处触发调度。代码如下:
LITE_OS_SEC_TEXT_MINOR VOID LOS_TaskUnlock(VOID)
{UINTPTR intSave;intSave = LOS_IntLock();
⑴ if (g_losTaskLock > 0) {g_losTaskLock--;
⑵ if (g_losTaskLock == 0) {LOS_IntRestore(intSave);
⑶ LOS_Schedule();return;}}LOS_IntRestore(intSave);
}
3.4 控制任务优先级
LiteOS-M
内核支持动态设置任务的优先级,提供了一些操作。
3.4.1 设置指定任务的优先级LOS_TaskPriSet
支持设置指定任务Id
的优先级,也支持对当前运行任务进行优先级设置。⑴处开始,做些基础校验,包含检验传入的优先级参数taskPrio
,指定任务的Id
,任务是否未创建等,如果没有通过参数校验,则返回错误码。⑵处调用函数设置任务优先级,稍后分析该函数。如果任务处于就绪状态或者运行状态,则会执行⑶主动触发任务调度。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_TaskPriSet(UINT32 taskID, UINT16 taskPrio)
{BOOL isReady = FALSE;UINTPTR intSave;LosTaskCB *taskCB = NULL;UINT16 tempStatus;⑴ if (taskPrio > OS_TASK_PRIORITY_LOWEST) {return LOS_ERRNO_TSK_PRIOR_ERROR;}if (taskID == g_idleTaskID) {return LOS_ERRNO_TSK_OPERATE_IDLE;}if (taskID == g_swtmrTaskID) {return LOS_ERRNO_TSK_OPERATE_SWTMR;}if (OS_CHECK_TSK_PID_NOIDLE(taskID)) {return LOS_ERRNO_TSK_ID_INVALID;}taskCB = OS_TCB_FROM_TID(taskID);intSave = LOS_IntLock();tempStatus = taskCB->taskStatus;if (tempStatus & OS_TASK_STATUS_UNUSED) {LOS_IntRestore(intSave);return LOS_ERRNO_TSK_NOT_CREATED;}⑵ isReady = OsSchedModifyTaskSchedParam(taskCB, taskPrio);LOS_IntRestore(intSave);if (isReady) {
⑶ LOS_Schedule();}return LOS_OK;
}
接下来,我们分析下函数OsSchedModifyTaskSchedParam()
。⑴处如果任务处于就绪状态,需要先出队设置优先级,然后入队就绪队列。如果非就绪状态,可以直接执行⑵处语句修改任务优先级。如果任务正在运行,需要返回TRUE,标记下需要任务调度。
BOOL OsSchedModifyTaskSchedParam(LosTaskCB *taskCB, UINT16 priority)
{if (taskCB->taskStatus & OS_TASK_STATUS_READY) {
⑴ OsSchedTaskDeQueue(taskCB);taskCB->priority = priority;OsSchedTaskEnQueue(taskCB);return TRUE;}⑵ taskCB->priority = priority;OsHookCall(LOS_HOOK_TYPE_TASK_PRIMODIFY, taskCB, taskCB->priority);if (taskCB->taskStatus & OS_TASK_STATUS_RUNNING) {return TRUE;}return FALSE;
}
3.4.2 获取指定任务的优先级LOS_TaskPriGet
获取指定任务的优先级LOS_TaskPriGet()
代码比较简单,⑴处如果任务编号无效,返回错误码。⑵处如果任务未创建返回错误码。如果参数校验通过,执行⑶获取任务的优先级数值。
LITE_OS_SEC_TEXT_MINOR UINT16 LOS_TaskPriGet(UINT32 taskID)
{UINTPTR intSave;LosTaskCB *taskCB = NULL;UINT16 priority;⑴ if (OS_CHECK_TSK_PID_NOIDLE(taskID)) {return (UINT16)OS_INVALID;}taskCB = OS_TCB_FROM_TID(taskID);intSave = LOS_IntLock();⑵ if (taskCB->taskStatus & OS_TASK_STATUS_UNUSED) {LOS_IntRestore(intSave);return (UINT16)OS_INVALID;}⑶ priority = taskCB->priority;LOS_IntRestore(intSave);return priority;
}
3.5 任务阻塞和唤醒
最后,我们分析下函数OsSchedTaskWait()
和OsSchedTaskWake()
,这2个函数定义在文件kernel\src\los_sched.c
中。任务在申请互斥锁、信号量、出入队列、读写事件时,都可能导致任务进入阻塞状态,对应地也需要任务唤醒重新进入就绪队列状态。这2个函数就负责任务的阻塞和唤醒,我们分析下他们的代码。
3.5.1 任务阻塞
我们分析下任务阻塞的函数OsSchedTaskWait()
,需要2个参数:LOS_DL_LIST *list
是互斥锁等资源的阻塞链表,阻塞的任务会挂这个链表里;UINT32 ticks
是任务阻塞的时间。分析下具体代码:
⑴获取正在请求互斥锁等资源的当前任务,⑵设置任务状态为阻塞状态。⑶把任务插入互斥锁等资源的阻塞链表的尾部。⑷如果不是永久阻塞等待,任务的状态还需要设置为OS_TASK_STATUS_PEND_TIME
,然后设置任务的等待时间为传入的参数。
VOID OsSchedTaskWait(LOS_DL_LIST *list, UINT32 ticks)
{
⑴ LosTaskCB *runTask = g_losTask.runTask;⑵ runTask->taskStatus |= OS_TASK_STATUS_PEND;
⑶ LOS_ListTailInsert(list, &runTask->pendList);if (ticks != LOS_WAIT_FOREVER) {
⑷ runTask->taskStatus |= OS_TASK_STATUS_PEND_TIME;runTask->waitTimes = ticks;}
}
3.5.2 任务唤醒
我们分析下任务唤醒的函数OsSchedTaskWake()
,需要1个参数:LosTaskCB *resumedTask
是需要唤醒的任务;任务唤醒函数会从阻塞链表里删除并加入就绪队列,下面分析下具体代码:
⑴把要唤醒的任务从所在的阻塞队列中删除,然后更改状态不再为阻塞状态。⑵如果任务不是永久等待,需要从定时器排序链表中删除,并设置状态不再是等待超时。⑶如果任务是阻塞状态,改为就绪状态并加入就绪队列。
VOID OsSchedTaskWake(LosTaskCB *resumedTask)
{
⑴ LOS_ListDelete(&resumedTask->pendList);resumedTask->taskStatus &= ~OS_TASK_STATUS_PEND;⑵ if (resumedTask->taskStatus & OS_TASK_STATUS_PEND_TIME) {OsDeleteSortLink(&resumedTask->sortList, OS_SORT_LINK_TASK);resumedTask->taskStatus &= ~OS_TASK_STATUS_PEND_TIME;}⑶ if (!(resumedTask->taskStatus & OS_TASK_STATUS_SUSPEND)) {OsSchedTaskEnQueue(resumedTask);}
}
小结
本文带领大家一起剖析了鸿蒙轻内核任务模块的源代码,包含任务模块的结构体,任务初始化过程源代码,任务常用操作的源代码。
如果大家想更加深入的学习 OpenHarmony 开发的内容,不妨可以参考以下相关学习文档进行学习,助你快速提升自己:
OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy
《OpenHarmony源码解析》:https://qr18.cn/CgxrRy
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ……
系统架构分析:https://qr18.cn/CgxrRy
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……
OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy
OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy
相关文章:

鸿蒙轻内核M核源码分析系列六 任务及任务调度(2)任务模块
任务是操作系统一个重要的概念,是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行。鸿蒙轻内核的任务模块可以给用户提供多个任务,实现任务间的切换,帮助用户管理业务程序流程。…...

解决找不到MSVCR120.dll,无法执行代码
msvcr120.dll是Microsoft Visual C 2013 Redistributable Package的一部分,它提供了运行使用Microsoft Visual C 2013编译器编译的程序所需的运行时环境。这个DLL文件包含了在运行使用Visual C编译器(特别是2013版)编译的应用程序时所必需的一…...

Linux iptables详解
前言:事情是这样的。最近部门在进行故障演练,攻方同学利用iptables制造了一个故障。演练最终肯定是取得了理想的效果,即业务同学在规定时间内定位了问题并恢复了业务(ps:你懂得)。 对我个人来讲一直知道iptables的存在࿰…...
Mac电脑arm64芯片Cocoapods 的 ffi 兼容问题
转载请标明出处:https://blog.csdn.net/donkor_/article/details/139505395 文章目录 前言问题分析解决方案总结 前言 今天在改Flutter项目的时候,构建IOS项目时,Cocoapods报错 Error: To set up CocoaPods for ARM macOS, run: arch -x86_6…...

如何提高逻辑性?(小妙招)
在现代社会中,逻辑性是一种至关重要的思维能力。不论是在工作、学习还是生活中,逻辑清晰的人总能更好地解决问题和做出决策。然而,如何提高逻辑性却是许多人头疼的问题。本文将从六个方面详细探讨如何提升逻辑性,包括细心态度、逼…...

2024050501-重学 Java 设计模式《实战命令模式》
重学 Java 设计模式:实战命令模式「模拟高档餐厅八大菜系,小二点单厨师烹饪场景」 一、前言 持之以恒的重要性 初学编程往往都很懵,几乎在学习的过程中会遇到各种各样的问题,哪怕别人那运行好好的代码,但你照着写完…...
0104__Linux 中 nm 命令简介
Linux 中 nm 命令简介_linux nm-CSDN博客...
Linux网络服务
01 Linux网络设置 02 DHCP原理与配置 03 DNS域名解析服务 04 远程访问及控制 05 部署YUM仓库及NFS共享服务 06 PXE高效批量网络装机...

Vue18-列表渲染
一、v-for渲染列表 1-1、遍历数组(用的多) 1-2、key属性 让每一个<li>都有一个唯一的标识! 1、写法一 只有用了遍历的方式(v-for)来生成多个同样结构的数据,必须给每个结构取一个唯一的标识。 2、写法二 或者:…...

【三维重建】增量SFM系统
在学习完鲁鹏老师的三维重建基础后,打算用C代码复现一下增量SFM系统(https://github.com/ldx-star/SFM)。 本项目的最终目标就是通过相机拍摄的多视角视图获取三维点云。由于资金有效,博主使用的是相机是小米12。 先来看一下最终…...

PyTorch 维度变换-Tensor基本操作
以如下 tensor a 为例,展示常用的维度变换操作 >>> a torch.rand(4,3,28,28) >>> a.shape torch.Size([4, 3, 28, 28])view / reshape 两者功能完全相同: a.view(shape) >>> a.view(4,3,28*28) ## a.view(4,3,28,28) 可恢复squeeze…...
spring 事务失效的几种场景
一、背景 在 springBoot 开发过程中,我们一般都是在业务方法上添加 Transactional 注解来让 spring 替我们管理事务,但在某些特定的场景下,添加完注解之后,事务是不生效的,接下来详细介绍下。 二、方法不是 public 2…...

45岁程序员独白:中年打工人出路在哪里?
作为一名也是JAVA方向的互联网从业者,我发现周围超过40岁以上的同事,基本都是部门负责人或者高层,真正还在一线做开发或者当个小领导的,已经是凤毛麟角了。 同事A今年刚满40,育有一儿一女,从进入公司到现在…...

深度探讨:为何训练精度不高却在测试中表现优异?
深度探讨:为何训练精度不高却在测试中表现优异? 在深度学习领域,我们经常遇到这样一个看似矛盾的现象:模型在训练集上的精度不是特别高,但在测试集上却能达到出色的表现。这种情况虽然不是常规,但其背后的…...

动态内存管理<C语言>
导言 在C语言学习阶段,指针、结构体和动态内存管理,是后期学习数据结构的最重要的三大知识模块,也是C语言比较难的知识模块,但是“天下无难事”,只要认真踏实的学习,也能解决,所以下文将介绍动态…...

第一百零二节 Java面向对象设计 - Java静态内部类
Java面向对象设计 - Java静态内部类 静态成员类不是内部类 在另一个类的主体中定义的成员类可以声明为静态。 例子 以下代码声明了顶级类A和静态成员类B: class A {// Static member classpublic static class B {// Body for class B goes here} }注意 静态成…...
给自己Linux搞个『回收站』,防止文件误删除
linux没有像windows里一样的回收站,工作时候删除文件容易不小心删错,造成麻烦的后果。所以给自己整了个回收站: 文件删除,新建~/opts/move_to_trash.sh,然后在里面新增,将${your_name}改成你的用户名。同时…...
Springboot接收参数的21种方式
前言 最近一直在忙着开发项目(ps:其实有些摆烂),好久没有更新博客了,打开csdn一看好多网友留言私信,继上篇博客(我是如何实现HttpGet请求传body参数的!),网友议论纷纷,各抒起见。今天正好抽出时间总结一下Springboot接受参数的21种方式(Post、Get、Delete),一并…...

打造出色开发者体验的十大原则
大约十年前我是一名CIO,当时我在评估一种技术解决方案,向潜在供应商的代表讲明了我们的主要需求。他展示了该公司的至少三款产品。每种工具都有各自的用户体验、开发方法和学习要求,但是解决我们的业务需求同时需要这三种工具。作为CIO&#…...

Vue3_对接腾讯云COS_大文件分片上传和下载
目录 一、腾讯云后台配置 二、安装SDK 1.script 引入方式 2.webpack 引入方式 三、文件上传 1.new COS 实例 2.上传文件 四、文件下载 腾讯云官方文档: 腾讯云官方文档https://cloud.tencent.com/document/product/436/11459 一、腾讯云后台配置 1.登录 对…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...

保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...

阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)
cd /home 进入home盘 安装虚拟环境: 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境: virtualenv myenv 3、激活虚拟环境(激活环境可以在当前环境下安装包) source myenv/bin/activate 此时,终端…...

如何做好一份技术文档?从规划到实践的完整指南
如何做好一份技术文档?从规划到实践的完整指南 🌟 嗨,我是IRpickstars! 🌌 总有一行代码,能点亮万千星辰。 🔍 在技术的宇宙中,我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…...

RushDB开源程序 是现代应用程序和 AI 的即时数据库。建立在 Neo4j 之上
一、软件介绍 文末提供程序和源码下载 RushDB 改变了您处理图形数据的方式 — 不需要 Schema,不需要复杂的查询,只需推送数据即可。 二、Key Features ✨ 主要特点 Instant Setup: Be productive in seconds, not days 即时设置 :在几秒钟…...