鸿蒙轻内核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.登录 对…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...