訊號處理函式陷阱:呼叫malloc導致死鎖
關於訊號處理signal()、sigaction()等的使用,相信很多人都已熟悉。 這裡主要想講一下訊號處理函式使用上的一個常見陷阱:訊號處理函式必須是可重入函式。如果訊號處理函式不可重入,那麼可能導致很多詭異問題。
《UNIX環境高階程式設計》“可重入函式”章節中這樣寫道:
“但在訊號處理程式中,不能判斷捕捉到訊號時程序在何處執行。如果程序正在執行malloc,在其堆中分配另外的儲存空間,而此時由於捕捉到訊號而插入執行該訊號處理程式,其中又呼叫malloc,這時會發生什麼?”
關於“可重入函式”相信其概念並不難理解,但真正使用訊號時,很多人都忽略了這一點,特別是一些比較隱晦的“不可重入函式”。本人在專案中就曾兩次遇到訊號處理函式中呼叫不可重入函式導致的死鎖:某專案執行一段時間後,程序基本停止響應各種外界命令,日誌也基本停止列印(只有個別簡單輪詢執行緒定時大義些資訊),但ps命令看到程序還在執行。看到這個問題,第一反應就是程序死鎖,gdb attach到程序上,檢視各個執行緒的堆疊,果然, 很多執行緒都卡在malloc呼叫上:
- Thread 152 (Thread 0x7f020abf5700 (LWP 7801)):
-
#0 0x00000032120f6dde in __lll_lock_wait_private () from /lib64/libc.so.6
- #1 0x000000321207c59b in _L_lock_9495 () from /lib64/libc.so.6
-
#2 0x0000003212079b86 in malloc () from /lib64/libc.so.6
- #3 0x00000030142bd09d in operator new(unsigned long) () from /usr/lib64/libstdc++.so.6
- #4 0x00000000005b9092 in __gnu_cxx::new_allocator<std::_List_node<Memory::TSmartObjectPtr<CPacketBase> > >::allocate(unsigned long, void const*) ()
- #5 0x00000000005b8f10 in std::_List_base<Memory::TSmartObjectPtr<CPacketBase>, std::allocator<Memory::TSmartObjectPtr<CPacketBase> > >::_M_g
- #6 0x00000000005b8cff in std::list<Memory::TSmartObjectPtr<CPacketBase>, std::allocator<Memory::TSmartObjectPtr<CPacketBase> > >::_M_create_
- #7 0x00000000005b889b in std::list<Memory::TSmartObjectPtr<CPacketBase>, std::allocator<Memory::TSmartObjectPtr<CPacketBase> > >::_M_insert(
- #8 0x00000000005b8020 in std::list<Memory::TSmartObjectPtr<CPacketBase>, std::allocator<Memory::TSmartObjectPtr<CPacketBase> > >::push_back(
- #9 0x00000000006194cd in CProtoParser::parser(char*, unsigned int) ()
- #10 0x0000000000618fb5 in CProtoParser::putDataLen(unsigned int) ()
- #11 0x00000000006666be in CConnection::handle_input(int) ()
- #12 0x000000000069595a in NetFramework::CNetThread::handle_netevent(NetFramework::list_node*) ()
- #13 0x0000000000695bbf in NetFramework::CNetThread::ThreadProc(Infra::CThreadLite&) ()
- #14 0x00000000006a3f38 in (anonymous namespace)::InternalThreadBody(void*) ()
- #15 0x0000003212407851 in start_thread () from /lib64/libpthread.so.0
- #16 0x00000032120e767d in clone () from /lib64/libc.so.6
- ------------------------------------------------------------------------------------------------------------------------
- Thread 19 (Thread 0x7f01019ec700 (LWP 7939)):
- #0 0x00000032120f6dde in __lll_lock_wait_private () from /lib64/libc.so.6
- #1 0x000000321207bede in _L_lock_44 () from /lib64/libc.so.6
- #2 0x0000003212074d4c in ptmalloc_lock_all () from /lib64/libc.so.6
- #3 0x00000032120ab9a5 in fork () from /lib64/libc.so.6
- #4 0x0000003212067c07 in [email protected]@GLIBC_2.2.5 () from /lib64/libc.so.6
- #5 0x0000003212067ef9 in [email protected]@GLIBC_2.2.5 () from /lib64/libc.so.6
- #6 0x0000000000548746 in os::shell(std::basic_ostream<char, std::char_traits<char> >*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
- #7 0x00000000005f5a5c in CDiskPerfCollector::IsCollectorRunning() ()
- #8 0x00000000005f5766 in CDiskPerfCollector::threadProc() ()
- #9 0x00000000006a3f38 in (anonymous namespace)::InternalThreadBody(void*) ()
- #10 0x0000003212407851 in start_thread () from /lib64/libpthread.so.0
- #11 0x00000032120e767d in clone () from /lib64/libc.so.6
看到這裡,有些人可能認為是glibc malloc 出什麼bug了。其實不然,仔細分析,就會發現其中蹊蹺:有一個執行緒,在執行malloc的過程中,跳轉到了訊號處理函式中。而訊號處理函式在呼叫某個系統api時,內部又呼叫了malloc。 看了glibc原始碼就會知道,malloc內部也是有鎖、而且是非巢狀的,如果在上一次呼叫中拿到鎖,又跳轉到訊號處理函式中再次malloc,自然就導致死鎖了。而且即使沒有死鎖,也極有可能破壞malloc內部維護的一些全域性資訊,導致後面莫名其妙的崩潰。
- Thread 63 (Thread 0x7f010b3f9700 (LWP 7890)):
- #0 0x00000032120f6dde in __lll_lock_wait_private () from /lib64/libc.so.6
- #1 0x000000321207c59b in _L_lock_9495 () from /lib64/libc.so.6
- #2 0x0000003212079b86 in malloc () from /lib64/libc.so.6
- #3 0x000000321180cb8d in _dl_map_object_deps () from /lib64/ld-linux-x86-64.so.2
- #4 0x0000003211812a11 in dl_open_worker () from /lib64/ld-linux-x86-64.so.2
- #5 0x000000321180e196 in _dl_catch_error () from /lib64/ld-linux-x86-64.so.2
- #6 0x000000321181246a in _dl_open () from /lib64/ld-linux-x86-64.so.2
- #7 0x00000032121250a0 in do_dlopen () from /lib64/libc.so.6
- #8 0x000000321180e196 in _dl_catch_error () from /lib64/ld-linux-x86-64.so.2
- #9 0x00000032121251f7 in __libc_dlopen_mode () from /lib64/libc.so.6
- #10 0x00000032120fd5f5 in init () from /lib64/libc.so.6
- #11 0x000000321240cb23 in pthread_once () from /lib64/libpthread.so.0
- #12 0x00000032120fd6f4 in backtrace () from /lib64/libc.so.6
- #13 0x0000000000614363 in printStackTrace() ()
- #14 0x000000000061497a in interruptTrigger(int, siginfo*, void*) ()
- #15 <signal handler called>
- #16 0x0000003212078c33 in _int_malloc () from /lib64/libc.so.6
- #17 0x0000003212079b91 in malloc () from /lib64/libc.so.6
- #18 0x00000030142bd09d in operator new(unsigned long) () from /usr/lib64/libstdc++.so.6
- #19 0x00000000006982ce in NetFramework::CSockAddrStorage::CSockAddrStorage() ()
- #20 0x000000000066639d in CConnection::attach(NetFramework::CSockStream&) ()
- #21 0x0000000000620be0 in CServiceBase::init(NetFramework::CSockStream&) ()
- #22 0x00000000005c1193 in CSession::init(NetFramework::CSockStream&) ()
- #23 0x00000000005705a8 in CDNServer::accept(NetFramework::CSockStream&) ()
- #24 0x000000000061f3e4 in CServer::Internal::handle_input(int) ()
- #25 0x000000000069595a in NetFramework::CNetThread::handle_netevent(NetFramework::list_node*) ()
- #26 0x0000000000695bbf in NetFramework::CNetThread::ThreadProc(Infra::CThreadLite&) ()
- #27 0x00000000006a3f38 in (anonymous namespace)::InternalThreadBody(void*) ()
- #28 0x0000003212407851 in start_thread () from /lib64/libpthread.so.0
- #29 0x00000032120e767d in clone () from /lib64/libc.so.6
由於LWP 7890 執行緒處理訊號時兩次進入malloc死鎖,導致很多其他執行緒在執行到malloc時卡主。而這些執行緒本身可能還持有一些業務上的鎖,導致死鎖迅速擴散,最終整個程序幾乎都卡主了。
而且需要指出的是,有時候我們對malloc的呼叫可能比較隱晦,比如為std::string 等賦值,列印日誌等,所以一不留神就容易栽進坑裡。文中鎖涉及的程式碼,更是我們專案組一些比較資深的骨幹同事寫的,其初衷是想在收到一些特殊訊號時通過backtrace等函式將當前執行緒的堆疊列印到日誌,方便定位問題。殊不知就是這個看似高明的處理,引發了更加複雜的問題。 由此可見,對於訊號處理函式“必須保證可重入”這一點,在實際編碼中必須慎之又慎,時刻謹記。
一般來說,訊號處理函式中要做的事情應該儘量簡單。通常可以置一個標識,由其他執行緒檢測到這個標識後再做相應處理,而不是直接在訊號處理函式中做這些事情。
相關推薦
訊號處理函式陷阱:呼叫malloc導致死鎖
關於訊號處理signal()、sigaction()等的使用,相信很多人都已熟悉。 這裡主要想講一下訊號處理函式使用上的一個常見陷阱:訊號處理函式必須是可重入函式。如果訊號處理函式不可重入,那麼可能導致很多詭異問題。 《UNIX環境高階程式設計》
Linux 訊號 向訊號處理函式傳遞資料
1.Linux 訊號是一種非同步機制,程序可以接收一個訊號,並有相應的處理操作,如果我們需要改變當該訊號發生時的預設行為,我們就需要捕捉該訊號,並且自己書寫訊號處理函式。 2.這種訊號處理函式就跟中斷差不多,當一個程序接收到一個訊號時,程序會暫停當前的執行流,轉而呼叫訊號處理函式,訊號處理函
訊號處理函式編寫規則
https://www.ibm.com/developerworks/cn/linux/l-cn-signalsec/ 關於編寫安全的訊號處理函式主要有以下一些規則: 訊號處理函式儘量只執行簡單的操作,譬如只是設定一個外部變數,其它複雜的操作留在訊號處理函式之外執行; errno 是執
Linux訊號 二 訊號處理函式註冊
每一個訊號都有一個訊號處理函式,可以是SIG_IGN, SIG_DFL或者是使用者自定義的處理函式。使用使用者自定義的處理函式需要註冊,註冊介面有如下兩種。 第一種是signal呼叫 #include <signal.h> /** * sighandle
關於執行緒與訊號處理函式獲得同一把互斥鎖的問題 (原)
轉發請註明出處:http://www.cppblog.com/mysileng/admin/EditPosts.aspx?postid=196971 剛寫了程式發現點問題。假設一個程式有多個執行緒,有一個全域性互斥鎖M....在某執行緒A獲得鎖以後,這個時候來了一個訊號(假設這個訊號註冊了自己的處理程式
訊號處理函式編碼原則備忘
很多伺服器都會涉及suspend, resume等操作,這個時候都要用到訊號: $kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6)
Linux訊號機制分析和訊號處理函式
【摘要】本文分析了Linux核心對於訊號的實現機制和應用層的相關處理。首先介紹了軟中斷訊號的本質及訊號的兩種不同分類方法尤其是不可靠訊號的原理。接著分析了核心對於訊號的處理流程包括訊號的觸發/註冊/執行及登出等。最後介紹了應用層的相關處理,主要包括訊號處理函式的安裝、訊號
Linux 多執行緒應用中如何編寫安全的訊號處理函式
關於程式碼的可重入性,設計開發人員一般只考慮到執行緒安全,非同步訊號處理函式的安全卻往往被忽略。本文首先介紹如何編寫安全的非同步訊號處理函式;然後舉例說明在多執行緒應用中如何構建模型讓非同步訊號在指定的執行緒中以同步的方式處理。 Linux 多執行緒應用中編寫安全的訊號處
linux程序訊號處理函式signal和sigaction
Linux中signal函式說明: NAME signal - ANSI C signal handling SYNOPSIS #include <signal.h> typedef void (*sighandler_
訊號程式設計之訊號傳送及訊號處理函式遇到不可重入函式
kill函式 函式原型: Int kill(pid_t pid, int siq) 功能:既可以向自身傳送訊號,也可以向其他程序傳送訊號; 引數: pid>0 將訊號sig發給pid程序 pid=0 將訊號sig發給同組程序 pid=-1 將訊號si
[譯]async/await中使用阻塞式程式碼導致死鎖
這篇博文主要是講解在async/await中使用阻塞式程式碼導致死鎖的問題,以及如何避免出現這種死鎖。內容主要是從作者Stephen Cleary的兩篇博文中翻譯過來. 原文1:Don'tBlock on Async Code 原文2:why the AspNet
golang 中 鎖的錯誤的用法會導致死鎖。
package main import ( "sync" "time" . "github.com/soekchl/myUtils" ) var mux sync.RWMutex func tt() { Notice() mux.Lock() // 3
mysql先刪除後插入導致死鎖
cti 插入語 adl err values 並不是 trying error 問題 所報的錯誤為:pymysql.err.OperationalError: (1213, ‘Deadlock found when trying to get lock; try resta
【MySQL】Merge Index導致死鎖
水稻:最近有個朋友生產環境出現MySQL死鎖問題,一聽是死鎖,那必須去看看啊,於是饒(si)有(qu)興(huo)致(lai)的研究了好幾天 菜瓜:MySQL死鎖,趕緊分享一下 水稻:能否先讓我裝完X,我從朋友那裡拿到資料結構,復現,分析,查資料,總。。。 菜瓜:今天的菜真香 水稻:。。。好吧,進入正題(資料
Qt 學習之路 2(19):事件的接受與忽略(當重寫事件回撥函式時,時刻注意是否需要通過呼叫父類的同名函式來確保原有實現仍能進行!有好幾個例子。為什麼要這麼做?而不是自己去手動呼叫這兩個函式呢?因為我們無法確認父類中的這個處理函式有沒有額外的操作)
版本: 2012-09-29 2013-04-23 更新有關accept()和ignore()函式的相關內容。 2013-12-02 增加有關accept()和ignore()函式的示例。 上一章我們介紹了有關事件的相關內容。我們曾經提到,事件可以依情況接受和忽略。現在,我們就
處理動態連結串列所需函式一:malloc
/* 每個結點都分為兩個域,一個是資料域,存放各種實際的資料。 另一個域為指標域,用來存放下一個結點的首地址。鏈中的每一個 結點都是同一種結構型別。 */ /* struct stu { //資料域
13.訊號捕捉函式:signal;sigaction
1.signal函式 sighandler_t signal(int signum, sighandler_t handler); __sighandler_t signal(int signo,sighandler handler) 返回值:返回前一次設定的handler 引數:
12.訊號集函式:sigemptyset;sigfillset;sigaddset;sigdelset;sigismember;sigpending
訊號處理的工作原理:阻塞訊號集/未決訊號集 未決訊號集: 沒有被當前程序處理的訊號集 阻塞訊號集: 將某個訊號放到阻塞訊號集,這個訊號就不會被程序處理 阻塞解除後,訊號被處理 -----------------------------------------------------
『PHP學習筆記』系列四:利用函式遞迴呼叫思想解決【斐波那契數列】問題和【猴子吃桃問題】問題
什麼是函式遞迴思想? 遞迴思想:把一個相對複雜的問題,轉化為一個與原問題相似的,且規模較小的問題來求解。 遞迴方法只需少量的程式就可描述出解題過程所需要的多次重複計算,大大地減少了程式的程式碼量。 但在帶來便捷的同時,也會有一些缺點,函式遞迴的執行效率不高(多次呼叫時)。
數字訊號處理專題(2)——利用FPGA進行基本運算及特殊函式定點運算
一、前言 FPGA以擅長高速並行資料處理而聞名,從有線/無線通訊到影象處理中各種DSP演算法,再到現今火爆的AI應用,都離不開卷積、濾波、變換等基本的數學運算。但由於FPGA的硬體結構和開發特性使得其對很多演算法不友好,之前本人零散地總結和轉載了些基本的數學運算在FPGA中的實現方式,今天做一個系統的總