天天看点

鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 百篇博客分析HarmonyOS源码 | v4.05

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >

百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< osc | 51cto | csdn | harmony >

任务即线程

在鸿蒙内核中,广义上可理解为一个任务就是一个线程

官方是怎么描述线程的

基本概念

从系统的角度看,线程是竞争系统资源的最小运行单元。线程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它线程运行。

鸿蒙内核每个进程内的线程独立运行、独立调度,当前进程内线程的调度不受其它进程内线程的影响。

鸿蒙内核中的线程采用抢占式调度机制,同时支持时间片轮转调度和FIFO调度方式。

鸿蒙内核的线程一共有32个优先级(0-31),最高优先级为0,最低优先级为31。

当前进程内高优先级的线程可抢占当前进程内低优先级线程,当前进程内低优先级线程必须在当前进程内高优先级线程阻塞或结束后才能得到调度。

线程状态说明:

初始化(Init):该线程正在被创建。

就绪(Ready):该线程在就绪列表中,等待CPU调度。

运行(Running):该线程正在运行。

阻塞(Blocked):该线程被阻塞挂起。Blocked状态包括:pend(因为锁、事件、信号量等阻塞)、suspend(主动pend)、delay(延时阻塞)、pendtime(因为锁、事件、信号量时间等超时等待)。

退出(Exit):该线程运行结束,等待父线程回收其控制块资源。

图 1 线程状态迁移示意图

鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 百篇博客分析HarmonyOS源码 | v4.05

注意官方文档说的是线程,没有提到task(任务),但内核源码中却有大量 task代码,很少有线程(thread)代码 ,这是怎么回事?

其实在鸿蒙内核中, task就是线程, 初学者完全可以这么理解,但二者还是有区别,否则干嘛要分两个词描述。

会有什么区别?是管理上的区别,task是调度层面的概念,线程是进程层面的概念。 就像同一个人在不同的管理体系中会有不同的身份一样,一个男人既可以是 孩子,爸爸,丈夫,或者程序员,视角不同功能也会不同。

如何证明是一个东西,继续再往下看。

执行task命令

看shell task 命令的执行结果:

鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 百篇博客分析HarmonyOS源码 | v4.05

task命令 查出每个任务在生命周期内的运行情况,它运行的内存空间,优先级,时间片,入口执行函数,进程ID,状态等等信息,非常的复杂。这么复杂的信息就需要一个结构体来承载。而这个结构体就是 LosTaskCB(任务控制块)

对应张大爷的故事:task就是一个用户的节目清单里的一个节目,用户总清单就是一个进程,所以上面会有很多的节目。

task长得什么样子

说LosTaskCB之前先说下官方文档任务状态对应的 define,可以看出task和线程是一个东西。

#define OS_TASK_STATUS_INIT         0x0001U
#define OS_TASK_STATUS_READY        0x0002U
#define OS_TASK_STATUS_RUNNING      0x0004U
#define OS_TASK_STATUS_SUSPEND      0x0008U
#define OS_TASK_STATUS_PEND         0x0010U
#define OS_TASK_STATUS_DELAY        0x0020U
#define OS_TASK_STATUS_TIMEOUT      0x0040U
#define OS_TASK_STATUS_PEND_TIME    0x0080U
#define OS_TASK_STATUS_EXIT         0x0100U
           

LosTaskCB长什么样?抱歉,它确实有点长,但还是要全部贴出全貌。

typedef struct {
    VOID            *stackPointer;      /**< Task stack pointer */ //非用户模式下的栈指针
    UINT16          taskStatus;         /**< Task status */   //各种状态标签,可以拥有多种标签,按位标识
    UINT16          priority;           /**< Task priority */  //任务优先级[0:31],默认是31级
    UINT16          policy;    //任务的调度方式(三种 .. LOS_SCHED_RR )
    UINT16          timeSlice;          /**< Remaining time slice *///剩余时间片
    UINT32          stackSize;          /**< Task stack size */  //非用户模式下栈大小
    UINTPTR         topOfStack;         /**< Task stack top */  //非用户模式下的栈顶 bottom = top + size
    UINT32          taskID;             /**< Task ID */    //任务ID,任务池本质是一个大数组,ID就是数组的索引,默认 < 128
    TSK_ENTRY_FUNC  taskEntry;          /**< Task entrance function */ //任务执行入口函数
    VOID            *joinRetval;        /**< pthread adaption */ //用来存储join线程的返回值
    VOID            *taskSem;           /**< Task-held semaphore */ //task在等哪个信号量
    VOID            *taskMux;           /**< Task-held mutex */  //task在等哪把锁
    VOID            *taskEvent;         /**< Task-held event */  //task在等哪个事件
    UINTPTR         args[4];            /**< Parameter, of which the maximum number is 4 */ //入口函数的参数 例如 main (int argc,char *argv[])
    CHAR            taskName[OS_TCB_NAME_LEN]; /**< Task name */ //任务的名称
    LOS_DL_LIST     pendList;           /**< Task pend node */  //如果任务阻塞时就通过它挂到各种阻塞情况的链表上,比如OsTaskWait时
    LOS_DL_LIST     threadList;         /**< thread list */   //挂到所属进程的线程链表上
    SortLinkList    sortList;           /**< Task sortlink node */ //挂到cpu core 的任务执行链表上
    UINT32          eventMask;          /**< Event mask */   //事件屏蔽
    UINT32          eventMode;          /**< Event mode */   //事件模式
    UINT32          priBitMap;          /**< BitMap for recording the change of task priority, //任务在执行过程中优先级会经常变化,这个变量用来记录所有曾经变化
                                             the priority can not be greater than 31 */   //过的优先级,例如 ..01001011 曾经有过 0,1,3,6 优先级
    INT32           errorNo;            /**< Error Num */
    UINT32          signal;             /**< Task signal */ //任务信号类型,(SIGNAL_NONE,SIGNAL_KILL,SIGNAL_SUSPEND,SIGNAL_AFFI)
    sig_cb          sig;    //信号控制块,这里用于进程间通讯的信号,类似于 linux singal模块
#if (LOSCFG_KERNEL_SMP == YES)
    UINT16          currCpu;            /**< CPU core number of this task is running on */ //正在运行此任务的CPU内核号
    UINT16          lastCpu;            /**< CPU core number of this task is running on last time */ //上次运行此任务的CPU内核号
    UINT16          cpuAffiMask;        /**< CPU affinity mask, support up to 16 cores */ //CPU亲和力掩码,最多支持16核,亲和力很重要,多核情况下尽量一个任务在一个CPU核上运行,提高效率
    UINT32          timerCpu;           /**< CPU core number of this task is delayed or pended */ //此任务的CPU内核号被延迟或挂起
#if (LOSCFG_KERNEL_SMP_TASK_SYNC == YES)
    UINT32          syncSignal;         /**< Synchronization for signal handling */ //用于CPU之间 同步信号
#endif
#if (LOSCFG_KERNEL_SMP_LOCKDEP == YES) //死锁检测开关
    LockDep         lockDep;
#endif
#if (LOSCFG_KERNEL_SCHED_STATISTICS == YES) //调度统计开关,显然打开这个开关性能会受到影响,鸿蒙默认是关闭的
    SchedStat       schedStat;          /**< Schedule statistics */ //调度统计
#endif
#endif
    UINTPTR         userArea;   //使用区域,由运行时划定,根据运行态不同而不同
    UINTPTR         userMapBase;  //用户模式下的栈底位置
    UINT32          userMapSize;        /**< user thread stack size ,real size : userMapSize + USER_STACK_MIN_SIZE */
    UINT32          processID;          /**< Which belong process *///所属进程ID
    FutexNode       futex;    //实现快锁功能
    LOS_DL_LIST     joinList;           /**< join list */ //联结链表,允许任务之间相互释放彼此
    LOS_DL_LIST     lockList;           /**< Hold the lock list */ //拿到了哪些锁链表
    UINT32          waitID;             /**< Wait for the PID or GID of the child process */ //等待孩子的PID或GID进程
    UINT16          waitFlag;           /**< The type of child process that is waiting, belonging to a group or parent,
                                             a specific child process, or any child process */
#if (LOSCFG_KERNEL_LITEIPC == YES)
    UINT32          ipcStatus;   //IPC状态
    LOS_DL_LIST     msgListHead;  //消息队列头结点,上面挂的都是任务要读的消息
    BOOL            accessMap[LOSCFG_BASE_CORE_TSK_LIMIT];//访问图,指的是task之间是否能访问的标识,LOSCFG_BASE_CORE_TSK_LIMIT 为任务池总数
#endif
} LosTaskCB;
           

结构体LosTaskCB内容很多,各代表什么含义?

LosTaskCB相当于任务在内核中的身份证,它反映出每个任务在生命周期内的运行情况。既然是周期就会有状态,要运行就需要内存空间,就需要被内核算法调度,被选中CPU就去执行代码段指令,CPU要执行就需要告诉它从哪里开始执行,因为是多线程,但只有一个CPU就需要不断的切换任务,那执行会被中断,也需要再恢复后继续执行,又如何保证恢复的任务执行不会出错,这些问题都需要说明白。

Task怎么管理

什么是任务池?

前面已经说了任务是内核调度层面的概念,调度算法保证了task有序的执行,调度机制详见其他姊妹篇的介绍。

如此多的任务怎么管理和执行?管理靠任务池和就绪队列,执行靠调度算法。

代码如下(OsTaskInit):

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

    g_taskMaxNum = LOSCFG_BASE_CORE_TSK_LIMIT;//任务池中最多默认128个,可谓铁打的任务池流水的线程
    size = (g_taskMaxNum + 1) * sizeof(LosTaskCB);//计算需分配内存总大小
    /*
     * This memory is resident memory and is used to save the system resources
     * of task control block and will not be freed.
     */
    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 < g_taskMaxNum; index++) {
        g_taskCBArray[index].taskStatus = OS_TASK_STATUS_UNUSED;
        g_taskCBArray[index].taskID = index;//任务ID最大默认127
        LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList);//都插入空闲任务列表 
    }//注意:这里挂的是pendList节点,所以取TCB要通过 OS_TCB_FROM_PENDLIST 取.

    ret = OsPriQueueInit();//创建32个任务优先级队列,即32个双向循环链表
    if (ret != LOS_OK) {
        return LOS_ERRNO_TSK_NO_MEMORY;
    }

    /* init sortlink for each core */
    for (index = 0; index < LOSCFG_KERNEL_CORE_NUM; index++) {
        ret = OsSortLinkInit(&g_percpu[index].taskSortLink);//每个CPU内核都有一个执行任务链表
        if (ret != LOS_OK) {
            return LOS_ERRNO_TSK_NO_MEMORY;
        }
    }
    return LOS_OK;
}
           

g_taskCBArray 就是个任务池,默认创建128个任务,常驻内存,不被释放。

g_losFreeTask是空闲任务链表,想创建任务时来这里申请一个空闲任务,用完了就回收掉,继续给后面的申请使用。

g_taskRecyleList是回收任务链表,专用来回收exit 任务,任务所占资源被确认归还后被彻底删除,就像员工离职一样,得有个离职队列和流程,要归还电脑,邮箱,有没有借钱要还的 等操作。

对应张大爷的故事:用户要来场馆领取表格填节目单,场馆只准备了128张表格,领完就没有了,但是节目表演完了会回收表格,这样多了一张表格就可以给其他人领取了,这128张表格对应鸿蒙内核这就是任务池,简单吧。

就绪队列是怎么回事

CPU执行速度是很快的,鸿蒙内核默认一个时间片是 10ms, 资源有限,需要在众多任务中来回的切换,所以绝不能让CPU等待任务,CPU就像公司最大的领导,下面很多的部门等领导来审批,吃饭。只有大家等领导,哪有领导等你们的道理,所以工作要提前准备好,每个部门的优先级又不一样,所以每个部门都要有个任务队列,里面放的是领导能直接处理的任务,没准备好的不要放进来,因为这是给CPU提前准备好的粮食!

这就是就绪队列的原理,一共有32个就绪队列,进程和线程都有,因为线程的优先级是默认32个, 每个队列中放同等优先级的task.

还是看源码吧

#define OS_PRIORITY_QUEUE_NUM 32
LITE_OS_SEC_BSS LOS_DL_LIST *g_priQueueList = NULL;//队列链表
LITE_OS_SEC_BSS UINT32 g_priQueueBitmap;//队列位图 UINT32每位代表一个优先级,共32个优先级
//内部队列初始化
UINT32 OsPriQueueInit(VOID)
{
    UINT32 priority;

    /* system resident resource *///常驻内存
    g_priQueueList = (LOS_DL_LIST *)LOS_MemAlloc(m_aucSysMem0, (OS_PRIORITY_QUEUE_NUM * sizeof(LOS_DL_LIST)));//分配32个队列头节点
    if (g_priQueueList == NULL) {
        return LOS_NOK;
    }

    for (priority = 0; priority < OS_PRIORITY_QUEUE_NUM; ++priority) {
        LOS_ListInit(&g_priQueueList[priority]);//队列初始化,前后指针指向自己
    }
    return LOS_OK;
}
           

注意看g_priQueueList 的内存分配,就是32个LOS_DL_LIST,还记得LOS_DL_LIST的妙用吗,不清楚的去 鸿蒙系统源码分析(总目录)里面翻。

对应张大爷的故事:就是门口那些排队的都是至少有一个节目单是符合表演标准的,资源都到位了,没有的连排队的资格都木有,就慢慢等吧。

任务栈是怎么回事

每个任务都是独立开的,任务之间也相互独立,之间通讯通过IPC,这里的“独立”指的是每个任务都有自己的运行环境 —— 栈空间,称为任务栈,栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等等

但系统中只有一个CPU,任务又是独立的,调度的本质就是CPU执行一个新task,老task在什么地方被中断谁也不清楚,是随机的。那如何保证老任务被再次调度选中时还能从上次被中断的地方继续玩下去呢?

答案是:任务上下文,CPU内有一堆的寄存器,CPU运行本质的就是这些寄存器的值不断的变化,只要切换时把这些值保存起来,再还原回去就能保证task的连续执行,让用户毫无感知。鸿蒙内核给一个任务执行的时间是 20ms ,也就是说有多任务竞争的情况下,一秒钟内最多要来回切换50次。

对应张大爷的故事:就是碰到节目没有表演完就必须打断的情况下,需要把当时的情况记录下来,比如小朋友在演躲猫猫的游戏,一半不演了,张三正在树上,李四正在厕所躲,都记录下来,下次再回来你们上次在哪就会哪呆着去,就位了继续表演。这样就接上了,观众就木有感觉了。

任务上下文(TaskContext)是怎样的呢?还是直接看源码

/* The size of this structure must be smaller than or equal to the size specified by OS_TSK_STACK_ALIGN (16 bytes). */
typedef struct {
   
#if !defined(LOSCFG_ARCH_FPU_DISABLE)
    UINT64 D[FP_REGS_NUM]; /* D0-D31 */
    UINT32 regFPSCR;       /* FPSCR */
    UINT32 regFPEXC;       /* FPEXC */
#endif
    UINT32 resved;          /* It's stack 8 aligned */
    UINT32 regPSR;
    UINT32 R[GEN_REGS_NUM]; /* R0-R12 */
    UINT32 SP;              /* R13 */
    UINT32 LR;              /* R14 */
    UINT32 PC;              /* R15 */
} TaskContext;
           

发现基本都是CPU寄存器的恢复现场值, 具体各寄存器有什么作用大家可以去网上详查,后续也有专门的文章来介绍。这里说其中的三个寄存器 SP, LR, PC

LR

用途有二,一是保存子程序返回地址,当调用BL、BX、BLX等跳转指令时会自动保存返回地址到LR;二是保存异常发生的异常返回地址。

PC(Program Counter)

为程序计数器,用于保存程序的执行地址,在ARM的三级流水线架构中,程序流水线包括取址、译码和执行三个阶段,PC指向的是当前取址的程序地址,所以32位ARM中,译码地址(正在解析还未执行的程序)为PC-4,执行地址(当前正在执行的程序地址)为PC-8, 当突然发生中断的时候,保存的是PC的地址。

SP

每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。

任务栈初始化

任务栈的初始化就是任务上下文的初始化,因为任务没开始执行,里面除了上下文不会有其他内容,注意上下文存放的位置在栈的底部。初始状态下 sp就是指向的栈底, 栈顶内容永远是 0xCCCCCCCC "烫烫烫烫",这几个字应该很熟悉吗? 如果不是那几个字了,那说明栈溢出了, 后续篇会详细说明这块,大家也可以自行去看代码,很有意思.

Task函数集

LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskID, UINT32 stackSize, VOID *topStack, BOOL initFlag)
{
   
    UINT32 index = 1;
    TaskContext *taskContext = NULL;

    if (initFlag == TRUE) {
   
        OsStackInit(topStack, stackSize);
    }
    taskContext = (TaskContext *)(((UINTPTR)topStack + stackSize) - sizeof(TaskContext));//注意看上下文将存放在栈的底部

    /* initialize the task context */
#ifdef LOSCFG_GDB
    taskContext->PC = (UINTPTR)OsTaskEntrySetupLoopFrame;
#else
    taskContext->PC = (UINTPTR)OsTaskEntry;//程序计数器,CPU首次执行task时跑的第一条指令位置
#endif
    taskContext->LR = (UINTPTR)OsTaskExit;  /* LR should be kept, to distinguish it's THUMB or ARM instruction */
    taskContext->resved = 0x0;
    taskContext->R[0] = taskID;             /* R0 */
    taskContext->R[index++] = 0x01010101;   /* R1, 0x01010101 : reg initialed magic word */
    for (; index < GEN_REGS_NUM; index++) {
   //R2 - R12的初始化很有意思,为什么要这么做?
        taskContext->R[index] = taskContext->R[index - 1] + taskContext->R[1]; /* R2 - R12 */
    }

#ifdef LOSCFG_INTERWORK_THUMB // 16位模式
    taskContext->regPSR = PSR_MODE_SVC_THUMB; /* CPSR (Enable IRQ and FIQ interrupts, THUMNB-mode) */
#else
    taskContext->regPSR = PSR_MODE_SVC_ARM;   /* CPSR (Enable IRQ and FIQ interrupts, ARM-mode) */
#endif

#if !defined(LOSCFG_ARCH_FPU_DISABLE)
    /* 0xAAA0000000000000LL : float reg initialed magic word */
    for (index = 0; index < FP_REGS_NUM; index++) {
   
        taskContext->D[index] = 0xAAA0000000000000LL + index; /* D0 - D31 */
    }
    taskContext->regFPSCR = 0;
    taskContext->regFPEXC = FP_EN;
#endif

    return (VOID *)taskContext;
}
           

使用场景和功能

任务创建后,内核可以执行锁任务调度,解锁任务调度,挂起,恢复,延时等操作,同时也可以设置任务优先级,获取任务优先级。任务结束的时候,则进行当前任务自删除操作。

Huawei LiteOS 系统中的任务管理模块为用户提供下面几种功能。

功能分类 接口名 描述
任务的创建和删除 LOS_TaskCreateOnly 创建任务,并使该任务进入suspend状态,并不调度。
LOS_TaskCreate 创建任务,并使该任务进入ready状态,并调度。
LOS_TaskDelete 删除指定的任务。
任务状态控制 LOS_TaskResume 恢复挂起的任务。
LOS_TaskSuspend 挂起指定的任务。
LOS_TaskDelay 任务延时等待。
LOS_TaskYield 显式放权,调整指定优先级的任务调度顺序。
任务调度的控制 LOS_TaskLock 锁任务调度。
LOS_TaskUnlock 解锁任务调度。
任务优先级的控制 LOS_CurTaskPriSet 设置当前任务的优先级。
LOS_TaskPriSet 设置指定任务的优先级。
LOS_TaskPriGet 获取指定任务的优先级。
任务信息获取 LOS_CurTaskIDGet 获取当前任务的ID。
LOS_TaskInfoGet 设置指定任务的优先级。
LOS_TaskPriGet 获取指定任务的信息。
LOS_TaskStatusGet 获取指定任务的状态。
LOS_TaskNameGet 获取指定任务的名称。
LOS_TaskInfoMonitor 监控所有任务,获取所有任务的信息。
LOS_NextTaskIDGet 获取即将被调度的任务的ID。

创建任务的过程

创建任务之前先了解另一个结构体 tagTskInitParam

typedef struct tagTskInitParam {//Task的初始化参数
    TSK_ENTRY_FUNC  pfnTaskEntry;  /**< Task entrance function */ //任务的入口函数
    UINT16          usTaskPrio;    /**< Task priority */ //任务优先级
    UINT16          policy;        /**< Task policy */  //任务调度方式
    UINTPTR         auwArgs[4];    /**< Task parameters, of which the maximum number is four */ //入口函数的参数,最多四个
    UINT32          uwStackSize;   /**< Task stack size */ //任务栈大小
    CHAR            *pcName;       /**< Task name */  //任务名称
#if (LOSCFG_KERNEL_SMP == YES)
    UINT16          usCpuAffiMask; /**< Task cpu affinity mask         */ //任务cpu亲和力掩码
#endif
    UINT32          uwResved;      /**< It is automatically deleted if set to LOS_TASK_STATUS_DETACHED.
                                        It is unable to be deleted if set to 0. */ //如果设置为LOS_TASK_STATUS_DETACHED,则自动删除。如果设置为0,则无法删除
    UINT16          consoleID;     /**< The console id of task belongs  */ //任务的控制台id所属
    UINT32          processID; //进程ID
    UserTaskParam   userParam; //在用户态运行时栈参数
} TSK_INIT_PARAM_S;
           

这些初始化参数是外露的任务初始参数,

pfnTaskEntry 对java来说就是你new进程的run(),

需要上层使用者提供.

看个例子吧:shell中敲 ping 命令看下它创建的过程

u32_t osShellPing(int argc, const char **argv)
{
   
    int ret;
    u32_t i = 0;
    u32_t count = 0;
    int count_set = 0;
    u32_t interval = 1000; /* default ping interval */
    u32_t data_len = 48; /* default data length */
    ip4_addr_t dst_ipaddr;
    TSK_INIT_PARAM_S stPingTask;
    // ...省去一些中间代码
    /* start one task if ping forever or ping count greater than 60 */
    if (count == 0 || count > LWIP_SHELL_CMD_PING_RETRY_TIMES) {
   
        if (ping_taskid > 0) {
   
            PRINTK("Ping task already running and only support one now\n");
            return LOS_NOK;
        }
        stPingTask.pfnTaskEntry = (TSK_ENTRY_FUNC)ping_cmd;//线程的执行函数
        stPingTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//0x4000 = 16K 
        stPingTask.pcName = "ping_task";
        stPingTask.usTaskPrio = 8; /* higher than shell 优先级高于10,属于内核态线程*/ 
        stPingTask.uwResved = LOS_TASK_STATUS_DETACHED;
        stPingTask.auwArgs[0] = dst_ipaddr.addr; /* network order */
        stPingTask.auwArgs[1] = count;
        stPingTask.auwArgs[2] = interval;
        stPingTask.auwArgs[3] = data_len;
        ret = LOS_TaskCreate((UINT32 *)(&ping_taskid), &stPingTask);
    }
 // ...
    return LOS_OK;
ping_error:
    lwip_ping_usage();
    return LOS_NOK;
}
           

发现ping的调度优先级是8,比shell 还高,那shell的是多少?答案是:看源码是 9

LITE_OS_SEC_TEXT_MINOR UINT32 ShellTaskInit(ShellCB *shellCB)
{
   
    CHAR *name = NULL;
    TSK_INIT_PARAM_S initParam = {
   0};
    if (shellCB->consoleID == CONSOLE_SERIAL) {
   
        name = SERIAL_SHELL_TASK_NAME;
    } else if (shellCB->consoleID == CONSOLE_TELNET) {
   
        name = TELNET_SHELL_TASK_NAME;
    } else {
   
        return LOS_NOK;
    }
    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ShellTask;
    initParam.usTaskPrio   = 9; /* 9:shell task priority */
    initParam.auwArgs[0]   = (UINTPTR)shellCB;
    initParam.uwStackSize  = 0x3000;
    initParam.pcName       = name;
    initParam.uwResved     = LOS_TASK_STATUS_DETACHED;
    (VOID)LOS_EventInit(&shellCB->shellEvent);
    return LOS_TaskCreate(&shellCB->shellTaskHandle, &initParam);
}
           

关于shell后续会详细介绍,请持续关注。

前置条件了解清楚后,具体看任务是如何一步步创建的,如何和进程绑定,加入调度就绪队列,还是继续看源码

//创建Task
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *initParam)
{
    UINT32 ret;
    UINT32 intSave;
    LosTaskCB *taskCB = NULL;

    if (initParam == NULL) {
        return LOS_ERRNO_TSK_PTR_NULL;
    }

    if (OS_INT_ACTIVE) {
        return LOS_ERRNO_TSK_YIELD_IN_INT;
    }

    if (initParam->uwResved & OS_TASK_FLAG_IDLEFLAG) {//OS_TASK_FLAG_IDLEFLAG 是属于内核 idle进程专用的
        initParam->processID = OsGetIdleProcessID();//获取空闲进程
    } else if (OsProcessIsUserMode(OsCurrProcessGet())) {//当前进程是否为用户模式
        initParam->processID = OsGetKernelInitProcessID();//不是就取"Kernel"进程
    } else {
        initParam->processID = OsCurrProcessGet()->processID;//获取当前进程 ID赋值
    }
    initParam->uwResved &= ~OS_TASK_FLAG_IDLEFLAG;//不能是 OS_TASK_FLAG_IDLEFLAG
    initParam->uwResved &= ~OS_TASK_FLAG_PTHREAD_JOIN;//不能是 OS_TASK_FLAG_PTHREAD_JOIN
    if (initParam->uwResved & LOS_TASK_STATUS_DETACHED) {//是否设置了自动删除
        initParam->uwResved = OS_TASK_FLAG_DETACHED;//自动删除,注意这里是 = ,也就是说只有 OS_TASK_FLAG_DETACHED 一个标签了
    }

    ret = LOS_TaskCreateOnly(taskID, initParam);//创建一个任务,这是任务创建的实体,前面都只是前期准备工作
    if (ret != LOS_OK) {
        return ret;
    }
    taskCB = OS_TCB_FROM_TID(*taskID);//通过ID拿到task实体

    SCHEDULER_LOCK(intSave);
    taskCB->taskStatus &= ~OS_TASK_STATUS_INIT;//任务不再是初始化
    OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB, 0);//进入调度就绪队列,新任务是直接进入就绪队列的
    SCHEDULER_UNLOCK(intSave);

    /* in case created task not running on this core,
       schedule or not depends on other schedulers status. */
    LOS_MpSchedule(OS_MP_CPU_ALL);//如果创建的任务没有在这个核心上运行,是否调度取决于其他调度程序的状态。
    if (OS_SCHEDULER_ACTIVE) {//当前CPU核处于可调度状态
        LOS_Schedule();//发起调度
    }

    return LOS_OK;
}
           

对应张大爷的故事:就是节目单要怎么填,按格式来,从哪里开始演,要多大的空间,王场馆好协调好现场的环境。这里注意 在同一个节目单只要节目没演完,王场馆申请场地的空间就不能给别人用,这个场地空间对应的就是鸿蒙任务的栈空间,除非整个节目单都完了,就回收了。把整个场地干干净净的留给下一个人的节目单来表演。

至此的创建已经完成,已各就各位,源码最后还申请了一次LOS_Schedule();因为鸿蒙的调度方式是抢占式的,如何本次task的任务优先级高于其他就绪队列,那么接下来要执行的任务就是它了!

鸿蒙源码百篇博客 往期回顾

在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感.百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.😛

写文章比写代码累多了,越深入研究,越觉得没写好,所以文章和注解会反复修正, .xx代表修改的次数, 将持续完善源码注解和文档内容,精雕细琢,言简意赅, 尽全力打磨精品内容.

  • v50.xx (编译环境篇) | 编译鸿蒙看这篇或许真的够了 < csdn | 51cto | osc >
  • v49.xx (信号消费篇) | 两次切换用户栈和内核栈 < csdn | 51cto | osc >
  • v48.xx (信号生产篇) | 如何安装和发送异步信号 < csdn | 51cto | osc >
  • v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | osc >
  • v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | osc >
  • v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 < csdn | 51cto | osc >
  • v44.xx (中断管理篇) | 硬中断的实现像观察者模式 < csdn | 51cto | osc >
  • v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | osc >
  • v42.xx (中断切换篇) | 中断切换又在切换什么 < csdn | 51cto | osc >
  • v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | osc >
  • v40.xx (汇编汇总篇) | 所有的汇编代码都在呢 < csdn | 51cto | osc >
  • v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | osc >
  • v38.xx (寄存器篇) | 看完再也不对寄存器怕怕 < csdn | 51cto | osc >
  • v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | osc >
  • v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆 < csdn | 51cto | osc >
  • v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | osc >
  • v34.xx (原子操作篇) | 是谁在为原子操作保驾护航 < csdn | 51cto | osc >
  • v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 < csdn | 51cto | osc >
  • v32.xx (cpu篇) | 整个内核就是一个死循环 < csdn | 51cto | osc >
  • v31.xx (定时器篇) | 内核最高优先级任务是谁 < csdn | 51cto | osc >
  • v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | osc >
  • v29.xx (信号量篇) | 信号量解决任务的什么问题 < csdn | 51cto | osc >
  • v28.xx (进程通讯篇) | 九种进程间通讯方式速揽 < csdn | 51cto | osc >
  • v27.xx (互斥锁篇) | 比自旋锁丰满的互斥锁 < csdn | 51cto | osc >
  • v26.xx (自旋锁篇) | 自旋锁当立贞节牌坊 < csdn | 51cto | osc >
  • v25.xx (并发并行篇) | 两个字永远记住二者区别 < csdn | 51cto | osc >
  • v24.xx (进程概念篇) | 进程在管理哪些资源 < csdn | 51cto | osc >
  • v23.xx (汇编传参篇) | 汇编如何传递复杂的参数 < csdn | 51cto | osc >
  • v22.xx (汇编基础篇) | cpu在哪里打卡上班 < csdn | 51cto | osc >
  • v21.xx (线程概念篇) | 是谁在不断的折腾cpu < csdn | 51cto | osc >
  • v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | osc >
  • v19.xx (位图管理篇) | 为何进程和线程优先级都是32个 < csdn | 51cto | osc >
  • v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | osc >
  • v17.xx (物理内存篇) | 怎么管理物理内存 < csdn | 51cto | osc >
  • v16.xx (内存规则篇) | 内存管理到底在管什么 < csdn | 51cto | osc >
  • v15.xx (内存映射篇) | 虚拟内存虚在哪里 < csdn | 51cto | osc >
  • v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础 < csdn | 51cto | osc >
  • v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | 51cto | osc >
  • v12.xx (内存管理篇) | 虚拟内存全景图是怎样的 < csdn | 51cto | osc >
  • v11.xx (内存分配篇) | 内存有哪些分配方式 < csdn | 51cto | osc >
  • v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处 < csdn | 51cto | osc >
  • v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | osc >
  • v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | osc >
  • v07.xx (调度机制篇) | 任务是如何被调度执行的 < csdn | 51cto | osc >
  • v06.xx (调度队列篇) | 内核有多少个调度队列 < csdn | 51cto | osc >
  • v05.xx (任务管理篇) | 任务池是如何管理的 < csdn | 51cto | osc >
  • v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | osc >
  • v03.xx (时钟任务篇) | 谁是触发调度的最大动力 < csdn | 51cto | osc >
  • v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | osc >
  • v01.xx (双向链表篇) | 谁是内核最重要结构体 < csdn | 51cto | osc >

进入 >> osc | csdn | 51cto | 掘金 | 公众号 | 头条号 | gitee | github

各大站点搜 "鸿蒙内核源码分析" .原创不易,欢迎转载,但请注明出处.

热爱是所有的理由和答案 - turing

继续阅读