鸿蒙轻内核M核源码分析系列七 任务及任务调度(4)任务模块维测能力
鸿蒙轻内核M核源码分析系列七 任务及任务调度(4)任务模块维测能力
在嵌入式开发时,经常遇到任务爆栈溢出的情况,鸿蒙轻内核提供了任务模块的维测能力,在任务栈出现异常时输出有用的调测信息,帮助开发者快速定位问题。本文我们来一起学习下任务模块的维测能力,文中所涉及的源代码,所涉及的源码,以OpenHarmony LiteOS-M
内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。
下面,我们剖析下任务模块维测能力的源代码。
1、任务模块维测能力相关的开关宏介绍
文件kernel\include\los_config.h
中定义了几个开关宏,开发者需要在自己工程的target_config.h文件中开启,相关的维测能力才会生效。
1.1 LOSCFG_BASE_CORE_TSK_MONITOR
开启该宏后,在任务调度切换时,对任务栈是否溢出,任务CPU使用情况,任务切换统计等进行检查监控。
/**
* @ingroup los_config
* Configuration item for task (stack) monitoring module tailoring
*/
#ifndef LOSCFG_BASE_CORE_TSK_MONITOR
#define LOSCFG_BASE_CORE_TSK_MONITOR 0
#endif
1.2 LOSCFG_MPU_ENABLE
在芯片架构已经支持MPU内存保护单元时,可以开启该宏。在LiteOS-M内核MPU模块被用户保护任务栈。
/**
* @ingroup los_config
* Configuration item for mpu.
*/
#ifndef LOSCFG_MPU_ENABLE
#define LOSCFG_MPU_ENABLE 0
#endif
1.3 LOSCFG_EXC_HARDWARE_STACK_PROTECTION
设置为1开启使用硬件保护栈的宏LOSCFG_EXC_HARDWARE_STACK_PROTECTION
后,需要同时开启宏LOSCFG_MPU_ENABLE
,这样会使用MPU模块来检测任务栈是否溢出。LOSCFG_EXC_HARDWARE_STACK_PROTECTION
宏设置为0时,使用任务栈栈顶的魔术字来检测是否栈溢出。
/**
* @ingroup los_config
* Configuration of hardware stack protection
*/
#ifndef LOSCFG_EXC_HARDWARE_STACK_PROTECTION
#define LOSCFG_EXC_HARDWARE_STACK_PROTECTION 0
#endif
1.4 LOSCFG_BASE_CORE_EXC_TSK_SWITCH
该宏开启时,会记录任务切换的次数等信息。
/**
* @ingroup los_config
* Configuration item for task perf task filter hook
*/
#ifndef LOSCFG_BASE_CORE_EXC_TSK_SWITCH
#define LOSCFG_BASE_CORE_EXC_TSK_SWITCH 0
#endif
开启该宏时涉及2个结构体,定义如下:
typedef struct {
UINT8 maxCnt : 7; // bits [6:0] 存储任务切换信息的数量
UINT8 isFull : 1; // bit [7] 存储is full状态
} TaskCountInfo;
typedef struct {
UINT8 idx;
TaskCountInfo cntInfo;
UINT16 pid[OS_TASK_SWITCH_INFO_COUNT];
CHAR name[OS_TASK_SWITCH_INFO_COUNT][LOS_TASK_NAMELEN];
} TaskSwitchInfo;
2、任务模块维测能力相关的函数
开启LOSCFG_BASE_CORE_TSK_MONITOR
时涉及的主要函数有OsTaskMonInit()
、OsTaskSwitchCheck()
,下文分别介绍。
2.1 OsTaskMonInit函数
OsTaskMonInit()
定义在kernel\src\los_task.c
,被kernel\src\los_init.c
文件中的LOS_KernelInit()
函数调用。该任务监控初始化函数很简单,在开启宏LOSCFG_BASE_CORE_EXC_TSK_SWITCH
时,初始化全局变量g_taskSwitchInfo
。OS_TASK_SWITCH_INFO_COUNT
默认值为0xA。
LITE_OS_SEC_TEXT_MINOR VOID OsTaskMonInit(VOID)
{
#if (LOSCFG_BASE_CORE_EXC_TSK_SWITCH == 1)
(VOID)memset_s(&g_taskSwitchInfo, sizeof(TaskSwitchInfo), 0, sizeof(TaskSwitchInfo));
g_taskSwitchInfo.cntInfo.maxCnt = OS_TASK_SWITCH_INFO_COUNT;
#endif
return;
}
2.2 OsTaskSwitchCheck函数
OsTaskSwitchCheck()
定义在kernel\src\los_task.c
,被kernel\src\los_sched.c
文件中的OsSchedTaskSwitch()
函数调用,在任务切换时进行检测。代码如下,逐行分析下。⑴处在LOSCFG_EXC_HARDWARE_STACK_PROTECTION
设置为0时,使用任务栈栈顶的魔术字来判断是否栈溢出。首先获取栈底endOfStack
,然后⑵处在栈顶不等于预设值的魔术字时,调用OsHandleRunTaskStackOverflow()
函数处理当前运行任务的栈溢出。⑶处在新任务的栈指针大于栈底小于栈顶时,调用函数OsHandleNewTaskStackOverflow()
处理新任务的栈溢出。⑷处表示在开启MPU硬件栈保护时,调用函数OsTaskStackProtect()
检测是否栈溢出。
在LOSCFG_BASE_CORE_EXC_TSK_SWITCH
设置为1开启时,继续执行⑸处的代码。pid[]
数组记录要调度执行的任务编号,⑹处name[]
数组记录要调度执行的任务名称,其中g_taskSwitchInfo.idx
来维护数组的索引。⑺处先增加数组的索引,如果索引达到了最大值OS_TASK_SWITCH_INFO_COUNT
,恢复为0并更新数组已满的状态。
⑻处如果开发者定义了任务切换的hook函数,则执行函数。⑼处如果开启了CPUP,则调用OsTskCycleEndStart
记录新任务的执行开始时间。
LITE_OS_SEC_TEXT VOID OsTaskSwitchCheck(VOID)
{
UINT32 intSave = LOS_IntLock();
#if (LOSCFG_EXC_HARDWARE_STACK_PROTECTION == 0)
⑴ UINT32 endOfStack = g_losTask.newTask->topOfStack + g_losTask.newTask->stackSize;
⑵ if ((*(UINT32 *)(UINTPTR)(g_losTask.runTask->topOfStack)) != OS_TASK_MAGIC_WORD) {
OsHandleRunTaskStackOverflow();
}
⑶ if (((UINT32)(UINTPTR)(g_losTask.newTask->stackPointer) <= (g_losTask.newTask->topOfStack)) ||
((UINT32)(UINTPTR)(g_losTask.newTask->stackPointer) > endOfStack)) {
OsHandleNewTaskStackOverflow();
}
#else
⑷ OsTaskStackProtect();
#endif
#if (LOSCFG_BASE_CORE_EXC_TSK_SWITCH == 1)
/* record task switch info */
⑸ g_taskSwitchInfo.pid[g_taskSwitchInfo.idx] = (UINT16)(g_losTask.newTask->taskID);
⑹ errno_t ret = memcpy_s(g_taskSwitchInfo.name[g_taskSwitchInfo.idx], LOS_TASK_NAMELEN,
g_losTask.newTask->taskName, LOS_TASK_NAMELEN);
if (ret != EOK) {
PRINT_ERR("exc task switch copy file name failed!\n");
}
g_taskSwitchInfo.name[g_taskSwitchInfo.idx][LOS_TASK_NAMELEN - 1] = '\0';
⑺ if (++g_taskSwitchInfo.idx == OS_TASK_SWITCH_INFO_COUNT) {
g_taskSwitchInfo.idx = 0;
g_taskSwitchInfo.cntInfo.isFull = TRUE;
}
#endif
⑻ LOSCFG_BASE_CORE_TSK_SWITCH_HOOK();
#if (LOSCFG_BASE_CORE_CPUP == 1)
⑼ OsTskCycleEndStart();
#endif /* LOSCFG_BASE_CORE_CPUP */
LOS_IntRestore(intSave);
}
2.3 OsHandleRunTaskStackOverflow函数
函数比较简单,打印当前运行任务的名称和编号,然后调用OsDoExcHook
输出异常信息,以后专门分析该函数。
LITE_OS_SEC_TEXT STATIC VOID OsHandleRunTaskStackOverflow(VOID)
{
PRINT_ERR("CURRENT task ID: %s:%d stack overflow!\n",
g_losTask.runTask->taskName, g_losTask.runTask->taskID);
OsDoExcHook(EXC_STACKOVERFLOW);
}
2.4 OsHandleNewTaskStackOverflow函数
该函数在调度运行的新任务的栈指针溢出时调用,首先打印新任务的名称和编号,然后输出栈指针和栈顶指针。接下来,从注释上可以看出,因为在使用OsDoExcHook
输出异常时使用到当前运行任务相关的LOS_CurTaskIDGet
和LOS_CurTaskNameGet
函数,先把当前运行任务备份,然哈把新任务设置为当前运行函数再调用OsDoExcHook
输出异常,最后恢复备份的当前运行函数。
LITE_OS_SEC_TEXT STATIC VOID OsHandleNewTaskStackOverflow(VOID)
{
LosTaskCB *tmp = NULL;
PRINT_ERR("HIGHEST task ID: %s:%d SP error!\n",
g_losTask.newTask->taskName, g_losTask.newTask->taskID);
PRINT_ERR("HIGHEST task StackPointer: 0x%x TopOfStack: 0x%x\n",
(UINT32)(UINTPTR)(g_losTask.newTask->stackPointer), g_losTask.newTask->topOfStack);
/*
* make sure LOS_CurTaskIDGet and LOS_CurTaskNameGet returns the ID and name of which task
* that occurred stack overflow exception in OsDoExcHook temporary.
*/
tmp = g_losTask.runTask;
g_losTask.runTask = g_losTask.newTask;
OsDoExcHook(EXC_STACKOVERFLOW);
g_losTask.runTask = tmp;
}
2.5 OsTaskStackProtect函数
在开启MPU保护任务栈时,会使用到该函数。MPU相关的功能后续专门讲述,先快速了解下该函数OsTaskStackProtect()
。⑴处内存区域编号,MPU可以支持管理8个内存区域,每个区域都有个编号。⑵处设置MPU内存区域的基地址,基地址在任务栈栈顶下方大小为OS_TASK_STACK_PROTECT_SIZE
的一块区域。然后设置内存类型、是否可执行、是否可共享、权限等属性信息。⑶处先关闭MPU特性,清除指定编号的内存区域,然后设置指定编号的内存区域的MPU属性,最后调用HalMpuEnable
使能MPU。
LITE_OS_SEC_TEXT STATIC VOID OsTaskStackProtect(VOID)
{
MPU_CFG_PARA mpuAttr = {0};
STATIC INT32 id = -1;
if (id == -1) {
⑴ id = HalMpuUnusedRegionGet();
if (id < 0) {
PRINT_ERR("%s %d, get unused id failed!\n", __FUNCTION__, __LINE__);
return;
}
}
⑵ mpuAttr.baseAddr = g_losTask.newTask->topOfStack - OS_TASK_STACK_PROTECT_SIZE;
mpuAttr.size = OS_TASK_STACK_PROTECT_SIZE;
mpuAttr.memType = MPU_MEM_ON_CHIP_RAM;
mpuAttr.executable = MPU_NON_EXECUTABLE;
mpuAttr.shareability = MPU_NO_SHARE;
mpuAttr.permission = MPU_RO_BY_PRIVILEGED_ONLY;
⑶ HalMpuDisable();
(VOID)HalMpuDisableRegion(id);
(VOID)HalMpuSetRegion(id, &mpuAttr);
HalMpuEnable(1);
}
2.6 LOS_TaskSwitchInfoGet函数
最后看下如何使用记录的任务切换信息,需要使用函数LOS_TaskSwitchInfoGet()
来获取。该函数需要2个参数,第一个参数是索引值,函数获取该索引值对应的任务的编号和名称。第二个参数是一块内存区域,首先使用32位长度来保存任务编号,然后保存任务名称,该内存区域的大小需要大于32+LOS_TASK_NAMELEN,避免发生踩内存。⑴处如果传入的索引大于数组长度,则取模后再获取任务切换信息。⑵处获取指定索引对应的任务编号,⑶处获取指定索引对应的任务名称。
#if (LOSCFG_BASE_CORE_EXC_TSK_SWITCH == 1)
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_TaskSwitchInfoGet(UINT32 index, UINT32 *taskSwitchInfo)
{
UINT32 intSave;
UINT32 curIndex;
curIndex = index;
⑴ if (curIndex >= OS_TASK_SWITCH_INFO_COUNT) {
curIndex %= OS_TASK_SWITCH_INFO_COUNT;
}
if (taskSwitchInfo == NULL) {
return LOS_ERRNO_TSK_PTR_NULL;
}
intSave = LOS_IntLock();
⑵ (*taskSwitchInfo) = g_taskSwitchInfo.pid[curIndex];
⑶ if (memcpy_s((VOID *)(taskSwitchInfo + 1), LOS_TASK_NAMELEN,
g_taskSwitchInfo.name[curIndex], LOS_TASK_NAMELEN) != EOK) {
PRINT_ERR("LOS_TaskSwitchInfoGet copy task name failed\n");
}
LOS_IntRestore(intSave);
return LOS_OK;
}
#endif
小结
本文带领大家一起剖析了鸿蒙轻内核任务模块的维测能力的源代码,包含如何使用魔术字、MPU来检测任务栈异常,如何记录、获取任务切换信息。后续也会陆续推出更多的分享文章,敬请期待,也欢迎大家分享学习、使用鸿蒙轻内核的心得,有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,关注Watch
、点赞Star
、并Fork
到自己账户下,谢谢。
- 点赞
- 收藏
- 关注作者
评论(0)