LiteOS内核源码分析系列六 -任务及调度(2)-任务LOS_Task

举报
zhushy 发表于 2021/04/01 08:15:12 2021/04/01
【摘要】 LiteOS内核源码分析系列六 -任务及调度(2)-任务LOS_Task任务是操作系统一个重要的概念,是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行。Huawei LiteOS的任务模块可以给用户提供多个任务,实现任务间的切换,帮助用户管理业务程序流程。本文我们来一起学习下LiteOS任务模块的源代码。文中所涉及的源代码,均可以在Lit...

LiteOS内核源码分析系列六 -任务及调度(2)-任务LOS_Task

任务是操作系统一个重要的概念,是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行。Huawei LiteOS的任务模块可以给用户提供多个任务,实现任务间的切换,帮助用户管理业务程序流程。本文我们来一起学习下LiteOS任务模块的源代码。文中所涉及的源代码,均可以在LiteOS开源站点https://gitee.com/LiteOS/LiteOS 获取。任务模块源代码、开发文档路径如下:


有关任务的基本概念、任务状态及状态迁移的介绍,请参考LiteOS开发指南任务模块文档。接下来,我们看下任务模块的结构体,任务初始化,任务常用操作的源代码。

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

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

typedef struct {
    VOID            *stackPointer;      /* 任务栈指针 */
    UINT16          taskStatus;         /* 任务状态 */
    UINT16          priority;           /* 任务优先级 */
    UINT32          taskFlags : 31;     /* 任务扩展标记,支持标记为自删除任务OS_TASK_FLAG_DETACHED、 系统级任务OS_TASK_FLAG_SYSTEM*/
    UINT32          usrStack : 1;       /* 是否使用用户栈 */
    UINT32          stackSize;          /* 任务栈大小 */
    UINTPTR         topOfStack;         /* 栈顶指针 */
    UINT32          taskId;             /* 任务Id */
    TSK_ENTRY_FUNC  taskEntry;          /* 任务入口函数 */
    VOID            *taskSem;           /* 任务持有的信号量 */
#ifdef LOSCFG_LAZY_STACK
    UINT32          stackFrame;         /* 栈帧: 0=Basic, 1=Extended */
#endif
#ifdef LOSCFG_COMPAT_POSIX
    VOID            *threadJoin;        /* pthread 适配 */
    VOID            *threadJoinRetval;  /* pthread 适配 */
#endif
    VOID            *taskMux;           /* 导致任务阻塞的互斥锁 */
#ifdef LOSCFG_OBSOLETE_API
    UINTPTR         args[4];            /* 任务入口函数的参数,兼容遗留API */
#else
    VOID            *args;              /* 任务入口函数的参数 */
#endif
    CHAR            *taskName;          /* 任务名称 */
    LOS_DL_LIST     pendList;           /* 任务就绪队列等链表节点 */
    SortLinkList    sortList;           /* 任务超时排序链表节点 */
#ifdef LOSCFG_BASE_IPC_EVENT
    EVENT_CB_S      event;
    UINT32          eventMask;          /* 事件掩码 */
    UINT32          eventMode;          /* 事件模式 */
#endif
    VOID            *msg;               /* 分给给队列的内存*/
    UINT32          priBitMap;          /* BitMap for recording the change of task priority,
                                             the priority can not be greater than 31 */
    UINT32          signal;             /* 任务信号 */
#ifdef LOSCFG_BASE_CORE_TIMESLICE
    UINT16          timeSlice;          /* 剩余的时间片 */
#endif
#ifdef LOSCFG_KERNEL_SMP
    UINT16          currCpu;            /* 当前运行的CPU核编号 */
    UINT16          lastCpu;            /* 上次运行的CPU核编号 */
    UINT32          timerCpu;           /* 任务阻塞或请求的CPU核编号 */
    UINT16          cpuAffiMask;        /* CPU亲和性掩码,最多支持16个核 */
#ifdef LOSCFG_KERNEL_SMP_TASK_SYNC
    UINT32          syncSignal;         /* 同步信号处理标记 */
#endif
#ifdef LOSCFG_KERNEL_SMP_LOCKDEP
    LockDep         lockDep;
#endif
#endif
#ifdef LOSCFG_DEBUG_SCHED_STATISTICS
    SchedStat       schedStat;          /* 调度统计*/
#endif
#ifdef LOSCFG_KERNEL_PERF
    UINTPTR         pc;
    UINTPTR         fp;
#endif
} LosTaskCB;

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

#ifdef LOSCFG_OBSOLETE_API
typedef VOID *(*TSK_ENTRY_FUNC)(UINTPTR param1,
                                UINTPTR param2,
                                UINTPTR param3,
                                UINTPTR param4);
#else
typedef VOID *(*TSK_ENTRY_FUNC)(VOID *param);
#endif

typedef struct tagTskInitParam {
    TSK_ENTRY_FUNC  pfnTaskEntry;  /** 任务入口函数,函数类型定义为TSK_ENTRY_FUNC */
    UINT16          usTaskPrio;    /** 任务优先级 */
#ifdef LOSCFG_OBSOLETE_API
    UINTPTR         auwArgs[4];    /**< 任务参数  */
#else
    VOID            *pArgs;        /**< 任务参数 */
#endif
    UINT32          uwStackSize;   /** 任务栈大小 */
    CHAR            *pcName;       /** 任务名称  */
#ifdef LOSCFG_KERNEL_SMP
    UINT16          usCpuAffiMask; /**< CPU亲和性掩码,需开启多核 */
#endif
    UINT32          uwResved;      /**< 若设置为 #LOS_TASK_STATUS_DETACHED,任务会自动删除 */
} TSK_INIT_PARAM_S;

2、任务模块初始化

在系统启动时,在kernel\init\los_init.c中调用OsTaskInit()进行任务模块初始化,还会调用OsIdleTaskCreate()创建空闲任务。

2.1 任务模块初始化

函数OsTaskInit()定义在kernel\base\los_task.c,我们分析下这个函数的执行过程。

⑴处代码获取开发板配置的最大任务数g_taskMaxNum,计算需要申请的内存大小size,为任务控制块TCB数组(也叫作任务池)g_taskCBArray申请内存。为什么比最大任务数多申请一个呢?在删除任务时会使用。下文分析删除任务的源码时再详细讲解其用意。⑵处代码初始化双向链表g_losFreeTask用作空闲的任务链表、g_taskRecycleList可以回收的任务链表。⑶处循环初始化每一个任务,任务状态未使用OS_TASK_STATUS_UNUSED,初始化任务Id,并把任务挂在空闲任务链表上。⑷处初始化优先级队列,详细的代码实现剖析,参见之前的源码剖析文章。⑸处互斥锁死锁检测的调测特性的,后续系列文章专题进行讲解。⑹处代码初始化排序链表,详细的代码实现剖析,参见之前的源码剖析文章。⑺处如果开启了惰性栈,计算TCB的成员变量stackFrame在其结构体中的偏移量g_stackFrameOffLenInTcb

LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID)
{
    UINT32 index;
    UINT32 ret;
    UINT32 size;

⑴  g_taskMaxNum = LOSCFG_BASE_CORE_TSK_LIMIT;
    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_taskRecycleList);for (index = 0; index < g_taskMaxNum; index++) {
        g_taskCBArray[index].taskStatus = OS_TASK_STATUS_UNUSED;
        g_taskCBArray[index].taskId = index;
        LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList);
    }

⑷  ret = OsPriQueueInit();
    if (ret != LOS_OK) {
        return LOS_ERRNO_TSK_NO_MEMORY;
    }

⑸  ret = OsMuxDlockCheckInitHook();
    if (ret != LOS_OK) {
        return LOS_ERRNO_TSK_NO_MEMORY;
    }for (index = 0; index < LOSCFG_KERNEL_CORE_NUM; index++) {
        ret = OsSortLinkInit(&g_percpu[index].taskSortLink);
        if (ret != LOS_OK) {
            return LOS_ERRNO_TSK_NO_MEMORY;
        }
    }

#ifdef LOSCFG_LAZY_STACK
⑺  g_stackFrameOffLenInTcb = (UINT16)LOS_OFF_SET_OF(LosTaskCB, stackFrame);
#endif

    return LOS_OK;
}

2.2 创建空闲任务IdleCore000

除了初始化任务池,在系统启动阶段还会创建idle空闲任务。⑴处设置任务初始化参数时,空闲任务的入口执行函数为OsIdleTask()。⑵处可以看出空闲任务标记为了系统级别的任务OS_TASK_FLAG_SYSTEM

LITE_OS_SEC_TEXT_INIT UINT32 OsIdleTaskCreate(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S taskInitParam;
    Percpu *perCpu = OsPercpuGet();
    UINT32 *idleTaskId = &perCpu->idleTaskId;

    (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;
#ifdef LOSCFG_KERNEL_SMP
    taskInitParam.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());
#endif
    ret = LOS_TaskCreate(idleTaskId, &taskInitParam);
    if (ret == LOS_OK) {OS_TCB_FROM_TID(*idleTaskId)->taskFlags |= OS_TASK_FLAG_SYSTEM;
    }

    return ret;
}

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

LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID)
{
    while (1) {
        LOS_TaskResRecycle();

#ifdef LOSCFG_KERNEL_LOWPOWER
        if (g_lowPowerHook != NULL) {
            g_lowPowerHook();
        }
#else
        wfi();
#endif
    }
}

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。