開源軟件已經普遍的被互聯網公司所應用,不只僅是由於其能給企業節省一大筆成本,並且最重要的是擁有更多的自主可控性,能從源頭上對軟件質量進行把控。另外一方面,因爲開源軟件背後每每沒有大型的商業公司,因此文檔相對來講不是很是完善(或者說文檔和代碼不必定相互對應),所以,做爲一名合格程序員,尤爲是基礎軟件開發的程序員,閱讀開源軟件源碼的能力是必備的素質。 html
MySQL做爲world most popular的開源數據庫,被廣大程序員所使用,其簡單、高效、易用等優勢被你們讚不絕口,做爲一款已經有20多年的開源數據庫,很多開源狂熱分子對其源碼進行了詳細的剖析,而後面對MySQL上百萬行的代碼,初學者每每無從下手。古語說的好,工欲善其事必先利其器,本文分享分享一些Linux下閱讀修改源碼經常使用工具的小技巧,筆者認爲這些小技巧對MySQL源碼(其實對其餘開源項目也同樣)分析以及後續的修改有莫大的幫助。 mysql
另外說明一下,這篇文章須要你對這些常見的工具備所瞭解,若是以前對vim/git/gdb/Ctags/Cscope/Taglist/gcc等沒有什麼瞭解,建議先上網找找基礎教程。 c++
衆所周知,MySQL數據庫採用插件式存儲引擎模式,即MySQL分Server層和plugin層,Server層主要作SQL語法的解析、優化、緩存,鏈接建立、認證以及Binlog複製等通用的功能,而plugin層纔是真正負責數據的存儲,讀取,奔潰恢復等操做。Server層定義一些接口,plugin層只要實現這些接口,那麼這個引擎就能在MySQL中使用,所以纔有了這麼多的引擎,例如InnoDB,TokuDB,MyRock等,但這個同時也表明着,引擎層的代碼和Server層的代碼風格會徹底不同,例如在Server層中,代碼縮進是2個空格而在InnoDB層中,代碼縮進是8個空格,當須要常常同時修改不一樣層的代碼時,容易形成格式混亂,從而影響閱讀。 git
Vim做爲一款Linux下經常使用的文本查看編輯工具,在源碼的閱讀中必屬主力。針對這個問題,經常使用的解決辦法是,在家目錄下,寫兩個不一樣的vimrc文件,一個對應Server層的風格,一個對應InnoDB層的風格,還須要編寫一個簡單的切換腳本,當須要修改Server層的代碼時,切換到Server層的風格,反之亦然。可是當須要同時修改Server和InnoDB多處代碼時候,會比較繁瑣,同時,在文件中切換,每每使用的是Ctags和Cscope,直接從Server層切換到InnoDB層的代碼了,根本沒有給你切換的機會(能夠直接在Vim中執行source命令,可是依然麻煩),若是Vim能根據不一樣的文件加載不一樣的格式那就方便多了。 程序員
在Vim的配置文件中有個內置的命令autocmd,後面能夠跟一些事件E,再後面能夠跟一些文件名F,最後放一些命令C,表示,當這些文件F觸發這些事件E後,執行這些命令C。在另一方面,MySQL的Server層代碼和InnoDB層代碼放在不一樣的目錄下,雖然有不少,可是能夠用通配符匹配。結合autocmd這個命令以及MySQL源碼分佈的規律,能夠寫出下面的vimrc配置文件: sql
" mysql server type au BufRead,BufNewFile /home/yuhui.wyh/polardb/sql/* source ~/.vimrc_server au BufRead,BufNewFile /home/yuhui.wyh/polardb/include/* source ~/.vimrc_server au BufRead,BufNewFile /home/yuhui.wyh/polardb/mysql-test/* source ~/.vimrc_server au BufRead,BufNewFile /home/yuhui.wyh/polardb/client/* source ~/.vimrc_server " mysql innodb type au BufRead,BufNewFile /home/yuhui.wyh/polardb/storage/innobase/* source ~/.vimrc_innodb
第一部分,這裏重點介紹一下BufRead和BufNewfile這兩個事件,前者表示當開始編輯新緩存區,讀入文件後。說的通俗易懂點就是,當你打開一個已經存在的文件後且這個文件內容都已經被加載完畢後,這個事件被觸發。後者,表示開始編輯不存在的文件,簡單的說,就是打開一個新的文件。 數據庫
第二部分,其中,au是autocmd的縮寫,/home/yuhui.wyh/polardb是筆者MySQL的根目錄,sql、include目錄下面放了大部分Server層的代碼,client目錄下是客戶端的代碼(好比mysqlbinlog, mysql等)也沿用了Server層的風格,同時團隊在testcase中也規定用Server層的代碼風格,所以也把它放在一塊。另一方面,InnoDB層的代碼就相對比較統一,都在storage/innobase下面。 vim
第三部分,就是source命令,這個命令表示加載並執行後面這個文件裏面的配置。vimrc_server和vimrc_innodb分別表示Server層和InnoDB層的不一樣格式,須要本身編寫。 數組
綜上所述,咱們能夠分析出這個vimrc配置文件所表達出的意思,這裏以最後一行爲例,其餘幾行相似。最後一行的意思就是,當打開/home/yuhui.wyh/polardb/storage/innodb/這個目錄下的全部文件或者在此目錄下建立一個新文件的時候,執行~/.vimrc_innodb這個配置文件。 緩存
至此,完美解決上述問題。
同時因爲這個方式是以緩存區爲粒度的,因此下述幾種使用方式都有效:
1. 當前文件A屬於Server層,使用Ctags跳轉到InnoDB層文件B,則文件B使用InnoDB風格,編輯或者閱讀後,若是使用Ctrl+T返回(或者其餘方式)A,則A依然使用Server層風格,不會被影響。
2. 多窗口支持,因爲緩存區獨立加載,即便同時打開多個終端中的多個vim,也不會相互影響。
3. 若是先打開Server層的文件A,而後使用:e命令打開另一個InnoDB層的文件B,而後使用:bn相互切換,格式依然不會亂掉,A永遠使用Server層風格,B永遠使用InnoDB風格。
4. 若是使用vim -O方式同時打開多個InnoDB和Server層文件,而後使用Ctrl+w在其之間切換,依然沒有什麼問題。
BufRead事件的威力就是如此牛X。
BTW,上面這圖只是個人配置文件的一部分,完整的文件以下:
" normal type au BufRead,BufNewFile * source ~/.vimrc_normal " mysql server type au BufRead,BufNewFile /home/yuhui.wyh/polardb/sql/* source ~/.vimrc_server au BufRead,BufNewFile /home/yuhui.wyh/polardb/include/* source ~/.vimrc_server au BufRead,BufNewFile /home/yuhui.wyh/polardb/mysql-test/* source ~/.vimrc_server au BufRead,BufNewFile /home/yuhui.wyh/polardb/client/* source ~/.vimrc_server " mysql innodb type au BufRead,BufNewFile /home/yuhui.wyh/polardb/storage/innobase/* source ~/.vimrc_innodb
" ic file color
au BufRead,BufNewFile /home/yuhui.wyh/polardb/polardb_src/storage/innobase/include/*.ic setfiletype c
source ~/.vimrc_base
倒數第二行的意思是,當遇到.ic結尾的文件時,把這個文件看成是C語言的文件來解析,這樣語法就會高亮啦~
這裏還有一點要說明的是,若是同時多個事件被觸發,則按照配置文件中出現的順序依次執行,因此如上圖所示,vimrc_normal放的是我本身經常使用的風格,畢竟不能被MySQL徹底同化麼~。而最後vimrc_base裏面放的是三種模式(normal,server,innodb)共有的配置,代碼複用麼,嘿嘿
Ctags和Cscope是頗有名的Linux命令行下閱讀代碼的神器,有Linux下的sourceinsight的美稱,網上已經有不少介紹,不熟悉的能夠先去網上找找。這裏分享一下筆者經常使用的配置,不一樣的配置可能致使搜索結果的不一樣。
[Sun Dec 11 17:45:10 ~] $ alias csfile alias csfile='find . -name "*.c" -o -name "*.cc" -o -name "*.cp" -o -name "*.cpp" -o -name "*.cxx" -o -name "*.h" -o -name "*.hh" -o -name "*.hp" -o -name "*.hpp" -o -name "*.hxx" -o -name "*.C" -o -name "*.H" -o -name "*.cs" -o -name "*.ic" -o -name "*.yy" -o -name "*.i" -o -name "errmsg-utf8.txt" > cscope.files' [Sun Dec 11 17:46:09 ~] $ alias cs alias cs='cscope -bqR -i cscope.files && ctags --extra=+q --fields=+aimSn --c-kinds=+l --c++-kinds=+l --totals --sort=foldcase -L cscope.files'
因爲源碼常常變更,所以我寫了一個alias方便重建tag數據庫。csfile其實就是生成源碼文件列表(並非MySQL源碼目錄下的全部文件都是源碼文件),這裏要注意把.ic和.i爲後綴名的文件也加進去,這種文件也是MySQL源碼文件,其餘的後綴名基本都是比較常規的。生成了源碼文件列表後就能夠用從scope和ctags生成對應的標籤了。這裏介紹一下我使用的參數:
cscope:
-b 創建tag數據庫文件,默認文件名爲cscope.out
-q 創建倒排索引加速檢索,會產生cscope.in.out和cscope.po.out兩個文件
-R 在目錄下遞歸搜索
-i 從指定文件中獲取源碼文件路徑,有了這個參數,不用上面這個參數也能夠
ctags:
--extra=+q 在tag中增長類的信息,這樣當一個tag有多處定義的時候,搜索時能夠幫助辨認
--field=+aimSn 主要也是在tag中增長一些信息(類的訪問權限,繼承關係,函數原型等),搜索時能夠根據這些額外信息把最有可能的定義排在前面
--c-kinds=+l 增長局部變量定義的索引,MySQL有一些函數很大,不方便查找,把這個開起來就方便多了
--total 產生tag文件後,輸出一些統計信息,例如,掃描了多少個源文件,多少行源代碼以及產生了多少個tags
--sort=foldcase 對產生tags數據庫使用大小寫不敏感的排序,便於後續檢索
-L cscope.files 從文件中獲取源代碼文件的路徑
這裏只是簡單的提一下,詳細能夠看幫助文檔。
接下來分享一下筆者經常使用的使用方法:
爲了方便跳轉,筆者在vimrc文件中加入了以下定義:
set cscopetagorder=1
這樣,當我搜索一個標籤(Ctrl+])的時候,先從ctags產生的標籤庫中搜索,而後再從cscope中搜索。
nmap <C-\>s :cs find s <C-R>=expand("<cword>")<CR><CR> nmap <C-\>g :cs find g <C-R>=expand("<cword>")<CR><CR> nmap <C-\>c :cs find c <C-R>=expand("<cword>")<CR><CR> nmap <C-\>t :cs find t <C-R>=expand("<cword>")<CR><CR> nmap <C-\>e :cs find e <C-R>=expand("<cword>")<CR><CR> nmap <C-\>f :cs find f <C-R>=expand("<cfile>")<CR><CR> nmap <C-\>i :cs find i ^<C-R>=expand("<cfile>")<CR>$<CR> nmap <C-\>d :cs find d <C-R>=expand("<cword>")<CR><CR>
同時在vimrc中加入上圖的定義,方便使用cscope的功能:
Ctrl+\+g:尋找定義處,筆者通常不多用了,通常用Ctrl+]代替。
Ctrl+\+s: 當你想查看一下這個標籤以C語言標準Symbol在哪些地方出現過期,能夠用這個,也就是說,搜索出的結果都是標準C語言Symbol的。
Ctrl+\+t: 這個是搜索出全部出現這個tag的位置,無論是否是C語言Symbol。
這裏介紹一個上面兩個命令的區別,通常來講,Ctrl+\+s這個命令搜索出的通常都在源代碼中且是全詞匹配的,而Ctrl+\+t這個命令可能搜索出注釋中的tag,也有多是半個詞匹配,可是Ctrl+\+t這個命令有實時性,即當你修改過文件後,若是不重建整個tags數據庫,用Ctrl+\+s搜索不到最新的標籤,而用Ctrl+\+t就能夠,固然Ctrl+\+t這個速度也會慢一點。換句話說,Ctrl+\+t是Ctrl+\+s的超集,若是你用Ctrl+\+s搜索不到,而後用Ctrl+\+t可能就能找到了,這種狀況在MySQL源碼中還比較常見,由於其用了不少宏定義來簡化代碼,這些宏定義有些不能被ctags正確的解析成C語言Symbol,因此只能用Ctrl+\+t才能搜索到,一個常見的例子就是InnoDB層線程函數基本都用相似DECLARE_THREAD()的形式來定義,只能用Ctrl+\+t來找,才能找到這個函數正確的定義處。
Ctrl+\+c:查找當前的標籤在哪些地方被引用過。筆者常常用這個功能,由於經常須要看當前這個函數在哪些地方被調用過。以下圖,能夠一眼看出recv_parse_log_recs這個函數被三個函數調用過(分別用《《和》》包括起來)。
在這例子中,若是你查看了編號爲1調用的地方,不用返回,能夠直接按下:tn(:tN表明反向)這個命令,而後會自動跳到編號爲2調用的地方,這樣能夠快速的在調用處查看。這個小技巧在cscope其餘命令中也支持。
Ctrl+\+d:查找這個函數中引用了哪些函數,用的相對較少一點。
Ctrl+\+f: 打開指定文件名的文件,須要在索引中。這個命令也仍是常常用的,例如,你當前在sql_parse.cc的Server層代碼中,須要查看一下ha_innodb.cc這個InnoDB層的文件,你能夠直接輸入:cs f f ha_innodb.cc,這樣文件能夠直接打開,而不須要你用:e或者其餘命令輸入完整的文件路徑,提升了很多效率。固然,你把光標停在一個include語句的頭文件上,也是能夠直接打開的。
Ctrl+\+e:使用了這個,你能夠在tag中指定通配符,這樣就支持模糊查詢了。
此外,當你沒打開任何一個文件的時候,忽然想查看一個tag(例如rds_update_malloc_size)的定義,你能夠直接在命令行輸入vim -t rds_update_malloc_size,注意要在tags數據庫所在的目錄,而後就會直接打開rds_update_malloc_size定義的文件並跳轉到定義處。這裏要求tag不能拼錯一點,也就是不支持模糊查詢,若是你想要模糊查詢的話,直接打開一個空的vim,而後輸入:tag rds_update,而後按Tab鍵,就能夠自動補全,若是補全的不是你想要的,接着按Tab直到找到你想要的。
最後,介紹一下TagList的小工具。這個工具就是把一個文件中的全部定義給抽取出來,顯示在一個分屏中,方便你查看。相似下圖:
它統計了變量,結構體,宏定義以及函數,打開後你能夠獲得這個文件的概覽,有些時候,你想查看一個函數,可是這個函數的名字又想不起來,你能夠打開這個,而後在函數列表裏面找,比你在文件中用]]命令一個個找快的多。經常使用命令:
回車:當你停留在某個標籤上,直接回車,便可跳轉到這個標籤的定義上,同時光標也會停留在定義所在的窗口上,若是你想接着查看TagList窗口,須要從新切換。
p: 同回車做用差很少,不一樣的就是,跳轉後光標依然停留在TagList窗口,你能夠接着查看其餘標籤,這個比較實用,通常如今TagList窗口中查找,找到後在敲回車,切換過去,同時能夠把TagList窗口關掉。
x: 若是你嫌TagList窗口過小,就能夠用這放大窗口
+,-,*,=:這些都是摺疊或者展開某一類或者所有的標籤
s:排序有兩種,一種是按照出現順序,一種是按照首字母排序,能夠用這個命令切換
此外,你能夠在vimrc中配置TagList相關配置,例如:
let Tlist_Exit_OnlyWindow = 1 let Tlist_Show_One_File = 1 let Tlist_Sort_Type = "name" let Tlist_Auto_Open = 1 let Tlist_Use_Right_Window = 1
其中,Tlist_Exit_OnlyWindow表示當只剩下TagList這個窗口時,退出vim。Tlist_Use_Right_Window表示TagList窗口顯示在vim右邊。當你打開多個文件的時候,若是不設置Tlist_Show_One_File爲1,就會把全部文件裏面的定義都輸出在TagList窗口中。Tlist_Auto_Open則表示TagList窗口是否默認打開。Tlist_Sort_Type表示默認按照首字符出現順序排序。
總之,在閱讀源碼的過程當中,要善於使用各類工具便於咱們快速找到咱們想要的東西,若是還有什麼使用技巧值得分享,能夠留言告訴筆者哈
有時候,當你在源碼中游走的時候,會被搞的暈頭轉向,不知道本身在哪裏了,這個時候你可使用Ctrl+G來查看本身在哪一個文件中,可是你還想知道本身在哪一個函數中呢?這個vim貌似沒有提供默認的快捷鍵,那麼咱們就本身造個輪子吧:
fun! ShowFuncName() let lnum = line(".") let col = col(".") echohl ModeMsg echo getline(search("^[^ \t#/]\\{2}.*[^:]\s*$", 'bW')) echohl None call search("\\%" . lnum . "l" . "\\%" . col . "c") endfun map f :call ShowFuncName() <CR>
這個showFuncName的函數跟快捷鍵f綁定起來了,你只需把這個函數放在vimrc中,而後在源碼中按下f,就能夠查看當前在哪一個函數中,可是有些時候會有問題,可能沒有找到正確的函數頭,這個時候,就只能用最原始的[[和]]命令來找函數頭了,而後使用Ctrl+O的方式返回以前停留的地方。
MySQL Server層的代碼對單行的註釋有點小要求:若是這行有代碼也有註釋,必須從第48列開始寫註釋。這個時候若是你用手調整到48列,會很麻煩,依然能夠寫一個函數,而後綁定一個快捷鍵(Shift+Tab):
function InsertShiftTabWrapper() let num_spaces = 48 - virtcol('.') let line = ' ' while (num_spaces > 0) let line = line . ' ' let num_spaces = num_spaces - 1 endwhile return line endfunction " jump to 48th column by Shift-Tab - to place a comment there inoremap <S-tab> <c-r>=InsertShiftTabWrapper()<cr>
介於MySQL Server層和InnoDB層的格式很容易搞錯,你須要常常查看格式是否正確,這個時候你可能須要把全部隱藏的不可見的字符給顯示出來,命令你給是set list,一樣,若是你頻繁使用,還不如加個快捷鍵綁定:
map l :set list! <CR>
這樣你只要按下l就能夠在是否顯示不可見字符中切換。
咱們在寫代碼中,通常不但願有多餘的空格,尤爲在一行代碼的結束後,後面不該該有多餘的空格,可是空格又是不可見的字符,很難察覺到,除了用上述set list查看外,能夠用一下的命令,這個命令會查找多餘的空格,而後用紅色高亮出來,時刻提醒你。
highlight WhitespaceEOL ctermbg=red guibg=red
match WhitespaceEOL /\s\+$/
此外,這邊總結了一些經常使用好用的vim命令,在閱讀源碼中頗有用。
set number: 顯示代碼行數
set ignorecase: 忽略大小寫,這個在使用/搜索中頗有用
set hlsearch: 搜索結果高亮
set incsearch:當你在搜索時,每輸入一個字母就開始搜索一次,這樣當你要搜索一個很複雜的東西時候,只須要輸入部分,就能夠找到了。例如,你要搜InsertShiftTabWrapper這個函數,若是這個參數不打開,須要等你輸入完全部,而後按回車纔開始搜索,而打開這個參數,則每輸入一個字母,就搜索一次,你可能只須要輸入Insert這個單詞,vim可能就已經跳轉到InsertShiftTabWrapper這個函數了。
set showmatch: 當你輸入後半個括號時候,打開這個開關,前半個括號會閃一下,提示你當前輸入的括號是跟他匹配的。
set paste: 能夠進入複製模式,複製入的東西不會被重排。
批量註釋連續多行: 光標移到第一列,切換到列選擇模式Ctrl+v,而後選擇中全部須要註釋的行,而後按一下Shift+i,接着輸入//,最後按兩下Esc鍵便可。
*: 光標停留在一個tag上,而後按下這個,就能夠在文件中找到全部這個tag,而且高亮出來,能夠用n查看下一個,用N查看上一個。
%:停留在括號上,能夠用來查看另外半個括號,通常用來查看括號匹配。
Ctrl+F,Ctrl+B:整頁滾動
gd:查看局部變量定義
gD:查看全局變量定義,只能查看這個文件中的
[[: 跳轉到上面一個定義
]]: 跳轉到下面一個定義
用gdb記得加上-g以及關掉-O的優化,否則單步調試中,沒法跟源代碼對應,看不清楚。
gdb啓動參數中加上-q能夠把煩人的版本信息給去除掉。
gdb可使用—args啓動,而後程序的參數就能夠直接寫在後面,不須要進入gdb後再指定。
能夠在家目錄下創建.gdbinit文件,把經常使用配置寫進去,以下圖:
set print elements 0 set print array-indexes on set print pretty on set print object on set history filename ~/.gdb_history set history save on
set print elements 0: 若是你要打印一個數組,set print elements 5,表示最多隻打印5個元素,set print elements 0表示打印全部元素
set print array-indexes on: 打印數組的時候,同時把索引也打印出來
set print pretty on: 打開的時候,顯示結構體會比較漂亮,按照多行縮進的格式顯示,關閉的時候,只是在一行中打印整個結構
set print object on: 打開的時候,若是使用type命令查看變量類型,會考慮虛函數的影響,即打印真正的類型,不然只打印編譯時候肯定的父類型
set history save on: 打開歷史命令記錄功能,這樣當你再次進入gdb的時候,你可使用方向鍵查看以前使用過的命令了
使用-tui參數啓動gdb,或者啓動gdb後按Ctrl+x+a,能夠進入gdb的圖形化調試界面,上半部分爲源代碼窗口,下半部分爲命令行界面,再按一下這個組合鍵就能返回傳統的字符界面:
源碼界面,執行到的代碼行會高亮出來,斷點行前面會有個B+>標識。默認的焦點在代碼窗口,即方向鍵控制的是代碼的移動,可使用focus cmd將焦點切換到命令行窗口,方向鍵便可控制查看以前執行過的命令,不然須要使用Ctrl+p或者Ctrl+n。其餘命令跟命令行gdb相似。
另外,咱們經常會碰到MySQL hang住的狀況,雖然這個時候你用kill命令殺掉,而後重啓,能解決燃眉之急,不過爲了找到hang的緣由,最好的辦法是保留住內存現場,方便後面排查。一種方法是使用kill -11的方法,讓內核產生一個coredump,可是若是當時MySQL內存使用的比較多,須要產生一個很大的文件,這對磁盤寫入形成很大的衝擊。另一種方式是使用pstack產生一個全部線程的函數調用堆棧關係,相似gdb中的bt命令,以下圖:
Thread 4 (Thread 0x7ff8f05fa700 (LWP 15335)): #0 0x0000003330ce0263 in select () from /lib64/libc.so.6 #1 0x000000000116e8ca in os_thread_sleep(unsigned long) () #2 0x00000000010ef1dd in log_wait_for_more(unsigned long, bool, log_reader_t*) () #3 0x000000000113eff3 in log_reader_t::read_log_state(unsigned char*, unsigned int*) () #4 0x000000000113e920 in log_reader_t::acquire_data(unsigned char*, unsigned int*, unsigned int*) () #5 0x0000000001013532 in innobase_read_redo_log(void*&, unsigned long, unsigned char*, unsigned int*, unsigned int*) () #6 0x0000000000a2a0a3 in com_polar_dump(THD*, char*, unsigned int) () #7 0x00000000009df128 in dispatch_command(enum_server_command, THD*, char*, unsigned int) () #8 0x00000000009dab90 in do_command(THD*) () #9 0x000000000096ea42 in do_handle_one_connection(THD*) () #10 0x000000000096e117 in handle_one_connection () #11 0x00000000016c1a11 in pfs_spawn_thread () #12 0x00007ff8f56e8851 in start_thread () from /lib64/libpthread.so.0 #13 0x0000003330ce767d in clone () from /lib64/libc.so.6 Thread 3 (Thread 0x7ff8f0578700 (LWP 15400)): #0 0x0000003330cda37d in read () from /lib64/libc.so.6 #1 0x0000003330c711e8 in _IO_new_file_underflow () from /lib64/libc.so.6 #2 0x0000003330c72cee in _IO_default_uflow_internal () from /lib64/libc.so.6 #3 0x0000003330c674da in _IO_getline_info_internal () from /lib64/libc.so.6 #4 0x0000003330c66339 in fgets () from /lib64/libc.so.6 #5 0x0000000000fbc817 in rds_pstack () #6 0x0000000000875085 in handle_fatal_signal () #7 <signal handler called> #8 0x000000000107bae9 in i_s_innodb_log_reader_fill_table(THD*, TABLE_LIST*, Item*) () #9 0x0000000000aae83d in do_fill_table(THD*, TABLE_LIST*, st_join_table*) () #10 0x0000000000aaf023 in get_schema_tables_result(JOIN*, enum_schema_table_state) () #11 0x0000000000a56065 in JOIN::prepare_result(List<Item>**) () #12 0x00000000009834d3 in JOIN::exec() () #13 0x0000000000a578f0 in mysql_execute_select(THD*, st_select_lex*, bool) () #14 0x0000000000a57f95 in mysql_select(THD*, TABLE_LIST*, unsigned int, List<Item>&, Item*, SQL_I_List<st_order>*, SQL_I_List<st_order>*, Item*, unsigned long long, select_result*, st_select_lex_unit*, st_select_lex*) () #15 0x0000000000a53ffe in handle_select(THD*, select_result*, unsigned long) () #16 0x00000000009f67c9 in execute_sqlcom_select(THD*, TABLE_LIST*) () #17 0x00000000009e5366 in mysql_execute_command(THD*) () #18 0x00000000009fc078 in mysql_parse(THD*, char*, unsigned int, Parser_state*) () #19 0x00000000009dd917 in dispatch_command(enum_server_command, THD*, char*, unsigned int) () #20 0x00000000009dab90 in do_command(THD*) () #21 0x000000000096ea42 in do_handle_one_connection(THD*) () #22 0x000000000096e117 in handle_one_connection () #23 0x00000000016c1a11 in pfs_spawn_thread () #24 0x00007ff8f56e8851 in start_thread () from /lib64/libpthread.so.0 #25 0x0000003330ce767d in clone () from /lib64/libc.so.6
這裏僅僅截取了兩個線程的函數堆棧信息。經過這個能夠看出,程序在i_s_innodb_log_reader_fill_table這個函數處奔潰了,而後你須要去那個函數裏面看到底發生了什麼。後面這種方法因爲只須要產生一個很小的文本文件,線上出問題了常用這種方式。可是這裏仍是有點小不爽,奔潰的位置既然能定位到函數級別,那麼能不能直接定位到源碼中的行級別,這樣即便這個函數很大,後期診斷起來也方便多了。解決方法很簡單,只須要改一下pstack的源碼:
$GDB --quiet $readnever -nx /proc/$1/exe $1 <<EOF 2>&1 |
把這行中的$readnever去掉就好了。readnever這個參數的做用以下:
`--readnever' Do not read each symbol file's symbolic debug information. This makes startup faster but at the expense of not being able to perform symbolic debugging.
說白了就是啓動效率,可是我的感受得不償失,既然程序已經發生問題了,提供更加詳細的診斷信息纔是王道。去掉這個參數後,之後看到的pstack結果就是相似下圖了:
Thread 2 (Thread 0x7ffa4c106700 (LWP 44741)): #0 0x0000003330cda37d in read () from /lib64/libc.so.6 #1 0x0000003330c711e8 in _IO_new_file_underflow () from /lib64/libc.so.6 #2 0x0000003330c72cee in _IO_default_uflow_internal () from /lib64/libc.so.6 #3 0x0000003330c674da in _IO_getline_info_internal () from /lib64/libc.so.6 #4 0x0000003330c66339 in fgets () from /lib64/libc.so.6 #5 0x0000000000fbfe7f in rds_pstack () at /home/yuhui.wyh/polardb/mysys/stacktrace.c:758 #6 0x0000000000878605 in handle_fatal_signal (sig=11) at /home/yuhui.wyh/polardb/sql/signal_handler.cc:269 #7 <signal handler called> #8 0x00000000010134b2 in innobase_get_read_lsn (uuid=0x7ffa4c105d40, start_lsn=0x7ffa4c105d50, orig_start_lsn=0x7ffa4c105d38) at /home/yuhui.wyh/polardb/storage/innobase/handler/ha_innodb.cc:11035 #9 0x0000000000a2a43c in polar_io_thread (arg=0x0) at /home/yuhui.wyh/polardb/sql/sql_polar.cc:1827 #10 0x00000000016dd785 in pfs_spawn_thread (arg=0x4872800) at /home/yuhui.wyh/polardb/storage/perfschema/pfs.cc:1858 #11 0x00007ffa78d7c851 in start_thread () from /lib64/libpthread.so.0 #12 0x0000003330ce767d in clone () from /lib64/libc.so.6 Thread 1 (Thread 0x7ffa7979e720 (LWP 44707)): #0 0x00007ffa78d807bb in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0 #1 0x0000000000fc7afd in safe_cond_timedwait (cond=0x2bfd900, mp=0x2bfd780, abstime=0x7ffff07562e0, file=0x19a97b0 "/home/yuhui.wyh/polardb/include/mysql/psi/mysql_thread.h", line=1199) at /home/yuhui.wyh/polardb/mysys/thr_mutex.c:278 #2 0x0000000000fbad17 in inline_mysql_cond_timedwait (that=0x2bfd900, mutex=0x2bfd780, abstime=0x7ffff07562e0, src_file=0x19a97f0 "/home/yuhui.wyh/polardb/mysys/my_thr_init.c", src_line=240) at /home/yuhui.wyh/polardb/include/mysql/psi/mysql_thread.h:1199 #3 0x0000000000fbb994 in my_thread_global_end () at /home/yuhui.wyh/polardb/mysys/my_thr_init.c:239 #4 0x0000000000faf4c6 in my_end (infoflag=0) at /home/yuhui.wyh/polardb/mysys/my_init.c:205 #5 0x000000000067c836 in mysqld_exit (exit_code=1) at /home/yuhui.wyh/polardb/sql/mysqld.cc:1913 #6 0x000000000067c72e in unireg_abort (exit_code=1) at /home/yuhui.wyh/polardb/sql/mysqld.cc:1894 #7 0x0000000000689655 in init_server_components () at /home/yuhui.wyh/polardb/sql/mysqld.cc:5185 #8 0x000000000068bee1 in mysqld_main (argc=26, argv=0x410e6d8) at /home/yuhui.wyh/polardb/sql/mysqld.cc:5850 #9 0x00000000006741d2 in main (argc=9, argv=0x7ffff0756ba8) at /home/yuhui.wyh/polardb/sql/main.cc:25
能夠看到函數在哪一個文件中的哪一行了。在MySQL發生死鎖時,用這招進行診斷頗有效。固然,記住,在編譯MySQL的時候必定要帶上-g,否則仍是沒有這些調試信息的。
在平時的代碼開發中,須要加新的feature,或者fix bug以及optimize等操做時,通常都會從master上拉一個分支出來,而後本身在上面隨便折騰,這也就致使在同一分支上,會有屢次commit,最後在把這些commit都提交到主幹,會致使主幹上比較亂,這時候git reset命令就有用了:
git reset --soft HEAD^^: 把最近的兩次提交的變更合併,結果以提交到暫存區的形式存在,即git add以後的文件狀態,這個時候,你只須要再git commit一下,就能把屢次提交合並。
git reset --mixed HEAD^^: 跟上面的相似,只不過文件回退到未加入暫存區以前的狀態,也就是說,你還須要執行一把git add,而後才能執行git commit。
git reset --hard HEAD^^: 這個操做直接把最近兩次的提交都給刪除掉,代碼沒有了,慎用。
合併本身的commit後,也不能直接就提交,最好把master上的變動給同步過來,由於在你開發分支的時候master上可能有新的提交。這個時候git rebase命令就上場了。筆者經常使用的方法是,首先checkout出master,而後git pull一把,而後切換回以前的分支並執行git rebase master,這樣就會把master上的變更給同步過來,master上的變更在前,你本身的變更在後,若是二者有衝突,git rebase會停下來,你本身把衝突的文件給處理好後,而後git add,再執行git rebase --continue。最後再把分支提交,發起code review過程,若是經過的話,就能夠直接merge到master,不會有衝突。使用git rebase還有一個好處是,能保證master上的提交是一條線,不像使用git merge提交的,會致使master上有不少分支,固然也有一個很差的地方,那就是會致使提交的時間發生變更,提交的時間不會保證是遞增的順序。
此外,還有一些命令也挺好用的:
git blame: 當你發現源碼中的Bug的時候,想找出這是誰的鍋,而後這條命令就排上用場了。固然其實更有用的一種用法是經過它來找到這個新的feature的issue:好比說,代碼中多了一個變量var_path,你想知道這個變量是幹啥的,除了看註釋和源碼,你能夠經過git blame找到提交的commit id,而後在git log的commit message中找到Issue id信息以及簡介,找到Issue id後就能夠在gitlab等代碼倉庫中,找到Issue的詳細信息,好比爲什麼建立,何時建立以及解決的辦法等。
mysql_declare_plugin(innobase) { MYSQL_STORAGE_ENGINE_PLUGIN, &innobase_storage_engine, innobase_hton_name, plugin_author, "Supports transactions, row-level locking, and foreign keys", PLUGIN_LICENSE_GPL, innobase_init, /* Plugin Init */ NULL, /* Plugin Deinit */ INNODB_VERSION_SHORT, innodb_status_variables_export,/* status variables */ innobase_system_variables, /* system variables */ NULL, /* reserved */ 0, /* flags */ }, i_s_innodb_trx, i_s_innodb_locks, i_s_innodb_lock_waits, i_s_innodb_cmp, i_s_innodb_cmp_reset, i_s_innodb_cmpmem, i_s_innodb_cmpmem_reset, i_s_innodb_cmp_per_index, i_s_innodb_cmp_per_index_reset, i_s_innodb_buffer_page, i_s_innodb_buffer_page_lru, i_s_innodb_buffer_stats, i_s_innodb_metrics, i_s_innodb_ft_default_stopword, i_s_innodb_ft_deleted, i_s_innodb_ft_being_deleted, i_s_innodb_ft_config, i_s_innodb_ft_index_cache, i_s_innodb_ft_index_table, i_s_innodb_sys_tables, i_s_innodb_sys_tablestats, i_s_innodb_sys_indexes, i_s_innodb_sys_columns, i_s_innodb_sys_fields, i_s_innodb_sys_foreign, i_s_innodb_sys_foreign_cols, i_s_innodb_sys_tablespaces, i_s_innodb_sys_datafiles mysql_declare_plugin_end;
這段代碼是用來定義InnoDB這個引擎的接口信息的,方便Server層的代碼調用。第一次看,你可能根本不知道這是個啥玩意,即便你用Ctags等工具跳轉,也不必定看的清楚,尤爲針對源碼的初學者,這個時候你能夠打開預編譯文件看一下:
int builtin_innobase_plugin_interface_version= 0x0104;
int builtin_innobase_sizeof_struct_st_plugin= sizeof(struct st_mysql_plugin);
struct st_mysql_plugin builtin_innobase_plugin[]= { { 1, &innobase_storage_engine, innobase_hton_name, plugin_author, "Supports transactions, row-level locking, and foreign keys", 1, innobase_init, __null, (5 << 8 | 6), innodb_status_variables_export, innobase_system_variables, __null, 0, }, i_s_innodb_trx, i_s_innodb_locks, i_s_innodb_lock_waits, i_s_innodb_cmp, i_s_innodb_cmp_reset, i_s_innodb_cmpmem, i_s_innodb_cmpmem_reset, i_s_innodb_cmp_per_index, i_s_innodb_cmp_per_index_reset, i_s_innodb_buffer_page, i_s_innodb_buffer_page_lru, i_s_innodb_buffer_stats, i_s_innodb_metrics, i_s_innodb_ft_default_stopword, i_s_innodb_ft_deleted, i_s_innodb_ft_being_deleted, i_s_innodb_ft_config, i_s_innodb_ft_index_cache, i_s_innodb_ft_index_table, i_s_innodb_sys_tables, i_s_innodb_sys_tablestats, i_s_innodb_sys_indexes, i_s_innodb_sys_columns, i_s_innodb_sys_fields, i_s_innodb_sys_foreign, i_s_innodb_sys_foreign_cols, i_s_innodb_sys_tablespaces, i_s_innodb_sys_datafiles ,{0,0,0,0,0,0,0,0,0,0,0,0,0}};
這下就很清楚了,這段代碼幹了兩件事:定義兩個int變量和定義一個結構體。同時還把結構體裏面兩個常量給打印了出來,看過去清晰多了。一樣道理,你還能夠在#ifdef分不清走哪條路徑的時候用這招,很好用的。
暫時先總結這麼多,後續會持續更新,若是你有什麼好用的小技巧,歡迎在下面留言。