更多内容请访问:与华为官方共建的鸿蒙技术社区https://harmonyos.51cto.com继续分析源码了解鸿蒙LightKernel的代码之后,我们开始分析本文的任务和任务调度模块。首先,我们介绍一下任务栈的基本概念。任务栈是一个从高地址向低地址递减的栈,栈指针指向即将入栈的元素位置。初始化后未使用的栈空间被初始化为宏OS_TASK_STACK_INIT所代表的值0xCACACACA,栈顶被初始化为宏OS_TASK_MAGIC_WORD所代表的值0xCCCCCCCC。一个任务栈的示意图如下,其中栈底指针为栈的最大内存地址,栈顶指针为栈的最小内存地址,栈指针从底部开始增长堆栈的顶部。任务上下文(TaskContext)是任务和任务调度模块的另一个重要概念,指的是任务运行的环境,包括程序计数器、堆栈指针、通用寄存器等。在多任务调度中,任务上下文切换(TaskContextSwitching)属于核心内容,是多个任务运行在同一个CPU核上的基础。任务调度时,将退出运行态的任务使用的寄存器信息保存到任务栈中,进入运行态的任务从栈中读取上下文信息恢复寄存器信息。接下来我们分析任务栈的源码和任务栈的初始化。如果涉及到开发板部分,则以开发板项目targets\cortex-m7_nucleo_f767zi_gcc\为例进行源码分析。首先,查看任务上下文结构。1、TaskContext上下文结构定义在文件kernel\arch\arm\cortex-m7\gcc\los_arch_context.h中。定义的上下文结构如下,主要是浮点寄存器和通用寄存器。typedefstructTagTskContext{#if((定义(__FPU_PRESENT)&&(__FPU_PRESENT==1U))&&\(定义(__FPU_USED)&&(__FPU_USED==1U)))UINT32S16;UINT32S17;UINT32S18;UINT32S19;UINT32S20;UINT32;S21;2UINT32UINT32S2;UINT32S25;UINT32S26;UINT32S27;UINT32S28;UINT32S29;UINT32S30;UINT32S31;#endifUINT32uwR4;UINT32uwR5;UINT32uwR6;UINT32uwR7;UINT32uwR8;UINT32uwR9;UINT32uwR10;UINT32uwR11;UINT32uwPriMask;UINT32uwR0;UINT32uwR1;UINT32uwR2;UINT32uwR3;UINT32uwR12;UINT32uwLR;UINT32uwPC;UINT32uwxPSR;#if((定义(__FPU_PRESENT)&&(__FPU_PRESENT==1U))&&\(定义(__FPU_USED)&&(__FPU_USED==1U)))UINT32S0;UINT32S1;UINT32S2;UINT32S3;UINT32S4;UINT32S5;UINT32S6;UINT32S7;UINT32S8;UINT32S9;UINT32S10;UINT32S11;UINT32S12;UINT32S13;UINT32S14;UINT32S15;UINT32FPSCR;UINT32NO_NAME;#endif}TaskContext;.c中定义了任务栈初始化函数VOID*HalTskStackInit(t())。该函数由文件kernel\src\los_task.c中的函数UINT32OsNewTaskInit()调用完成任务初始化,并在任务创建函数UINT32LOS_TaskCreateOnly()中进一步调用完成新创建任务栈的初始化任务。这个函数用了3个参数,一个是任务号UINT32taskID,一个是初始化栈的大小UINT32stackSize,第三个参数是栈顶指针VOID*topStack。⑴代码初始化栈内容为OS_TASK_STACK_INIT,⑵初始化栈顶为OS_TASK_MAGIC_WORD。⑶此处代码获取任务上下文的指针地址TaskContext*context。对于一个新创建的任务,从栈底开始,sizeof(TaskContext)的栈空间存放上下文数据。(4)如果支持浮点运算,需要初始化与浮点数相关的寄存器。⑷初始化通用寄存器,其中.uwLR初始化为(UINT32)(UINTPTR)HalSysExit。.uwPC被初始化为(UINT32)(UINTPTR)OsTaskEntry,这是CPU第一次执行任务时运行的第一条指令所在的位置。下面将对这两个函数进行分析。⑴处的返回值是指针(VOID*)taskContext,即任务初始化后的栈指针。请注意,它不是从堆栈底部开始的。栈底存放上下文,栈指针必须减去上下文占用的栈大小。在栈中,从TaskContext*context指针递增的方向,依次保存context结构体的第一个成员和第二个成员……另外,初始化栈的时候,除了几个特殊的寄存器,虽然不同寄存器的初始值没有什么意义,有一些初始化规则。例如R2寄存器初始化为0x02020202L,R12寄存器初始化为0x12121212L。初始化的内容与寄存器号有关,其他类似。LITE_OS_SEC_TEXT_INITVOID*HalTskStackInit(UINT32taskID,UINT32stackSize,VOID*topStack){TaskContext*context=NULL;errno_tresult;/*初始化taskstack,writemagicnumtostacktop*/⑴result=memset_s(topStack,stackSize,(INT32)(OS_ITSKystack!);=EOK){printf("memset_sisfailed:%s[%d]\r\n",__FUNCTION__,__LINE__);}⑵*((UINT32*)(topStack))=OS_TASK_MAGIC_WORD;⑶context=(TaskContext*)(((UINTPTR)topStack+stackSize)-sizeof(TaskContext));#if((defined(__FPU_PRESENT)&&(__FPU_PRESENT==1U))&&\(defined(__FPU_USED)&&(__FPU_USED==1U)))⑷context->S16=0xAA000010;上下文->S17=0xAA000011;上下文->S18=0xAA000012;上下文->S19=0xAA000013;上下文->S20=0xAA000014;上下文->S21=0xAA000015;上下文->S22=0xAA000016;上下文->S19=0xAA000013;上下文->S22=0xAA000016;上下文->AAS230=00x17;context->S24=0xAA000018;context->S25=0xAA000019;context->S26=0xAA00001A;context->S27=0xAA00001B;context->S28=0xAA00001C;context->S29=0xAA00001D;c上下文->S30=0xAA00001E;上下文->S31=0xAA00001F;上下文->S0=0xAA000000;上下文->S1=0xAA000001;上下文->S2=0xAA000002;上下文->S3=0xAA000003;上下文->S4=0xAA000004;上下文->S5=0xAA000005;上下文->S6=0xAA000006;上下文->S7=0xAA000007;上下文->S8=0xAA000008;上下文->S9=0xAA000009;上下文->S10=0xAA00000A;上下文->S11=0xAA00000B;上下文->S12=0xAA00000C;context->S13=0xAA00000D;context->S14=0xAA00000E;context->S15=0xAA00000F;context->FPSCR=0x00000000;context->NO_NAME=0xAA000011;#endif⑸context->uwR40context-0x04L4;>uwr5=0x050505l;context->uwr6=0x06060606l;context->uwr7=0x07070707L;context-context-->uwr8=0x08080808l;context-上下文-uwPriMask=0;context->uwR0=taskID;context->uwR1=0x01010101L;context->uwR2=0x02020202L;context->uwR3=0x03030303L;context->uwR12=0x12121212L;context->uwLR=(UINT32)(UINTPTR)HalSysExit;context->uwPC=(UINT32)(UINTPTR)OsTaskEntry;context->uwxPSR=0x01000000L;⑩return(VOID*)context;}2.2获取任务栈水线函数进出任务栈Stack,当前栈的大小不一定是最大值,通过UINT32OsGetTaskWaterLine(UINT32taskID)可以得到的栈的最大值就是水线WaterLine这个函数定义在文件kernel\src\los_task.c中,需要1个参数,即UINT32taskID任务号,返回值UINT32peakUsed表示获取到的watermark值,即任务栈使用的最大值。让我们详细看看代码。(1)处的代码表示,如果栈顶等于设置的魔法字,则说明栈没有溢出损坏。从栈顶开始用宏OS_TASK_STACK_INIT填充的栈部分是未使用的栈空间。使用临时栈指针stackPtr指针变量依次递增到栈底,判断栈是否已经被使用,while循环结束,栈指针stackPtr指向未使用的最大栈地址。(2)代码获取最大已用栈空间大小,即需要的waterline。(3)如果栈顶溢出,则返回无效值OS_NULL_INT。该函数由kernel\base\los_task.c中的函数LOS_TaskInfoGet(UINT32taskId,TSK_INFO_S*taskInfo)调用,获取任务信息。堆栈信息也用在shell模块中。UINT32OsStackWaterLineGet(constUINTPTR*stackBottom,constUINTPTR*stackTop,UINT32*peakUsed){UINT32size;constUINTPTR*tmp=NULL;⑴if(*stackTop==OS_STACK_MAGIC_WORD){tmp=stackTop+1;while((tmp
