v04.03 鸿蒙内核源码分析(任务调度) | 任务是内核调度的单元 原创
子谓公冶长,“可妻也。虽在缧绁之中,非其罪也”。以其子妻之。《论语》:公冶长篇
百篇博客系列篇.本篇为:
v04.xx 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元
任务管理相关篇为:
- v03.06 鸿蒙内核源码分析(时钟任务) | 触发调度谁的贡献最大
- v04.03 鸿蒙内核源码分析(任务调度) | 任务是内核调度的单元
- v05.05 鸿蒙内核源码分析(任务管理) | 任务池是如何管理的
- v06.03 鸿蒙内核源码分析(调度队列) | 内核有多少个调度队列
- v07.08 鸿蒙内核源码分析(调度机制) | 任务是如何被调度执行的
- v21.07 鸿蒙内核源码分析(线程概念) | 是谁在不断的折腾CPU
- v25.05 鸿蒙内核源码分析(并发并行) | 听过无数遍的两个概念
- v32.03 鸿蒙内核源码分析(CPU) | 整个内核就是一个死循环
- v37.06 鸿蒙内核源码分析(系统调用) | 开发者永远的口头禅
- v41.03 鸿蒙内核源码分析(任务切换) | 看汇编如何切换任务
任务即线程
在鸿蒙内核中,广义上可理解为一个任务就是一个线程
官方是怎么描述线程的
基本概念
从系统的角度看,线程是竞争系统资源的最小运行单元。线程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它线程运行。
鸿蒙内核每个进程内的线程独立运行、独立调度,当前进程内线程的调度不受其它进程内线程的影响。
鸿蒙内核中的线程采用抢占式调度机制,同时支持时间片轮转调度和FIFO调度方式。
鸿蒙内核的线程一共有32个优先级(0-31),最高优先级为0,最低优先级为31。
当前进程内高优先级的线程可抢占当前进程内低优先级线程,当前进程内低优先级线程必须在当前进程内高优先级线程阻塞或结束后才能得到调度。
线程状态说明:
初始化(Init):该线程正在被创建。
就绪(Ready):该线程在就绪列表中,等待CPU调度。
运行(Running):该线程正在运行。
阻塞(Blocked):该线程被阻塞挂起。Blocked状态包括:pend(因为锁、事件、信号量等阻塞)、suspend(主动pend)、delay(延时阻塞)、pendtime(因为锁、事件、信号量时间等超时等待)。
退出(Exit):该线程运行结束,等待父线程回收其控制块资源。
图 1 线程状态迁移示意图
注意官方文档说的是线程,没有提到task(任务),但内核源码中却有大量 task代码,很少有线程(thread)代码 ,这是怎么回事?
其实在鸿蒙内核中, task就是线程, 初学者完全可以这么理解,但二者还是有区别,否则干嘛要分两个词描述。
会有什么区别?是管理上的区别,task是调度层面的概念,线程是进程层面的概念。 就像同一个人在不同的管理体系中会有不同的身份一样,一个男人既可以是 孩子,爸爸,丈夫,或者程序员,视角不同功能也会不同。
如何证明是一个东西,继续再往下看。
执行task命令
看shell task 命令的执行结果:
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的任务优先级高于其他就绪队列,那么接下来要执行的任务就是它了!
百万汉字注解.精读内核源码
百篇博客分析.深挖内核地基
给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 :P
与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,.xx
代表修改的次数,精雕细琢,言简意赅,力求打造精品内容。
基础工具>> 双向链表 | 位图管理 | 用栈方式 | 定时器 | 原子操作 | 时间管理 |
加载运行>> ELF格式 | ELF解析 | 静态链接 | 重定位 | 进程映像 |
进程管理>> 进程管理 | 进程概念 | Fork | 特殊进程 | 进程回收 | 信号生产 | 信号消费 | Shell编辑 | Shell解析 |
编译构建>> 编译环境 | 编译过程 | 环境脚本 | 构建工具 | gn应用 | 忍者ninja |
进程通讯>> 自旋锁 | 互斥锁 | 进程通讯 | 信号量 | 事件控制 | 消息队列 |
内存管理>> 内存分配 | 内存管理 | 内存汇编 | 内存映射 | 内存规则 | 物理内存 |
前因后果>> 总目录 | 调度故事 | 内存主奴 | 源码注释 | 源码结构 | 静态站点 |
任务管理>> 时钟任务 | 任务调度 | 任务管理 | 调度队列 | 调度机制 | 线程概念 | 并发并行 | CPU | 系统调用 | 任务切换 |
文件系统>> 文件概念 | 文件系统 | 索引节点 | 挂载目录 | 根文件系统 | 字符设备 | VFS | 文件句柄 | 管道文件 |
硬件架构>> 汇编基础 | 汇编传参 | 工作模式 | 寄存器 | 异常接管 | 汇编汇总 | 中断切换 | 中断概念 | 中断管理 |
鸿蒙研究站 | 每天死磕一点点,原创不易,欢迎转载,但请注明出处。
这个大宝藏(此系列)会被越来越多人发现
感谢支持,但愿如此!