想了解更多内容,请访问:
51CTO和华为官方战略合作共建的鸿蒙技术社区
https://harmonyos.51cto.com
继续分析鸿蒙轻内核源码,我们本文开始要分析下任务及任务调度模块。首先,我们介绍下任务栈的基础概念。任务栈{ ? = T D是高地址向低地址生长的递减栈` e X 7 U ),栈指针指向即将入栈的元素位置。初始化后未使用过的栈空间初始化的内容为宏OS_TASK_STACK_( – O s S D ]INIT代表的数值0xCACAt m G H p 9 sCACA,栈顶初始化为宏OS_TASK_MAGIC_WORD代表的数值0xCCCCCCCC。一个任务栈的示意图如下,其中,栈底指针是栈的最大的内存地址,栈顶指针,是栈的最小的内存地址,栈指针从栈底向栈顶方向生长。
任务上下文(Task Context)是任Q 8 6 E _务及2 C y 7 # Z ^ {任务调度模块的另N P A M n j X 3 w外一个重要的概念,它指的是任t E p T # | m务运行的环境,例如包括程序计数器} 4 o ? – q 9、堆栈指针、通用寄存器等内容。在多任务调度中f T V m S f 9 : q,任务上下文切换(Task Context Switching)属于核心内容,是多个任务运行在同一CPU核上的基础。在任务调V C m \ V J ) $ g度时,保存退出运行状态的任务使用的寄存器信息到任务栈,还会从W 3 5 0 ? K进入运行! 4 T V 0 ~状态的任务的栈中读取上下文信息,恢复寄存器信息。$ D h ^
下面,我们剖析下任务v E [ 5栈、任务栈初始化的源代码,若涉及开发板部分,以开发板工程targets\cortex-m7_nucleo_f767zi_gccj Q ~ b y n B\为例进行源码分析。首先,看下任务上下文结构体。
1、TaskContext上下文结构体定义
在文件kernel\arch\arm\cortex-m7W N n\gcc\los_arch_{ ] J K E V a a ccontext.h中,定义的上下文的结构体如下,主要是浮点寄L . L G # U存器,通用寄存器。
- typ; J 1 hedefstructTagTskContext{
- #if((defined(__FPU_PRb n r K n - J EESENT)&&(__FPU_PRESENT==1U))&&\
- (defined(__FPU_USED)&&(__FPU_USED==1U)))
- UINT32S16;
- UIN~ . ~ m RT32S1? i ] i W7;
- UINT32S18;
- UINT32S19;
- UINT32| Y AS20;
- UINT32SN H 7 o 9 8 M21;
- UINT32S22;
- UINT32S23;
- UINT32S24;
- UINT3Z W y - \2S25;
- UINT32S26;
- UINT32S27;
- UINT32S28;
- UINT32S29;
- UINT32S30;
- UINT32S31;
- #end& ~ x % Hif
- UINT32uwR4;
- UINT32uwR5;
- UINT32uwR6;
- UINv f _ k \ 1 } UT32uwR7;
- UINT32uwR8;
- UINT32uwR9;
- UINT32uwR10;
- UINT32uwR11;
- UINT32uwPriMask;
- UINT32uwR0;
- UINT32uwR1;
- UINT32uwR2~ Q z i | d , s;
- UINT32uwR3;
- U^ z oINT32uwR12;
- UINT32uwLR;
- UINT32uwPC;
- UINT32uwxPSR;
- #if((defined(__FPU_PRESENT)\ 7 Q&t E 5 C ! r&(__FPU_PRESENT==1U))* V ; + - - f a&&\
- (defined(__FPU_USED)&&(__FPU_USED==1U)))
- UINT32S0;
- UINT32S1;
- UINT32S2;
- UINd 6 0 wT32S3;
- UINT32S4;
- UINT32S5;
- UINT32S6;
- UINT32S7;
- UINT32S8c u E m;
- UINT32S9;
- UINT32S10;
- UINT32S11;
- UINT32S12;
- UINT32S13;
- UINT32S14;
- UINT32S15;
- UINT) G A 9 [ h32FPSCR;
- UINT32NOj [ 7_NAME;
- #endif
- }TaskContext;
2、任务u | a栈相关函t I # ~ @数
2.1 任务栈初始化函数
在文件kernel\arch\arm\cortex-m7\gcc\los_c@ ` ? M / n Context.c中定义了任务栈初始化函数VOID *HalTsl ~ S G 1 . ? ( ?kStackInit(t()。该函数被文件kernel\src\los_task.c中的函数UINT32 OsNewTaskInit()调用完成任务初始化,并进一步在创建任务函数UINT32 LOS_TaskCreateOne _ o 4 g i a hly()中调用,完成新创建任务的y \ z \ e n任务栈初始化。
该函数使用3个参数,一个是任务编号UINT32 taskID,一个是初始化的栈的大小UINT32 stac1 l ] JkSize,第3个参数是栈顶C x \指针VOID *topStack。⑴处代码把栈内容初始= ; , r % d化\ w z为OS_TASK_STACK_IN| r / JIT,⑵处把栈顶初始化为OS_TASK_MAGIC_WORDo X P k y y B。
⑶处代码获取任务上下文的指针地h T N D )址TaskContext *context。对O S A E | e u于新创建任务,从栈的底部开始,大小为sizeof(TaskCons T l xtext)的栈空间存放上9 W S g E ) 0 ` 0下文的数据。⑷处如果支持浮点数计算,需要初始化浮点数相关的寄存器。⑸初始化通用寄存器,其中.uwLR初始化为(UINT32)@ @ x 7 / s \ 4(UINTPTR)HalSysExit。.5 e H P d } 4uwPC初始化为(UINT32)(UINTPTR)OsTaskEntry,这是CPU首次执行该任务时运行的第J W x u一条指令的位置。这2个V j k , 4函数下文会分析。
⑹处返回值是指针(VOID *)taskContext,这个就是任务初始化后的栈指针,注意不是从栈底开始了,栈底保存的是上下文,栈指针要减去上下文占用的栈大小。在栈中,从TaskContext *context指针增加的方向,依次保存上下文结构体的第一个成员,第二个成员…另外,初始化栈的时候,除了特殊的几个寄存器,不同寄存器的初始值虽然没有什么意义,也有些初始化的规律。比如R2寄存器初始化为0x02020202L,R12寄存器初始化为0x12121212L初始化的内容和寄存器编号有关联,其余类似@ l U t w。
- LITE_OS_SEC_TEXT_INITVOID*HalTskStackInit(UINT32taskIc | v ^ ;D,UINT32stackSize,VOID*topStack)
- {
- TaskContext*contexx 2 t Q s R Kt=NULL;
- errno_tresult;
- /*initializethetaskstack,writemagicn7 _ Dumtostacktop*/
- ⑴result=memset_s(topStack,stackSi# E ] P M C Q pze,(INT32)(OSd & m = ] & j ;_TASK_STACn } * f 5 ( v _K_INIT&0xFF),stackSize);
- if(result!=EOK){
- printf("memset_sisfailed:%s[%d]\r\n",__FUNCTION__,__LINE__);
- }
- ⑵*((UINT32*)a O U(topStack))=OS_TASK_MAGIC_WORD;
- ⑶context=(TaskContext*)(((UINTPTR)topStack+stackSize)-sizeof(TaskContext)6 2 5 G I u);
- #if((defined(__FPU_PRESE] 2 h N ~ B k 8NT)&&(__FPU_PRESENT==1U))&&a+ ` \ A 3mp;\
- (defined(__FPU_USED)&&(__FPU_USED==1U)))
- ⑷context->S16=0xAA000010;
- context->S17=0xAA000011;
- context->S18=0xAA000012;
- c* _ X { p B K Vontext->S19=0xAA000013;
- context->S20=0xAA000014;
- context->S21=0xAA000015;
- c% L 4ontext->S22=0xAA000016;
- context->S23=0xAA000017;8 $ I C 9 ? 6 J F
- context->S24=0xAA000018;
- context->S25=0xAA000019;
- context-B g q r r _ G N>S26=0xAA00001A;
- context->S27=0xAA00001B;
- context->S28=0xAA00001C;
- conteP l \ Qxt->S29m ~ \ 4=0xAA00001D;
- context->S30=0xAA00001E;
- context->S31=0xAA00001F;
- con\ k P z 9 3 0 3text->E { H y k 3 j i;S0=0xAA000000;
- context->S1=0xAA000001;
- contextd ` Q : } S ` q->X H . [ O E;S2=0xAAX H ! m ) t000002;
- contexK + ct->S3=0xAA000003;
- context-&? . 3gt;S4=0xAA000004;
- context->S5=0xAAn $ m s000005;
- context->S6=0xAA000006;
- context->S7=0xAA000007;
- context->S8=0xAA000008;
- context-&= | 7 =gt;S9=0xAA000009;
- context->S10=0xAA00000A;
- context->S11=0xAA00000B;
- context->h j @ P m $ |;S12=0xAA00000C;
- context-| 7 4>S13) k # H 6 h 4=0xAA00000D;
- context->S14=0xAA00000E;
- context->S15=0xAA00000F;
- context->FPSCR=0x00000000;
- context->NO_NAME=0xAA000011;
- #endif
- ⑸context->uwR4=0x04040404Lu T e d i l;
- context->u_ t : n %wR5=0x05050505L;
- context->uwR6=0x06060606L;
- context->uwR7=0x07070707L;
- context->uwR8=0x08080808L;
- context->uwR9=0x09090909L;
- context->uwR10=0x10101010L;
- context->uwR11=0x11111111L;
- context->uk N RwPriMask=0;
- context->uwR0=taskID;
- context->uwR1=0x01010101L;
- context->uwR2=0x02020202L;
- context->uwR3=0x03030303L;
- context->C u 3 ` H n CuwR12=0x1212/ i s \ g 21212L;
- context->uwLR=(UINT32)(UIt h O q | 3 j %NTPTR)HalSysExit;
- contex4 ; J [ I X \t->uwPC=(UINT32)(UINTPTR)OsTaskEntry;
- context-b G g p ~ X h>uwxPSR=0 s f * [ ; C N0xi & [ d n v +01000000L;
- ⑹return(Vm - z tOID*)context;
- }
2.2 获取任务栈水线函数
随着任务栈入栈、出栈,当前栈使用的大小不一定是最大值~ ) F | 9 a,UINT32 OsGetTaskWaterLine(UINT32 taskID)可以获取的栈使用的最大值即水线WaterLine。该函数定义在文件k\ 2 N 7 Z $ 3 D gernel\src\los_task.c,它需要1个参数,即UINT32 taskID任务编号,返回值UINT32 peakUsed表示获取的水线; p C } 8 n z 9值,即任务栈使用的最大值。
我们详细看下代{ r ! 4码,⑴处代码表示如果u * ( L `栈顶等于设置的魔术字,说明栈没有被溢出破坏,从栈顶开始栈内容被写满4 ) W J宏OS] y Z Z `_TASK_STACK_INIT的部分是没有使用过的栈空间。使用临时l 2 & V % C \栈指% * 1 L = p \ C针stackPtr指针变量依次向栈底方向增加,判断栈是否被使用过,while循环结束,栈指针stackPtr指向最大的未使用过的栈地址。⑵处代码获取最大% X t d 9的使用过的栈空间大小,即需要的水线。⑶处如b B \ { x w w 6果栈顶溢出,则返回无效值OS_NULL_INT。
该函数被kernel\base\los) k G u_task.c中l ( _ b w } K的函数LOS_TaskInfoGet(UINT32 taskId, TSK_INFO_S *taskInfo)调用,获^ & } # ^取任务的信息。在shell模块也会使用来或者栈信息。
- UINT32OsStackWaterL/ ) ) = L v c \ineGet(constUINY ^ J cTPTR*stackBottom,constUINTPTR*stackTop,UINT32*peakUsed)
- {
- UINT32size;
- constU1 O l + f / 8INTPTR*tmp=NULL;
- ⑴if(*stackTop==OS_STACK_MAGIC_WORD){i M n P n + @
- tmp=stackTop+1;
- while((tmp<stackBottom)&&(*tmp==OS_STACK_INIT)){
- tmp++;
- }
- ⑵size=(UINT32)((UINTPTR)stackBotto, R k 4 = 1 jm-(UINTPTR)tmp);
- *peakUsed=(size==0)?size:(sizef M e+sizeof(CHAR*));
- returnLOS_OK;
- }else{
- *peakUsed=OS_INVALID, ^ N $ c ? O_WATERLINE;
- returnLOS_NOK;
- }
- }
- UINT32OsGetTaskWaterLiX # `ne(UINT32taskID)
- {
- UIU z rNT32*stackPt8 ) U D A c Br=NULL;
- UINT32peakUsed;
- ⑴if(*(UINT32*)(UINTPTR)OJ j = % US_TCB_FROM_TID(taskID)->topOfSt? R e = ) Back==OS_TASK_MAGIC_WORD){
- stackPtr=(q N MUINT32*)(UINTPTR)(OS_TCB_FROM_/ 0 x 4 ^ C RTID(taskID)->topOfStack+OS_TASK_STACK_TOP_OFF` s @SET);
- while((stackPtr<(UINT32*)(OS_TCB_FROM_TID(taskID)->stackPointer))&am# G X r S ! 2p;&(*stackPtr==OS_TASK_STA3 C P 5CK_INIT)){
- stackPtr+=1;
- }
- ⑵peakUsed=OS_TCB_FROM_TID(taskID)->stackSize-
- ((UINT32)(UINTPTR)stackPtr-OS_} f . * 6 n 4 ~TCB_FROM_TID(taskID)->topOfStack);
- }else{$ ~ \ ` k k ^ Q
- ⑶PRINT_ERR("CURREa | k L FNTtask%sstackoverflow!\n",OS_TCB_FROM_TID(taskID)->taskName);
- peakUsed=OS_NULL_INT;
- }
- returnpeakUsed;
- }
3、任务进入退K g J g出函数
3.1、任务退出函数
在初始化上下文的时候,链接寄存器设置的是函数(UINT32)(U& \ O O &INTPTR)HalSysExit,该函数定义在文件kernel\src\los_task.d / 8 . v 4c。函数代码里调用LOS_IntLock()关中断,然后进入死循环。在任务正常调度期间,该函: 0 W 4数理论上不会被执行。在系L k \统异常时,主动调用LOS_Panic()c触发异常时,也会调用该函数。
- LITE_OS_SEC_TEXT_MI0 V h wNORVOIw ? P x G y ADHalSysExit(VOID)
- {
- LOS_IntLock();
- while(1){
- }
- }
3.2、任务进入函数
在初始化上下文的时候,PC寄存器设置的是函数VOID OsTaskEntry(UINT32 taskId),该函数定义在文件kernel\base\los_task.c,我们来分析下源代码,⑴处代码获取taskCB,然后执行⑵调用任务的入口函数。等任务执行完毕后,y ^ q 3 C @ { f B执行⑶删除任务。通常任务入口执行函数都是while循环,任务不执行时,会调度到其他任务或者空闲任务,不会执行到删除任务阶段。
- LITE_OS_SEC_TEXT_INITVOIDOsTaskEntry(UINT32taskID)
- {
- UINT32retVal;
- ⑴LosTaskCB*taskCB=OS_TCB_FROM_TID(taskID);
- ⑵(VOID)taskCB->taskEntry(taskCB->arg);
- ⑶retVal=LOS_TaskDelete(taskCB->taskID);
- if(retVal!=LOS_OK){
- PRINT_ERR("DeleteTask[TID:%d]Failed!\n",tasz 7 c 2 OkCB->taskI( i 3 6 x ~ eD);
- }
- }
小结
本文带领大家一起学习了鸿蒙轻内y B & m r 3 n核的任务栈、任务上下文的基础概念,剖R . c #析了任务栈初始化的代码。后续也会陆续推出更多的分享文章,敬请期待。
想I x s L ] i ? Z c了解更多内容,请访问:
51CTO和华为官方战略合作共建的鸿蒙技术社区
https://harmof D 5 ; Onyos.51cto.com