Task/线程管理篇
提示:本文基于开源鸿蒙内核分析,详细进入kernel_liteos_a查看源码。
本文作者:鸿蒙内核发烧友,将持续研究鸿蒙内核,更新博文,敬请关注。内容仅代表个人观点,错误之处,欢迎大家指正完善。
本文分析Task/线程管理源码 详见:los_task.c
因编辑器的原因 本文更详细全面的介绍请看 鸿蒙内核源码分析(Task/线程管理篇)
先看下鸿蒙 task 命令的执行结果:
鸿蒙 一个task 就是一个线程, 详见代码: los_task.c
那有何什么区别呢?是管理上的区别,task是调度层面的概念,线程是进程层面的概念。
task的管理靠任务池,任务池最大是128个task,也就是说最多装128个线程,但线程是可以很多很多的,任务池满了就不能再装线程了,可谓铁打的任务池流水的线程,线程要先进任务池再进入就绪队列,内核根据task优先级开辟了32个队列等待CPU执行。
以下是官方的文档说明:
基本概念
从系统的角度看,线程是竞争系统资源的最小运行单元。线程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它线程运行。
鸿蒙内核每个进程内的线程独立运行、独立调度,当前进程内线程的调度不受其它进程内线程的影响。
鸿蒙内核中的线程采用抢占式调度机制,同时支持时间片轮转调度和FIFO调度方式。
鸿蒙内核的线程一共有32个优先级(0-31),最高优先级为0,最低优先级为31。
当前进程内高优先级的线程可抢占当前进程内低优先级线程,当前进程内低优先级线程必须在当前进程内高优先级线程阻塞或结束后才能得到调度。
线程状态说明:
-
初始化(Init):该线程正在被创建。
-
就绪(Ready):该线程在就绪列表中,等待CPU调度。
-
运行(Running):该线程正在运行。
-
阻塞(Blocked):该线程被阻塞挂起。Blocked状态包括:pend(因为锁、事件、信号量等阻塞)、suspend(主动pend)、delay(延时阻塞)、pendtime(因为锁、事件、信号量时间等超时等待)。
-
退出(Exit):该线程运行结束,等待父线程回收其控制块资源。
-
typedef
struct {
-
VOID *stackPointer;
/**< Task stack pointer */
-
UINT16 taskStatus;
/**< Task status */
-
UINT16 priority;
/**< Task priority */
-
UINT16 policy;
-
UINT16 timeSlice;
/**< Remaining time slice */
-
UINT32 stackSize;
/**< Task stack size */
-
UINTPTR topOfStack;
/**< Task stack top */
-
UINT32 taskID;
/**< Task ID */
-
TSK_ENTRY_FUNC taskEntry;
/**< Task entrance function */
-
VOID *joinRetval;
/**< pthread adaption */
-
VOID *taskSem;
/**< Task-held semaphore */
-
VOID *taskMux;
/**< Task-held mutex */
-
VOID *taskEvent;
/**< Task-held event */
-
UINTPTR args[
4];
/**< Parameter, of which the maximum number is 4 */
-
CHAR taskName[OS_TCB_NAME_LEN];
/**< Task name */
-
LOS_DL_LIST pendList;
/**< Task pend node */
-
LOS_DL_LIST threadList;
/**< thread list */
-
SortLinkList sortList;
/**< Task sortlink node */
-
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 */
-
INT32 errorNo;
/**< Error Num */
-
UINT32 signal;
/**< Task signal */
-
sig_cb sig;
-
#if (LOSCFG_KERNEL_SMP == YES)
-
UINT16 currCpu;
/**< CPU core number of this task is running on */
-
UINT16 lastCpu;
/**< CPU core number of this task is running on last time */
-
UINT16 cpuAffiMask;
/**< CPU affinity mask, support up to 16 cores */
-
UINT32 timerCpu;
/**< CPU core number of this task is delayed or pended */
-
#if (LOSCFG_KERNEL_SMP_TASK_SYNC == YES)
-
UINT32 syncSignal;
/**< Synchronization for signal handling */
-
#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 */
-
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 */
-
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;
-
LOS_DL_LIST msgListHead;
-
BOOL accessMap[LOSCFG_BASE_CORE_TSK_LIMIT];
-
#endif
-
} LosTaskCB;
结构体LosTaskCB内容很多,各代表什么含义?
LosTaskCB相当于任务在内核中的身份证,它反映出每个任务在生命周期内的运行情况
在鸿蒙的内核中,任务就是线程的概念, 一个任务就是一个线程,在调度队列中只存放就绪状态的任务,有32个优先级队列,详细查看 原创 鸿蒙LOS内核源码分析(TASK调度队列篇)
整个任务管理是如何初始化的?看OsTaskInit()就清楚了
每个任务都是独立开的,任务之间也相互独立,之间通讯通过IPC,这里的“独立”指的是每个任务都有自己的运行环境 —— 栈空间,称为任务栈,栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等等
但系统中只有一个CPU,任务又是独立的,调度的本质就是CPU执行一个新task,老task在什么地方被中断谁也不清楚,是随机的。那如何保证老任务被再次调度选中时还能从上次被中断的地方继续玩下去呢?
答案是:任务上下文,CPU内有一堆的寄存器,CPU运行本质的就是这些寄存器的值不断的变化,只要切换时把这些值保存起来,再还原回去就能保证task的连续执行,让用户毫无感知。
-
VOID *stackPointer;
/**< Task stack pointer 任务栈指针 */
-
UINT32 stackSize;
/**< Task stack size 任务栈大小 */
-
UINTPTR topOfStack;
/**< Task stack top 任务栈顶位置*/
-
TSK_ENTRY_FUNC taskEntry;
/**< Task entrance function 入口函数的函数指针 */
-
UINTPTR args[
4];
/**< Parameter, of which the maximum number is 4 入口函数的参数列表 */
初始化和保存现场的工作当然是交给任务自己来做了,任务栈就是那个保护现场的箱子,当然任务执行期间箱子里还会装其他东西。
每个任务的执行都有个开端,从哪开始执行需要用户指定,也就是入口函数。在任务第一次启动进入运行态时被程序计数器指向运行。
任务上下文(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进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。
任务栈是如何初始化的?
任务栈的初始化就是任务上下文的初始化,因为任务没开始执行,里面除了上下文不会有其他内容,注意上下文存放的位置在栈的底部。
单个 task 是如何创建的?
可跟踪函数LOS_TaskCreateOnly,和进程的绑定,栈运行空间,上下文等都在其中完成
-
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreateOnly(UINT32 *taskID, TSK_INIT_PARAM_S *initParam)
-
{
-
UINT32 intSave, errRet;
-
VOID *topStack =
NULL;
-
VOID *stackPtr =
NULL;
-
LosTaskCB *taskCB =
NULL;
-
VOID *pool =
NULL;
-
-
errRet = OsTaskCreateParamCheck(taskID, initParam, &pool);
-
if (errRet != LOS_OK) {
-
return errRet;
-
}
-
-
taskCB = OsGetFreeTaskCB();
//从g_losFreeTask中获取,还记得吗任务池中最多只有128个
-
if (taskCB ==
NULL) {
-
errRet = LOS_ERRNO_TSK_TCB_UNAVAILABLE;
-
goto LOS_ERREND;
-
}
-
-
errRet = OsTaskSyncCreate(taskCB);
//SMP cpu多核间负载均衡相关
-
if (errRet != LOS_OK) {
-
goto LOS_ERREND_REWIND_TCB;
-
}
-
-
OsTaskStackAlloc(&topStack, initParam->uwStackSize, pool);
//为任务栈分配空间
-
if (topStack ==
NULL) {
-
errRet = LOS_ERRNO_TSK_NO_MEMORY;
-
goto LOS_ERREND_REWIND_SYNC;
-
}
-
-
stackPtr = OsTaskStackInit(taskCB->taskID, initParam->uwStackSize, topStack,
TRUE);
//初始化上下文
-
errRet = OsTaskCBInit(taskCB, initParam, stackPtr, topStack);
//初始化TCB,包括绑定进程等操作
-
if (errRet != LOS_OK) {
-
goto LOS_ERREND_TCB_INIT;
-
}
-
if (OsConsoleIDSetHook !=
NULL) {
-
OsConsoleIDSetHook(taskCB->taskID, OsCurrTaskGet()->taskID);
-
}
-
-
*taskID = taskCB->taskID;
-
return LOS_OK;
-
-
LOS_ERREND_TCB_INIT:
-
(
VOID)LOS_MemFree(pool, topStack);
-
LOS_ERREND_REWIND_SYNC:
-
#if (LOSCFG_KERNEL_SMP_TASK_SYNC == YES)
-
OsTaskSyncDestroy(taskCB->syncSignal);
-
#endif
-
LOS_ERREND_REWIND_TCB:
-
SCHEDULER_LOCK(intSave);
-
OsInsertTCBToFreeList(taskCB);
//归还freetask
-
SCHEDULER_UNLOCK(intSave);
-
LOS_ERREND:
-
return errRet;
-
}
-
-
-
LITE_OS_SEC_TEXT_INIT
STATIC UINT32 OsTaskCBInit(LosTaskCB *taskCB,
const TSK_INIT_PARAM_S *initParam,
-
const
VOID *stackPtr,
const
VOID *topStack)
-
{
-
UINT32 intSave;
-
UINT32 ret;
-
UINT32 numCount;
-
UINT16 mode;
-
LosProcessCB *processCB =
NULL;
-
-
OsTaskCBInitBase(taskCB, stackPtr, topStack, initParam);
-
-
SCHEDULER_LOCK(intSave);
-
processCB = OS_PCB_FROM_PID(initParam->processID);
//通过ID获取PCB ,单核进程数最多64个
-
taskCB->processID = processCB->processID;
//进程-线程的父子关系绑定
-
mode = processCB->processMode;
//调度方式同步process
-
LOS_ListTailInsert(&(processCB->threadSiblingList), &(taskCB->threadList));
//插入进程的线程链表
-
if (mode == OS_USER_MODE) {
//用户模式
-
taskCB->userArea = initParam->userParam.userArea;
-
taskCB->userMapBase = initParam->userParam.userMapBase;
-
taskCB->userMapSize = initParam->userParam.userMapSize;
-
OsUserTaskStackInit(taskCB->stackPointer, taskCB->taskEntry, initParam->userParam.userSP);
//用户任务栈上下文初始化
-
}
-
-
if (!processCB->threadNumber) {
-
processCB->threadGroupID = taskCB->taskID;
-
}
-
processCB->threadNumber++;
//这里说明 线程和TASK是一个意思
-
-
numCount = processCB->threadCount;
-
processCB->threadCount++;
-
SCHEDULER_UNLOCK(intSave);
-
-
if (initParam->pcName ==
NULL) {
-
(
VOID)memset_s(taskCB->taskName, sizeof(CHAR) * OS_TCB_NAME_LEN,
0, sizeof(CHAR) * OS_TCB_NAME_LEN);
-
(
VOID)snprintf_s(taskCB->taskName, sizeof(CHAR) * OS_TCB_NAME_LEN,
-
(sizeof(CHAR) * OS_TCB_NAME_LEN) -
1,
"thread%u", numCount);
-
return LOS_OK;
-
}
-
-
if (mode == OS_KERNEL_MODE) {
-
ret = memcpy_s(taskCB->taskName, sizeof(CHAR) * OS_TCB_NAME_LEN, initParam->pcName, strlen(initParam->pcName));
-
if (ret != EOK) {
-
taskCB->taskName[
0] =
'\0';
-
}
-
}
-
-
return LOS_OK;
-
}
进程是分内核态和用户态的,TASK本质是线程,在进程内运行,所以系统提供了不同的创建方式
转载:https://blog.csdn.net/kuangyufei/article/details/108621428