开源软件已经普遍的被互联网公司所应用,不只仅是由于其能给企业节省一大笔成本,并且最重要的是拥有更多的自主可控性,能从源头上对软件质量进行把控。另外一方面,因为开源软件背后每每没有大型的商业公司,因此文档相对来讲不是很是完善(或者说文档和代码不必定相互对应),所以,做为一名合格程序员,尤为是基础软件开发的程序员,阅读开源软件源码的能力是必备的素质。 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分不清走哪条路径的时候用这招,很好用的。
暂时先总结这么多,后续会持续更新,若是你有什么好用的小技巧,欢迎在下面留言。