专栏
天翼云开发者社区

Linux云桌面卡死问题从coredump上的分析

2023-03-28 18:32:40 96阅读

有同事反馈在测试环境有个Linux的云桌面出现了卡死的现象,表现为云桌面内无法进行任何操作,但从其它网络互通的环境还可以ssh登录到卡死的云桌面上。

经过简单分析,我初步断定这个问题是进程死锁造成的卡死,于是让该同事手动触发内核crash以留下coredump做进一步分析。以下是针对该问题的一些分析记录。

 

通过加载出问题时的 coredump,可以发现 kworker/3:2 这个线程处于D状态,卡死在 qxl 相关的 work 里在等待 mutex lock:

顺着上面的函数调用栈来看,ttm_eu_reserve_buffers()在下图的位置处调用了__ww_mutex_lock():

结合代码可以看出对应的是下面的内核代码:

__ttm_bo_reserve()是个内联函数,其会逐级调用到__ww_mutex_lock(),并且调用过程中涉及到的函数也都是内联函数,因而从代码上可以看出ttm_eu_reserve_buffers()调用__ttm_bo_reserve()时传递的第一个参数 bo ,该参数里的一个成员 bo->resv 最终就对应到上面 kworker/3:2 卡死时的函数栈里的 mutex lock 这个锁的地址:

因而,如果能找出ttm_eu_reserve_buffers()调用__ttm_bo_reserve()时传递的第一个参数 bo 对应的地址,就能直接获得mutex lock 这个锁的地址并查看其内容了。

 

分析 ttm_eu_reserve_buffers()的反汇编代码,可以知道ttm_eu_reserve_buffers()调用__ww_mutex_lock()时传递的第一个参数(也即 mutex lock 的地址)是这么来的:

 

 

因此,R14的值就是ttm_eu_reserve_buffers()调用__ttm_bo_reserve()时传递的第一个参数 bo 对应的地址了。由于 __ww_mutex_lock() 里会把R14压栈保存:

故而我们可以从栈上保存的内容找到 R14 的值是0xffff9ae457386858:

因此,bo 参数(对应的结构体类型是struct ttm_buffer_object)的内容就可以从 coredump 中查看了。我们关心的是bo->resv:

从上图就可以看到 mutex lock 里面的内容了。当前持有这把 mutex lock 的线程的 task_struct 结构体指针保存在 owner 成员上,可以看到锁的持有者就是 kworker/3:2 内核线程它自己:

另一方面,从上图 mutex lock 的 wait_list 成员,我们也可以看到当前在等待这把锁的线程只有一个,也是 kworker/3:2 内核线程它自己!

分析结论:

从上述分析可见这是一个因为重复上锁导致的死锁的case,怀疑这是 qxl 驱动的一个bug。qxl驱动将预留buffer等操作以 workqueue 的形式扔给 kworker 线程来完成,这些操作会涉及不少获取和释放锁的操作,这里时序处理不当就可能造成死锁。目前推测这个问题的可能原因:

  1. 之前 kworker 上的一个 work 拿了某个buffer的 mutex lock 后,因为某些异常导致漏了释放锁,造成后面新的 work 在获取这个buffer时,由于需要拿 mutex lock 而死锁;
  2. kworker 上的 workqueue 链表乱了,导致链表上残留了一个已处理完成的work,造成此work重复处理引起重复上锁。

下一步思路:

重新编译替换云桌面用的内核,开启 lockdep 相关的机制,然后重新尝试复现问题,看看 lockdep 的打印能不能抓到第一次获取 mutex lock 的函数调用栈,以此来进一步分析此调用路径为何没有释放锁。

lockdep 是 Linux 内核用于做spinlock、mutex lock等锁的依赖性检查的一个机制,通常用于在开发阶段 debug 死锁相关的issue或者检测可能的潜在死锁路径。

  • 0
  • 0
  • 0
0 评论
0/1000
评论(0) 发表评论
吴****楷

吴****楷

3 篇文章 1 粉丝
关注

Linux云桌面卡死问题从coredump上的分析

2023-03-28 18:32:40 96阅读

有同事反馈在测试环境有个Linux的云桌面出现了卡死的现象,表现为云桌面内无法进行任何操作,但从其它网络互通的环境还可以ssh登录到卡死的云桌面上。

经过简单分析,我初步断定这个问题是进程死锁造成的卡死,于是让该同事手动触发内核crash以留下coredump做进一步分析。以下是针对该问题的一些分析记录。

 

通过加载出问题时的 coredump,可以发现 kworker/3:2 这个线程处于D状态,卡死在 qxl 相关的 work 里在等待 mutex lock:

顺着上面的函数调用栈来看,ttm_eu_reserve_buffers()在下图的位置处调用了__ww_mutex_lock():

结合代码可以看出对应的是下面的内核代码:

__ttm_bo_reserve()是个内联函数,其会逐级调用到__ww_mutex_lock(),并且调用过程中涉及到的函数也都是内联函数,因而从代码上可以看出ttm_eu_reserve_buffers()调用__ttm_bo_reserve()时传递的第一个参数 bo ,该参数里的一个成员 bo->resv 最终就对应到上面 kworker/3:2 卡死时的函数栈里的 mutex lock 这个锁的地址:

因而,如果能找出ttm_eu_reserve_buffers()调用__ttm_bo_reserve()时传递的第一个参数 bo 对应的地址,就能直接获得mutex lock 这个锁的地址并查看其内容了。

 

分析 ttm_eu_reserve_buffers()的反汇编代码,可以知道ttm_eu_reserve_buffers()调用__ww_mutex_lock()时传递的第一个参数(也即 mutex lock 的地址)是这么来的:

 

 

因此,R14的值就是ttm_eu_reserve_buffers()调用__ttm_bo_reserve()时传递的第一个参数 bo 对应的地址了。由于 __ww_mutex_lock() 里会把R14压栈保存:

故而我们可以从栈上保存的内容找到 R14 的值是0xffff9ae457386858:

因此,bo 参数(对应的结构体类型是struct ttm_buffer_object)的内容就可以从 coredump 中查看了。我们关心的是bo->resv:

从上图就可以看到 mutex lock 里面的内容了。当前持有这把 mutex lock 的线程的 task_struct 结构体指针保存在 owner 成员上,可以看到锁的持有者就是 kworker/3:2 内核线程它自己:

另一方面,从上图 mutex lock 的 wait_list 成员,我们也可以看到当前在等待这把锁的线程只有一个,也是 kworker/3:2 内核线程它自己!

分析结论:

从上述分析可见这是一个因为重复上锁导致的死锁的case,怀疑这是 qxl 驱动的一个bug。qxl驱动将预留buffer等操作以 workqueue 的形式扔给 kworker 线程来完成,这些操作会涉及不少获取和释放锁的操作,这里时序处理不当就可能造成死锁。目前推测这个问题的可能原因:

  1. 之前 kworker 上的一个 work 拿了某个buffer的 mutex lock 后,因为某些异常导致漏了释放锁,造成后面新的 work 在获取这个buffer时,由于需要拿 mutex lock 而死锁;
  2. kworker 上的 workqueue 链表乱了,导致链表上残留了一个已处理完成的work,造成此work重复处理引起重复上锁。

下一步思路:

重新编译替换云桌面用的内核,开启 lockdep 相关的机制,然后重新尝试复现问题,看看 lockdep 的打印能不能抓到第一次获取 mutex lock 的函数调用栈,以此来进一步分析此调用路径为何没有释放锁。

lockdep 是 Linux 内核用于做spinlock、mutex lock等锁的依赖性检查的一个机制,通常用于在开发阶段 debug 死锁相关的issue或者检测可能的潜在死锁路径。

文章来自专栏

Linux内核

3 篇文章 1 订阅
0 评论
0/1000
评论(0) 发表评论
  • 0
    点赞
  • 0
    收藏
  • 0
    评论