本文分享自华为云社区《LiteOS内核源码分析系列六 -任务及调度(2)-任务LOS_Task》,原文作者:zhushy 。

我们介绍下LiteOS任务栈的基础概念。LiteOS任务栈是高地址向低地址生长的递减栈,栈指针指向即将入栈的元素位置。初始化后未使用过的栈空间初始化的内容为宏OS_STACK_INIT代表的数值0xCACACACA,栈顶初始化为宏OS_STACK_MAGIC_WORD代表的数值0xCCCCCCCC。一个任务栈的示意图如下,其中,栈底指针是栈的最大的内存地址,栈顶指针,是栈的最小的内存地址,栈指针从栈底向栈顶方向生长。

1、 LOS_StackInfo任务栈结构体定义

typedef struct {VOID *stackTop;     // 栈顶指针UINT32 stackSize;   // 栈大小CHAR *stackName;    // 栈名称
} StackInfo;

另外定义了一个宏函数OS_STACK_MAGIC_CHECK(topstack)用于检测栈是否有效,当栈顶等于OS_STACK_MAGIC_WORD栈是正常的,没有溢出,否则栈顶被改写,发生栈溢出。

/* 1:有效正常的栈 0:无效,发生溢出的栈 */
#define OS_STACK_MAGIC_CHECK(topstack) (*(UINTPTR *)(topstack) == OS_STACK_MAGIC_WORD)

2、 LOS_StackInfo任务栈支持的操作

2.1 任务栈初始化

栈初始化函数VOID OsStackInit()使用2个参数,一个是栈顶指针VOID *stacktop,一个是初始化的栈的大小。把栈内容初始化为OS_STACK_INIT,把栈顶初始化为OS_STACK_MAGIC_WORD。

该函数被arch\arm\cortex_m\src\task.c的*OsTaskStackInit(UINT32 taskId, UINT32 stackSize, VOID *topStack)方法调用,进一步被创建任务时的OsTaskCreateOnly()方法调用,完成新创建任务的任务栈初始化。

VOID OsStackInit(VOID *stacktop, UINT32 stacksize)
{(VOID)memset_s(stacktop, stacksize, (INT32)OS_STACK_INIT, stacksize);*((UINTPTR *)stacktop) = OS_STACK_MAGIC_WORD;
}

2.2 获取任务栈水线

随着任务栈入栈、出栈,当前栈使用的大小不一定是最大值,OsStackWaterLineGet()可以获取的栈使用的最大值即水线WaterLine。

该函数需要3个参数,UINTPTR *stackBottom是栈底指针,const UINTPTR *stackTop栈顶指针,UINT32 *peakUsed用于返回获取的水线值,即任务栈使用的最大值。

⑴处代码表示如果*stackTop == OS_STACK_MAGIC_WORD,说明栈没有被溢出破坏,从栈顶开始栈内容被写满宏OS_STACK_INIT的部分是没有使用过的栈空间。使用tmp指针变量依次向栈底方向增加,判断栈是否被使用过,while循环结束,栈指针tmp指向最大的未使用过的栈地址。⑵处代码使用栈底指针stackBottom减去tmp,获取最大的使用过的栈空间大小,即需要的水线。⑶处如果栈顶溢出,则返回无效值OS_INVALID_WATERLINE。

该函数被kernel\base\los_task.c中的函数LOS_TaskInfoGet(UINT32 taskId, TSK_INFO_S *taskInfo)调用,获取任务的信息。在shell模块也会使用来或者栈信息。

UINT32 OsStackWaterLineGet(const UINTPTR *stackBottom, const UINTPTR *stackTop, UINT32 *peakUsed)
{UINT32 size;const UINTPTR *tmp = NULL;
⑴  if (*stackTop == OS_STACK_MAGIC_WORD) {tmp = stackTop + 1;while ((tmp < stackBottom) && (*tmp == OS_STACK_INIT)) {tmp++;}
⑵      size = (UINT32)((UINTPTR)stackBottom - (UINTPTR)tmp);*peakUsed = (size == 0) ? size : (size + sizeof(CHAR *));return LOS_OK;} else {*peakUsed = OS_INVALID_WATERLINE;return LOS_NOK;}
}

3、 LOS_Task任务栈初始化

我们以AArch32 Cortex-M核为例,剖析下任务栈初始化的过程,相关代码分布在arch\arm\cortex_m\include\arch\task.h、arch\arm\cortex_m\src\task.c。首先看下任务上下文。

3.1 TaskContext上下文结构体定义

任务上下文(Task Context)指的是任务运行的环境,例如包括程序计数器、堆栈指针、通用寄存器等内容。在多任务调度中,任务上下文切换(Task Context Switching)属于核心内容,是多个任务运行在同一CPU核上的基础。LiteOS内核中,上下文的结构体定义如下:

typedef struct tagContext {
#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \(defined (__FPU_USED) && (__FPU_USED == 1U)))UINT32 S16;UINT32 S17;UINT32 S18;UINT32 S19;UINT32 S20;UINT32 S21;UINT32 S22;UINT32 S23;UINT32 S24;UINT32 S25;UINT32 S26;UINT32 S27;UINT32 S28;UINT32 S29;UINT32 S30;UINT32 S31;
#endifUINT32 R4;UINT32 R5;UINT32 R6;UINT32 R7;UINT32 R8;UINT32 R9;UINT32 R10;UINT32 R11;UINT32 PriMask;UINT32 R0;UINT32 R1;UINT32 R2;UINT32 R3;UINT32 R12;UINT32 LR;UINT32 PC;UINT32 xPSR;
#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \(defined (__FPU_USED) && (__FPU_USED == 1U)))UINT32 S0;UINT32 S1;UINT32 S2;UINT32 S3;UINT32 S4;UINT32 S5;UINT32 S6;UINT32 S7;UINT32 S8;UINT32 S9;UINT32 S10;UINT32 S11;UINT32 S12;UINT32 S13;UINT32 S14;UINT32 S15;UINT32 FPSCR;UINT32 NO_NAME;
#endif
} TaskContext;

3.2 LOS_Task任务栈初始化

上文中提到在创建任务的时候,会使用VOID *OsTaskStackInit()函数初始化任务栈。我们分析下函数代码,它需要3个参数,UINT32 taskId待创建任务的编号,UINT32 stackSize任务栈的大小,VOID *topStack任务栈的栈顶指针。

⑴处代码调用OsStackInit()函数初始化栈,初始化栈内容和栈顶为魔术字。⑵处代码获取任务上下文的指针地址TaskContext *taskContext,栈的底部大小为sizeof(TaskContext)的栈空间存放上下文的数据。⑶处如果支持浮点数计算,需要初始化浮点数相关的寄存器。⑷初始化通用寄存器,其中LR初始化为(UINT32)OsTaskExit,PC初始化为(UINT32)OsTaskEntry,CPU首次执行该任务时运行的第一条指令的位置,这2个函数下文会分析。⑸处返回值是指针(VOID *)taskContext,这个就是任务初始化后的栈指针,注意不是从栈底开始了,栈底保存的是上下文,栈指针要减去上下文占用的栈大小。

在栈中,从TaskContext *taskContext指针增加的方向,依次保存上下文结构体的第一个成员,第二个成员…另外,初始化栈的时候,除了特殊的几个寄存器,不同寄存器的初始值没有什么意义,也有些初始化的规律。比如R2寄存器初始化为0x02020202L,R12寄存器初始化为0x12121212L初始化的内容和寄存器编号有关联,其余类似。

LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskId, UINT32 stackSize, VOID *topStack)
{TaskContext *taskContext = NULL;⑴  OsStackInit(topStack, stackSize);
⑵  taskContext = (TaskContext *)(((UINTPTR)topStack + stackSize) - sizeof(TaskContext));#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
⑶  (defined (__FPU_USED) && (__FPU_USED == 1U)))taskContext->S16 = 0xAA000010;taskContext->S17 = 0xAA000011;taskContext->S18 = 0xAA000012;taskContext->S19 = 0xAA000013;taskContext->S20 = 0xAA000014;taskContext->S21 = 0xAA000015;taskContext->S22 = 0xAA000016;taskContext->S23 = 0xAA000017;taskContext->S24 = 0xAA000018;taskContext->S25 = 0xAA000019;taskContext->S26 = 0xAA00001A;taskContext->S27 = 0xAA00001B;taskContext->S28 = 0xAA00001C;taskContext->S29 = 0xAA00001D;taskContext->S30 = 0xAA00001E;taskContext->S31 = 0xAA00001F;taskContext->S0  = 0xAA000000;taskContext->S1  = 0xAA000001;taskContext->S2  = 0xAA000002;taskContext->S3  = 0xAA000003;taskContext->S4  = 0xAA000004;taskContext->S5  = 0xAA000005;taskContext->S6  = 0xAA000006;taskContext->S7  = 0xAA000007;taskContext->S8  = 0xAA000008;taskContext->S9  = 0xAA000009;taskContext->S10 = 0xAA00000A;taskContext->S11 = 0xAA00000B;taskContext->S12 = 0xAA00000C;taskContext->S13 = 0xAA00000D;taskContext->S14 = 0xAA00000E;taskContext->S15 = 0xAA00000F;taskContext->FPSCR = 0x00000000;taskContext->NO_NAME = 0xAA000011;
#endif⑷  taskContext->R4  = 0x04040404L;taskContext->R5  = 0x05050505L;taskContext->R6  = 0x06060606L;taskContext->R7  = 0x07070707L;taskContext->R8  = 0x08080808L;taskContext->R9  = 0x09090909L;taskContext->R10 = 0x10101010L;taskContext->R11 = 0x11111111L;taskContext->PriMask = 0;taskContext->R0  = taskId;taskContext->R1  = 0x01010101L;taskContext->R2  = 0x02020202L;taskContext->R3  = 0x03030303L;taskContext->R12 = 0x12121212L;taskContext->LR  = (UINT32)OsTaskExit;taskContext->PC  = (UINT32)OsTaskEntry;taskContext->xPSR = 0x01000000L;⑸  return (VOID *)taskContext;
}

3.3 OsTaskExit()函数

在初始化上下文的时候,链接寄存器设置的是函数VOID OsTaskExit(VOID),该函数定义在文件arch\arm\cortex_m\src\task.c。函数代码里调用__disable_irq()关中断,然后进入死循环。该函数理论上不会被执行,忽略即可。

LITE_OS_SEC_TEXT_MINOR VOID OsTaskExit(VOID)
{__disable_irq();while (1) { }
}

3.4 LOS_Task任务进入函数

在初始化上下文的时候,PC寄存器设置的是函数VOID OsTaskEntry(UINT32 taskId),该函数定义在文件kernel\base\los_task.c,我们来分析下源代码,⑴处释放任务的自旋锁,开中断。然后执行⑵处代码获取taskCB,并调用任务的入口函数。等任务执行完毕后,检查taskCB->taskFlags是否设置为自删除标记OS_TASK_FLAG_DETACHED,如果是则删除任务。

LITE_OS_SEC_TEXT_INIT VOID OsTaskEntry(UINT32 taskId)
{LosTaskCB *taskCB = NULL;VOID *ret = NULL;LOS_ASSERT(OS_TSK_GET_INDEX(taskId) < g_taskMaxNum);⑴  LOS_SpinUnlock(&g_taskSpin);(VOID)LOS_IntUnLock();⑵  taskCB = OS_TCB_FROM_TID(taskId);#ifdef LOSCFG_OBSOLETE_APIret = taskCB->taskEntry(taskCB->args[0], taskCB->args[1], taskCB->args[2],taskCB->args[3]); /* 0~3: just for args array index */
#elseret = taskCB->taskEntry(taskCB->args);
#endif⑶  if (OsTaskDeleteCheckDetached(taskCB)) {OsTaskDeleteDetached(taskCB);} else {OsTaskDeleteJoined(taskCB, ret);}
}

点击关注,第一时间了解华为云新鲜技术~

LiteOS内核源码分析:任务栈信息相关推荐

  1. LiteOS内核源码分析:静态内存Static Memory

    本文分享自华为云社区<LiteOS内核源码分析系列十二 静态内存Static Memory>,原文作者:zhushy . 内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包 ...

  2. LiteOS内核源码分析:任务LOS_Schedule

    本文分享自华为云社区<LiteOS内核源码分析系列六 -任务及调度(5)-任务LOS_Schedule>,原文作者:zhushy . 本文我们来一起学习下LiteOS调度模块的源代码,文中 ...

  3. LiteOS内核源码分析:动态内存之Bestfit分配算法

    本文分享自华为云社区<LiteOS内核源码分析系列十三 动态内存Bestfit分配算法>,原文作者:zhushy . 内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内 ...

  4. LiteOS内核源码分析:消息队列Queue

    本文分享自华为云社区<LiteOS内核源码分析系列十 消息队列Queue>,原文作者:zhushy . 队列(Queue)是一种常用于任务间通信的数据结构.任务能够从队列里面读取消息,当队 ...

  5. LiteOS内核源码分析:位操作模块

    摘要:本文带领大家一起剖析了LiteOS位操作模块的源代码,代码非常简单,参考官方示例程序代码,实际编译运行一下,加深理解. 本文分享自华为云社区<LiteOS内核源码分析系列五 LiteOS内 ...

  6. RT-Thread内核源码分析-线程栈结构分析

    RT-Thread提供了一套满足POSIX标准的接口,因此基于RT-Thread编写的应用程序,可以相对轻松的移植到linux平台. pthread_create接口用来创建一个线程, 接下来我们基于 ...

  7. v35.03 鸿蒙内核源码分析(时间管理) | 内核基本时间单位是谁 | 百篇博客分析HarmonyOS源码

    子曰:"譬如为山,未成一篑,止,吾止也:譬如平地,虽覆一篑,进,吾往也." <论语>:子罕篇 百篇博客系列篇.本篇为: v35.xx 鸿蒙内核源码分析(时间管理篇) | ...

  8. v34.04 鸿蒙内核源码分析(原子操作) | 谁在为完整性保驾护航 | 百篇博客分析HarmonyOS源码

    子曰:"吾未见好德如好色者也." <论语>:子罕篇 百篇博客系列篇.本篇为: v34.xx 鸿蒙内核源码分析(原子操作篇) | 谁在为完整性保驾护航 基础工具相关篇为: ...

  9. v19.04 鸿蒙内核源码分析(位图管理) | 特节俭的苦命孩子 | 百篇博客分析HarmonyOS源码

    子曰:"饭疏食,饮水,曲肱而枕之,乐亦在其中矣.不义而富且贵,于我如浮云." <论语>:述而篇 百篇博客系列篇.本篇为: v19.xx 鸿蒙内核源码分析(位图管理篇) ...

最新文章

  1. TCP/IP 协议理解
  2. 安装 Windows 7 64位系统 相关注意事项
  3. STM32 电机教程 8 - 步进电机开环电流控制
  4. 东北能源大数据中心正式成立,一期将建设2.4万平方米数据中心
  5. crawler_x-requested-with 请求头
  6. 用node.js读写文件
  7. Android-构建不同环境的Apk
  8. 一、crontab 定时任务
  9. Spring框架----IOC的概念和作用之程序的耦合和解耦
  10. Redis系列(七)--Sentinel哨兵模式
  11. Linux用户和用户组详解
  12. BZOJ4305 数列的GCD
  13. tensorflowgpu利用率为0_「活动」体验新一代主机 天翼云数十款云产品0元试用
  14. 基于ssm的简单员工信息管理系统
  15. 【线性代数】线性代数的几何意义
  16. Android ToolBar修改返回按钮图标
  17. Warshall算法代码实现
  18. c#中https通讯如何添加证书
  19. 基于S3C2440丛SD卡启动WinCE(或其它程序)的实现
  20. LOG高斯-拉普拉斯算子

热门文章

  1. CSS两栏布局之右栏布局
  2. 怎么用计算机弹柯南,柯迷们的骚操作有哪些?用计算器弹柯南主题曲,自制缩小药丸...
  3. 简述计算机通信网络的技术指标,计算机网络基础知识之数据通信中的主要技术指标...
  4. JavaScript 真值和假值
  5. html基础-table标签
  6. The Google File System
  7. Sdut 2165 Crack Mathmen(数论)(山东省ACM第二届省赛E 题)
  8. C++复数运算 重载
  9. 【慢慢学Android】:11.对话框大全
  10. 叨叨20220304