龙芯平台一次死锁分析过程
一.问题现象如图1所示:
图1 问题现象
1.根据问题现象初步分析:出现问题时epc的值为rt_spin_lock_slowlock函数中的地址,分析该函数的源代码及反汇编代码如图2和图3所示。
图2 函数源码
图3 函数汇编码
根据图1-3得出,由于函数task_blocks_on_rt_mutex返回非0值,导致函数进入BUG_ON时报出异常信息。
2.函数task_blocks_on_rt_mutex返回值存储在v0寄存器中,根据图1得出该返回值为0xffffffffffffffd3即-5,经过分析该函数的源码,该值为-EDEADLK。
二、分析过程(需要了解rt_mutex锁的工作机制)
1.分析函数task_blocks_on_rt_mutex中返回-EDEADLK值的代码处。添加调试信息,如图4所示。
图4 返回EDEADLK代码
添加调试信息后继续复现问题,复现问题后的调试信息输出如图5所示。
图5 调试信息输出
根据图4和图5得出:ksoftirqd/0进程在申请锁,该锁的地址为0x98000000bc10d838,即bond->lock(标记为锁A)。
kworker/u2:0进程拥有锁bond→lock,进程的控制块的地址为0x98000000bc00af80。
systemd-resolve进程在申请锁bond→lock。
ksoftirqd/0进程申请锁A时,最终推导出systemd-resolve进程也在申请锁A。在kworker/u2:0进程和systemd-resolve进程之间还存在一把锁B,systemd-resolve进程拥有,kworker/u2:0进程申请。目前打印信息中并未打印该信息,需要通过kworker/u2:0进程的进程控制块来查找该进程所申请的锁B是什么。
第一步分析结论为:ksoftirqd/0进程申请锁A;而锁A的拥有者时kworker/u2:0,它在申请锁B;锁B的拥有者时systemd-resolve,它也在申请锁A。因而造成循环死锁。(在kworker 和systemd-resolve 之间可能还存在其它进程和锁)
2.分析kworker/u2:0进程正在申请的锁,根据进程控制块的地址打印出进程控制块中的pi_blocked_on的地址,如图6所示。
图6 kworker进程控制块pi_blocked_on地址
根据图6得出pi_blocked_on的地址为0x98000000bc02bc00,该地址内容如图7所示。
图7 pi_blocked_on地址内容
根据图7得出pi_block_on->lock地址为0x98000000bc10d1f0(锁B)。该锁的具体内容如图8所示。
图8 锁B的具体内容
根据图8 lock->owner得出锁的拥有者为0x98000000bcc90000,该地址为某进程控制块的地址,继续打印出该地址如图9所示。
图9 lock->owner具体内容
根据图9得出该锁的拥有者为systemd-resolve进程。
第二步分析结论:通过(kworker)task->pi_block_on->lock得出kworker进程申请的锁为0x98000000bc10d1f0(锁B)。根据lock->owner,得出该锁的拥有者为systemd-resolve。
3.分析进程systemd-resolve进程的调用栈,分析出锁B具体内容
根据task(systemd-resolve)→thread(进程被切换后最后的现场寄存器信息),具体内容如图10。
图10 systemd-resolve 进程的thread信息
根据task→thread信息得出,该进程最后的sp值为0x98000000bcb67880。根据进程的sp信息,ra值0x809c9d5c和vmlinux的二进制反汇编推导出调用栈,如图11所示。
图11 systemd-resolve进程调用栈
根据进程调用栈中的信息最终得出systemd-resolve拥有的锁B为net_dev(bond)->addr_list_lock。
第三的结论:systemd-resolve进程的调用栈为ipv6_dev_mc_inc->igmp6_group_added->__dev_mc_add->bond_set_multicast_list->rt_read_lock->rt_spinlock_slowlock。拥有锁B(netdev(bond)->addr_list_lock)与kworker申请的锁B地址一致。
根据1,2和3分析总结:ksoftirqd/0进程申请锁A;而锁A的拥有者时kworker/u2:0,它在申请锁B;锁B的拥有者时systemd-resolve,它也在申请锁A。因而造成循环死锁。锁A和锁B具体内容前面已提及。
4.分析systemd-resolve进程和kwoker进程的源代码。
分析锁申请和释放的相关流程。最终分析得出kworker进程调用bond_mii_monitor函数首先申请 锁A(bond->lock),再调用函数bond_mc_swap申请锁B(netdev->addr_list_lock)。进程systemd-resolve进程调用__dev_mc_add函数首先申请锁B,在调用函数 bond_set_multicast_list申请锁A。