互斥锁调测方法
使用场景
多任务系统使用互斥锁达到资源互斥的目的,其他任务不能强行抢占任务已经占有的资源。使用互斥锁时,可能存在任务间相互等对方释放资源的情况,从而造成死锁。死锁会使任务陷入无限循环等待,导致业务功能障碍。
功能说明
开启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; }
针对以上场景出现的问题,在怀疑发生死锁后,可以参考一下步骤定位问题:
- 在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俩任务分别只持有一把锁。
- 在Shell中运行task命令显示当前所有正在运行的任务状态和信息。
输出结果如下图:
根据步骤1,任务app_Task和mutexDlock_Task是找到的疑似发生死锁的任务。上图中的TaskEntryAddr列为发生死锁时互斥锁pend的任务入口函数地址,如本例中为任务app_Task(0x8026f28c)和mutexDlock_Task(0x8026eef4)。
- 在反汇编文件中找到相应函数。
打开 .asm反汇编文件(默认在Huawei_LiteOS/out/<platform>目录下,其中的platform为具体的平台名),在.asm文件中找到相应的地址,以mutexDlock_Task(0x8026eef4)为例如下图所示,即可定位到互斥锁pend的位置及调用的接口:
- 查看单个任务的栈调用信息。
如果需要进一步确认任务的调用关系,可以在Shell中运行task命令加ID号查看该任务的栈调用信息,最后再根据上下文判断是否存在死锁。