0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

用crash工具分析Linux内核死锁的一次实战分享

Linux阅码场 来源:未知 作者:李建兵 2018-03-17 09:27 次阅读

本文简介:

内核死锁问题一般是读写锁(rw_semaphore)和互斥锁(mutex)引起的,本文主要讲如何通过ramdump+crash工具来分析这类死锁问题。

0、背景知识点

ramdump是内存转存机制,我们可以在某个时刻把系统的内存转存到一个文件中,然后与符号信息(vmlinux)一起导入到trace32或crash等内存分析工具中做离线分析。是分析崩溃、死锁、内存泄露等内核疑难问题的重要调试手段。

crash是用于解析ramdump的开源工具(http://people.redhat.com/anderson/),是命令行式的交互模式,提供诸多功能强大的调试命令,是分析定位内核复杂问题的利器。

死锁是指两个或两个以上的执行流在执行过程中,由于竞争锁资源而造成的一种阻塞的现象。如图:

1、问题描述

Android7.1系统中跑monkey时出现界面卡死现象:

1)没有任何刷新,所有输入事件无效,包括电源

2)watchdog没有重启system_server

3)可以连adb,但ps等调试命令卡住

2、初步分析

由于无法直接用adb调试,用长按电源键的方式进入dump模式并导出ramdump文件,之后再用crash工具载入randump文件开始离线分析。

一般卡死时可能是因为核心线程处在UNINTERRUPTIBLE状态,所以先在crash环境下用ps命令查看手机中UNINTERRUPTIBLE状态的线程,参数-u可过滤掉内核线程:

bt命令可查看某个线程的调用栈,我们看一下上面UN状态的最关键的watchdog线程:

从调用栈中可以看到proc_pid_cmdline_read()函数中被阻塞的,对应的代码为:

这里是要获取被某个线程mm的mmap_sem锁,而这个锁又被另外一个线程持有。

3、推导读写锁

要想知道哪个线程持有了这把锁,我们得先用汇编推导出这个锁的具体值。可用dis命令看一下proc_pid_cmdline_read()的汇编代码:

0xffffff99a680aaa0处就是调用down_read()的地方,它的第一个参数x0就是sem锁,如:

x0和x28寄存器存放的就是sem的值,那x21自然就是mm_struct的地址了,因为mm_struct的mmap_sem成员的offset就是104(0x68),用whatis命令可以查看结构体的声明,如:

因此我们只需要知道x21或者x28就知道mm和mmap_sem锁的值。

函数调用时被调用函数会在自己的栈帧中保存即将被修改到的寄存器,所以我们可以在down_read()及它之后的函数调用中找到这两个寄存器:

也就是说下面几个函数中,只要找到用到x21或x28,必然会在它的栈帧中保存这些寄存器。

先从最底部的down_read()开始找:

显然它没有用到x21或x28,继续看rwsem_down_read_failed()的汇编代码:

在这个函数中找到x21,它保存在rwsem_down_read_failed栈帧的偏移32字节的位置。

rwsem_down_read_failed()的sp是0xffffffd6d9e4bcb0

sp + 32 =0xffffffd6d9e4bcd0,用rd命令查看地址0xffffffd6d9e4bcd0中存放的x21的值为:

用struct命令查看这个mm_struct:

这里的owner是mm_struct所属线程的task_struct:

sem锁的地址为0xffffffd76e349a00+0x68= 0xffffffd76e349a68,因此:

分析到这里我们知道watchdog线程是在读取1651线程的proc节点时被阻塞了,原因是这个进程的mm,它的mmap_sem锁被其他线程给拿住了,那到底是谁持了这把锁呢?

4、持读写锁的线程

带着问题我们继续分析,首先通过list命令遍历wait_list来看一下共有多少个线程在等待这个读写锁:

从上面的输出可以看到一共有2个写者和有17个读者在等待,这19个线程都处于UNINTERRUPTIBLE状态。

再回顾一下当前系统中所有UNINTERRUPTIBLE状态的线程:

其中除标注红颜色的5个线程外的19个线程,都是上面提到的等待读写锁的线程。当持锁线程是写者,我们可以通过rw_semaphore结构的owner找到持锁线程。可惜这里owner是0,这表示持锁者是读者线程,因此我们无法通过owner找到持锁线程。这种情况下可以通过search命令加-t参数从系统中所有的线程的栈空间里查找当前锁:

一般锁的值都会保存在寄存器中,而寄存器又会在子函数调用过程中保存在栈中。所以只要在栈空间中找到当前锁的值(0xffffffd76e349a68),那这个线程很可能就是持锁或者等锁线程

这里搜出的20个线程中19个就是前面提到的等锁线程,剩下的1个很可能就是持锁线程了:

查看这个线程的调用栈:

由于2124线程中存放锁的地址是0xffffffd6d396b8b0,这个是在handle_mm_fault()的栈帧范围内,因此可以推断持锁的函数应该是在handle_mm_fault()之前。

我们先看一下do_page_fault函数:

代码中确实是存在持mmap_sem的地方,并且是读者,因此可以确定是2124持有的读写锁阻塞了watchdog在内的19个线程。

接下来我们需要看一下2124线程为什么会持锁后迟迟不释放就可以了,但在这之前我们先看一下system_server的几个UNINTERRUPTIBLE状态的线程阻塞的原因。

5、其他被阻塞的线程(互斥锁的推导)

先看一下ActivityManager线程:

通过调用栈能看到是在binder_alloc_new_buf时候被挂起的,我们得先找出这个锁的地址。

首先从mutex_lock()函数入手:

从它的声明中可以看到它的参数只有1个,就是mutex结构体指针。

再看看mutex_lock函数的实现:

mutex_lock的第一个参数x0就是我们要找的struct mutex,在0xffffff99a74e1648处被保存在x19寄存器中,接着在0xffffff99a74e1664处调用了__mutex_lock_slowpath(),因此我们可以在__mutex_lock_slowpath()中查找x19:

由于__mutex_lock_slowpath()的sp是0xffffffd75ca379a0:

因此x19的值保存在0xffffffd75ca379a0+ 16 = 0xffffffd75ca379b0

我们要找的mutex就是0xffffffd6dfa02200:

其中owner就是持有该所的线程的task_struct指针。它的pid为:

查看这个线程的调用栈:

这个3337线程就是前面提到的被读写锁锁住的19个线程之一。

用同样的方法可找到audioserver的1643线程、system_server的1909、2650线程也都是被这个3337线程持有的mutex锁给阻塞的。

总结起来的话:1)一共有4个线程在等待同一个mutex锁,持锁的是3337线程2)包括3337的19个线程等待着同一个读写锁,持锁的是2124线程。

也就是说大部分的线程都是直接或者间接地被2124线程给阻塞了。

6、死锁

最后一个UNINTERRUPTIBLE状态的线程就是2767(sdcard)线程:

可以看出2124线程是等待fuse的处理结果,而我们知道fuse的请求是sdcard来处理的。

这很容易联想到2124的挂起可能跟2767(sdcard)线程有关,但2124线程是在做read请求,而2767线程是在处理open请求时被挂起的。

就是说sdcard线程并不是在处理2124线程的请求,不过即使这种情况下sdcard线程依然能阻塞2124线程。因为对于一个APP进程来说,只会有一个特定的sdcard线程服务于它,如果同一个进程的多线程sdcard访问请求,sdcard线程会串行的进行处理。

如果前一个请求得不到处理,那后来的请求都会被阻塞。跟之前mutex锁的推导方法一样,得2767线程等待的mutex锁是0xffffffd6948f4090,

它的owner的task和pid为:

先通过bt命令查找2124的栈范围为0xffffffd6d396b4b0~0xffffffd6d396be70:

从栈里面可以找到mutex:

mutex值在ffffffd6d396bc40这个地址上找到了,它是在__generic_file_write_iter的栈帧里。

那可以肯定是在__generic_file_write_iter之前就持锁了,并且很可能是ext4_file_write_iter中,查看其源码:

这下清楚了,原来2124在等待2767处理fuse请求,而2767又被2124线程持有的mutex锁给锁住了,也就是说两个线程互锁了。

本文只限于介绍如何定位死锁问题,至于如何解决涉及到模块的具体实现,由于篇幅的关系这里就不再赘述了。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10943

    浏览量

    206547

原文标题:朴英敏: 用crash工具分析Linux内核死锁的一次实战

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux内核开发工具介绍

    进行嵌入式Linux产品开发,往往需要对内核进行裁剪和定制,以满足嵌入式产品的功能和性能需求。本文介绍几种阅读Linux内核源码的工具和方法
    发表于 12-29 15:20 4528次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>开发<b class='flag-5'>工具</b>介绍

    一款随Linux内核代码维护的性能诊断工具

    Perf Event 是一款随 Linux 内核代码一同发布和维护的性能诊断工具,由内核社区维护和发展。Perf 不仅可以用于应用程序的性能统计分析
    的头像 发表于 04-06 09:23 7367次阅读
    一款随<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>代码维护的性能诊断<b class='flag-5'>工具</b>

    Linux内核内存泄漏怎么办

    Linux内核开发中,Kmemleak是一种用于检测内核中内存泄漏的工具
    发表于 07-04 11:04 561次阅读

    Linux内核模块有什么

    内核LINUX内核Linux开发
    jf_97106930
    发布于 :2022年08月26日 19:45:17

    Linux内核开发工具介绍

    接触到Linux内核代码的开发人员,都有无从下手的感觉。下面推荐几个源码阅读和索引工具,能为后续内核开发提供些便利。1、Source In
    发表于 01-06 17:20

    Linux内核链表详讲(1)

    课讲解,今天是第一次课。请点击下面,观看。如果你观看之后,感觉不错,请帮我评价。当然有什么好的意见,请说说吧内核链表(1)
    发表于 07-10 18:23

    linux内核分析及编程

    linux内核分析及编程
    发表于 04-20 08:26

    linux内核分析及编程--

    本帖最后由 lee_st 于 2018-5-19 10:26 编辑 linux内核分析及编程--
    发表于 05-09 09:49

    Linux内核源码之我见——内核源码的分析方法

    ,还是系统启动的代码等等。内核的庞大决定着我们不能一次性将内核代码全部分析完成,因此我们需要给自己个合理的分工。正如算法设计告诉我们的,要
    发表于 05-11 07:00

    linux系统异常重启,如何获取最后一次启动日志并分析异常?

    亲爱的 NXP IMX8 支持团队。 linux系统异常重启,如何获取最后一次启动日志并分析异常?
    发表于 06-08 07:21

    linux内核启动内核解压过程分析

    linux启动时内核解压过程分析,一份不错的文档,深入了解内核必备
    发表于 03-09 13:39 1次下载

    基于Linux 2.6内核Makefile分析

    基于2.4内核的,可以说关于2.6内核Makefile相关的文章凤毛麟角,笔者抽时间完成了这篇分析文章,让读者迅速熟悉Linux最新Makefile体系,从而加深对
    发表于 09-18 19:09 0次下载
    基于<b class='flag-5'>Linux</b> 2.6<b class='flag-5'>内核</b>Makefile<b class='flag-5'>分析</b>

    你知道perf学习-linux自带性能分析工具怎么用?

    Linux性能调优工具,32内核以上自带的工具,软件性能分析。在2.6.31及后续版本的linux
    发表于 05-16 14:54 2441次阅读

    Linux内核GPIO操作函数的详解分析

    本文档的主要内容详细介绍的是Linux内核GPIO操作函数的详解分析免费下载。
    发表于 01-22 16:58 28次下载

    Linux内核死锁lockdep功能

    的编程思路,也不可能避免会发生死锁。在Linux内核中,常见的死锁有如下两种: 递归死锁:如在中断延迟操作中使用了锁,和外面的锁构成了递归
    的头像 发表于 09-27 15:13 329次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>死锁</b>lockdep功能