想了解更多内容,请访问:

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存器,通用寄存器。

  1. typ; J 1 hedefstructTagTskContext{
  2. #if((defined(__FPU_PRb n r K n - J EESENT)&&(__FPU_PRESENT==1U))&&\
  3. (defined(__FPU_USED)&&(__FPU_USED==1U)))
  4. UINT32S16;
  5. UIN~ . ~ m RT32S1? i ] i W7;
  6. UINT32S18;
  7. UINT32S19;
  8. UINT32| Y AS20;
  9. UINT32SN H 7 o 9 8 M21;
  10. UINT32S22;
  11. UINT32S23;
  12. UINT32S24;
  13. UINT3Z W y - \2S25;
  14. UINT32S26;
  15. UINT32S27;
  16. UINT32S28;
  17. UINT32S29;
  18. UINT32S30;
  19. UINT32S31;
  20. #end& ~ x % Hif
  21. UINT32uwR4;
  22. UINT32uwR5;
  23. UINT32uwR6;
  24. UINv f _ k \ 1 } UT32uwR7;
  25. UINT32uwR8;
  26. UINT32uwR9;
  27. UINT32uwR10;
  28. UINT32uwR11;
  29. UINT32uwPriMask;
  30. UINT32uwR0;
  31. UINT32uwR1;
  32. UINT32uwR2~ Q z i | d , s;
  33. UINT32uwR3;
  34. U^ z oINT32uwR12;
  35. UINT32uwLR;
  36. UINT32uwPC;
  37. UINT32uwxPSR;
  38. #if((defined(__FPU_PRESENT)\ 7 Q&t E 5 C ! r&(__FPU_PRESENT==1U))* V ; + - - f a&&\
  39. (defined(__FPU_USED)&&(__FPU_USED==1U)))
  40. UINT32S0;
  41. UINT32S1;
  42. UINT32S2;
  43. UINd 6 0 wT32S3;
  44. UINT32S4;
  45. UINT32S5;
  46. UINT32S6;
  47. UINT32S7;
  48. UINT32S8c u E m;
  49. UINT32S9;
  50. UINT32S10;
  51. UINT32S11;
  52. UINT32S12;
  53. UINT32S13;
  54. UINT32S14;
  55. UINT32S15;
  56. UINT) G A 9 [ h32FPSCR;
  57. UINT32NOj [ 7_NAME;
  58. #endif
  59. }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

  1. LITE_OS_SEC_TEXT_INITVOID*HalTskStackInit(UINT32taskIc | v ^ ;D,UINT32stackSize,VOID*topStack)
  2. {
  3. TaskContext*contexx 2 t Q s R Kt=NULL;
  4. errno_tresult;
  5. /*initializethetaskstack,writemagicn7 _ Dumtostacktop*/
  6. ⑴result=memset_s(topStack,stackSi# E ] P M C Q pze,(INT32)(OSd & m = ] & j ;_TASK_STACn } * f 5 ( v _K_INIT&0xFF),stackSize);
  7. if(result!=EOK){
  8. printf("memset_sisfailed:%s[%d]\r\n",__FUNCTION__,__LINE__);
  9. }
  10. ⑵*((UINT32*)a O U(topStack))=OS_TASK_MAGIC_WORD;
  11. ⑶context=(TaskContext*)(((UINTPTR)topStack+stackSize)-sizeof(TaskContext)6 2 5 G I u);
  12. #if((defined(__FPU_PRESE] 2 h N ~ B k 8NT)&&(__FPU_PRESENT==1U))&&a+ ` \ A 3mp;\
  13. (defined(__FPU_USED)&&(__FPU_USED==1U)))
  14. ⑷context->S16=0xAA000010;
  15. context->S17=0xAA000011;
  16. context->S18=0xAA000012;
  17. c* _ X { p B K Vontext->S19=0xAA000013;
  18. context->S20=0xAA000014;
  19. context->S21=0xAA000015;
  20. c% L 4ontext->S22=0xAA000016;
  21. context->S23=0xAA000017;8 $ I C 9 ? 6 J F
  22. context->S24=0xAA000018;
  23. context->S25=0xAA000019;
  24. context-B g q r r _ G N>S26=0xAA00001A;
  25. context->S27=0xAA00001B;
  26. context->S28=0xAA00001C;
  27. conteP l \ Qxt->S29m ~ \ 4=0xAA00001D;
  28. context->S30=0xAA00001E;
  29. context->S31=0xAA00001F;
  30. con\ k P z 9 3 0 3text->E { H y k 3 j i;S0=0xAA000000;
  31. context->S1=0xAA000001;
  32. contextd ` Q : } S ` q->X H . [ O E;S2=0xAAX H ! m ) t000002;
  33. contexK + ct->S3=0xAA000003;
  34. context-&? . 3gt;S4=0xAA000004;
  35. context->S5=0xAAn $ m s000005;
  36. context->S6=0xAA000006;
  37. context->S7=0xAA000007;
  38. context->S8=0xAA000008;
  39. context-&= | 7 =gt;S9=0xAA000009;
  40. context->S10=0xAA00000A;
  41. context->S11=0xAA00000B;
  42. context->h j @ P m $ |;S12=0xAA00000C;
  43. context-| 7 4>S13) k # H 6 h 4=0xAA00000D;
  44. context->S14=0xAA00000E;
  45. context->S15=0xAA00000F;
  46. context->FPSCR=0x00000000;
  47. context->NO_NAME=0xAA000011;
  48. #endif
  49. ⑸context->uwR4=0x04040404Lu T e d i l;
  50. context->u_ t : n %wR5=0x05050505L;
  51. context->uwR6=0x06060606L;
  52. context->uwR7=0x07070707L;
  53. context->uwR8=0x08080808L;
  54. context->uwR9=0x09090909L;
  55. context->uwR10=0x10101010L;
  56. context->uwR11=0x11111111L;
  57. context->uk N RwPriMask=0;
  58. context->uwR0=taskID;
  59. context->uwR1=0x01010101L;
  60. context->uwR2=0x02020202L;
  61. context->uwR3=0x03030303L;
  62. context->C u 3 ` H n CuwR12=0x1212/ i s \ g 21212L;
  63. context->uwLR=(UINT32)(UIt h O q | 3 j %NTPTR)HalSysExit;
  64. contex4 ; J [ I X \t->uwPC=(UINT32)(UINTPTR)OsTaskEntry;
  65. context-b G g p ~ X h>uwxPSR=0 s f * [ ; C N0xi & [ d n v +01000000L;
  66. return(Vm - z tOID*)context;
  67. }

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模块也会使用来或者栈信息。

  1. UINT32OsStackWaterL/ ) ) = L v c \ineGet(constUINY ^ J cTPTR*stackBottom,constUINTPTR*stackTop,UINT32*peakUsed)
  2. {
  3. UINT32size;
  4. constU1 O l + f / 8INTPTR*tmp=NULL;
  5. ⑴if(*stackTop==OS_STACK_MAGIC_WORD){i M n P n + @
  6. tmp=stackTop+1;
  7. while((tmp<stackBottom)&&(*tmp==OS_STACK_INIT)){
  8. tmp++;
  9. }
  10. size=(UINT32)((UINTPTR)stackBotto, R k 4 = 1 jm-(UINTPTR)tmp);
  11. *peakUsed=(size==0)?size:(sizef M e+sizeof(CHAR*));
  12. returnLOS_OK;
  13. }else{
  14. *peakUsed=OS_INVALID, ^ N $ c ? O_WATERLINE;
  15. returnLOS_NOK;
  16. }
  17. }

  1. UINT32OsGetTaskWaterLiX # `ne(UINT32taskID)
  2. {
  3. UIU z rNT32*stackPt8 ) U D A c Br=NULL;
  4. UINT32peakUsed;
  5. ⑴if(*(UINT32*)(UINTPTR)OJ j = % US_TCB_FROM_TID(taskID)->topOfSt? R e = ) Back==OS_TASK_MAGIC_WORD){
  6. stackPtr=(q N MUINT32*)(UINTPTR)(OS_TCB_FROM_/ 0 x 4 ^ C RTID(taskID)->topOfStack+OS_TASK_STACK_TOP_OFF` s @SET);
  7. while((stackPtr<(UINT32*)(OS_TCB_FROM_TID(taskID)->stackPointer))&am# G X r S ! 2p;&(*stackPtr==OS_TASK_STA3 C P 5CK_INIT)){
  8. stackPtr+=1;
  9. }
  10. ⑵peakUsed=OS_TCB_FROM_TID(taskID)->stackSize-
  11. ((UINT32)(UINTPTR)stackPtr-OS_} f . * 6 n 4 ~TCB_FROM_TID(taskID)->topOfStack);
  12. }else{$ ~ \ ` k k ^ Q
  13. ⑶PRINT_ERR("CURREa | k L FNTtask%sstackoverflow!\n",OS_TCB_FROM_TID(taskID)->taskName);
  14. peakUsed=OS_NULL_INT;
  15. }
  16. returnpeakUsed;
  17. }

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触发异常时,也会调用该函数。

  1. LITE_OS_SEC_TEXT_MI0 V h wNORVOIw ? P x G y ADHalSysExit(VOID)
  2. {
  3. LOS_IntLock();
  4. while(1){
  5. }
  6. }

3.2、任务进入函数

在初始化上下文的时候,PC寄存器设置的是函数VOID OsTaskEntry(UINT32 taskId),该函数定义在文件kernel\base\los_task.c,我们来分析下源代码,⑴处代码获取taskCB,然后执行⑵调用任务的入口函数。等任务执行完毕后,y ^ q 3 C @ { f B执行⑶删除任务。通常任务入口执行函数都是while循环,任务不执行时,会调度到其他任务或者空闲任务,不会执行到删除任务阶段。

  1. LITE_OS_SEC_TEXT_INITVOIDOsTaskEntry(UINT32taskID)
  2. {
  3. UINT32retVal;
  4. ⑴LosTaskCB*taskCB=OS_TCB_FROM_TID(taskID);
  5. ⑵(VOID)taskCB->taskEntry(taskCB->arg);
  6. ⑶retVal=LOS_TaskDelete(taskCB->taskID);
  7. if(retVal!=LOS_OK){
  8. PRINT_ERR("DeleteTask[TID:%d]Failed!\n",tasz 7 c 2 OkCB->taskI( i 3 6 x ~ eD);
  9. }
  10. }

小结

本文带领大家一起学习了鸿蒙轻内y B & m r 3 n核的任务栈、任务上下文的基础概念,剖R . c #析了任务栈初始化的代码。后续也会陆续推出更多的分享文章,敬请期待。

I x s L ] i ? Z c了解更多内容,请访问:

51CTO和华为官方战略合作共建的鸿蒙技术社区

https://harmof D 5 ; Onyos.51cto.com