鸿蒙轻内核M核源码分析系列六任务及任务调度(2)任务模块

开发
本文带领大家一起剖析了鸿蒙轻内核任务模块的源代码,包含任务模块的结构体,任务初始化过程源代码,任务常用操作的源代码。

[[399389]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

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

接下来,我们看下任务模块的结构体,任务初始化,任务常用操作的源代码。

1、任务模块的结构体定义

在文件kernel\include\los_task.h定义的任务控制块结构体LosTaskCB,源代码如下,结构体成员的解释见注释部分。

  1. typedef struct { 
  2.     VOID                        *stackPointer;            /* 任务栈指针 */ 
  3.     UINT16                      taskStatus;               /* 任务状态 */ 
  4.     UINT16                      priority;                 /* 任务优先级 */ 
  5.     INT32                       timeSlice;                /* 剩余的时间片 */ 
  6.     UINT32                      waitTimes; 
  7.     SortLinkList                sortList;                 /* 任务超时排序链表节点 */ 
  8.     UINT64                      startTime; 
  9.     UINT32                      stackSize;                /* 任务栈大小 */ 
  10.     UINT32                      topOfStack;               /* 栈顶指针 */ 
  11.     UINT32                      taskID;                   /* 任务编号Id */ 
  12.     TSK_ENTRY_FUNC              taskEntry;                /* 任务入口函数 */ 
  13.     VOID                        *taskSem;                 /* 任务持有的信号量 */ 
  14.     VOID                        *taskMux;                 /* 导致任务阻塞的互斥锁 */ 
  15.     UINT32                      arg;                      /* 任务入口函数的参数 */ 
  16.     CHAR                        *taskName;                /* 任务名称 */ 
  17.     LOS_DL_LIST                 pendList;                 /* 就绪队列等链表节点 */ 
  18.     LOS_DL_LIST                 timerList;                /* 任务超时排序链表节点 */ 
  19.     EVENT_CB_S                  event; 
  20.     UINT32                      eventMask;                /* 事件掩码 */ 
  21.     UINT32                      eventMode;                /* 事件模式 */ 
  22.     VOID                        *msg;                     /* 分给给队列的内存*/ 
  23.     INT32                       errorNo; 
  24. } LosTaskCB; 

 另外一个比较重要的结构体是TSK_INIT_PARAM_S,创建任务时,需要指定任务初始化的参数。源代码如下,结构体成员的解释见注释部分。

  1. typedef struct tagTskInitParam { 
  2.     TSK_ENTRY_FUNC       pfnTaskEntry;              /** 任务入口函数 */ 
  3.     UINT16               usTaskPrio;                /** 任务参数  */ 
  4.     UINT32               uwStackSize;               /** 任务栈大小 */ 
  5.     CHAR                 *pcName;                   /** 任务名称  */ 
  6.     UINT32               uwResved;                  /** 保留  */ 
  7. } 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。

  1. LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID) 
  2.     UINT32 size
  3.     UINT32 index
  4.  
  5. ⑴  size = (g_taskMaxNum + 1) * sizeof(LosTaskCB); 
  6.     g_taskCBArray = (LosTaskCB *)LOS_MemAlloc(m_aucSysMem0, size); 
  7.     if (g_taskCBArray == NULL) { 
  8.         return LOS_ERRNO_TSK_NO_MEMORY; 
  9.     } 
  10.     (VOID)memset_s(g_taskCBArray, size, 0, size); 
  11.  
  12. ⑵  LOS_ListInit(&g_losFreeTask); 
  13.     LOS_ListInit(&g_taskRecyleList); 
  14. ⑶  for (index = 0; index <= LOSCFG_BASE_CORE_TSK_LIMIT; index++) { 
  15.         g_taskCBArray[index].taskStatus = OS_TASK_STATUS_UNUSED; 
  16.         g_taskCBArray[index].taskID = index
  17.         LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList); 
  18.     } 
  19.  
  20.     // Ignore the return code when matching CSEC rule 6.6(4). 
  21. ⑷  (VOID)memset_s((VOID *)(&g_losTask), sizeof(g_losTask), 0, sizeof(g_losTask)); 
  22.     g_losTask.runTask = &g_taskCBArray[g_taskMaxNum]; 
  23.     g_losTask.runTask->taskID = index
  24.     g_losTask.runTask->taskStatus = (OS_TASK_STATUS_UNUSED | OS_TASK_STATUS_RUNNING); 
  25.     g_losTask.runTask->priority = OS_TASK_PRIORITY_LOWEST + 1; 
  26.  
  27. ⑸  g_idleTaskID = OS_INVALID; 
  28. ⑹  return OsSchedInit(); 

 2.2 创建空闲任务IdleCore000

除了初始化任务池,在系统启动阶段还会创建idle空闲任务。⑴处设置任务初始化参数时,空闲任务的入口执行函数为OsIdleTask()。⑵处调用函数把空闲任务状态设置为就绪状态。

  1. LITE_OS_SEC_TEXT_INIT UINT32 OsIdleTaskCreate(VOID) 
  2.     UINT32 retVal; 
  3.     TSK_INIT_PARAM_S taskInitParam; 
  4.     // Ignore the return code when matching CSEC rule 6.6(4). 
  5.     (VOID)memset_s((VOID *)(&taskInitParam), sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S)); 
  6. ⑴  taskInitParam.pfnTaskEntry = (TSK_ENTRY_FUNC)OsIdleTask; 
  7.     taskInitParam.uwStackSize = LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE; 
  8.     taskInitParam.pcName = "IdleCore000"
  9.     taskInitParam.usTaskPrio = OS_TASK_PRIORITY_LOWEST; 
  10.     retVal = LOS_TaskCreateOnly(&g_idleTaskID, &taskInitParam); 
  11.  
  12.     if (retVal != LOS_OK) { 
  13.         return retVal; 
  14.     } 
  15.  
  16. ⑵  OsSchedSetIdleTaskSchedPartam(OS_TCB_FROM_TID(g_idleTaskID)); 
  17.     return LOS_OK; 

 我们看下空闲任务的入口执行函数为OsIdleTask(),它调用OsRecyleFinishedTask()回收任务栈资源,后文会分析如何回收任务资源。

  1. LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID) 
  2.     while (1) { 
  3.         OsRecyleFinishedTask(); 
  4.         HalEnterSleep(OS_SYS_DEEP_SLEEP); 
  5.     } 

 3、任务模块常用操作

3.1 创建和删除任务

3.1.1 创建任务

鸿蒙轻内核提供了2个创建任务的函数,有LOS_TaskCreate、LOS_TaskCreateOnly。LOS_TaskCreate和LOS_TaskCreateOnly的区别是,前者创建任务完毕就使任务进入就绪状态,并触发调度,如果就绪队列中没有更高优先级的任务,则运行该任务。后者只创建任务,设置任务状态为阻塞suspend状态,需要开发者去调用LOS_TaskResume使该任务进入ready状态。

函数LOS_TaskCreate代码如下,可以看出创建任务的时候,调用⑴处的函数LOS_TaskCreateOnly()来创建任务。创建任务后,执行⑵处的代码使任务进入ready就绪队列,如果系统启动完成,允许任务调度,则执行⑶触发任务调度。如果新创建的任务优先级最高,则会被调度运行。 

  1. LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam) 
  2.     UINT32 retVal; 
  3.     UINTPTR intSave; 
  4.     LosTaskCB *taskCB = NULL
  5.  
  6. ⑴  retVal = LOS_TaskCreateOnly(taskID, taskInitParam); 
  7.     if (retVal != LOS_OK) { 
  8.         return retVal; 
  9.     } 
  10.     taskCB = OS_TCB_FROM_TID(*taskID); 
  11.  
  12.     intSave = LOS_IntLock(); 
  13. #if (LOSCFG_BASE_CORE_CPUP == 1) 
  14.     g_cpup[taskCB->taskID].cpupID = taskCB->taskID; 
  15.     g_cpup[taskCB->taskID].status = taskCB->taskStatus; 
  16. #endif 
  17.  
  18. ⑵  OsSchedTaskEnQueue(taskCB); 
  19.     LOS_IntRestore(intSave); 
  20.  
  21. ⑶  if (g_taskScheduled) { 
  22.         LOS_Schedule(); 
  23.     } 
  24.  
  25.     return LOS_OK; 

我们接着分析下如何使用函数UINT32 LOS_TaskCreateOnly()创建任务。⑴处调用OsTaskInitParamCheck()检测创建任务的参数的合法性。⑵处调用函数回收释放的任务。⑶处如果任务池为空,无法创建任务,返回错误码。⑷处从任务池获取一个空闲的任务控制块taskCB,然后从空闲任务链表中删除。⑸处根据指定的任务栈大小为任务栈申请内存,⑹处判断任务栈内存申请释放成功,如果申请失败,则把任务控制块归还到空闲任务链表中,并返回错误码。⑺处调用函数初始化任务栈,更新任务控制块成员信息。详细见后面对该函数的分析。 

  1. LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreateOnly(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam) 
  2.     UINTPTR intSave; 
  3.     VOID  *topOfStack = NULL
  4.     LosTaskCB *taskCB = NULL
  5.     UINT32 retVal; 
  6.  
  7.     if (taskID == NULL) { 
  8.         return LOS_ERRNO_TSK_ID_INVALID; 
  9.     } 
  10.  
  11. ⑴  retVal = OsTaskInitParamCheck(taskInitParam); 
  12.     if (retVal != LOS_OK) { 
  13.         return retVal; 
  14.     } 
  15.  
  16. ⑵  OsRecyleFinishedTask(); 
  17.  
  18.     intSave = LOS_IntLock(); 
  19. ⑶  if (LOS_ListEmpty(&g_losFreeTask)) { 
  20.         retVal = LOS_ERRNO_TSK_TCB_UNAVAILABLE; 
  21.         OS_GOTO_ERREND(); 
  22.     } 
  23.  
  24. ⑷  taskCB = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&g_losFreeTask)); 
  25.     LOS_ListDelete(LOS_DL_LIST_FIRST(&g_losFreeTask)); 
  26.  
  27.     LOS_IntRestore(intSave); 
  28.  
  29. #if (LOSCFG_EXC_HRADWARE_STACK_PROTECTION == 1) 
  30.     UINTPTR stackPtr = (UINTPTR)LOS_MemAllocAlign(OS_TASK_STACK_ADDR, taskInitParam->uwStackSize + 
  31.         OS_TASK_STACK_PROTECT_SIZE, OS_TASK_STACK_PROTECT_SIZE); 
  32.     topOfStack = (VOID *)(stackPtr + OS_TASK_STACK_PROTECT_SIZE); 
  33. #else 
  34. ⑸      topOfStack = (VOID *)LOS_MemAllocAlign(OS_TASK_STACK_ADDR, taskInitParam->uwStackSize, 
  35.         LOSCFG_STACK_POINT_ALIGN_SIZE); 
  36. #endif 
  37. ⑹  if (topOfStack == NULL) { 
  38.         intSave = LOS_IntLock(); 
  39.         LOS_ListAdd(&g_losFreeTask, &taskCB->pendList); 
  40.         LOS_IntRestore(intSave); 
  41.         return LOS_ERRNO_TSK_NO_MEMORY; 
  42.     } 
  43.  
  44. ⑺  retVal = OsNewTaskInit(taskCB, taskInitParam, topOfStack); 
  45.     if (retVal != LOS_OK) { 
  46.         return retVal; 
  47.     } 
  48.  
  49.     *taskID = taskCB->taskID; 
  50.     OsHookCall(LOS_HOOK_TYPE_TASK_CREATE, taskCB); 
  51.     return retVal; 
  52.  
  53. LOS_ERREND: 
  54.     LOS_IntRestore(intSave); 
  55.     return retVal; 

 我们看下创建任务函数调用的函数OsRecyleFinishedTask(),该函数在系统进入空闲时也会调用。删除运行状态的任务时,会把任务挂在双向链表里g_taskRecyleList。任务回收函数就用来回收此类任务,实现任务资源回收。我们分析下它的代码。⑴处循环遍历回收链表,⑵从回收链表获取第一个任务taskCB,从回收链表删除并插入到空闲任务链表里。任务栈保护在后续系列再深入分析,继续往下看代码,⑶处获取任务栈栈顶指针,接着调用内存释放函数来释放任务栈占用的内存,并设置任务栈的栈顶为空。

  1. STATIC VOID OsRecyleFinishedTask(VOID) 
  2.     LosTaskCB *taskCB = NULL
  3.     UINTPTR intSave; 
  4.     UINTPTR stackPtr; 
  5.  
  6.     intSave = LOS_IntLock(); 
  7. ⑴  while (!LOS_ListEmpty(&g_taskRecyleList)) { 
  8. ⑵      taskCB = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&g_taskRecyleList)); 
  9.         LOS_ListDelete(LOS_DL_LIST_FIRST(&g_taskRecyleList)); 
  10.         LOS_ListAdd(&g_losFreeTask, &taskCB->pendList); 
  11. #if (LOSCFG_EXC_HRADWARE_STACK_PROTECTION == 1) 
  12.         stackPtr = taskCB->topOfStack - OS_TASK_STACK_PROTECT_SIZE; 
  13. #else 
  14. ⑶      stackPtr = taskCB->topOfStack; 
  15. #endif 
  16.         (VOID)LOS_MemFree(OS_TASK_STACK_ADDR, (VOID *)stackPtr); 
  17.         taskCB->topOfStack = (UINT32)NULL
  18.     } 
  19.     LOS_IntRestore(intSave); 

 我们继续分析下函数OsNewTaskInit(),⑴处调用函数初始化任务栈,上一系列已经分析过该函数,代码的其余部分用来更新任务控制块的成员信息,比如⑵处任务状态设置为阻塞状态。

  1. LITE_OS_SEC_TEXT_INIT UINT32 OsNewTaskInit(LosTaskCB *taskCB, TSK_INIT_PARAM_S *taskInitParam, VOID *topOfStack) 
  2. ⑴  taskCB->stackPointer    = HalTskStackInit(taskCB->taskID, taskInitParam->uwStackSize, topOfStack); 
  3.     taskCB->arg             = taskInitParam->uwArg; 
  4.     taskCB->topOfStack      = (UINT32)(UINTPTR)topOfStack; 
  5.     taskCB->stackSize       = taskInitParam->uwStackSize; 
  6.     taskCB->taskSem         = NULL
  7.     taskCB->taskMux         = NULL
  8. ⑵  taskCB->taskStatus      = OS_TASK_STATUS_SUSPEND; 
  9.     taskCB->priority        = taskInitParam->usTaskPrio; 
  10.     taskCB->timeSlice       = 0; 
  11.     taskCB->waitTimes       = 0; 
  12.     taskCB->taskEntry       = taskInitParam->pfnTaskEntry; 
  13.     taskCB->event.uwEventID = OS_NULL_INT; 
  14.     taskCB->eventMask       = 0; 
  15.     taskCB->taskName        = taskInitParam->pcName; 
  16.     taskCB->msg             = NULL
  17.     SET_SORTLIST_VALUE(&taskCB->sortList, OS_SORT_LINK_INVALID_TIME); 
  18.     return LOS_OK; 

 3.1.2 删除任务UINT32 LOS_TaskDelete()

该函数根据传入的参数UINT32 taskId删除任务。我们分析下删除任务的源代码,⑴处检验传入的参数,⑵处如果任务还未创建,返回错误码。⑶处如果删除的任务正在运行,又处于锁任务调度情况下,打印信息,告诉用户不推荐在锁任务调度期间进行任务删除,然后执行⑷,把全局变量赋值0来解锁任务调度。

⑸处调用函数处理任务状态,如果处于就绪状态设置为非就绪状态,并从就绪队列删除。如果处于阻塞状态,从阻塞队列中删除。如果任务处于超时等待状态,从超时排序链表中删除。⑹恢复任务控制块事件相关的成员信息。⑺如果任务正在运行,设置任务为未使用状态,接着调用函数OsRunningTaskDelete()把任务放入回收链表,然后主动触发任务调度,稍后详细分析该函数。如果删除的任务不是出于运行状态,则执行⑻,设置任务为未使用状态,接着把任务回收到空闲任务链表里,然后获取任务栈的栈顶指针,调用内存释放函数释放任务栈的内存。

  1. LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskDelete(UINT32 taskID) 
  2.     UINTPTR intSave; 
  3.     LosTaskCB *taskCB = OS_TCB_FROM_TID(taskID); 
  4.     UINTPTR stackPtr; 
  5.  
  6. ⑴  UINT32 ret = OsCheckTaskIDValid(taskID); 
  7.     if (ret != LOS_OK) { 
  8.         return ret; 
  9.     } 
  10.  
  11.     intSave = LOS_IntLock(); 
  12.  
  13. ⑵  if ((taskCB->taskStatus) & OS_TASK_STATUS_UNUSED) { 
  14.         LOS_IntRestore(intSave); 
  15.         return LOS_ERRNO_TSK_NOT_CREATED; 
  16.     } 
  17.  
  18.     /* If the task is running and scheduler is locked then you can not delete it */ 
  19. ⑶  if (((taskCB->taskStatus) & OS_TASK_STATUS_RUNNING) && (g_losTaskLock != 0)) { 
  20.         PRINT_INFO("In case of task lock, task deletion is not recommended\n"); 
  21. ⑷      g_losTaskLock = 0; 
  22.     } 
  23.  
  24.     OsHookCall(LOS_HOOK_TYPE_TASK_DELETE, taskCB); 
  25. ⑸  OsSchedTaskExit(taskCB); 
  26.  
  27. ⑹  taskCB->event.uwEventID = OS_NULL_INT; 
  28.     taskCB->eventMask = 0; 
  29. #if (LOSCFG_BASE_CORE_CPUP == 1) 
  30.     // Ignore the return code when matching CSEC rule 6.6(4). 
  31.     (VOID)memset_s((VOID *)&g_cpup[taskCB->taskID], sizeof(OsCpupCB), 0, sizeof(OsCpupCB)); 
  32. #endif 
  33.     if (taskCB->taskStatus & OS_TASK_STATUS_RUNNING) { 
  34. ⑺      taskCB->taskStatus = OS_TASK_STATUS_UNUSED; 
  35.         OsRunningTaskDelete(taskID, taskCB); 
  36.         LOS_IntRestore(intSave); 
  37.         LOS_Schedule(); 
  38.         return LOS_OK; 
  39.     } else { 
  40. ⑻       taskCB->taskStatus = OS_TASK_STATUS_UNUSED; 
  41.         LOS_ListAdd(&g_losFreeTask, &taskCB->pendList); 
  42. #if (LOSCFG_EXC_HRADWARE_STACK_PROTECTION == 1) 
  43.         stackPtr = taskCB->topOfStack - OS_TASK_STACK_PROTECT_SIZE; 
  44. #else 
  45.         stackPtr = taskCB->topOfStack; 
  46. #endif 
  47.         (VOID)LOS_MemFree(OS_TASK_STACK_ADDR, (VOID *)stackPtr); 
  48.         taskCB->topOfStack = (UINT32)NULL
  49.     } 
  50.  
  51.     LOS_IntRestore(intSave); 
  52.     return LOS_OK; 

 我们看下函数OsRunningTaskDelete()的源码。⑴处把当前运行的任务放入待回收链表里,然后执行⑵把当前运行的任务放入任务池的最后一个位置g_taskCBArray[g_taskMaxNum]。为什么这么操作呢?等后续分析源码的时候再来解答。

  1. LITE_OS_SEC_TEXT_INIT STATIC_INLINE VOID OsRunningTaskDelete(UINT32 taskID, LosTaskCB *taskCB) 
  2. ⑴  LOS_ListTailInsert(&g_taskRecyleList, &taskCB->pendList); 
  3. ⑵  g_losTask.runTask = &g_taskCBArray[g_taskMaxNum]; 
  4.     g_losTask.runTask->taskID = taskID; 
  5.     g_losTask.runTask->taskStatus = taskCB->taskStatus | OS_TASK_STATUS_RUNNING; 
  6.     g_losTask.runTask->topOfStack = taskCB->topOfStack; 
  7.     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)中的一种,这几个状态影响恢复挂起的任务。如果非上述几个状态,执行⑸调用函数,把任务状态改为就绪状态,插入任务就绪队列。如果支持支持调度,则执行⑹触发调度。

  1. LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskResume(UINT32 taskID) 
  2.     UINTPTR intSave; 
  3.     LosTaskCB *taskCB = NULL
  4.     UINT16 tempStatus; 
  5.     UINT32 retErr = OS_ERROR; 
  6.  
  7.     if (taskID > LOSCFG_BASE_CORE_TSK_LIMIT) { 
  8.         return LOS_ERRNO_TSK_ID_INVALID; 
  9.     } 
  10.  
  11. ⑴  taskCB = OS_TCB_FROM_TID(taskID); 
  12.     intSave = LOS_IntLock(); 
  13.     tempStatus = taskCB->taskStatus; 
  14.  
  15. ⑵  if (tempStatus & OS_TASK_STATUS_UNUSED) { 
  16.         retErr = LOS_ERRNO_TSK_NOT_CREATED; 
  17.         OS_GOTO_ERREND(); 
  18.     } else if (!(tempStatus & OS_TASK_STATUS_SUSPEND)) { 
  19.         retErr = LOS_ERRNO_TSK_NOT_SUSPENDED; 
  20.         OS_GOTO_ERREND(); 
  21.     } 
  22.  
  23. ⑶  taskCB->taskStatus &= (~OS_TASK_STATUS_SUSPEND); 
  24. ⑷  if (!(taskCB->taskStatus & OS_CHECK_TASK_BLOCK)) { 
  25. ⑸      OsSchedTaskEnQueue(taskCB); 
  26.         if (g_taskScheduled) { 
  27.             LOS_IntRestore(intSave); 
  28. ⑹          LOS_Schedule(); 
  29.             return LOS_OK; 
  30.         } 
  31.     } 
  32.  
  33.     LOS_IntRestore(intSave); 
  34.     return LOS_OK; 
  35.  
  36. LOS_ERREND: 
  37.     LOS_IntRestore(intSave); 
  38.     return retErr; 

 3.2.2 挂起指定的任务LOS_TaskSuspend()

函数用于挂起指定的任务。⑴处获取任务的TCB,⑵处开始获取任务的状态进行判断,如果任务没有创建、任务已经挂起,返回相应的错误码。⑶处如果任务是运行状态,并且锁任务调度时,跳转到LOS_ERREND结束挂起操作。⑷处如果任务是就绪状态,调用函数从就绪队列出队,并取消任务的就绪状态。⑸处语句设置任务状态为阻塞状态。⑹如果挂起的是当前运行的任务,则会主动触发调度。

  1. LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskSuspend(UINT32 taskID) 
  2.     UINTPTR intSave; 
  3.     LosTaskCB *taskCB = NULL
  4.     UINT16 tempStatus; 
  5.     UINT32 retErr; 
  6.  
  7.     retErr = OsCheckTaskIDValid(taskID); 
  8.     if (retErr != LOS_OK) { 
  9.         return retErr; 
  10.     } 
  11.  
  12. ⑴  taskCB = OS_TCB_FROM_TID(taskID); 
  13.     intSave = LOS_IntLock(); 
  14. ⑵  tempStatus = taskCB->taskStatus; 
  15.     if (tempStatus & OS_TASK_STATUS_UNUSED) { 
  16.         retErr = LOS_ERRNO_TSK_NOT_CREATED; 
  17.         OS_GOTO_ERREND(); 
  18.     } 
  19.  
  20.     if (tempStatus & OS_TASK_STATUS_SUSPEND) { 
  21.         retErr = LOS_ERRNO_TSK_ALREADY_SUSPENDED; 
  22.         OS_GOTO_ERREND(); 
  23.     } 
  24.  
  25. ⑶  if ((tempStatus & OS_TASK_STATUS_RUNNING) && (g_losTaskLock != 0)) { 
  26.         retErr = LOS_ERRNO_TSK_SUSPEND_LOCKED; 
  27.         OS_GOTO_ERREND(); 
  28.     } 
  29.  
  30. ⑷  if (tempStatus & OS_TASK_STATUS_READY) { 
  31.         OsSchedTaskDeQueue(taskCB); 
  32.     } 
  33.  
  34. ⑸  taskCB->taskStatus |= OS_TASK_STATUS_SUSPEND; 
  35.     OsHookCall(LOS_HOOK_TYPE_MOVEDTASKTOSUSPENDEDLIST, taskCB); 
  36. ⑹  if (taskID == g_losTask.runTask->taskID) { 
  37.         LOS_IntRestore(intSave); 
  38.         LOS_Schedule(); 
  39.         return LOS_OK; 
  40.     } 
  41.  
  42.     LOS_IntRestore(intSave); 
  43.     return LOS_OK; 
  44.  
  45. LOS_ERREND: 
  46.     LOS_IntRestore(intSave); 
  47.     return retErr; 

 3.2.3 任务延时等待LOS_TaskDelay()

任务延时等待,释放CPU,等待时间到期后该任务会重新进入就绪状态。

⑴处代码判断系统处于中断,如果是,则返回错误码,不允许任务延时等待。

⑵如果处于锁任务调度期间,则返回错误码。

⑶处如果延迟的时间为0,则执行让权操作,否则执行。

⑷调用函数OsSchedDelay()把当前任务设置为延时等待状态,然后调用LOS_Schedule()触发调度。

  1. LITE_OS_SEC_TEXT UINT32 LOS_TaskDelay(UINT32 tick) 
  2.     UINTPTR intSave; 
  3.  
  4. ⑴  if (OS_INT_ACTIVE) { 
  5.         return LOS_ERRNO_TSK_DELAY_IN_INT; 
  6.     } 
  7.  
  8. ⑵  if (g_losTaskLock != 0) { 
  9.         return LOS_ERRNO_TSK_DELAY_IN_LOCK; 
  10.     } 
  11.  
  12.     OsHookCall(LOS_HOOK_TYPE_TASK_DELAY, tick); 
  13. ⑶  if (tick == 0) { 
  14.         return LOS_TaskYield(); 
  15.     } else { 
  16.         intSave = LOS_IntLock(); 
  17. ⑷      OsSchedDelay(g_losTask.runTask, tick); 
  18.         OsHookCall(LOS_HOOK_TYPE_MOVEDTASKTODELAYEDLIST, g_losTask.runTask); 
  19.         LOS_IntRestore(intSave); 
  20.         LOS_Schedule(); 
  21.     } 
  22.  
  23.     return LOS_OK; 

 另外还提供了函数LOS_Msleep()和LOS_UDelay(),前者以毫秒为单位进行延迟等待。后者也是以毫秒为单位进行延迟等待,但是不会触发任务调度,当前任务不会释放CPU。

  1. LITE_OS_SEC_TEXT_MINOR VOID LOS_Msleep(UINT32 mSecs) 
  2.     UINT32 interval; 
  3.  
  4.     if (OS_INT_ACTIVE) { 
  5.         return
  6.     } 
  7.  
  8.     if (mSecs == 0) { 
  9.         interval = 0; 
  10.     } else { 
  11.         interval = LOS_MS2Tick(mSecs); 
  12.         if (interval == 0) { 
  13.             interval = 1; 
  14.         } 
  15.     } 
  16.  
  17.     (VOID)LOS_TaskDelay(interval); 
  18.  
  19. VOID LOS_UDelay(UINT64 microseconds) 
  20.     UINT64 endTime; 
  21.  
  22.     if (microseconds == 0) { 
  23.         return
  24.     } 
  25.  
  26.     endTime = (microseconds / OS_SYS_US_PER_SECOND) * OS_SYS_CLOCK + 
  27.             (microseconds % OS_SYS_US_PER_SECOND) * OS_SYS_CLOCK / OS_SYS_US_PER_SECOND; 
  28.     endTime = LOS_SysCycleGet() + endTime; 
  29.     while (LOS_SysCycleGet() < endTime) { 
  30.     } 
  31.  
  32.     return

 3.2.4 任务让权LOS_TaskYield()

让权函数通过把当前任务时间片设置为0,释放CPU占用,重新调度给其他高优先级任务执行。⑴处调用函数把当前任务时间片设置为0,然后执行⑵主动触发任务调度。

  1. LITE_OS_SEC_TEXT_MINOR UINT32 LOS_TaskYield(VOID) 
  2.     UINTPTR intSave; 
  3.  
  4.     intSave = LOS_IntLock(); 
  5. ⑴  OsSchedYield(); 
  6.     LOS_IntRestore(intSave); 
  7. ⑵  LOS_Schedule(); 
  8.     return LOS_OK; 

 接下来看下函数OsSchedYield()的源码。代码很简单,获取当前运行的任务,然后把其时间片设置为0,如下:

  1. VOID OsSchedYield(VOID) 
  2.     LosTaskCB *runTask = g_losTask.runTask; 
  3.  
  4.     runTask->timeSlice = 0; 

 3.3 控制任务调度

3.3.1 锁任务调度LOS_TaskLock()

锁任务调度LOS_TaskLock()比较简单,把任务锁调度计数器全局变量增加1即可,代码如下。

  1. LITE_OS_SEC_TEXT_MINOR VOID LOS_TaskLock(VOID) 
  2.     UINTPTR intSave; 
  3.  
  4.     intSave = LOS_IntLock(); 
  5.     g_losTaskLock++; 
  6.     LOS_IntRestore(intSave); 

 3.3.2 解锁任务调度LOS_TaskUnlock()

我们看看解锁任务调度函数LOS_TaskUnlock(),⑴处如果任务锁调度计数器全局变量数值大于0,对其减1。⑵处如果任务锁调度计数器等于0,则执行⑶处触发调度。代码如下:

  1. LITE_OS_SEC_TEXT_MINOR VOID LOS_TaskUnlock(VOID) 
  2.     UINTPTR intSave; 
  3.  
  4.     intSave = LOS_IntLock(); 
  5. ⑴  if (g_losTaskLock > 0) { 
  6.         g_losTaskLock--; 
  7. ⑵      if (g_losTaskLock == 0) { 
  8.             LOS_IntRestore(intSave); 
  9. ⑶          LOS_Schedule(); 
  10.             return
  11.         } 
  12.     } 
  13.  
  14.     LOS_IntRestore(intSave); 

 3.4 控制任务优先级

LiteOS-M内核支持动态设置任务的优先级,提供了一些操作。

3.4.1 设置指定任务的优先级LOS_TaskPriSet

支持设置指定任务Id的优先级,也支持对当前运行任务进行优先级设置。⑴处开始,做些基础校验,包含检验传入的优先级参数taskPrio,指定任务的Id,任务是否未创建等,如果没有通过参数校验,则返回错误码。⑵处调用函数设置任务优先级,稍后分析该函数。如果任务处于就绪状态或者运行状态,则会执行⑶主动触发任务调度。

  1. LITE_OS_SEC_TEXT_MINOR UINT32 LOS_TaskPriSet(UINT32 taskID, UINT16 taskPrio) 
  2.     BOOL isReady = FALSE
  3.     UINTPTR intSave; 
  4.     LosTaskCB *taskCB = NULL
  5.     UINT16 tempStatus; 
  6.  
  7. ⑴  if (taskPrio > OS_TASK_PRIORITY_LOWEST) { 
  8.         return LOS_ERRNO_TSK_PRIOR_ERROR; 
  9.     } 
  10.  
  11.     if (taskID == g_idleTaskID) { 
  12.         return LOS_ERRNO_TSK_OPERATE_IDLE; 
  13.     } 
  14.  
  15.     if (taskID == g_swtmrTaskID) { 
  16.         return LOS_ERRNO_TSK_OPERATE_SWTMR; 
  17.     } 
  18.  
  19.     if (OS_CHECK_TSK_PID_NOIDLE(taskID)) { 
  20.         return LOS_ERRNO_TSK_ID_INVALID; 
  21.     } 
  22.  
  23.     taskCB = OS_TCB_FROM_TID(taskID); 
  24.     intSave = LOS_IntLock(); 
  25.     tempStatus = taskCB->taskStatus; 
  26.     if (tempStatus & OS_TASK_STATUS_UNUSED) { 
  27.         LOS_IntRestore(intSave); 
  28.         return LOS_ERRNO_TSK_NOT_CREATED; 
  29.     } 
  30.  
  31. ⑵  isReady = OsSchedModifyTaskSchedParam(taskCB, taskPrio); 
  32.     LOS_IntRestore(intSave); 
  33.     if (isReady) { 
  34. ⑶      LOS_Schedule(); 
  35.     } 
  36.  
  37.     return LOS_OK; 

 接下来,我们分析下函数OsSchedModifyTaskSchedParam()。⑴处如果任务处于就绪状态,需要先出队设置优先级,然后入队就绪队列。如果非就绪状态,可以直接执行⑵处语句修改任务优先级。如果任务正在运行,需要返回TRUE,标记下需要任务调度。

  1. BOOL OsSchedModifyTaskSchedParam(LosTaskCB *taskCB, UINT16 priority) 
  2.     if (taskCB->taskStatus & OS_TASK_STATUS_READY) { 
  3. ⑴      OsSchedTaskDeQueue(taskCB); 
  4.         taskCB->priority = priority; 
  5.         OsSchedTaskEnQueue(taskCB); 
  6.         return TRUE
  7.     } 
  8.  
  9. ⑵  taskCB->priority = priority; 
  10.     OsHookCall(LOS_HOOK_TYPE_TASK_PRIMODIFY, taskCB, taskCB->priority); 
  11.     if (taskCB->taskStatus & OS_TASK_STATUS_RUNNING) { 
  12.         return TRUE
  13.     } 
  14.  
  15.     return FALSE

 3.4.2 获取指定任务的优先级LOS_TaskPriGet

获取指定任务的优先级LOS_TaskPriGet()代码比较简单,⑴处如果任务编号无效,返回错误码。⑵处如果任务未创建返回错误码。如果参数校验通过,执行⑶获取任务的优先级数值。

  1. LITE_OS_SEC_TEXT_MINOR UINT16 LOS_TaskPriGet(UINT32 taskID) 
  2.     UINTPTR intSave; 
  3.     LosTaskCB *taskCB = NULL
  4.     UINT16 priority; 
  5.  
  6. ⑴  if (OS_CHECK_TSK_PID_NOIDLE(taskID)) { 
  7.         return (UINT16)OS_INVALID; 
  8.     } 
  9.  
  10.     taskCB = OS_TCB_FROM_TID(taskID); 
  11.  
  12.     intSave = LOS_IntLock(); 
  13.  
  14. ⑵  if (taskCB->taskStatus & OS_TASK_STATUS_UNUSED) { 
  15.         LOS_IntRestore(intSave); 
  16.         return (UINT16)OS_INVALID; 
  17.     } 
  18.  
  19. ⑶  priority = taskCB->priority; 
  20.     LOS_IntRestore(intSave); 
  21.     return priority; 

 3.5 任务阻塞和唤醒

最后,我们分析下函数OsSchedTaskWait()和OsSchedTaskWake(),这2个函数定义在文件kernel\src\los_sched.c中。任务在申请互斥锁、信号量、出入队列、读写事件时,都可能导致任务进入阻塞状态,对应地也需要任务唤醒重新进入就绪队列状态。这2个函数就负责任务的阻塞和唤醒,我们分析下他们的代码。

3.5.1 任务阻塞

我们分析下任务阻塞的函数OsSchedTaskWait(),需要2个参数:LOS_DL_LIST *list是互斥锁等资源的阻塞链表,阻塞的任务会挂这个链表里;UINT32 ticks是任务阻塞的时间。分析下具体代码:

⑴获取正在请求互斥锁等资源的当前任务,⑵设置任务状态为阻塞状态。⑶把任务插入互斥锁等资源的阻塞链表的尾部。⑷如果不是永久阻塞等待,任务的状态还需要设置为:

  1. VOID OsSchedTaskWait(LOS_DL_LIST *list, UINT32 ticks) 
  2. ⑴  LosTaskCB *runTask = g_losTask.runTask; 
  3.  
  4. ⑵  runTask->taskStatus |= OS_TASK_STATUS_PEND; 
  5. ⑶  LOS_ListTailInsert(list, &runTask->pendList); 
  6.  
  7.     if (ticks != LOS_WAIT_FOREVER) { 
  8. ⑷      runTask->taskStatus |= OS_TASK_STATUS_PEND_TIME; 
  9.         runTask->waitTimes = ticks; 
  10.     } 

 3.5.2 任务唤醒

我们分析下任务唤醒的函数OsSchedTaskWake(),需要1个参数:LosTaskCB *resumedTask是需要唤醒的任务;任务唤醒函数会从阻塞链表里删除并加入就绪队列,下面分析下具体代码:

⑴把要唤醒的任务从所在的阻塞队列中删除,然后更改状态不再为阻塞状态。⑵如果任务不是永久等待,需要从定时器排序链表中删除,并设置状态不再是等待超时。⑶如果任务是阻塞状态,改为就绪状态并加入就绪队列。

  1. VOID OsSchedTaskWake(LosTaskCB *resumedTask) 
  2. ⑴  LOS_ListDelete(&resumedTask->pendList); 
  3.     resumedTask->taskStatus &= ~OS_TASK_STATUS_PEND; 
  4.  
  5. ⑵  if (resumedTask->taskStatus & OS_TASK_STATUS_PEND_TIME) { 
  6.         OsDeleteSortLink(&resumedTask->sortList, OS_SORT_LINK_TASK); 
  7.         resumedTask->taskStatus &= ~OS_TASK_STATUS_PEND_TIME; 
  8.     } 
  9.  
  10. ⑶  if (!(resumedTask->taskStatus & OS_TASK_STATUS_SUSPEND)) { 
  11.         OsSchedTaskEnQueue(resumedTask); 
  12.     } 

 小结

本文带领大家一起剖析了鸿蒙轻内核任务模块的源代码,包含任务模块的结构体,任务初始化过程源代码,任务常用操作的源代码。后续也会陆续推出更多的分享文章,敬请期待。

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2021-05-20 09:50:20

鸿蒙HarmonyOS应用

2021-05-13 09:47:08

鸿蒙HarmonyOS应用

2021-05-12 09:45:20

鸿蒙HarmonyOS应用

2021-05-10 15:05:56

鸿蒙HarmonyOS应用

2021-06-04 09:57:49

鸿蒙HarmonyOS应用

2022-01-12 10:50:23

鸿蒙HarmonyOS应用

2022-01-10 15:31:44

鸿蒙HarmonyOS应用

2021-10-20 16:08:57

鸿蒙HarmonyOS应用

2021-05-25 09:28:34

鸿蒙HarmonyOS应用

2021-06-04 14:15:10

鸿蒙HarmonyOS应用

2021-05-08 15:14:50

鸿蒙HarmonyOS应用

2021-05-17 09:28:59

鸿蒙HarmonyOS应用

2021-05-31 20:30:55

鸿蒙HarmonyOS应用

2022-04-13 11:02:12

鸿蒙事件模块事件Event

2022-03-03 18:28:28

Harmony进程任务管理模块

2022-03-11 20:23:14

鸿蒙源码分析进程管理

2021-06-09 09:48:01

鸿蒙HarmonyOS应用

2021-05-11 09:54:55

鸿蒙HarmonyOS应用

2021-05-27 09:43:56

鸿蒙HarmonyOS应用

2021-05-21 09:25:11

鸿蒙HarmonyOS应用
点赞
收藏

51CTO技术栈公众号