更新时间:2021-07-08 GMT+08:00
分享

互斥锁调测方法

使用场景

多任务系统使用互斥锁达到资源互斥的目的,其他任务不能强行抢占任务已经占有的资源。使用互斥锁时,可能存在任务间相互等对方释放资源的情况,从而造成死锁。死锁会使任务陷入无限循环等待,导致业务功能障碍。

功能说明

开启dlock互斥锁死锁检测功能后,每个任务在成功获取互斥锁时,会记录该互斥锁为本任务持有,因此通过任务ID可以得知持有的互斥锁。此外,互斥锁控制块本身会记录那些因申请不到该锁而被阻塞的任务。dlock命令会输出系统中所有任务持有互斥锁的信息及任务调用栈信息,再结合.asm反汇编文件和代码就可以确定哪些任务发生了死锁。

互斥锁死锁检测机制

任务发生死锁后,无法得到调度,通过记录任务上次调度的时间,设置一个超时时间阈值,如果任务在这段时间内都没有得到调度,则怀疑该任务发生了死锁。

使用方法

配置宏LOSCFG_DEBUG_DEADLOCK,该宏开关可以通过make menuconfig在菜单项中开启“Enable Mutex Deadlock Debugging”使能,若关闭该菜单项,则关闭死锁检测功能。

Debug ---> Enable a Debug Version ---> Enable Debug LiteOS Kernel Resource ---> Enable Mutex Deadlock Debugging

注意事项

死锁检测输出的是超过时间阈值(默认为10min)的任务信息,但不代表这些任务都发生了死锁,需要通过互斥锁信息及任务调用栈信息进一步确认。

死锁定位实例

构造ABBA互斥锁死锁场景,具体如下:

有两个任务,分别为app_Task和mutexDlock_Task,同时系统中还存在其他系统默认初始任务。在任务app_Task中执行MutexDlockDebug函数,并在该函数中创建任务mutexDlock_Task。函数MutexDlockDebug(即任务app_Task)创建了个互斥锁mutex0,并持有mutex0,接着创建更高优先级的任务mutexDlock_Task,休眠一段时间后去申请mutex1被阻塞(任务mutexDlock_Task已经率先持有mutex1)。任务mutexDlock_Task创建并持有mutex1,然后申请mutex0被阻塞(任务app_Task已经率先持有mutex0)。代码如下:

#include "unistd.h"
#include "los_mux.h"
#include "los_task.h"

static UINT32 mutexTest[2];
extern UINT32 OsShellCmdMuxDeadlockCheck(UINT32 argc, const CHAR **argv);

VOID DlockDebugTask(VOID)
{
    UINT32 ret;

    ret = LOS_MuxPend(mutexTest[1], LOS_WAIT_FOREVER);
    if (ret != LOS_OK) {
        PRINT_ERR("pend mutex1 error %u\n", ret);
    }

    ret = LOS_MuxPend(mutexTest[0], LOS_WAIT_FOREVER);
    if (ret != LOS_OK) {
        PRINT_ERR("pend mutex0 error %u\n", ret);
    }

    ret = LOS_MuxPost(mutexTest[1]);
    if (ret != LOS_OK) {
        PRINT_ERR("post mutex1 error %u\n", ret);
    }

    ret = LOS_MuxPost(mutexTest[0]);
    if (ret != LOS_OK) {
        PRINT_ERR("post mutex0 error %u\n", ret);
    }
}

// MutexDlockDebug函数在用户任务app_Task中被调度
STATIC UINT32 MutexDlockDebug(VOID)
{
    UINT32 ret;
    UINT32 taskId;
    TSK_INIT_PARAM_S debugTask;

    ret = LOS_MuxCreate(&mutexTest[0]);
    if (ret != LOS_OK) {
        PRINT_ERR("create mutex0 error %u\n", ret);
    }

    ret = LOS_MuxCreate(&mutexTest[1]);
    if (ret != LOS_OK) {
        PRINT_ERR("create mutex1 error %u\n", ret);
    }

    ret = LOS_MuxPend(mutexTest[0], LOS_WAIT_FOREVER);
    if (ret != LOS_OK) {
        PRINT_ERR("pend mutex0 error %u\n", ret);
    }

    (VOID)memset_s(&debugTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
    debugTask.pfnTaskEntry = (TSK_ENTRY_FUNC)DlockDebugTask;
    debugTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    debugTask.pcName = "mutexDlock_Task";
    debugTask.usTaskPrio = 9;
    debugTask.uwResved = LOS_TASK_STATUS_DETACHED;

    ret = LOS_TaskCreate(&taskId, &debugTask);   // 创建mutexDlock_Task任务,任务入口函数DlockDebugTask,优先级9高于app_Task任务
    if (ret != LOS_OK) {
        PRINT_ERR("create debugTask error %u\n", ret);
    }

    sleep(2);
    ret = LOS_MuxPend(mutexTest[1], LOS_WAIT_FOREVER);
    if (ret != LOS_OK) {
        PRINT_ERR("pend mutex1 error %u\n", ret);
    }

    ret = LOS_MuxPost(mutexTest[0]);
    if (ret != LOS_OK) {
        PRINT_ERR("post mutex0 error %u\n", ret);
    }

    ret = LOS_MuxPost(mutexTest[1]);
    if (ret != LOS_OK) {
        PRINT_ERR("post mutex1 error %u\n", ret);
    }
    return ret;
}

针对以上场景出现的问题,在怀疑发生死锁后,可以参考一下步骤定位问题:

  1. 在Shell中运行dlock命令检测死锁。

    输出结果如下图:

    • “Task_name:app_Task, ID:0x5, holds the Mutexs below:”和“Task_name:mutexDlock_Task, ID:0xc, holds the Mutexs below:”这两行后面有mutex信息,表示可能是任务app_Task(任务ID为5)和mutexDlock_Task (任务ID为c)发生了死锁。
    • <Mutex0 info>:其后几行是该互斥锁的详细信息,包括“Ptr handle”为互斥锁句柄、“Owner”为锁的持有者、“Count”为该锁的引用计数、“Pended task”为阻塞在这把锁上的任务。如果该任务持有多把锁,会逐个打印这些锁的信息(Mutex0~MutexN)。当前app_Task和mutexDlock_Task俩任务分别只持有一把锁。

  2. 在Shell中运行task命令显示当前所有正在运行的任务状态和信息。

    输出结果如下图:

    根据步骤1,任务app_Task和mutexDlock_Task是找到的疑似发生死锁的任务。上图中的TaskEntryAddr列为发生死锁时互斥锁pend的任务入口函数地址,如本例中为任务app_Task(0x8026f28c)和mutexDlock_Task(0x8026eef4)。

  3. 在反汇编文件中找到相应函数。

    打开 .asm反汇编文件(默认在Huawei_LiteOS/out/<platform>目录下,其中的platform为具体的平台名),在.asm文件中找到相应的地址,以mutexDlock_Task(0x8026eef4)为例如下图所示,即可定位到互斥锁pend的位置及调用的接口:

  4. 查看单个任务的栈调用信息。

    如果需要进一步确认任务的调用关系,可以在Shell中运行task命令加ID号查看该任务的栈调用信息,最后再根据上下文判断是否存在死锁。

相关文档