天天看點

在Linux上,你做死鎖分析的簡單方法

簡介

死鎖 (deallocks): 是指兩個或兩個以上的程序(線程)在執行過程中,因争奪資源而造成的一種互相等待的現象,若無外力作用,它們都将無法推進下去。此時稱系統處于死鎖狀态或系統産生了死鎖,這些永遠在互相等待的程序(線程)稱為死鎖程序(線程)。 由于資源占用是互斥的,當某個程序提出申請資源後,使得有關程序(線程)在無外力協助下,永遠配置設定不到必需的資源而無法繼續運作,這就産生了一種特殊現象死鎖。

一種交叉持鎖死鎖的情形,此時執行程式中兩個或多個線程發生永久堵塞(等待),每個線程都在等待被其它線程占用并堵塞了的資源。例如,如果線程 1 鎖住了記錄 a 并等待記錄 b,而線程 2 鎖住了記錄 b 并等待記錄 a,這樣兩個線程就發生了死鎖現象。在計算機系統中 , 如果系統的資源配置設定政策不當,更常見的可能是程式員寫的程式有錯誤等,則會導緻程序因競争資源不當而産生死鎖的現象。

産生死鎖的四個必要條件

(1) 互斥條件:一個資源每次隻能被一個程序(線程)使用。

(2) 請求與保持條件:一個程序(線程)因請求資源而阻塞時,對已獲得的資源保持不放。

(3) 不剝奪條件 : 此程序(線程)已獲得的資源,在末使用完之前,不能強行剝奪。

(4) 循環等待條件 : 多個程序(線程)之間形成一種頭尾相接的循環等待資源關系。

圖 1. 交叉持鎖的死鎖示意圖:

在Linux上,你做死鎖分析的簡單方法

注釋:在執行 func2 和 func4 之後,子線程 1 獲得了鎖 a,正試圖獲得鎖 b,但是子線程 2 此時獲得了鎖 b,正試圖獲得鎖 a,是以子線程 1 和子線程 2 将沒有辦法得到鎖 a 和鎖 b,因為它們各自被對方占有,永遠不會釋放,是以發生了死鎖的現象。

使用 pstack 和 gdb 工具對死鎖程式進行分析

pstack 在 linux 平台上的簡單介紹

pstack 是 linux(比如 red hat linux 系統、ubuntu linux 系統等)下一個很有用的工具,它的功能是列印輸出此程序的堆棧資訊。可以輸出所有線程的調用關系棧。

gdb 在 linux 平台上的簡單介紹

gdb 是 gnu 開源組織釋出的一個強大的 unix 下的程式調試工具。linux 系統中包含了 gnu 調試程式 gdb,它是一個用來調試 c 和 c++ 程式的調試器。可以使程式開發者在程式運作時觀察程式的内部結構和記憶體的使用情況 .

gdb 所提供的一些主要功能如下所示:

1 運作程式,設定能影響程式運作的參數和環境 ;

2 控制程式在指定的條件下停止運作;

3 當程式停止時,可以檢查程式的狀态;

4 當程式 crash 時,可以檢查 core 檔案;

5 可以修改程式的錯誤,并重新運作程式;

6 可以動态監視程式中變量的值;

7 可以單步執行代碼,觀察程式的運作狀态。

gdb 程式調試的對象是可執行檔案或者程序,而不是程式的源代碼檔案。然而,并不是所有的可執行檔案都可以用 gdb 調試。如果要讓産生的可執行檔案可以用來調試,需在執行 g++(gcc)指令編譯程式時,加上 -g 參數,指定程式在編譯時包含調試資訊。調試資訊包含程式裡的每個變量的類型和在可執行檔案裡的位址映射以及源代碼的行号。gdb 利用這些資訊使源代碼和機器碼相關聯。gdb 的基本指令較多,不做詳細介紹,大家如果需要進一步了解,請參見 gdb 手冊。

清單 1. 測試程式

#include <unistd.h>  

#include <pthread.h>  

#include <string.h>  

pthread_mutex_t mutex1 = pthread_mutex_initializer;  

pthread_mutex_t mutex2 = pthread_mutex_initializer;  

pthread_mutex_t mutex3 = pthread_mutex_initializer;  

pthread_mutex_t mutex4 = pthread_mutex_initializer;  

static int sequence1 = 0;  

static int sequence2 = 0;  

int func1()  

{  

   pthread_mutex_lock(&mutex1);  

   ++sequence1;  

   sleep(1);  

   pthread_mutex_lock(&mutex2);  

   ++sequence2;  

   pthread_mutex_unlock(&mutex2);  

   pthread_mutex_unlock(&mutex1);  

   return sequence1;  

}  

int func2()  

   return sequence2;  

void* thread1(void* arg)  

   while (1)  

   {  

       int iretvalue = func1();  

       if (iretvalue == 100000)  

       {  

           pthread_exit(null);  

       }  

   }  

void* thread2(void* arg)  

       int iretvalue = func2();  

void* thread3(void* arg)  

       sleep(1);  

       char szbuf[128];  

       memset(szbuf, 0, sizeof(szbuf));  

       strcpy(szbuf, "thread3");  

void* thread4(void* arg)  

int main()  

   pthread_t tid[4];  

   if (pthread_create(&tid[0], null, &thread1, null) != 0)  

       _exit(1);  

   if (pthread_create(&tid[1], null, &thread2, null) != 0)  

   if (pthread_create(&tid[2], null, &thread3, null) != 0)  

   if (pthread_create(&tid[3], null, &thread4, null) != 0)  

   sleep(5);  

   //pthread_cancel(tid[0]);  

   pthread_join(tid[0], null);  

   pthread_join(tid[1], null);  

   pthread_join(tid[2], null);  

   pthread_join(tid[3], null);  

   pthread_mutex_destroy(&mutex1);  

   pthread_mutex_destroy(&mutex2);  

   pthread_mutex_destroy(&mutex3);  

   pthread_mutex_destroy(&mutex4);  

   return 0;  

清單 2. 編譯測試程式

[dyu@xilinuxbldsrv purify]$ g++ -g lock.cpp -o lock -lpthread 

清單 3. 查找測試程式的程序号

[dyu@xilinuxbldsrv purify]$ ps -ef|grep lock  

 dyu       6721  5751  0 15:21 pts/3    00:00:00 ./lock  

清單 4. 對死鎖程序第一次執行 pstack(pstack –程序号)的輸出結果

[dyu@xilinuxbldsrv purify]$ pstack 6721  

 thread 5 (thread 0x41e37940 (lwp 6722)):  

 #0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0  

 #1  0x0000003d1a808e1a in _l_lock_1034 () from /lib64/libpthread.so.0  

 #2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0  

 #3  0x0000000000400a9b in func1() ()  

 #4  0x0000000000400ad7 in thread1(void*) ()  

 #5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  

 #6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  

 thread 4 (thread 0x42838940 (lwp 6723)):  

 #3  0x0000000000400a17 in func2() ()  

 #4  0x0000000000400a53 in thread2(void*) ()  

 thread 3 (thread 0x43239940 (lwp 6724)):  

 #0  0x0000003d19c9a541 in nanosleep () from /lib64/libc.so.6  

 #1  0x0000003d19c9a364 in sleep () from /lib64/libc.so.6  

 #2  0x00000000004009bc in thread3(void*) ()  

 #3  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  

 #4  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  

 thread 2 (thread 0x43c3a940 (lwp 6725)):  

 #2  0x0000000000400976 in thread4(void*) ()  

 thread 1 (thread 0x2b984ecabd90 (lwp 6721)):  

 #0  0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0  

 #1  0x0000000000400900 in main ()  

清單 5. 對死鎖程序第二次執行 pstack(pstack –程序号)的輸出結果

thread 5 (thread 0x40bd6940 (lwp 6722)):  

#0  0x0000003d1a80d4c4 in __lll_lock_wait () from /lib64/libpthread.so.0  

#1  0x0000003d1a808e1a in _l_lock_1034 () from /lib64/libpthread.so.0  

#2  0x0000003d1a808cdc in pthread_mutex_lock () from /lib64/libpthread.so.0  

#3  0x0000000000400a87 in func1() ()  

#4  0x0000000000400ac3 in thread1(void*) ()  

#5  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  

#6  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  

thread 4 (thread 0x415d7940 (lwp 6723)):  

#3  0x0000000000400a03 in func2() ()  

#4  0x0000000000400a3f in thread2(void*) ()  

thread 3 (thread 0x41fd8940 (lwp 6724)):  

#0  0x0000003d19c7aec2 in memset () from /lib64/libc.so.6  

#1  0x00000000004009be in thread3(void*) ()  

#2  0x0000003d1a80673d in start_thread () from /lib64/libpthread.so.0  

#3  0x0000003d19cd40cd in clone () from /lib64/libc.so.6  

thread 2 (thread 0x429d9940 (lwp 6725)):  

#0  0x0000003d19c7ae0d in memset () from /lib64/libc.so.6  

#1  0x0000000000400982 in thread4(void*) ()  

thread 1 (thread 0x2af906fd9d90 (lwp 6721)):  

#0  0x0000003d1a807b35 in pthread_join () from /lib64/libpthread.so.0  

#1  0x0000000000400900 in main ()  

連續多次檢視這個程序的函數調用關系堆棧進行分析:當程序吊死時,多次使用 pstack 檢視程序的函數調用堆棧,死鎖線程将一直處于等鎖的狀态,對比多次的函數調用堆棧輸出結果,确定哪兩個線程(或者幾個線程)一直沒有變化且一直處于等鎖的狀态(可能存在兩個線程 一直沒有變化)。

輸出分析:

根據上面的輸出對比可以發現,線程 1 和線程 2 由第一次 pstack 輸出的處在 sleep 函數變化為第二次 pstack 輸出的處在 memset 函數。但是線程 4 和線程 5 一直處在等鎖狀态(pthread_mutex_lock),在連續兩次的 pstack 資訊輸出中沒有變化,是以我們可以推測線程 4 和線程 5 發生了死鎖。

gdb into thread輸出:

清單 6. 然後通過 gdb attach 到死鎖程序

(gdb) info thread  

  5 thread 0x41e37940 (lwp 6722)  0x0000003d1a80d4c4 in __lll_lock_wait ()  

  from /lib64/libpthread.so.0  

  4 thread 0x42838940 (lwp 6723)  0x0000003d1a80d4c4 in __lll_lock_wait ()  

  3 thread 0x43239940 (lwp 6724)  0x0000003d19c9a541 in nanosleep ()  

 from /lib64/libc.so.6  

  2 thread 0x43c3a940 (lwp 6725)  0x0000003d19c9a541 in nanosleep ()  

 * 1 thread 0x2b984ecabd90 (lwp 6721)  0x0000003d1a807b35 in pthread_join ()  

 from /lib64/libpthread.so.0  

清單 7. 切換到線程 5 的輸出

(gdb) thread 5  

 [switching to thread 5 (thread 0x41e37940 (lwp 6722))]#0  0x0000003d1a80d4c4 in  

 __lll_lock_wait () from /lib64/libpthread.so.0  

 (gdb) where  

 #3  0x0000000000400a9b in func1 () at lock.cpp:18  

 #4  0x0000000000400ad7 in thread1 (arg=0x0) at lock.cpp:43  

清單 8. 線程 4 和線程 5 的輸出

(gdb) f 3  

 18          pthread_mutex_lock(&mutex2);  

 (gdb) thread 4  

 [switching to thread 4 (thread 0x42838940 (lwp 6723))]#0  0x0000003d1a80d4c4 in  

 (gdb) f 3  

 #3  0x0000000000400a17 in func2 () at lock.cpp:31  

 31          pthread_mutex_lock(&mutex1);  

 (gdb) p mutex1  

 $1 = {__data = {__lock = 2, __count = 0, __owner = 6722, __nusers = 1, __kind = 0,  

 __spins = 0, __list = {__prev = 0x0, __next = 0x0}},  

  __size = "\002\000\000\000\000\000\000\000b\032\000\000\001", '\000' 

 <repeats 26 times>, __align = 2}  

 (gdb) p mutex3  

 $2 = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0,  

 __kind = 0, __spins = 0, __list = {__prev = 0x0, __next = 0x0}},  

 __size = '\000' <repeats 39 times>, __align = 0}  

 (gdb) p mutex2  

 $3 = {__data = {__lock = 2, __count = 0, __owner = 6723, __nusers = 1,  

  __size = "\002\000\000\000\000\000\000\000c\032\000\000\001", '\000' 

 (gdb)  

從上面可以發現,線程 4 正試圖獲得鎖 mutex1,但是鎖 mutex1 已經被 lwp 為 6722 的線程得到(__owner = 6722),線程 5 正試圖獲得鎖 mutex2,但是鎖 mutex2 已經被 lwp 為 6723 的 得到(__owner = 6723),從 pstack 的輸出可以發現,lwp 6722 與線程 5 是對應的,lwp 6723 與線程 4 是對應的。是以我們可以得出, 線程 4 和線程 5 發生了交叉持鎖的死鎖現象。檢視線程的源代碼發現,線程 4 和線程 5 同時使用 mutex1 和 mutex2,且申請順序不合理。

總結

本文簡單介紹了一種在 linux 平台下分析死鎖問題的方法,對一些死鎖問題的分析有一定作用。希望對大家有幫助。了解了死鎖的原因,尤其是産生死鎖的四個必要條件,就可以最大可能地避免、預防和解除死鎖。是以,在系統設計、程序排程等方面注意如何不讓這四個必要條件成立,如何确定資源的合理配置設定算法,避免程序永久占據系統資源。此外,也要防止程序在處于等待狀态的情況下占用資源 , 在系統運作過程中,對程序發出的每一個系統能夠滿足的資源申請進行動态檢查,并根據檢查結果決定是否配置設定資源,若配置設定後系統可能發生死鎖,則不予配置設定,否則予以配置設定。是以,對資源的配置設定要給予合理的規劃,使用有序資源配置設定法和銀行家算法等是避免死鎖的有效方法。

本文作者:佚名

來源:51cto