標籤:linux debugging 調試 core分析 dump分析 線程變數
本周遇到了好幾個core都很有典型性。在這裡和大家分享下。
相信有過Linux編程經驗的人,肯定都遇到過。感覺周圍人很多對core有天然的恐懼感,尤其對剛入行不久的同學來說。當然了,也有工作好幾年看到core也束手無策的。今天就分析一下,core,其實大部分都是很容易解決的。如果一個core很難以複現,那麼說明還是很複雜的,算是Corner case,可能需要很長時間,腦子裡要有很好的運行時狀態才可以(閱讀源碼,學習的是邏輯;將源碼對應到運行時的狀態,分析一些狀態機器的轉換,再去分析可能會發生的情況)。相信前幾篇文章會對這種Corner case的分析與解決打下比較好的基礎。
相反,那種每次必現,或者複現比率非常高的case,是非常容易解決的。
多線程必然出core?
如果是你新加入的代碼引入的core,實際上非常容易解決的,簡單的對比一下修改的diff,然後看一下是否有比較低級的錯誤。如果發現不了,看是否是多線程的問題?單線程如果沒有出core,改成多線程就出core,那麼就說明多線程競爭某些變數了:同時修改某些變數導致出問題。這個時候你可能第一反應會加鎖。我本人非常反感加鎖;即使你加鎖的粒度很小,範圍也夠小,但是只要是加鎖,就代表有阻塞,就代表維護起來會很麻煩。這些共用變數真的那麼值得加鎖嗎?可否換成局部變數?如果他是一塊動態記憶體,為了調用某個介面時不要頻繁申請釋放記憶體(比如這個介面每秒幾千次的調用),那麼初始化時候申請一塊記憶體是絕對合理的:請把它設定為線程變數吧。每個線程初始化時候申請這塊記憶體。
當然了你如果實現的是一個架構或者架構調用的介面,這個介面要做到安全執行緒的。那麼看起來你並不能控制這個線程什麼時候啟動;線程數目會是多少個,那麼就沒有辦法了嗎?
實際上,方法有很多,比如,你可以在一個map中維護一個“線程”變數的對應關係
__gnu_cxx::hash_map< pid_t, void *> thread_data_map;void * thread_buffer;std::map<pid_t, void *>::iterator it;lockit = thread_data_map.find(pid);if (it == thread_data_map.end()) { //init "thread data" thread_data_map[pid] = create_buffer();} else { thread_buffer = it->second;}unlock
這裡不得不使用了一個鎖。實際上由於線程數是有限的,因此這個效率還好。我本周實現了一個qps可以達到2000+的線上應用,基本上鎖的代價在整個的call stack中可以忽略不計。
當然了,比較好的架構可能會提供OnThreadInit這種介面,那麼在這邊申請線程變數吧:
int pthread_setspecific (pthread_key_t key, const void *value);
在實現邏輯的函數擷取該變數即可:
void *pthread_getspecific (pthread_key_t key);
什麼時候要使用線程變數?看多線程下是否對該變數有寫操作,如果有就要申請線程變數(或者加鎖),否則必然出core。
不要給自己埋下一個坑:
今天一個同學的core看起來是做了一個“最佳化”,節省了申請變數的時間。
void init() { my_struct * some_var; ... some_var->res = new some_res; some_var->res->set_value1(some_common_value1); some_var->res->set_value2(some_common_value2);}void * thread_func(my_struct * some_var) { some_var->res->set_value3(value_3); ...}
set_value3就是一個出core的原因。這個也是一個典型的多線程必然出core的case。實際上,res沒必要提前申請吧。把它改成一個局部變數,效能幾乎沒有的損耗。當然了,如果這個資源很大,那麼就當成線程變數吧。
那麼如何分析core?
實際上,上述的情境出現的core如果僅從core本身,可能一時不好排查問題出在哪裡;而且core的位置可能還不一樣,不可避免的出現替罪羊;比如調用第三方的模組,實際上是自己的全域變數導致到了第三方的調用棧時出core了。因此一定要排查自己的處理是否正確。
確定調用的介面是否安全執行緒;如果是你的同事,你得確定他說的是對的。就像今天又另外的一個core,調用者堅稱他寫的是安全執行緒的,彷彿不是安全執行緒的就像是代碼寫的不好似得;最後排查出他寫的代碼不是安全執行緒時,他問你什麼是安全執行緒的。
那麼回答一下,如何分析core?
首先瞭解清楚core出現的應用情境,比如長句子處理會有core;多執行緒會有core;特殊字元會有core;這個QA會給你一個比較清楚的說明。然後通過core的call stack去大概確定大概的源碼位置。
源碼面前,了無秘密。
你讀源碼,它展現的是邏輯;但是你腦中,要有個runtime的運行時調用棧,多線程的調度;天馬行空。
當然了,還是解決不了,開始從core得到更詳細的資訊吧!看看調用棧的參數是什麼,切換一個線程看看其他的幾個線程的frame是什麼。
還解決不了?
那麼它是Corner case,只要把應用在core後立即啟動就行了。記得EMC有個bug的選項是unable to root cause, 形容的很貼切。同時不要忘記自我安慰:碼的碼多了,肯定有bug,誰能保證服務100%呢?
Linux Debugging(八): core真的那麼難以追蹤嗎?