鸿蒙轻内核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_TaskCreateLOS_TaskCreateOnlyLOS_TaskCreateLOS_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));
#endif
    if (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;
#else
        stackPtr = 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/682725.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙北向开发 IDE DevEco Studio 3.1 傻瓜式安装闭坑指南

首先下载 安装IDE 本体程序 DevEco Studio 下载链接 当前最新版本是3.1.1,下载windows版本的 下载下来后是一个压缩包, 解压解锁包后会出现一个exe安装程序 双击运行安装程序 一路 next ( 这里涉及安装文件目录,我因为C盘够大所以全部默认了,各位根据自己情况选择自己的文件…

我的创作纪念日--我和CSDN一起走过的1825天

机缘 第一次在CSDN写文章&#xff0c;是自己在记录学习Java8新特性中Lambda表达式的内容过程中收获的学习心得。之前也有记录工作和生活中的心得体会、难点的解决办法、bug的排查处理过程等等。一直都用的有道笔记&#xff0c;没有去和大家区分享的想法&#xff0c;是一起的朋…

MotionEditor_ 通过内容感知扩散编辑视频运动

图1. MotionEditor&#xff1a;一种基于扩散的视频编辑方法&#xff0c;旨在将参考视频的运动转移到源视频中。 摘要 现有的基于扩散的视频编辑模型在随时间编辑源视频的属性方面取得了显著进展&#xff0c;但在修改运动信息的同时保持原始主角的外观和背景方面存在困难。为…

WordPress 插件推荐:菜单缓存插件——Menu Caching

今天在缙哥哥博客上发现了一个 WordPress 速度优化插件的优化感觉很不错&#xff0c;明月自己装上也体验了一番&#xff0c; WordPress 菜单的载入速度无论是 PC 端和移动端都非常不错&#xff0c;并且这个叫 Menu Caching 的菜单缓存插件还完美的兼容 WPRocket&#xff0c;W3 …

三.一布局和布局切换的实践与探索

在前端开发中&#xff0c;灵活的布局切换是一项非常实用的功能。今天&#xff0c;我想和大家分享一下如何在主组件中通过更换 Layout 目录下的组件来实现布局切换。 首先&#xff0c;我们有一个主组件 index.vue&#xff0c;它承担着整个页面的主要逻辑和展示。 而在 Layout …

PHP序列化、反序列化

目录 一、PHP序列化&#xff1a;serialize() 1.对象序列化 2.pop链序列化 3.数组序列化 二、反序列化&#xff1a;unserialize() 三、魔术方法 ​四、NSSCTF相关简单题目 1.[SWPUCTF 2021 新生赛]ez_unserialize 2.[SWPUCTF 2021 新生赛]no_wakeup 学习参考&#xff1…

使用 Logback.xml 配置文件输出日志信息

官方链接&#xff1a;Chapter 3: Configurationhttps://logback.qos.ch/manual/configuration.html 配置使用 logback 的方式有很多种&#xff0c;而使用配置文件是较为简单的一种方式&#xff0c;下述就是简单描述一个 logback 配置文件基本的配置项&#xff1a; 由于 logba…

优化 mac 储存空间的方法 只需一招为你的苹果电脑提速

在职场中&#xff0c;许多人都对苹果电脑情有独钟。苹果电脑以其简洁美观的设计、流畅稳定的性能以及出色的用户体验&#xff0c;成为了众多职场人士的得力助手。无论是处理文档、制作演示文稿&#xff0c;还是进行创意设计等工作&#xff0c;苹果电脑都能展现出其独特的优势&a…

HCIP-Datacom-ARST自选题库__多种协议简答【11道题】

1.BGP/MPLSIP VPN的典型组网场景如图所示&#xff0c;PE1和PE2通过LoopbackO建立MP-IBGP&#xff0c;PE1和PE2之间只传递VPN路由&#xff0c;其中PE1BGP进程的部分配置已在图中标出&#xff0c;则编号为0的命令不是必须的。(填写阿拉伯数字) 3 2.在如图所示的Hub&amp;Spok…

蒙层(css)

如何在 Vue 中实现一个包含图像和蒙层效果的组件&#xff1f;这个组件根据某个条件显示或隐藏蒙层&#xff0c;用于表示图像是否已读。 1. 创建基础模板 首先&#xff0c;我们在模板中使用 div 包裹我们的图像组件 GraphImage&#xff0c;并为最外层 div 设置 position: relat…

OTTO、亚马逊、Temu卖家如何运用测评补单来提高购买率?

在跨境电商的广阔舞台上&#xff0c;测评补单无疑是一股不可或缺的强劲动力。商家们深知&#xff0c;通过补单这一手段&#xff0c;能够快速为产品注入活力&#xff0c;使销量迅猛攀升&#xff0c;评论如潮涌至&#xff0c;进而在激烈的竞争中脱颖而出&#xff0c;勇攀销量之巅…

【Python数据预处理系列】Pandas 数据操作实战:掌握 .loc[] 方法进行高效数据选取

文章将详细介绍.loc[]方法的各种使用场景&#xff0c;帮助读者深入理解并掌握这一核心功能。 在Pandas库中&#xff0c;.loc[]方法是一种强大而灵活的数据选取工具。本文将通过详细的步骤和示例&#xff0c;手把手教您如何利用这一工具进行高效的数据操作。 首先&#xff0c;我…

【数据集划分】假如你有超百万条oracle数据库数据(成真版)

【数据集划分】假如你有接近百万条oracle数据库数据&#xff08;成真版&#xff09; 写在最前面小结 数据集划分原因注意事项 1. 留出法&#xff08;Hold-out Method&#xff09;原理算法复杂度代码示例Scikit-learn的train_test_split分布式计算框架&#xff08;如Apache Spar…

信不信,马上教会你Purple Pi OH开发板之ADB常用命令

开源鸿蒙硬件方案领跑者 触觉智能 本文适用于在Purple Pi OH开发板进行分区镜像烧录。触觉智能的Purple Pi OH鸿蒙开源主板&#xff0c;是华为Laval官方社区主荐的一款鸿蒙开发主板。 该主板主要针对学生党&#xff0c;极客&#xff0c;工程师&#xff0c;极大降低了开源鸿蒙开…

【保姆级讲解Outlook邮箱的使用技巧】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

代码随想录第25天|回溯part5 通用的去重法:set

491.非递减子序列 中等题 这个题给出的实例很有陷阱性&#xff0c;之前的题是通过排序来对于相同树层的元素去重&#xff0c;而本题是求非递减子序列&#xff0c;如果排序了那就已经是自增子序列了&#xff0c;达不到题目的要求。 看图 可以看出&#xff0c;对于一个集合[4,…

超实用的新闻稿撰写模板分享,纯干货

一篇优秀的新闻稿&#xff0c;能为企业带来良好的口碑和传播效果。本文伯乐网络传媒将深入探讨新闻稿撰写前的准备工作&#xff0c;并提供一套实用的新闻稿结构模板&#xff0c;助你轻松打造高质量新闻稿。 一、新闻稿撰写前的准备 1. 明确新闻稿的主题和目的 在动笔之前&…

免费开源图片转文字识别软件:Umi-OCR

目录 1.介绍 2.项目亮点 3.项目功能&#xff08;已实现&#xff09; 4.功能体验 5.项目集成&#xff08;调用接口&#xff09; 6.项目地址 1.介绍 Umi-OCR&#xff1a;免费&#xff0c;开源&#xff0c;可批量的离线OCR软件&#xff0c;目前适用于 Windows7 x64 及以上。…

Unity开发Cosmos使用BNG Framework获取按键信息

Unity开发Cosmos使用BNG Framework获取按键信息 1、新建一个脚本&#xff0c;复制下面代码 using BNG;[Header("Input")]//[Tooltip("The key(s) to use to toggle locomotion type")]public List<ControllerBinding> locomotionToggleInput new …

Tomcat相关概述和部署

目录 一、Tomcat知识 1.Tomcat概述 2.Tomcat组件构成 3.Tomcat 功能组件结构 4.Tomcat的请求过程 二、tomcat服务部署 1.老样子准备工作——关闭防火墙和selinux&#xff0c;防止其对安装过程的干扰 2.将准备好的软件包拖入/opt目录下&#xff0c;进行安装JDK 3.设置J…