鸿蒙轻内核M核源码分析系列六 任务及任务调度(1)任务栈 原创 精华
鸿蒙轻内核M核源码分析系列六 任务及任务调度(1)任务栈
继续分析鸿蒙轻内核源码,我们本文开始要分析下任务及任务调度模块。首先,我们介绍下任务栈的基础概念。任务栈是高地址向低地址生长的递减栈,栈指针指向即将入栈的元素位置。初始化后未使用过的栈空间初始化的内容为宏OS_TASK_STACK_INIT
代表的数值0xCACACACA
,栈顶初始化为宏OS_TASK_MAGIC_WORD
代表的数值0xCCCCCCCC
。一个任务栈的示意图如下,其中,栈底指针是栈的最大的内存地址,栈顶指针,是栈的最小的内存地址,栈指针从栈底向栈顶方向生长。
任务上下文(Task Context)是任务及任务调度模块的另外一个重要的概念,它指的是任务运行的环境,例如包括程序计数器、堆栈指针、通用寄存器等内容。在多任务调度中,任务上下文切换(Task Context Switching)属于核心内容,是多个任务运行在同一CPU
核上的基础。在任务调度时,保存退出运行状态的任务使用的寄存器信息到任务栈,还会从进入运行状态的任务的栈中读取上下文信息,恢复寄存器信息。
下面,我们剖析下任务栈、任务栈初始化的源代码,若涉及开发板部分,以开发板工程targets\cortex-m7_nucleo_f767zi_gcc\
为例进行源码分析。首先,看下任务上下文结构体。
1、 TaskContext上下文结构体定义
在文件kernel\arch\arm\cortex-m7\gcc\los_arch_context.h
中,定义的上下文的结构体如下,主要是浮点寄存器,通用寄存器。
2、 任务栈相关函数
2.1 任务栈初始化函数
在文件kernel\arch\arm\cortex-m7\gcc\los_context.c
中定义了任务栈初始化函数VOID *HalTskStackInit(t()
。该函数被文件kernel\src\los_task.c
中的函数UINT32 OsNewTaskInit()
调用完成任务初始化,并进一步在创建任务函数UINT32 LOS_TaskCreateOnly()
中调用,完成新创建任务的任务栈初始化。
该函数使用3个参数,一个是任务编号UINT32 taskID
,一个是初始化的栈的大小UINT32 stackSize
,第3个参数是栈顶指针VOID *topStack
。⑴处代码把栈内容初始化为OS_TASK_STACK_INIT
,⑵处把栈顶初始化为OS_TASK_MAGIC_WORD
。
⑶处代码获取任务上下文的指针地址TaskContext *context
。对于新创建任务,从栈的底部开始,大小为sizeof(TaskContext)
的栈空间存放上下文的数据。⑷处如果支持浮点数计算,需要初始化浮点数相关的寄存器。⑸初始化通用寄存器,其中.uwLR
初始化为(UINT32)(UINTPTR)HalSysExit
。.uwPC
初始化为(UINT32)(UINTPTR)OsTaskEntry
,这是CPU
首次执行该任务时运行的第一条指令的位置。这2个函数下文会分析。
⑹处返回值是指针(VOID *)taskContext
,这个就是任务初始化后的栈指针,注意不是从栈底开始了,栈底保存的是上下文,栈指针要减去上下文占用的栈大小。在栈中,从TaskContext *context
指针增加的方向,依次保存上下文结构体的第一个成员,第二个成员…另外,初始化栈的时候,除了特殊的几个寄存器,不同寄存器的初始值虽然没有什么意义,也有些初始化的规律。比如R2
寄存器初始化为0x02020202L
,R12
寄存器初始化为0x12121212L
初始化的内容和寄存器编号有关联,其余类似。
2.2 获取任务栈水线函数
随着任务栈入栈、出栈,当前栈使用的大小不一定是最大值,UINT32 OsGetTaskWaterLine(UINT32 taskID)
可以获取的栈使用的最大值即水线WaterLine
。该函数定义在文件kernel\src\los_task.c
,它需要1个参数,即UINT32 taskID
任务编号,返回值UINT32 peakUsed
表示获取的水线值,即任务栈使用的最大值。
我们详细看下代码,⑴处代码表示如果栈顶等于设置的魔术字,说明栈没有被溢出破坏,从栈顶开始栈内容被写满宏OS_TASK_STACK_INIT
的部分是没有使用过的栈空间。使用临时栈指针stackPtr
指针变量依次向栈底方向增加,判断栈是否被使用过,while
循环结束,栈指针stackPtr
指向最大的未使用过的栈地址。⑵处代码获取最大的使用过的栈空间大小,即需要的水线。⑶处如果栈顶溢出,则返回无效值OS_NULL_INT
。
该函数被kernel\base\los_task.c
中的函数LOS_TaskInfoGet(UINT32 taskId, TSK_INFO_S *taskInfo)
调用,获取任务的信息。在shell模块也会使用来或者栈信息。
3、 任务进入退出函数
3.1、任务退出函数
在初始化上下文的时候,链接寄存器设置的是函数(UINT32)(UINTPTR)HalSysExit
,该函数定义在文件kernel\src\los_task.c
。函数代码里调用LOS_IntLock()
关中断,然后进入死循环。在任务正常调度期间,该函数理论上不会被执行。在系统异常时,主动调用LOS_Panic()
c触发异常时,也会调用该函数。
3.2、任务进入函数
在初始化上下文的时候,PC
寄存器设置的是函数VOID OsTaskEntry(UINT32 taskId)
,该函数定义在文件kernel\base\los_task.c
,我们来分析下源代码,⑴处代码获取taskCB
,然后执行⑵调用任务的入口函数。等任务执行完毕后,执行⑶删除任务。通常任务入口执行函数都是while
循环,任务不执行时,会调度到其他任务或者空闲任务,不会执行到删除任务阶段。
小结
本文带领大家一起学习了鸿蒙轻内核的任务栈、任务上下文的基础概念,剖析了任务栈初始化的代码。后续也会陆续推出更多的分享文章,敬请期待,也欢迎大家分享学习、使用鸿蒙轻内核的心得,有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,关注Watch
、点赞Star
、并Fork
到自己账户下,谢谢。
感谢楼主分享
大佬,不考虑开个专栏,把自己成系列的文章做个归纳整理?
专栏传送门:https://harmonyos-m.51cto.com/posts/1976
打算开个专栏哈
一起学习
https://harmonyos.51cto.com/column/47
专栏哈 希望和更多开发者交流
已关注