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

任务即线程

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

官方是怎么描述线程的

基本概念
从系统的角度看,线程是竞争系统资源的最小运行单元。线程可以使用或等待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.mzph.cn/bicheng/4243.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

细致讲解——不同类型LSA是作用以及相互之间的联系

目录 一.常见的LSA类型 二.OSPF特殊区域 1.区域类型 2.stub区域和totally stub区域 &#xff08;1&#xff09;stub区域 &#xff08;2&#xff09;totally stub区域 3.nssa区域和totally nssa区域 &#xff08;1&#xff09;nssa区域 &#xff08;2&#xff09;totall…

【java数据结构之八大排序(上)-直接插入排序,希尔排序,选择排序,堆排序,向下调整(大根堆,小根堆)等知识详解】

&#x1f308;个人主页&#xff1a;努力学编程’ ⛅个人推荐&#xff1a;基于java提供的ArrayList实现的扑克牌游戏 |C贪吃蛇详解 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 …

微信小程序使用echarts实现条形统计图功能

微信小程序使用echarts组件实现条形统计图功能 使用echarts实现在微信小程序中统计图的功能&#xff0c;其实很简单&#xff0c;只需要简单的两步就可以实现啦&#xff0c;具体思路如下&#xff1a; 引入echarts组件调用相应的函数方法 由于需要引入echarts组件&#xff0c;代…

SpringCloudStream 3.x rabbit 使用

1. 前言 今天带来的是SpringCloudStream 3.x 的新玩法&#xff0c;通过四大函数式接口的方式进行数据的发送和监听。本文将通过 rabbitMQ 的方式进行演示 3.x版本后是 可以看到 StreamListener 和 EnableBinding 都打上了Deprecated 注解。后续的版本更新中会逐渐替换成函数式…

【Kotlin】Channel简介

1 前言 Channel 是一个并发安全的阻塞队列&#xff0c;可以通过 send 函数往队列中塞入数据&#xff0c;通过 receive 函数从队列中取出数据。 当队列被塞满时&#xff0c;send 函数将被挂起&#xff0c;直到队列有空闲缓存&#xff1b;当队列空闲时&#xff0c;receive 函数将…

电脑的无用设置功能(建议关闭)

目录 1、传递优化 ​2、常规​ 3、电源 1、传递优化 2、常规3、电源

数据结构七:线性表之链式栈的设计

在上篇博客&#xff0c;学习了用数组实现链的顺序存储结构&#xff0c;那是否存在用单链表实现栈的链式存储结构&#xff0c;答案是当然的&#xff0c;相比于顺序栈&#xff0c;用数组实现的栈效率很高&#xff0c;但若同时使用多个栈&#xff0c;顺序栈将浪费很多空间。用单链…

ThinkPHP Lang多语言本地文件包含漏洞(QVD-2022-46174)漏洞复现

1 漏洞描述 ThinkPHP是一个在中国使用较多的PHP框架。在其6.0.13版本及以前&#xff0c;存在一处本地文件包含漏洞。当ThinkPHP开启了多语言功能时&#xff0c;攻击者可以通过lang参数和目录穿越实现文件包含&#xff0c;当存在其他扩展模块如 pear 扩展时&#xff0c;攻击者可…

高级IO|从封装epoll服务器到实现Reactor服务器|Part1

从封装epoll_server到实现reactor服务器(part1) 项目复习&#xff1a;从封装epoll_server到实现reactor服务器(part1)EPOLL模式服务器初步 select, poll, epoll的优缺点epoll的几个细节封装epoll_server基本框架先写好创建监听套接字和创建epoll模型可以Accept了吗&#xff1f…

《架构风清扬-Java面试系列第25讲》聊聊ArrayBlockingQueue的特点及使用场景

ArrayBlockingQueue是BlockingQueue接口的一个实现类之一 这个属于基础性问题&#xff0c;老规矩&#xff0c;我们将从使用场景和代码示例来进行讲解 来&#xff0c;思考片刻&#xff0c;给出你的答案 1&#xff0c;使用场景 实现&#xff1a;基于数组实现的有界阻塞队列&…

Stability AI 推出稳定音频 2.0:为创作者提供先进的 AI 生成音频 - Circle 阅读助手

概述 Stability AI 的发布再次突破了创新的界限。这一尖端模型以其前身的成功为基础&#xff0c;引入了一系列突破性的功能&#xff0c;有望彻底改变艺术家和音乐家创建和操作音频内容的方式。 Stable Audio 2.0 代表了人工智能生成音频发展的一个重要里程碑&#xff0c;为质量…

mysql UNION 联合查询

mysql UNION 联合查询 业务需要拉数据&#xff0c;这里需要对查询不同格式的数据进行组装&#xff0c;此处采用联合查询 注意1&#xff1a;null as 设备关爱 &#xff0c;结果为null&#xff0c;表头为设备关爱 注意2&#xff1a; UNION 或者 UNION ALL 联合查询自行选用 注意3…

如何避免被恶意攻击的IP地址

随着互联网的普及和发展&#xff0c;网络安全问题日益受到关注&#xff0c;恶意攻击成为网络安全的一大威胁。而IP地址作为网络通信的基础&#xff0c;常常成为恶意攻击的目标之一。本文将探讨如何避免被恶意攻击的IP地址&#xff0c;提高网络安全水平。 1. 定期更新安全补丁 …

【kettle003】kettle访问SQL Server数据库并处理数据至execl文件

一直以来想写下基于kettle的系列文章&#xff0c;作为较火的数据ETL工具&#xff0c;也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 熟悉、梳理、总结下Microsoft SQL Server 2022关系数据库相关知识体系 kettle访问SQL Server数…

ITMS-90426: Invalid Swift Support

原文 Please correct the following issues and upload a new binary to App Store Connect. ITMS-90426: Invalid Swift Support - The SwiftSupport folder is missing. Rebuild your app using the current public (GM) version of Xcode and resubmit it. 解决方式 ITMS-…

uniapp小程序订阅通知

服务 开通订阅服务 const tmplIds ref([tsdasdadasdfgdrtwexQHdEsjZV])//换成自己的 function confirm(){uni.requestSubscribeMessage({tmplIds: tmplIds.value,success: (res) > {// console.log(res)let auth_notice res[tmplIds.value[0]] accept ? 1 : 2 //1是接…

在Redux Toolkit中使用redux-persist进行状态持久化

在 Redux Toolkit 中使用 redux-persist 持久化插件的步骤如下: 安装依赖 npm install redux-persist配置 persistConfig 在 Redux store 配置文件中(例如 rootReducer.js)&#xff0c;导入必要的模块并配置持久化选项: import { combineReducers } from redux; import { p…

MIT 6.172 笔记 现代硬件算法案例分析

本文是https://en.algorithmica.org/hpc/和MIT 6.172的课后题解析 课程地址&#xff1a; 文章目录 HW2 Profiling Serial Merge Sort测试DEBUG和非DEBUG区别测试inline和非inline区别Coarsening HW3 向量化为什么用负偏移量测量向量化跨步向量化 HW4 Reducer Hyperobjects比较o…

[Qt的学习日常]--信号和槽

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习&#xff…

【JAVA】一文掌握Java并发编程

Java 开发中&#xff0c;并发编程属于相当重要的一个知识点&#xff0c;可以说&#xff0c;Java 的并发能力&#xff0c;是成就今日 Java 地位的因素之一。Java 的并发编程由浅入深实质上是包含 Java&#xff08;API&#xff09;层、JVM&#xff08;虚拟机&#xff09;层、内核…