0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

用crash工具分析Linux內(nèi)核死鎖的一次實戰(zhàn)分享

Linux閱碼場 ? 來源:未知 ? 作者:李建兵 ? 2018-03-17 09:27 ? 次閱讀

本文簡介:

內(nèi)核死鎖問題一般是讀寫鎖(rw_semaphore)和互斥鎖(mutex)引起的,本文主要講如何通過ramdump+crash工具來分析這類死鎖問題。

0、背景知識點

ramdump是內(nèi)存轉(zhuǎn)存機制,我們可以在某個時刻把系統(tǒng)的內(nèi)存轉(zhuǎn)存到一個文件中,然后與符號信息(vmlinux)一起導入到trace32或crash等內(nèi)存分析工具中做離線分析。是分析崩潰、死鎖、內(nèi)存泄露等內(nèi)核疑難問題的重要調(diào)試手段。

crash是用于解析ramdump的開源工具(http://people.redhat.com/anderson/),是命令行式的交互模式,提供諸多功能強大的調(diào)試命令,是分析定位內(nèi)核復雜問題的利器。

死鎖是指兩個或兩個以上的執(zhí)行流在執(zhí)行過程中,由于競爭鎖資源而造成的一種阻塞的現(xiàn)象。如圖:

1、問題描述

Android7.1系統(tǒng)中跑monkey時出現(xiàn)界面卡死現(xiàn)象:

1)沒有任何刷新,所有輸入事件無效,包括電源

2)watchdog沒有重啟system_server

3)可以連adb,但ps等調(diào)試命令卡住

2、初步分析

由于無法直接用adb調(diào)試,用長按電源鍵的方式進入dump模式并導出ramdump文件,之后再用crash工具載入randump文件開始離線分析。

一般卡死時可能是因為核心線程處在UNINTERRUPTIBLE狀態(tài),所以先在crash環(huán)境下用ps命令查看手機中UNINTERRUPTIBLE狀態(tài)的線程,參數(shù)-u可過濾掉內(nèi)核線程:

bt命令可查看某個線程的調(diào)用棧,我們看一下上面UN狀態(tài)的最關鍵的watchdog線程:

從調(diào)用棧中可以看到proc_pid_cmdline_read()函數(shù)中被阻塞的,對應的代碼為:

這里是要獲取被某個線程mm的mmap_sem鎖,而這個鎖又被另外一個線程持有。

3、推導讀寫鎖

要想知道哪個線程持有了這把鎖,我們得先用匯編推導出這個鎖的具體值。可用dis命令看一下proc_pid_cmdline_read()的匯編代碼:

0xffffff99a680aaa0處就是調(diào)用down_read()的地方,它的第一個參數(shù)x0就是sem鎖,如:

x0和x28寄存器存放的就是sem的值,那x21自然就是mm_struct的地址了,因為mm_struct的mmap_sem成員的offset就是104(0x68),用whatis命令可以查看結(jié)構(gòu)體的聲明,如:

因此我們只需要知道x21或者x28就知道m(xù)m和mmap_sem鎖的值。

函數(shù)調(diào)用時被調(diào)用函數(shù)會在自己的棧幀中保存即將被修改到的寄存器,所以我們可以在down_read()及它之后的函數(shù)調(diào)用中找到這兩個寄存器:

也就是說下面幾個函數(shù)中,只要找到用到x21或x28,必然會在它的棧幀中保存這些寄存器。

先從最底部的down_read()開始找:

顯然它沒有用到x21或x28,繼續(xù)看rwsem_down_read_failed()的匯編代碼:

在這個函數(shù)中找到x21,它保存在rwsem_down_read_failed棧幀的偏移32字節(jié)的位置。

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節(jié)點時被阻塞了,原因是這個進程的mm,它的mmap_sem鎖被其他線程給拿住了,那到底是誰持了這把鎖呢?

4、持讀寫鎖的線程

帶著問題我們繼續(xù)分析,首先通過list命令遍歷wait_list來看一下共有多少個線程在等待這個讀寫鎖:

從上面的輸出可以看到一共有2個寫者和有17個讀者在等待,這19個線程都處于UNINTERRUPTIBLE狀態(tài)。

再回顧一下當前系統(tǒng)中所有UNINTERRUPTIBLE狀態(tài)的線程:

其中除標注紅顏色的5個線程外的19個線程,都是上面提到的等待讀寫鎖的線程。當持鎖線程是寫者,我們可以通過rw_semaphore結(jié)構(gòu)的owner找到持鎖線程??上н@里owner是0,這表示持鎖者是讀者線程,因此我們無法通過owner找到持鎖線程。這種情況下可以通過search命令加-t參數(shù)從系統(tǒng)中所有的線程的??臻g里查找當前鎖:

一般鎖的值都會保存在寄存器中,而寄存器又會在子函數(shù)調(diào)用過程中保存在棧中。所以只要在棧空間中找到當前鎖的值(0xffffffd76e349a68),那這個線程很可能就是持鎖或者等鎖線程

這里搜出的20個線程中19個就是前面提到的等鎖線程,剩下的1個很可能就是持鎖線程了:

查看這個線程的調(diào)用棧:

由于2124線程中存放鎖的地址是0xffffffd6d396b8b0,這個是在handle_mm_fault()的棧幀范圍內(nèi),因此可以推斷持鎖的函數(shù)應該是在handle_mm_fault()之前。

我們先看一下do_page_fault函數(shù):

代碼中確實是存在持mmap_sem的地方,并且是讀者,因此可以確定是2124持有的讀寫鎖阻塞了watchdog在內(nèi)的19個線程。

接下來我們需要看一下2124線程為什么會持鎖后遲遲不釋放就可以了,但在這之前我們先看一下system_server的幾個UNINTERRUPTIBLE狀態(tài)的線程阻塞的原因。

5、其他被阻塞的線程(互斥鎖的推導)

先看一下ActivityManager線程:

通過調(diào)用棧能看到是在binder_alloc_new_buf時候被掛起的,我們得先找出這個鎖的地址。

首先從mutex_lock()函數(shù)入手:

從它的聲明中可以看到它的參數(shù)只有1個,就是mutex結(jié)構(gòu)體指針。

再看看mutex_lock函數(shù)的實現(xiàn):

mutex_lock的第一個參數(shù)x0就是我們要找的struct mutex,在0xffffff99a74e1648處被保存在x19寄存器中,接著在0xffffff99a74e1664處調(diào)用了__mutex_lock_slowpath(),因此我們可以在__mutex_lock_slowpath()中查找x19:

由于__mutex_lock_slowpath()的sp是0xffffffd75ca379a0:

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

我們要找的mutex就是0xffffffd6dfa02200:

其中owner就是持有該所的線程的task_struct指針。它的pid為:

查看這個線程的調(diào)用棧:

這個3337線程就是前面提到的被讀寫鎖鎖住的19個線程之一。

用同樣的方法可找到audioserver的1643線程、system_server的1909、2650線程也都是被這個3337線程持有的mutex鎖給阻塞的。

總結(jié)起來的話:1)一共有4個線程在等待同一個mutex鎖,持鎖的是3337線程2)包括3337的19個線程等待著同一個讀寫鎖,持鎖的是2124線程。

也就是說大部分的線程都是直接或者間接地被2124線程給阻塞了。

6、死鎖

最后一個UNINTERRUPTIBLE狀態(tài)的線程就是2767(sdcard)線程:

可以看出2124線程是等待fuse的處理結(jié)果,而我們知道fuse的請求是sdcard來處理的。

這很容易聯(lián)想到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鎖給鎖住了,也就是說兩個線程互鎖了。

本文只限于介紹如何定位死鎖問題,至于如何解決涉及到模塊的具體實現(xiàn),由于篇幅的關系這里就不再贅述了。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11171

    瀏覽量

    208478

原文標題:樸英敏: 用crash工具分析Linux內(nèi)核死鎖的一次實戰(zhàn)

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Linux內(nèi)核開發(fā)工具介紹

    進行嵌入式Linux產(chǎn)品開發(fā),往往需要對內(nèi)核進行裁剪和定制,以滿足嵌入式產(chǎn)品的功能和性能需求。本文介紹幾種閱讀Linux內(nèi)核源碼的工具和方法
    發(fā)表于 12-29 15:20 ?4664次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>開發(fā)<b class='flag-5'>工具</b>介紹

    款隨Linux內(nèi)核代碼維護的性能診斷工具

    Perf Event 是款隨 Linux 內(nèi)核代碼同發(fā)布和維護的性能診斷工具,由內(nèi)核社區(qū)維護
    的頭像 發(fā)表于 04-06 09:23 ?7576次閱讀
    <b class='flag-5'>一</b>款隨<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>代碼維護的性能診斷<b class='flag-5'>工具</b>

    文詳解Linux內(nèi)核源碼組織結(jié)構(gòu)

    概要:本文內(nèi)容包含Linux源碼樹結(jié)構(gòu)分析、Linux Makefile分析、Kconfig文件分析、L
    的頭像 發(fā)表于 05-10 19:28 ?5662次閱讀

    Linux內(nèi)核開發(fā)工具介紹

    接觸到Linux內(nèi)核代碼的開發(fā)人員,都有無從下手的感覺。下面推薦幾個源碼閱讀和索引工具,能為后續(xù)內(nèi)核開發(fā)提供些便利。1、Source In
    發(fā)表于 01-06 17:20

    Linux內(nèi)核源碼之我見——內(nèi)核源碼的分析方法

    ,還是系統(tǒng)啟動的代碼等等。內(nèi)核的龐大決定著我們不能一次性將內(nèi)核代碼全部分析完成,因此我們需要給自己個合理的分工。正如算法設計告訴我們的,要
    發(fā)表于 05-11 07:00

    linux系統(tǒng)異常重啟,如何獲取最后一次啟動日志并分析異常?

    親愛的 NXP IMX8 支持團隊。 linux系統(tǒng)異常重啟,如何獲取最后一次啟動日志并分析異常?
    發(fā)表于 06-08 07:21

    一次網(wǎng)絡“抖動”分析

    一次網(wǎng)絡抖動分析:
    發(fā)表于 03-21 15:10 ?35次下載
    <b class='flag-5'>一次</b>網(wǎng)絡“抖動”<b class='flag-5'>分析</b>

    linux內(nèi)核啟動內(nèi)核解壓過程分析

    linux啟動時內(nèi)核解壓過程分析,份不錯的文檔,深入了解內(nèi)核必備
    發(fā)表于 03-09 13:39 ?1次下載

    基于Linux 2.6內(nèi)核Makefile分析

    基于2.4內(nèi)核的,可以說關于2.6內(nèi)核Makefile相關的文章鳳毛麟角,筆者抽時間完成了這篇分析文章,讓讀者迅速熟悉Linux最新Makefile體系,從而加深對
    發(fā)表于 09-18 19:09 ?0次下載
    基于<b class='flag-5'>Linux</b> 2.6<b class='flag-5'>內(nèi)核</b>Makefile<b class='flag-5'>分析</b>

    關于Linux 2.6內(nèi)核Makefile的分析

    的介紹文章都是基于2.4內(nèi)核的,可以說關于2.6內(nèi)核Makefile相關的文章鳳毛麟角,筆者抽時間完成了這篇分析文章,讓讀者迅速熟悉Linux最新Makefile體系,從而加深對
    發(fā)表于 11-02 10:12 ?1次下載

    你知道perf學習-linux自帶性能分析工具怎么?

    Linux性能調(diào)優(yōu)工具,32內(nèi)核以上自帶的工具,軟件性能分析。在2.6.31及后續(xù)版本的linux
    發(fā)表于 05-16 14:54 ?2552次閱讀

    Linux內(nèi)核GPIO操作函數(shù)的詳解分析

    本文檔的主要內(nèi)容詳細介紹的是Linux內(nèi)核GPIO操作函數(shù)的詳解分析免費下載。
    發(fā)表于 01-22 16:58 ?28次下載

    Linux內(nèi)核死鎖lockdep功能

    的編程思路,也不可能避免會發(fā)生死鎖。在Linux內(nèi)核中,常見的死鎖有如下兩種: 遞歸死鎖:如在中斷延遲操作中使用了鎖,和外面的鎖構(gòu)成了遞歸
    的頭像 發(fā)表于 09-27 15:13 ?631次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>死鎖</b>lockdep功能

    Linux內(nèi)核實際項目中的死鎖

    實際項目中的死鎖 下面的例子要復雜些,這是從實際項目中抽取出來的死鎖,更具有代表性。 # include # include # include # include # include
    的頭像 發(fā)表于 09-27 15:24 ?681次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>實際項目中的<b class='flag-5'>死鎖</b>

    基波是一次諧波么 基波與一次諧波的區(qū)別

    基波是一次諧波么 基波與一次諧波的區(qū)別? 基波和一次諧波是兩個不同的概念。 基波是在諧波分析中指的是頻率最低且沒有任何諧波成分的波形,它是構(gòu)成復雜波形的基礎。在正弦波中,基波就是正弦波
    的頭像 發(fā)表于 04-08 17:11 ?5233次閱讀