鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元

2024-04-28 06:12

本文主要是介绍鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

任务即线程

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

官方是怎么描述线程的

基本概念
从系统的角度看,线程是竞争系统资源的最小运行单元。线程可以使用或等待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 + sizeUINT32          taskID;             /**< Task ID */    //任务ID,任务池本质是一个大数组,ID就是数组的索引,默认 < 128TSK_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
#endifUINTPTR         userArea;   //使用区域,由运行时划定,根据运行态不同而不同UINTPTR         userMapBase;  //用户模式下的栈底位置UINT32          userMapSize;        /**< user thread stack size ,real size : userMapSize + USER_STACK_MIN_SIZE */UINT32          processID;          /**< Which belong process *///所属进程IDFutexNode       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最大默认127LOS_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 */
#endifUINT32 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_GDBtaskContext->PC = (UINTPTR)OsTaskEntrySetupLoopFrame;
#elsetaskContext->PC = (UINTPTR)OsTaskEntry;//程序计数器,CPU首次执行task时跑的第一条指令位置
#endiftaskContext->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) */
#elsetaskContext->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;
#endifreturn (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亲和力掩码
#endifUINT32          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; //进程IDUserTaskParam   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_IDLEFLAGinitParam->uwResved &= ~OS_TASK_FLAG_PTHREAD_JOIN;//不能是 OS_TASK_FLAG_PTHREAD_JOINif (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;
}

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

这篇关于鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/942478

相关文章

鸿蒙原生应用数量激增20倍,鸿蒙生态“一路狂奔”!

过去几个月,在各地政府和千行百业伙伴的全面支持下,鸿蒙生态建设正在以前所未有的速度和规模蓬勃发展。 鸿蒙生态跑出“加速度”,再迎里程碑进展 从1月华为宣布首批200多家应用厂商加速开发鸿蒙原生应用以来,到3月底已有超4000款应用加入鸿蒙生态阵营,短短两个月内增长幅度高达20倍,实现了完善的垂直行业覆盖,包括便捷生活、出行文旅、金融理财、社交资讯、生产力工具、影音娱乐、游戏等各个领域在内的头部

R语言:ROC分析

> install.packages("pROC") > library(pROC)                    > inputFile="结果.txt"       > rt=read.table(inputFile, header=T, sep="\t", check.names=F, row.names=1) > head(rt) con treatTCGA-

Centos7 上安装配置 RabbitMQ(源码安装方式)

前言:           最近服务器上搭建了一下rabbitmq, 网上找了很多教程, 采了灰常灰常多的坑。   现在终于从坑里面爬出来了。赶紧写下来,避免下次掉坑里面               好了,废话不多说,下面步入正题 一、安装Erlang环境   网上百度了不少安装erlang的教程,大部分都是安装到一半就他丫的翻车了,搞得我好心累                1、

摊还分析

一、摊还分析         概念:是求数据结构中一个操作序列执行所有操作的平均时间,与平均复杂度不同,它不涉及输入概率,能够保证在最坏情况下操作的平均性能。         适用场景:用含 n 个操作的序列(o1,o2,,,,,on) 维护某数据结构         操作代价:单次操作代价可能会很大,在最坏情况下代价为 max(oi) 二、摊还分析的三种方法         以栈的操作

【计算机毕业设计】基于SSM++jsp的高校专业信息管理系统【源码+lw+部署文档+讲解】

目录 第1章 绪论 1.1 课题背景 1.2 课题意义 1.3 研究内容 第2章 开发环境与技术 2.1 MYSQL数据库 2.2 JSP技术  2.3 SSM框架 第3章 系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.2 系统流程 3.2.1 操作流程 3.2.2 登录流程 3.2.3 删除信息流程 3.2.4

深入学习Linux内核之v4l2应用编程(二)

一,用户空间访问v4l2设备步骤 V4L2(Video for Linux 2)是Linux中关于视频设备的内核驱动,它使得Linux系统能够支持视频设备,如摄像头。对于Camera V4L2的应用编程,一般遵循以下步骤: 1,打开设备: 使用open()函数打开视频设备文件,通常位于/dev/videoX(X为设备编号,如0、1等)。 2,查询设备功能: 使用ioctl()函数和VIDIOC

在SQL Server中使用临时表与普通表的性能差异分析

在SQL Server中,临时表和普通表的性能确实存在差异,具体表现和影响因素如下: 临时表和普通表的区别 存储位置: 临时表:存储在tempdb数据库中,生命周期仅限于当前会话或批处理。当会话结束或批处理完成时,临时表会自动删除。普通表:存储在用户定义的数据库中,生命周期由用户管理,可以永久存在,直到明确删除。 创建和管理: 临时表:分为本地临时表(以#开头)和全局临时表(以##开头)。本

自然语言处理-情感分析

情感分析python 包 https://www.jianshu.com/p/d50a14541d01 英语:textBlob包 中文:snownlp 实战:IMDb电影评论 https://baijiahao.baidu.com/s?id=1598913702189213115&wfr=spider&for=pc 步骤: 1、清洗文本数据:删除不需要的字符 2、标记文档:分词、词干化、去停

CentOS7 源码编译 rtpengine9.5.8

CentOS7 不好搞,但应客户要求,不得不搞 选择 rtpengine9.5.8 版本而不是更新的版本,肯定是有自己的考虑的 不废话了,直接上脚本: #!/bin/bash# CentOS7 最小安装yum -y install epel-releaseyum -y install git gcc-c++ glib2 glib2-devel gcc zlib zlib-devel ope

7nm项目之模块实现——02 Placeopt分析

一、Log需要看什么 1.log最后的error 注意:warnning暂时可以不用过于关注,如果特别的warning出现问题,在其他方面也会体现 2.run time 在大型项目实际开发中,周期一般较长,可能几天过这几周,所以这就需要在run过程中看log,提前对症下药,以缩短开发周期完成项目。 3.最终优化结果(Final Summary) 4.hotspot 代表有