鸿蒙轻内核M核源码分析系列七 任务及任务调度(4)任务模块维测能力

举报
zhushy 发表于 2021/09/26 17:54:41 2021/09/26
【摘要】 鸿蒙轻内核M核源码分析系列七 任务及任务调度(4)任务模块维测能力在嵌入式开发时,经常遇到任务爆栈溢出的情况,鸿蒙轻内核提供了任务模块的维测能力,在任务栈出现异常时输出有用的调测信息,帮助开发者快速定位问题。本文我们来一起学习下任务模块的维测能力,文中所涉及的源代码,所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点https://gitee.com/ope...

鸿蒙轻内核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_taskSwitchInfoOS_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();
    }
#elseOsTaskStackProtect();
#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;
    }
#endifLOSCFG_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_CurTaskIDGetLOS_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到自己账户下,谢谢。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。