本文簡(jiǎn)介:
內(nèi)核死鎖問(wèn)題一般是讀寫(xiě)鎖(rw_semaphore)和互斥鎖(mutex)引起的,本文主要講如何通過(guò)ramdump+crash工具來(lái)分析這類(lèi)死鎖問(wèn)題。
0、背景知識(shí)點(diǎn)
ramdump是內(nèi)存轉(zhuǎn)存機(jī)制,我們可以在某個(gè)時(shí)刻把系統(tǒng)的內(nèi)存轉(zhuǎn)存到一個(gè)文件中,然后與符號(hào)信息(vmlinux)一起導(dǎo)入到trace32或crash等內(nèi)存分析工具中做離線(xiàn)分析。是分析崩潰、死鎖、內(nèi)存泄露等內(nèi)核疑難問(wèn)題的重要調(diào)試手段。
crash是用于解析ramdump的開(kāi)源工具(http://people.redhat.com/anderson/),是命令行式的交互模式,提供諸多功能強(qiáng)大的調(diào)試命令,是分析定位內(nèi)核復(fù)雜問(wèn)題的利器。
死鎖是指兩個(gè)或兩個(gè)以上的執(zhí)行流在執(zhí)行過(guò)程中,由于競(jìng)爭(zhēng)鎖資源而造成的一種阻塞的現(xiàn)象。如圖:
1、問(wèn)題描述
在Android7.1系統(tǒng)中跑monkey時(shí)出現(xiàn)界面卡死現(xiàn)象:
1)沒(méi)有任何刷新,所有輸入事件無(wú)效,包括電源鍵
2)watchdog沒(méi)有重啟system_server
3)可以連adb,但ps等調(diào)試命令卡住
2、初步分析
由于無(wú)法直接用adb調(diào)試,用長(zhǎng)按電源鍵的方式進(jìn)入dump模式并導(dǎo)出ramdump文件,之后再用crash工具載入randump文件開(kāi)始離線(xiàn)分析。
一般卡死時(shí)可能是因?yàn)楹诵木€(xiàn)程處在UNINTERRUPTIBLE狀態(tài),所以先在crash環(huán)境下用ps命令查看手機(jī)中UNINTERRUPTIBLE狀態(tài)的線(xiàn)程,參數(shù)-u可過(guò)濾掉內(nèi)核線(xiàn)程:
bt命令可查看某個(gè)線(xiàn)程的調(diào)用棧,我們看一下上面UN狀態(tài)的最關(guān)鍵的watchdog線(xiàn)程:
從調(diào)用棧中可以看到proc_pid_cmdline_read()函數(shù)中被阻塞的,對(duì)應(yīng)的代碼為:
這里是要獲取被某個(gè)線(xiàn)程mm的mmap_sem鎖,而這個(gè)鎖又被另外一個(gè)線(xiàn)程持有。
3、推導(dǎo)讀寫(xiě)鎖
要想知道哪個(gè)線(xiàn)程持有了這把鎖,我們得先用匯編推導(dǎo)出這個(gè)鎖的具體值??捎胐is命令看一下proc_pid_cmdline_read()的匯編代碼:
0xffffff99a680aaa0處就是調(diào)用down_read()的地方,它的第一個(gè)參數(shù)x0就是sem鎖,如:
x0和x28寄存器存放的就是sem的值,那x21自然就是mm_struct的地址了,因?yàn)閙m_struct的mmap_sem成員的offset就是104(0x68),用whatis命令可以查看結(jié)構(gòu)體的聲明,如:
因此我們只需要知道x21或者x28就知道m(xù)m和mmap_sem鎖的值。
函數(shù)調(diào)用時(shí)被調(diào)用函數(shù)會(huì)在自己的棧幀中保存即將被修改到的寄存器,所以我們可以在down_read()及它之后的函數(shù)調(diào)用中找到這兩個(gè)寄存器:
也就是說(shuō)下面幾個(gè)函數(shù)中,只要找到用到x21或x28,必然會(huì)在它的棧幀中保存這些寄存器。
先從最底部的down_read()開(kāi)始找:
顯然它沒(méi)有用到x21或x28,繼續(xù)看rwsem_down_read_failed()的匯編代碼:
在這個(gè)函數(shù)中找到x21,它保存在rwsem_down_read_failed棧幀的偏移32字節(jié)的位置。
rwsem_down_read_failed()的sp是0xffffffd6d9e4bcb0
sp + 32 =0xffffffd6d9e4bcd0,用rd命令查看地址0xffffffd6d9e4bcd0中存放的x21的值為:
用struct命令查看這個(gè)mm_struct:
這里的owner是mm_struct所屬線(xiàn)程的task_struct:
sem鎖的地址為0xffffffd76e349a00+0x68= 0xffffffd76e349a68,因此:
分析到這里我們知道watchdog線(xiàn)程是在讀取1651線(xiàn)程的proc節(jié)點(diǎn)時(shí)被阻塞了,原因是這個(gè)進(jìn)程的mm,它的mmap_sem鎖被其他線(xiàn)程給拿住了,那到底是誰(shuí)持了這把鎖呢?
4、持讀寫(xiě)鎖的線(xiàn)程
帶著問(wèn)題我們繼續(xù)分析,首先通過(guò)list命令遍歷wait_list來(lái)看一下共有多少個(gè)線(xiàn)程在等待這個(gè)讀寫(xiě)鎖:
從上面的輸出可以看到一共有2個(gè)寫(xiě)者和有17個(gè)讀者在等待,這19個(gè)線(xiàn)程都處于UNINTERRUPTIBLE狀態(tài)。
再回顧一下當(dāng)前系統(tǒng)中所有UNINTERRUPTIBLE狀態(tài)的線(xiàn)程:
其中除標(biāo)注紅顏色的5個(gè)線(xiàn)程外的19個(gè)線(xiàn)程,都是上面提到的等待讀寫(xiě)鎖的線(xiàn)程。當(dāng)持鎖線(xiàn)程是寫(xiě)者,我們可以通過(guò)rw_semaphore結(jié)構(gòu)的owner找到持鎖線(xiàn)程??上н@里owner是0,這表示持鎖者是讀者線(xiàn)程,因此我們無(wú)法通過(guò)owner找到持鎖線(xiàn)程。這種情況下可以通過(guò)search命令加-t參數(shù)從系統(tǒng)中所有的線(xiàn)程的??臻g里查找當(dāng)前鎖:
一般鎖的值都會(huì)保存在寄存器中,而寄存器又會(huì)在子函數(shù)調(diào)用過(guò)程中保存在棧中。所以只要在棧空間中找到當(dāng)前鎖的值(0xffffffd76e349a68),那這個(gè)線(xiàn)程很可能就是持鎖或者等鎖線(xiàn)程
這里搜出的20個(gè)線(xiàn)程中19個(gè)就是前面提到的等鎖線(xiàn)程,剩下的1個(gè)很可能就是持鎖線(xiàn)程了:
查看這個(gè)線(xiàn)程的調(diào)用棧:
由于2124線(xiàn)程中存放鎖的地址是0xffffffd6d396b8b0,這個(gè)是在handle_mm_fault()的棧幀范圍內(nèi),因此可以推斷持鎖的函數(shù)應(yīng)該是在handle_mm_fault()之前。
我們先看一下do_page_fault函數(shù):
代碼中確實(shí)是存在持mmap_sem的地方,并且是讀者,因此可以確定是2124持有的讀寫(xiě)鎖阻塞了watchdog在內(nèi)的19個(gè)線(xiàn)程。
接下來(lái)我們需要看一下2124線(xiàn)程為什么會(huì)持鎖后遲遲不釋放就可以了,但在這之前我們先看一下system_server的幾個(gè)UNINTERRUPTIBLE狀態(tài)的線(xiàn)程阻塞的原因。
5、其他被阻塞的線(xiàn)程(互斥鎖的推導(dǎo))
先看一下ActivityManager線(xiàn)程:
通過(guò)調(diào)用棧能看到是在binder_alloc_new_buf時(shí)候被掛起的,我們得先找出這個(gè)鎖的地址。
首先從mutex_lock()函數(shù)入手:
從它的聲明中可以看到它的參數(shù)只有1個(gè),就是mutex結(jié)構(gòu)體指針。
再看看mutex_lock函數(shù)的實(shí)現(xiàn):
mutex_lock的第一個(gè)參數(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就是持有該所的線(xiàn)程的task_struct指針。它的pid為:
查看這個(gè)線(xiàn)程的調(diào)用棧:
這個(gè)3337線(xiàn)程就是前面提到的被讀寫(xiě)鎖鎖住的19個(gè)線(xiàn)程之一。
用同樣的方法可找到audioserver的1643線(xiàn)程、system_server的1909、2650線(xiàn)程也都是被這個(gè)3337線(xiàn)程持有的mutex鎖給阻塞的。
總結(jié)起來(lái)的話(huà):1)一共有4個(gè)線(xiàn)程在等待同一個(gè)mutex鎖,持鎖的是3337線(xiàn)程2)包括3337的19個(gè)線(xiàn)程等待著同一個(gè)讀寫(xiě)鎖,持鎖的是2124線(xiàn)程。
也就是說(shuō)大部分的線(xiàn)程都是直接或者間接地被2124線(xiàn)程給阻塞了。
6、死鎖
最后一個(gè)UNINTERRUPTIBLE狀態(tài)的線(xiàn)程就是2767(sdcard)線(xiàn)程:
可以看出2124線(xiàn)程是等待fuse的處理結(jié)果,而我們知道fuse的請(qǐng)求是sdcard來(lái)處理的。
這很容易聯(lián)想到2124的掛起可能跟2767(sdcard)線(xiàn)程有關(guān),但2124線(xiàn)程是在做read請(qǐng)求,而2767線(xiàn)程是在處理open請(qǐng)求時(shí)被掛起的。
就是說(shuō)sdcard線(xiàn)程并不是在處理2124線(xiàn)程的請(qǐng)求,不過(guò)即使這種情況下sdcard線(xiàn)程依然能阻塞2124線(xiàn)程。因?yàn)閷?duì)于一個(gè)APP進(jìn)程來(lái)說(shuō),只會(huì)有一個(gè)特定的sdcard線(xiàn)程服務(wù)于它,如果同一個(gè)進(jìn)程的多線(xiàn)程sdcard訪(fǎng)問(wèn)請(qǐng)求,sdcard線(xiàn)程會(huì)串行的進(jìn)行處理。
如果前一個(gè)請(qǐng)求得不到處理,那后來(lái)的請(qǐng)求都會(huì)被阻塞。跟之前mutex鎖的推導(dǎo)方法一樣,得2767線(xiàn)程等待的mutex鎖是0xffffffd6948f4090,
它的owner的task和pid為:
先通過(guò)bt命令查找2124的棧范圍為0xffffffd6d396b4b0~0xffffffd6d396be70:
從棧里面可以找到mutex:
mutex值在ffffffd6d396bc40這個(gè)地址上找到了,它是在__generic_file_write_iter的棧幀里。
那可以肯定是在__generic_file_write_iter之前就持鎖了,并且很可能是ext4_file_write_iter中,查看其源碼:
這下清楚了,原來(lái)2124在等待2767處理fuse請(qǐng)求,而2767又被2124線(xiàn)程持有的mutex鎖給鎖住了,也就是說(shuō)兩個(gè)線(xiàn)程互鎖了。
本文只限于介紹如何定位死鎖問(wèn)題,至于如何解決涉及到模塊的具體實(shí)現(xiàn),由于篇幅的關(guān)系這里就不再贅述了。
-
Linux
+關(guān)注
關(guān)注
87文章
11420瀏覽量
212361
原文標(biāo)題:樸英敏: 用crash工具分析Linux內(nèi)核死鎖的一次實(shí)戰(zhàn)
文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
Linux內(nèi)核開(kāi)發(fā)工具介紹

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

一文詳解Linux內(nèi)核源碼組織結(jié)構(gòu)
Linux內(nèi)核開(kāi)發(fā)工具介紹
Linux內(nèi)核源碼之我見(jiàn)——內(nèi)核源碼的分析方法
linux系統(tǒng)異常重啟,如何獲取最后一次啟動(dòng)日志并分析異常?
linux內(nèi)核啟動(dòng)內(nèi)核解壓過(guò)程分析
基于Linux 2.6內(nèi)核Makefile分析

關(guān)于Linux 2.6內(nèi)核Makefile的分析
你知道perf學(xué)習(xí)-linux自帶性能分析工具怎么用?
Linux內(nèi)核GPIO操作函數(shù)的詳解分析
Linux內(nèi)核死鎖lockdep功能

Linux內(nèi)核實(shí)際項(xiàng)目中的死鎖

評(píng)論