<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>godorz...</title>
	<atom:link href="http://godorz.info/feed/" rel="self" type="application/rss+xml" />
	<link>http://godorz.info</link>
	<description>我要有将能力转化为AC的勇气.</description>
	<lastBuildDate>Sat, 21 Jan 2012 03:25:19 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>vim入门,进阶与折腾</title>
		<link>http://godorz.info/2012/01/vim/</link>
		<comments>http://godorz.info/2012/01/vim/#comments</comments>
		<pubDate>Fri, 20 Jan 2012 23:19:03 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[vim]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=1130</guid>
		<description><![CDATA[作为编辑器之神,vim一直是我编辑文本的不二选择,哪怕其坎坷的学习曲线让人头疼不已.末学总结一下经验教训,以作备忘. 入门 个人习惯编译选项 ./configure &#8211;with-features=huge &#8211;enable-cscope &#8211;enable-fontset &#8211;enable-multibyte &#8211;enable-perlinterp &#8211;enable-rubyinterp &#8211;enable-pythoninterp 工作目录 :pw[d] 显示当前工作目录 :cd[!] {path} 工作目录切换到path 移动: h j k l 0 ^ $ e E b B w W f F t T ; , % gg G [ ] 编辑: d y p r c o 在vim中还存在如x,s等编辑命令,这些命令只是 编辑命令 + 移动命令 的简答组合,如x X [...]]]></description>
			<content:encoded><![CDATA[<p>作为编辑器之神,vim一直是我编辑文本的不二选择,哪怕其坎坷的学习曲线让人头疼不已.末学总结一下经验教训,以作备忘.</p>
<h3>入门</h3>
<p><strong>个人习惯编译选项</strong></p>
<p>./configure &#8211;with-features=huge &#8211;enable-cscope &#8211;enable-fontset &#8211;enable-multibyte &#8211;enable-perlinterp &#8211;enable-rubyinterp &#8211;enable-pythoninterp</p>
<p><strong>工作目录</strong></p>
<p>:pw[d] 显示当前工作目录<br />
:cd[!] {path} 工作目录切换到path</p>
<p><strong>移动:</strong> h j k l 0 ^ $ e E b B w W f F t T ; , % gg G [ ]<br />
<strong>编辑:</strong> d y p r c o</p>
<p>在vim中还存在如x,s等编辑命令,这些命令只是 <span style="color: #ff0000;"><strong>编辑命令 + 移动命令</strong></span> 的简答组合,如x X D C s S Y</p>
<p><strong>简单总结大小写操作命令的区别:</strong></p>
<p>作用于行首: I<br />
作用于行尾: D,C,A,R<br />
作用于整行: Y,S,V<br />
作用于逆方向: X,F,P,O,N<br />
搜索时大小写相关: \C<br />
改变大小写: gU</p>
<p>关于编辑命令,需要说明一点: <span style="color: #ff0000;"><strong>x,d,c这些命令会把脏数据置入vim的粘贴板</strong></span>(所谓脏数据,也就是被命令删除或改变的那部分数据).</p>
<p>利用vim的这个特性,我们可以轻易的实现文本剪切,如交换两个字符,交换两行文本,甚至是交换两段文本:</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/swap.gif" alt="" title="swap" width="280" height="197" class="aligncenter size-full wp-image-1177" /></p>
<p><strong>16进制编辑</strong>,码农必备,lol</p>
<p>:%!xxd 16进制编辑<br />
:%!xxd -r 文本编辑</p>
<p><strong>保存会话</strong></p>
<p>Session可以忠实地记录vim当前的视图,如windows和tabs,甚至是高亮.真是谁用谁知道.</p>
<p>:mks[!] [file] 把当前视图保存到file,如file未指定,则缺省为Session.vim<br />
vim -S Session.vim 打开Session.vim,也就是Reload view,重新打开视图.</p>
<p><strong>可视化编辑</strong></p>
<p>v: charwise<br />
V: linewise</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/visual.gif" alt="" title="visual" width="351" height="162" class="aligncenter size-full wp-image-1172" /></p>
<p>Ctrl-V: blockwise,后文介绍.</p>
<p><strong>gv 重选上次选中的区域,谁用谁知道</strong></p>
<p><strong>区域</strong></p>
<p>% 作用于当前打开的整个文件<br />
&#8216;<,'> 作用于当前选中的区域,其中,&#8217;<表示选中区域第一行,'>表示选中区域的最后一行.<br />
{num1},{num2} 作用于从第num1行到第num2行</p>
<p>内置命令</p>
<p>. 当前行,比如说,想要作用于从当前行开始的总共8行文本,则可以8:,<br />
$ 最后一行,</p>
<p><strong>:[range] substitute/from/to/[flags]</strong> 替换文本</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/substitute.gif" alt="" title="substitute" width="498" height="261" class="aligncenter size-full wp-image-1135" /></p>
<p><strong>:[range] copy {num1} {num2}</strong> 复制文本</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/copy.gif" alt="" title="copy" width="498" height="261" class="aligncenter size-full wp-image-1134" /></p>
<p><strong>:[range] Tabluarize /{char1}</strong> 对齐文本</p>
<p><img class="aligncenter size-full wp-image-1137" title="Tabularize" src="http://godorz.info/wp-content/uploads/2012/01/Tabularize.gif" alt="" width="484" height="207" /></p>
<p>甚至可以是:<br />
<strong>:[range] TOhtml</strong> 文本转为html</p>
<p><strong>查找:</strong> / ? * #</p>
<p>:help pattern</p>
<p>/ 向前查找keyword<br />
? 向后查找keyword</p>
<p>n 重做最后一次/或?<br />
N 反方向重做最后一次/或?</p>
<p>\c 查找时忽略大小写<br />
\C 查找时大小写相关</p>
<p>\< 表示一个词的开始<br />
\> 表示一个词的结束</p>
<p><strong>tabs</strong></p>
<p>:help tabpage</p>
<p>:tabnew 新建tab<br />
:tabclose 关闭当前tab<br />
:tabedit {file} 新建tab,并在新创建的tab打开file (等价于: tabnew后:edit {file})<br />
:tabmove {idx} 把当前tab移动到第idx tab之后</p>
<p>gt 切换到下一tab<br />
gT 切换到上一tab<br />
{idx}gt 切换到第idx tab</p>
<p>为了方便,我的vimrc配置如下:</p>
<p><font color="#cdaa7d">536 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">A-1</font><font color="#76eec6">&gt;</font>&nbsp;1gt<br />
<font color="#cdaa7d">537 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">A-2</font><font color="#76eec6">&gt;</font>&nbsp;2gt<br />
<font color="#cdaa7d">538 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">A-3</font><font color="#76eec6">&gt;</font>&nbsp;3gt<br />
<font color="#cdaa7d">539 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">A-4</font><font color="#76eec6">&gt;</font>&nbsp;4gt<br />
<font color="#cdaa7d">540 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">A-5</font><font color="#76eec6">&gt;</font>&nbsp;5gt<br />
<font color="#cdaa7d">541 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">A-6</font><font color="#76eec6">&gt;</font>&nbsp;6gt<br />
<font color="#cdaa7d">542 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">A-7</font><font color="#76eec6">&gt;</font>&nbsp;7gt<br />
<font color="#cdaa7d">543 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">A-8</font><font color="#76eec6">&gt;</font>&nbsp;8gt<br />
<font color="#cdaa7d">544 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">A-9</font><font color="#76eec6">&gt;</font>&nbsp;9gt</p>
<p>尽管很dirty,好歹能工作.</p>
<p><strong>windows</strong></p>
<p>:sp {file} 横向(horizontally)切割窗口,并在新窗口打开file<br />
:vsp {file} 竖向(vertically)切割窗口,并在新窗口打开file</p>
<p><C-w>h 切换到左侧窗口<br />
<C-w>j 切换到下方窗口<br />
<C-w>k 切换到上方窗口<br />
<C-w>l 切换到右侧窗口</p>
<p>如果对所有vimer的配置做统计,下面这段配置绝对是出现频率最高的:</p>
<p><font color="#cdaa7d">577 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-h</font><font color="#76eec6">&gt;</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-w</font><font color="#76eec6">&gt;</font>h<br />
<font color="#cdaa7d">578 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-j</font><font color="#76eec6">&gt;</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-w</font><font color="#76eec6">&gt;</font>j<br />
<font color="#cdaa7d">579 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-k</font><font color="#76eec6">&gt;</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-w</font><font color="#76eec6">&gt;</font>k<br />
<font color="#cdaa7d">580 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-l</font><font color="#76eec6">&gt;</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-w</font><font color="#76eec6">&gt;</font>l</p>
<p>省下的绝对不仅是一次w按键,vimer你懂的.</p>
<p>关于窗口的扩大缩小, :help window-resize</p>
<p><strong>marks</strong></p>
<p>:help marks</p>
<p>我用得最多的marks操作是:</p>
<p>&#8216;[ 跳到上一次被改变(changed)或者复制的文本段的第一个字符<br />
'' 跳回上一次跳转的地方<br />
'^ 跳到插入模式最后一次结束的地方<br />
'. 跳到上一次文本被修改的地方</p>
<p><strong>缩进与对齐</strong></p>
<p>:help indent.txt</p>
<p>> 向右缩进shiftwidth个字符大小<br />
< 同上,但是向左缩进<br />
= 对齐文本</p>
<p>>,<.=这三个命令的作用域既可以是选中的一段文本,也可以是一个文本对象(后文进阶部门会解释).</p>
<h3>进阶</h3>
<p><strong>[N]&lt;command&gt;</strong> 执行&lt;command&gt;N次</p>
<p>NOTE: [N]&lt;command&gt;&lt;range&gt;和&lt;command&gt;[N]&lt;range&gt;是不一样的,如:d3w和3dw两个操作虽然看似一样,但实际上它们在vim内部的行为是有本质区别的:<br />
d3w表示一次删除3个w,而3dw表示一次删除一个w,重复3次.</p>
<p><strong>[start pos]&lt;command&gt;[end pos]</strong> 从start pos开始执行&lt;command&gt;到end pos,[]表示其内部的命令不是必须的,也就是说,start pos和end pos都不是必须的.</p>
<p>gg=G和^y$是两个极好的例子:</p>
<p>gg=G 对齐整个文件(gg跳到第一行,=对齐,G最后一行)<br />
^y$ 从当前行行首复制至行尾(^行首,y复制,$行尾),你能看出^y$和Y的区别吗?</p>
<p><strong>NOTE:</strong> start pos和end pos仅仅表示一个位置(黑话叫锚点),至于如何从光标移动到start pos或者end pos,vim并没有做出要求.于是,我们可以轻松地敲出如下命令,大大提高文本编辑的效率:</p>
<p>df=, yf=, cf=, vf= 从当前字符开始删除(复制,改变,选中),直到遇到=之后<br />
dt&#8221;, yt&#8221;, ct&#8221;, vt&#8221; 从当前字符开始删除(复制,改变,选中),直到遇到&#8221;之前</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/ft.gif" alt="" title="ft" width="253" height="97" class="aligncenter size-full wp-image-1188" /></p>
<p><strong>text object</strong></p>
<p>:help text-objects</p>
<p><strong>&lt;action&gt;i&lt;object&gt;</strong> 作用于对象内部(i: inner)<br />
<strong>&lt;action&gt;a&lt;object&gt;</strong> 作用于整个对象(a: an)</p>
<p>其中,action可以是v,d,c,y,甚至可以是>,<等<br />
而object可以是w,W,s,p,b,B,以及各种成对符号,如',",<,{,(,[等</p>
<p>有了文本对象,写起代码来更是得心应手,如:</p>
<p>向右缩进一段代码: >i{<br />
删除(复制,改变,选中)光标所在单词: diw, yiw, ciw, viw<br />
删除(复制,改变,选中)&#8221;"内所有文本: di&#8221;, yi&#8221;, ci&#8221;, vi&#8221;<br />
删除(复制,改变,选中)&#8221;"号内所有文本,包括引号本身: da&#8221;, ya&#8221;, ca&#8221;, va&#8221;</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/text-objects.gif" alt="" title="text-objects" width="282" height="192" class="aligncenter size-full wp-image-1194" /></p>
<p><strong>visual block</strong></p>
<p><strong><C-v>${select region}&lt;commands&gt;<Esc><Esc></strong></p>
<p>块操作可以一次编辑多行文本,对有规律的编辑需要实在是一大利器.如:</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/block.gif" alt="" title="block" width="416" height="154" class="aligncenter size-full wp-image-1180" /></p>
<p><strong>宏</strong></p>
<p>所谓宏,就是一段录制好的操作.</p>
<p><strong>q${register}&lt;commands&gt;q</strong> 录制commands到寄存器register<br />
<strong>[N]@register</strong> 重放寄存器register中的宏N次</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/macros.gif" alt="" title="macros" width="664" height="443" class="aligncenter size-full wp-image-1181" /></p>
<p>看起来,宏和块操作的区别非常明显: 宏<strong>&#8220;可以认为&#8221;</strong>是linewise,而块操作是blockwise,也就是说,宏对应的是几行文本,块操作对应的则是选中的block.</p>
<p>从这明显的区别中我们可以推出一个重要的结论: 宏中的锚点有相对的概念,而块操作是绝对的.比如说,行尾就是一个最简单的相对概念,每一行的行尾所在的锚点可能都不一样,但这丝毫不影响宏正确的在所有行行尾插入一段文本.而在块操作中,命令A(在行尾插入)对应的语义却变成了block的尾部,显然,&#8221;block的尾部&#8221;这一概念对block中的所有行都是相同的,也就是所谓绝对的位置.</p>
<h3>折腾</h3>
<p><strong>配色</strong></p>
<p>:help syntax</p>
<p>vim自带了许多配色方案(在这里有各种预览),可以用colorscheme命令选择,如: <font color="#eedc82">colorscheme</font>&nbsp;desertEx</p>
<p>哪怕再性感的配色,看久了也会生烦,所幸vim自带了synIDattr函数,在vimrc中加入如下脚本:</p>
<p><font color="#cdaa7d">215 </font><font color="#eedc82">nmap</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-S-P</font><font color="#76eec6">&gt;</font>&nbsp;:call <font color="#76eec6">&lt;</font><font color="#76eec6">SID</font><font color="#76eec6">&gt;</font>SynStack()<font color="#76eec6">&lt;</font><font color="#76eec6">CR</font><font color="#76eec6">&gt;</font><br />
<font color="#cdaa7d">216 </font><font color="#eedc82">function</font>! <font color="#76eec6">&lt;SID&gt;</font>SynStack<font color="#eedc82">()</font><br />
<font color="#cdaa7d">217 </font>&nbsp;&nbsp; <font color="#eedc82">if</font>&nbsp;!<font color="#87ceeb">exists</font><font color="#eedc82">(</font>&quot;*synstack&quot;<font color="#eedc82">)</font><br />
<font color="#cdaa7d">218 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">return</font><br />
<font color="#cdaa7d">219 </font>&nbsp;&nbsp; <font color="#eedc82">endif</font><br />
<font color="#cdaa7d">220 </font>&nbsp;&nbsp; <font color="#eedc82">echo</font>&nbsp;<font color="#87ceeb">map</font>(<font color="#87ceeb">synstack</font>(<font color="#87ceeb">line</font>(&#8216;.&#8217;), <font color="#87ceeb">col</font>(&#8216;.&#8217;)), &#8216;synIDattr(v:val, &quot;name&quot;)&#8217;)<br />
<font color="#cdaa7d">221 </font><font color="#eedc82">endfunc</font></p>
<p>:so %安装后就可以通过Ctrl-Shift-P组合键方便地查看某段文本的ID了.</p>
<p>得到文本ID之后,需要通过指定颜色来实现自定义.vim支持rgb配色(#445599之流),可是,身为毫无艺术感的二逼后台开发,我自然更偏爱skyblue这类见其名即可知其意的配色方案了:).为了让生活容易些,可以:runtime syntax/colortest.vim直接预览,谁用谁知道.</p>
<p>vim的强大总是让人爱不释手,我们甚至可以自定义ID,比如说,我的代码中TODO横行,为了更直观的显示TODO项,于是便有了这段配置:</p>
<p><font color="#cdaa7d">60 </font><font color="#eedc82">highlight</font>&nbsp;RipGroup&nbsp;<font color="#ffa54f">ctermbg</font>=yellow&nbsp;<font color="#ffa54f">cterm</font>=<font color="#ee799f">none</font>&nbsp;<font color="#ffa54f">ctermfg</font>=black<br />
<font color="#cdaa7d">61 </font><font color="#eedc82">match</font>&nbsp;RipGroup&nbsp;/TODO/</p>
<p>btw,在终端下切记要打开256色: <font color="#eedc82">set</font>&nbsp;<font color="#ee799f">t_Co</font>=256</p>
<p>再次btw,简单回答一个可以很好的区分vim新手和老鸟的问题,对Alt键的map为何在终端模式下如此虐心? 因为终端他妈的自动在Alt前面加了Esc前缀,这该让人多胸闷啊.</p>
<p><strong>代码折叠</strong></p>
<p>:help fold.txt</p>
<p>vim支持多种折叠方法(fold methods),如indent,expr,marker,syntax等.我偏向于按syntax折叠,配置如下:</p>
<p><font color="#cdaa7d">716 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">foldenable</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; enable folden</i></font><br />
<font color="#cdaa7d">717 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">foldmethod</font>=syntax&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; manual : Folds are created manually.</i></font><br />
<font color="#cdaa7d">718 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot; indent : Lines with equal indent form a fold.</i></font><br />
<font color="#cdaa7d">719 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot; expr&nbsp;&nbsp; : &#8216;foldexpr&#8217; gives the fold level of a line.</i></font><br />
<font color="#cdaa7d">720 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot; marker : Markers are used to specify folds.</i></font><br />
<font color="#cdaa7d">721 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot; syntax : Syntax highlighting items specify folds.</i></font><br />
<font color="#cdaa7d">722 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot; diff&nbsp;&nbsp; : Fold text that is not changed.</i></font><br />
<font color="#cdaa7d">728 </font><br />
<font color="#cdaa7d">729 </font><font color="#7ccd7c"><i>&quot;set foldclose=all</i></font><br />
<font color="#cdaa7d">730 </font><font color="#7ccd7c"><i>&quot; use space to folden</i></font><br />
<font color="#cdaa7d">731 </font><font color="#eedc82">nnoremap</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">space</font><font color="#76eec6">&gt;</font>&nbsp;@=((foldclosed(line(&#8216;.&#8217;)) &lt; 0) ? &#8216;zc&#8217; : &#8216;zo&#8217;)<font color="#76eec6">&lt;</font><font color="#76eec6">CR</font><font color="#76eec6">&gt;</font></p>
<p>其中,.表示当前行,zo表示展开,zc表示折叠,整行配置的意思就是通过空格键折叠代码,效果如下:</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/fold.gif" alt="" title="fold" width="319" height="209" class="aligncenter size-full wp-image-1196" /></p>
<p>vim默认在搜索和undo时会展开你辛辛苦苦设置好的折叠,这是让人非常难受的,所以我会追加这么一段配置:</p>
<p><font color="#cdaa7d">726 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">foldopen</font>-=search&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; dont open folds when I search into thm</i></font><br />
<font color="#cdaa7d">727 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">foldopen</font>-=undo&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; dont open folds when I undo stuff</i></font></p>
<p><strong>编码</strong></p>
<p><font color="#cdaa7d">249 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">encoding</font>=utf-8<br />
<font color="#cdaa7d">250 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">fileencodings</font>=ucs-bom<font color="#eedc82">,</font>utf-8<font color="#eedc82">,</font>cp936<font color="#eedc82">,</font>gb18030<font color="#eedc82">,</font>big5<font color="#eedc82">,</font>gbk<font color="#eedc82">,</font>euc-jp<font color="#eedc82">,</font>euc-kr<font color="#eedc82">,</font>latin1<br />
<font color="#cdaa7d">251 </font><font color="#eedc82">if</font>&nbsp;<font color="#87ceeb">has</font><font color="#eedc82">(</font>&quot;win32&quot;<font color="#eedc82">)</font><br />
<font color="#cdaa7d">252 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">fileencoding</font>=chinese<br />
<font color="#cdaa7d">253 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&quot; fix menu gibberish</i></font><br />
<font color="#cdaa7d">254 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">source</font>&nbsp;<font color="#ee799f">$VIMRUNTIME</font>/delmenu<font color="#eedc82">.</font><font color="#eedc82">vim</font><br />
<font color="#cdaa7d">255 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">source</font>&nbsp;<font color="#ee799f">$VIMRUNTIME</font>/<font color="#eedc82">menu</font><font color="#ee799f">.vim</font><br />
<font color="#cdaa7d">256 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&quot; fix console gibberish</i></font><br />
<font color="#cdaa7d">257 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">language</font>&nbsp;messages&nbsp;zh_CN<font color="#eedc82">.</font>utf<font color="#eedc82">-</font><font color="#fa8072">8</font><br />
<font color="#cdaa7d">258 </font><font color="#eedc82">else</font><br />
<font color="#cdaa7d">259 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">termencoding</font>=utf-8<br />
<font color="#cdaa7d">260 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">fileencoding</font>=utf-8<br />
<font color="#cdaa7d">261 </font><font color="#eedc82">endif</font></p>
<p><strong>gui设置</strong></p>
<p>简单介绍一下gvim的设置,首先是字体,我的配置如下:</p>
<p><font color="#cdaa7d">229 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">guifont</font>=Courier_New<font color="#eedc82">:</font>h9<font color="#eedc82">:</font>cANSI<br />
<font color="#cdaa7d">231 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">guifontwide</font>=幼圆<font color="#eedc82">:</font>h10<font color="#eedc82">:</font>cGB2312</p>
<p>guifont对应的应为字体,guifontwide对应所谓的宽字节字体,中文就是宽字节.</p>
<p>我个人倾向于隐藏gvim菜单栏,工具栏,滚动条等,以最大化代码可视面积:</p>
<p><font color="#cdaa7d">237 </font><font color="#eedc82">if</font>&nbsp;<font color="#87ceeb">has</font><font color="#eedc82">(</font>&quot;gui_running&quot;<font color="#eedc82">)</font><br />
<font color="#cdaa7d">238 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&quot; set guioptions-=m&nbsp;&nbsp;&quot; remove menu bar</i></font><br />
<font color="#cdaa7d">239 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">guioptions</font>-=T&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; remove toolbar</i></font><br />
<font color="#cdaa7d">240 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">guioptions</font>-=r&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; remove right-hand scroll bar</i></font><br />
<font color="#cdaa7d">241 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">guioptions</font>-=l&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; remove left-hand scroll bar</i></font><br />
<font color="#cdaa7d">242 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">guioptions</font>-=L&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; remove left-hand scroll bar even if there is a vertical split</i></font><br />
<font color="#cdaa7d">243 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">guioptions</font>-=b&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; remove bottom scroll bar</i></font><br />
<font color="#cdaa7d">244 </font><font color="#eedc82">endif</font></p>
<p>我要吐槽的是,即便设置了set guioptions-=l,当切割了横向窗口时,左侧的滚动条还是会如幽灵般出现.各种不解后查了手册才明白,原来还要set guioptions-=L,但是,右侧滚动条却没有这个坑,简直坑爹.</p>
<p><strong>tags</strong></p>
<p>:help tags</p>
<p>tags是什么,程序员都懂.通过<a href="http://ctags.sourceforge.net/" target="_blank">ctags</a>程序可以很方便的为C++/C项目生成tags: </p>
<p><strong>ctags -R &#8211;c++-kinds=+p &#8211;fields=+iaS &#8211;extra=+q .</strong></p>
<p>&#8211;c++-kinds=+p 生成函数原型,该选项默认关闭.同样默认关闭的选项还有l(局部变量)和x(外部变量).<br />
&#8211;fileds=+iaS  分别对应类的继承inheritabce,类成员访问权限(access)和routine签名(Signature, 如原型或参数列表等).<br />
&#8211;extra=+q     为类成员生成的tag加上其所属的类信息.</p>
<p>这行命令敲起来太累了,不如按一下F5来的痛快: </p>
<p><font color="#cdaa7d">492 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">F5</font><font color="#76eec6">&gt;</font>&nbsp;:!ctags -R &#8211;c++-kinds=+p &#8211;fields=+iaS &#8211;extra=+q .<font color="#76eec6">&lt;</font><font color="#76eec6">CR</font><font color="#76eec6">&gt;</font></p>
<p>该命令生成的文件为当前目录下的tags.</p>
<p>作为后台开发程序员,查阅系统源码是常有的事,不妨为/usr/include目录生成tags,然后配置vimrc,以便每次启动vim时自动加载(即便有再多的autoload,即便vim启动速度再慢,也足以秒杀emacs了&#8230;这算是降维攻击不?):</p>
<p><font color="#cdaa7d">274 </font><font color="#eedc82">if</font>&nbsp;<font color="#87ceeb">has</font><font color="#eedc82">(</font>&quot;win32&quot;<font color="#eedc82">)</font><br />
<font color="#cdaa7d">275 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">tags</font>+=E<font color="#eedc82">:</font>\workspace\linux\tags&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; tags for /usr/include/</i></font><br />
<font color="#cdaa7d">276 </font><font color="#eedc82">else</font><br />
<font color="#cdaa7d">277 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">tags</font>+=~/.vim/tags/include/tags&nbsp;<font color="#7ccd7c"><i>&quot; tags for /usr/include/</i></font><br />
<font color="#cdaa7d">278 </font><font color="#eedc82">endif</font><br />
<font color="#cdaa7d">279 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">tags</font>+=tags&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; tags for current project</i></font></p>
<p>需要说明的是,我偶尔需要在windows上写代码,所以我把linux下的/usr/include目录拷贝到了windows上,然后用ctags windows版生成了tags,于是在windows上写代码也舒适了许多.</p>
<p><strong>插件</strong></p>
<p>vim什么都硬,只是有一点比较短: 扩展性,vim在这一点被咖啡机emacs拉下远远不止一条街,但是对于普通青年我,却也算是差强人意了.&#8221;Do one thing and do it well&#8221;,这恐怕是不少vim拥趸自我解脱的说辞.</p>
<p>下面是个人最喜欢的一些插件,排名不分先后:</p>
<p><strong><a href="http://vimdoc.sourceforge.net/htmldoc/quickfix.html" target="_blank">Quickfix.vim</a></strong></p>
<p>:help quickfix</p>
<p>Quickfix是vim的标准插件,它是一个典型的plumber: 只要输入符合<a href="http://vim.wikia.com/wiki/Errorformats" target="_blank">error format(efm)</a>,则vim可以正确理解和识别&#8221;错误列表&#8221;,并跳转到对应行.</p>
<p>我们通过gcc和grep的输出来更好地理解error format:</p>
<p>Error: Unclassifiable statement at hello-world.f90:9.4 gcc编译器的错误提示<br />
./sys/net/bpf.c:137: bpf_wakeup __P((struct bpf_d *)); grep的输出(加上-n)</p>
<p>可以看到,它们的格式是非常类似的.</p>
<p>虽然Quickfix插件原意是为了更方便地调试代码,可是借用error format实现文本的匹配也是非常拉轰的:</p>
<p>
<font color="#cdaa7d">505 </font><font color="#7ccd7c"><i>&quot; search word under cursor like source insight</i></font><br />
<font color="#cdaa7d">506 </font><font color="#7ccd7c"><i>&quot; &lt;cword&gt; is replaced with the word under the cursor (like |star|) (:help cmdline or :help cword)</i></font><br />
<font color="#cdaa7d">507 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">C-F</font><font color="#76eec6">&gt;</font>&nbsp;:execute &quot;let g:word=expand(\&quot;<font color="#76eec6">&lt;</font><font color="#76eec6">cword</font><font color="#76eec6">&gt;</font>\&quot;)&quot;<font color="#76eec6">&lt;</font><font color="#76eec6">Bar</font><font color="#76eec6">&gt;</font>execute &quot;vimgrep /\\&lt;&quot; . g:word .&quot;\\&gt;/g **/*.[ch] **/*.cpp&quot;<font color="#76eec6">&lt;</font><font color="#76eec6">Bar</font><font color="#76eec6">&gt;</font>execute &quot;cc 1&quot;<font color="#76eec6">&lt;</font><font color="#76eec6">Bar</font><font color="#76eec6">&gt;</font>execute &quot;cw&quot;<font color="#76eec6">&lt;</font><font color="#76eec6">CR</font><font color="#76eec6">&gt;</font><br />
<font color="#cdaa7d">508 </font><font color="#7ccd7c"><i>&quot; next matched line</i></font><br />
<font color="#cdaa7d">509 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">silent</font><font color="#76eec6">&gt;</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">F10</font><font color="#76eec6">&gt;</font>&nbsp;:cnext<font color="#76eec6">&lt;</font><font color="#76eec6">CR</font><font color="#76eec6">&gt;</font><br />
<font color="#cdaa7d">510 </font><font color="#7ccd7c"><i>&quot; previous matched line</i></font><br />
<font color="#cdaa7d">511 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">silent</font><font color="#76eec6">&gt;</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">F11</font><font color="#76eec6">&gt;</font>&nbsp;:cprevious<font color="#76eec6">&lt;</font><font color="#76eec6">CR</font><font color="#76eec6">&gt;</font><br />
<font color="#cdaa7d">512 </font><font color="#7ccd7c"><i>&quot; open QuickFix</i></font><br />
<font color="#cdaa7d">513 </font><font color="#7ccd7c"><i>&quot; :copen</i></font><br />
<font color="#cdaa7d">514 </font><font color="#7ccd7c"><i>&quot; close QuickFix</i></font><br />
<font color="#cdaa7d">515 </font><font color="#7ccd7c"><i>&quot; :cclose</i></font></p>
<p>简单说明一下这段配置,<cword>表示当前光标下的单词(:help cmdline or :help cword),<Bar>也就是|,表示串联命令.cc 1表示跳到&#8221;错误列表&#8221;第一条,cw表示打开quickfix窗口,如果存在可识别的错误列表.cnext和cprevious表示在错误列表中切换.</p>
<p><img src="http://godorz.info/wp-content/uploads/2012/01/quickfix.gif" alt="" title="quickfix" width="699" height="455" class="aligncenter size-full wp-image-1198" /></p>
<p>btw,vim在匹配时默认使用自带的vimgrep插件,如果觉得不方便,可以显示指定使用grep: <font color="#eedc82">set</font>&nbsp;<font color="#ee799f">grepprg</font>=grep</p>
<p><strong><a href="http://www.vim.org/scripts/script.php?script_id=31" target="_blank">A.vim</a></strong></p>
<p>A.vim插件可以方便地切换源文件和头文件,还是那句话,谁用谁知道啊.</p>
<p>:A 在同一tab切换源文件/头文件<br />
:AV 竖向切割窗口,打开对应的源文件/头文件.<br />
:AS 横向切割窗口,打开对应的源文件/头文件.</p>
<p><strong><a href="http://www.vim.org/scripts/script.php?script_id=1658" target="_blank">NERDTree.vim</a></strong></p>
<p>NERDTree插件可以清楚地展示目录树,而且支持许多快捷键.个人最喜欢的快捷键是t: 在新建的tab打开光标所对应的文件.可惜的是,NERDTree原版插件对所新建的tab的命名看起来没什么具体的含义,于是我用上了<a href="https://github.com/jistr/vim-nerdtree-tabs">二手版</a>,配置如下:</p>
<p><font color="#cdaa7d">378 </font><font color="#eedc82">map</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">F6</font><font color="#76eec6">&gt;</font>&nbsp;<font color="#76eec6">&lt;</font><font color="#76eec6">plug</font><font color="#76eec6">&gt;</font>NERDTreeTabsToggle<font color="#76eec6">&lt;</font><font color="#76eec6">CR</font><font color="#76eec6">&gt;</font><br />
<font color="#cdaa7d">379 </font><br />
<font color="#cdaa7d">380 </font><font color="#eedc82">let</font>&nbsp;g:nerdtree_tabs_open_on_gui_startup<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; Open NERDTree on gvim/macvim startup</i></font><br />
<font color="#cdaa7d">381 </font><font color="#eedc82">let</font>&nbsp;g:nerdtree_tabs_open_on_console_startup<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;<font color="#7ccd7c"><i>&quot; Open NERDTree on console vim startup</i></font><br />
<font color="#cdaa7d">382 </font><font color="#eedc82">let</font>&nbsp;g:nerdtree_tabs_open_on_new_tab<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; Open NERDTree on new tab creation</i></font><br />
<font color="#cdaa7d">383 </font><font color="#eedc82">let</font>&nbsp;g:nerdtree_tabs_meaningful_tab_names<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; Unfocus NERDTree when leaving a tab for descriptive tab names</i></font><br />
<font color="#cdaa7d">384 </font><font color="#eedc82">let</font>&nbsp;g:nerdtree_tabs_autoclose<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; Close current tab if there is only one window in it and it&#8217;s NERDTree</i></font><br />
<font color="#cdaa7d">385 </font><font color="#eedc82">let</font>&nbsp;g:nerdtree_tabs_synchronize_view<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; Synchronize view of all NERDTree windows (scroll and cursor position)</i></font><br />
<font color="#cdaa7d">386 </font><br />
<font color="#cdaa7d">387 </font><font color="#7ccd7c"><i>&quot; When switching into a tab, make sure that focus is on the file window, not in the NERDTree window.</i></font><br />
<font color="#cdaa7d">388 </font><font color="#eedc82">let</font>&nbsp;g:nerdtree_tabs_focus_on_files<font color="#eedc82">=</font><font color="#fa8072">1</font></p>
<p><strong><a href="http://www.vim.org/scripts/script.php?script_id=1520" target="_blank">OmniCppCompelete.vim</a></strong></p>
<p>OmniCppComplete借助于tags实现智能补全,Ctrl-X Ctrl-O弹出待选择tags菜单,Ctrl-N切换至下一选项,Ctrl-P切换至上一选项.</p>
<p>个人配置如下:</p>
<p><font color="#cdaa7d">318 </font><font color="#7ccd7c"><i>&quot; :help omnicppcomplete</i></font><br />
<font color="#cdaa7d">319 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">completeopt</font>=longest<font color="#eedc82">,</font>menu&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; I really HATE the preview window!!!</i></font><br />
<font color="#cdaa7d">320 </font><font color="#eedc82">let</font>&nbsp;OmniCpp_NameSpaceSearch<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; 0: namespaces disabled</i></font><br />
<font color="#cdaa7d">321 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot; 1: search namespaces in the current buffer [default]</i></font><br />
<font color="#cdaa7d">322 </font><font color="#7ccd7c"><i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot; 2: search namespaces in the current buffer and in included files</i></font><br />
<font color="#cdaa7d">323 </font><font color="#eedc82">let</font>&nbsp;OmniCpp_GlobalScopeSearch<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; 0: disabled 1:enabled</i></font><br />
<font color="#cdaa7d">324 </font><font color="#eedc82">let</font>&nbsp;OmniCpp_ShowAccess<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; 1: show access</i></font><br />
<font color="#cdaa7d">325 </font><font color="#eedc82">let</font>&nbsp;OmniCpp_ShowPrototypeInAbbr<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;<font color="#7ccd7c"><i>&quot; 1: display prototype in abbreviation</i></font><br />
<font color="#cdaa7d">326 </font><font color="#eedc82">let</font>&nbsp;OmniCpp_MayCompleteArrow<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; autocomplete after -&gt;</i></font><br />
<font color="#cdaa7d">327 </font><font color="#eedc82">let</font>&nbsp;OmniCpp_MayCompleteDot<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; autocomplete after .</i></font><br />
<font color="#cdaa7d">328 </font><font color="#eedc82">let</font>&nbsp;OmniCpp_MayCompleteScope<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; autocomplete after ::</i></font></p>
<p>在智能补全时,OmniCppComplete会在当前工作窗口上方横向切割出一个preview窗口,preview窗口包含当前待选项的各种说明.杯具的是,当通过Ctrl-N或者Ctrl-P切换待选tags时,该preview窗口将会随着待选说明的变化而增大缩小,如果切换速度较快,则preview窗口看起来就像抖动一般.这也让人很难受,我们可以如下配置,关闭preview特性.</p>
<p><font color="#cdaa7d">319 </font><font color="#eedc82">set</font>&nbsp;<font color="#ee799f">completeopt</font>=longest<font color="#eedc82">,</font>menu&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; I really HATE the preview window!!!</i></font></p>
<p>另外,不要被插件名字欺骗了,OmniCppCompelete同样支持其他语言,如python,xml等.配置如下:</p>
<p><font color="#cdaa7d">331 </font><font color="#eedc82">autocmd</font>&nbsp;<font color="#ffa54f">FileType</font>&nbsp;python&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">omnifunc</font>=pythoncomplete#Complete<br />
<font color="#cdaa7d">332 </font><font color="#eedc82">autocmd</font>&nbsp;<font color="#ffa54f">FileType</font>&nbsp;javascript&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">omnifunc</font>=javascriptcomplete#CompleteJS<br />
<font color="#cdaa7d">333 </font><font color="#eedc82">autocmd</font>&nbsp;<font color="#ffa54f">FileType</font>&nbsp;html&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">omnifunc</font>=htmlcomplete#CompleteTags<br />
<font color="#cdaa7d">334 </font><font color="#eedc82">autocmd</font>&nbsp;<font color="#ffa54f">FileType</font>&nbsp;css&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">omnifunc</font>=csscomplete#CompleteCSS<br />
<font color="#cdaa7d">335 </font><font color="#eedc82">autocmd</font>&nbsp;<font color="#ffa54f">FileType</font>&nbsp;xml&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">omnifunc</font>=xmlcomplete#CompleteTags<br />
<font color="#cdaa7d">336 </font><font color="#eedc82">autocmd</font>&nbsp;<font color="#ffa54f">FileType</font>&nbsp;php&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">omnifunc</font>=phpcomplete#CompletePHP<br />
<font color="#cdaa7d">337 </font><font color="#eedc82">autocmd</font>&nbsp;<font color="#ffa54f">FileType</font>&nbsp;c&nbsp;<font color="#eedc82">set</font>&nbsp;<font color="#ee799f">omnifunc</font>=ccomplete#Complete</p>
<p>关于autocmd,找manual: help autocmd</p>
<p><strong><a href="http://www.vim.org/scripts/script.php?script_id=182" target="_blank">Supertab.vim</a></strong></p>
<p>vim在插入模式下支持13种补全方式(:help ins-completion),普通青年记不住,于是有了Supertab插件,配置如下:</p>
<p><font color="#cdaa7d">312 </font><font color="#eedc82">let</font>&nbsp;g:SuperTabRetainCOmpletionType<font color="#eedc82">=</font><font color="#fa8072">2</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; 2: remember last autocomplete type, unless I use ESC to exit insert mode</i></font><br />
<font color="#cdaa7d">313 </font><font color="#eedc82">let</font>&nbsp;g:SuperTabDefaultCompletionType<font color="#eedc82">=</font>&quot;&lt;C-X&gt;&lt;C-O&gt;&quot;</p>
<p><strong><a href="http://www.vim.org/scripts/script.php?script_id=273" target="_blank">Taglist.vim</a></strong></p>
<p>该插件展示当前文件对应的tags列表.配置如下:</p>
<p><font color="#cdaa7d">299 </font><font color="#eedc82">if</font>&nbsp;<font color="#87ceeb">has</font><font color="#eedc82">(</font>&quot;win32&quot;<font color="#eedc82">)</font><br />
<font color="#cdaa7d">300 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">let</font>&nbsp;Tlist_Ctags_Cmd<font color="#eedc82">=</font>&#8216;ctags&#8217;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; set ctags path</i></font><br />
<font color="#cdaa7d">301 </font><font color="#eedc82">else</font><br />
<font color="#cdaa7d">302 </font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#eedc82">let</font>&nbsp;Tlist_Ctags_Cmd<font color="#eedc82">=</font>&#8216;~/ctags-5.8/ctags&#8217;&nbsp;<font color="#7ccd7c"><i>&quot; set ctags path</i></font><br />
<font color="#cdaa7d">303 </font><font color="#eedc82">endif</font><br />
<font color="#cdaa7d">304 </font><font color="#eedc82">let</font>&nbsp;Tlist_Show_One_File<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; only show current file&#8217;s taglist</i></font><br />
<font color="#cdaa7d">305 </font><font color="#eedc82">let</font>&nbsp;Tlist_Exit_OnlyWindow<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#7ccd7c"><i>&quot; if taglist is of the last windows, exit vim</i></font><br />
<font color="#cdaa7d">306 </font><font color="#eedc82">let</font>&nbsp;Tlist_Use_Right_Window<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; show taglist at right</i></font><br />
<font color="#cdaa7d">307 </font><font color="#eedc82">let</font>&nbsp;Tlist_File_Fold_Auto_Close<font color="#eedc82">=</font><font color="#fa8072">1</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#7ccd7c"><i>&quot; hide taglist if it&#8217;s not for current file</i></font></p>
<p><strong><a href="http://www.vim.org/scripts/script.php?script_id=1238" target="_blank">mark.vim</a></strong></p>
<p>这是一款高亮插件.&lt;leader&gt;m高亮当前光标所对应的单词,再次&lt;leader&gt;m清除高亮.&lt;leader&gt;n清除所有高亮.</p>
<p>mark.vim默认只有下面这6种高亮颜色.如果觉得太少,可以自由地在mark.vim中添加.</p>
<p><font color="#cdaa7d">&nbsp;68 </font><font color="#7ccd7c"><i>&quot; default colors/groups</i></font><br />
<font color="#cdaa7d">&nbsp;69 </font><font color="#7ccd7c"><i>&quot; you may define your own colors in you vimrc file, in the form as below:</i></font><br />
<font color="#cdaa7d">&nbsp;70 </font><font color="#eedc82">hi</font>&nbsp;MarkWord1&nbsp;&nbsp;<font color="#ffa54f">ctermbg</font>=Cyan&nbsp;&nbsp;&nbsp;&nbsp; <font color="#ffa54f">ctermfg</font>=Black&nbsp;&nbsp;<font color="#ffa54f">guibg</font>=<font color="#fa8072">#8CCBEA</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#ffa54f">guifg</font>=Black<br />
<font color="#cdaa7d">&nbsp;71 </font><font color="#eedc82">hi</font>&nbsp;MarkWord2&nbsp;&nbsp;<font color="#ffa54f">ctermbg</font>=Green&nbsp;&nbsp;&nbsp;&nbsp;<font color="#ffa54f">ctermfg</font>=Black&nbsp;&nbsp;<font color="#ffa54f">guibg</font>=<font color="#fa8072">#A4E57E</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#ffa54f">guifg</font>=Black<br />
<font color="#cdaa7d">&nbsp;72 </font><font color="#eedc82">hi</font>&nbsp;MarkWord3&nbsp;&nbsp;<font color="#ffa54f">ctermbg</font>=Yellow&nbsp;&nbsp; <font color="#ffa54f">ctermfg</font>=Black&nbsp;&nbsp;<font color="#ffa54f">guibg</font>=<font color="#fa8072">#FFDB72</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#ffa54f">guifg</font>=Black<br />
<font color="#cdaa7d">&nbsp;73 </font><font color="#eedc82">hi</font>&nbsp;MarkWord4&nbsp;&nbsp;<font color="#ffa54f">ctermbg</font>=Red&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#ffa54f">ctermfg</font>=Black&nbsp;&nbsp;<font color="#ffa54f">guibg</font>=<font color="#fa8072">#FF7272</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#ffa54f">guifg</font>=Black<br />
<font color="#cdaa7d">&nbsp;74 </font><font color="#eedc82">hi</font>&nbsp;MarkWord5&nbsp;&nbsp;<font color="#ffa54f">ctermbg</font>=Magenta&nbsp;&nbsp;<font color="#ffa54f">ctermfg</font>=Black&nbsp;&nbsp;<font color="#ffa54f">guibg</font>=<font color="#fa8072">#FFB3FF</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#ffa54f">guifg</font>=Black<br />
<font color="#cdaa7d">&nbsp;75 </font><font color="#eedc82">hi</font>&nbsp;MarkWord6&nbsp;&nbsp;<font color="#ffa54f">ctermbg</font>=Blue&nbsp;&nbsp;&nbsp;&nbsp; <font color="#ffa54f">ctermfg</font>=Black&nbsp;&nbsp;<font color="#ffa54f">guibg</font>=<font color="#fa8072">#9999FF</font>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#ffa54f">guifg</font>=Black</p>
<h3>最后</h3>
<p>推荐两大暗爽已久的神器,一是<a href="http://vimperator.org/vimperator" target="_blank">vimperator</a>,在firefox上高度仿真了vim.一是<a href="http://en.wikipedia.org/wiki/Happy_Hacking_Keyboard" target="_blank">hhkb</a>,其惊艳的键位布局彻底释放了我被压抑多时的小拇指(码农你懂的),而奢侈的电容键盘更让人概叹<a href="http://www.waishehome.cn/thread-26420-1-1.html" target="_blank">&#8220;RealForce的素质,那仅仅是HHKB的起点而已&#8221;</a>.当然,如果拿hhkb来码中文,那就是另一回事了&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2012/01/vim/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>初探Linux网络协议栈</title>
		<link>http://godorz.info/2011/09/linux-network-kernel/</link>
		<comments>http://godorz.info/2011/09/linux-network-kernel/#comments</comments>
		<pubDate>Sun, 04 Sep 2011 04:21:00 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[kernel]]></category>
		<category><![CDATA[TCP/IP]]></category>
		<category><![CDATA[unp]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=1072</guid>
		<description><![CDATA[一点声明 原文链接: http://www.ecsl.cs.sunysb.edu/elibrary/linux/network/LinuxKernel.pdf 译者注: 原文写于2003年,文中描述的不少内容已经发生了改变,在不影响愿意的情况下,我擅自增删了一些内容. 翻译过程中找到的好资料: How SKBs Work Evaluation of TCP retransmission delays Congestion Control in Linux TCP Anatomy of the Linux networking stack &#8212; From sockets to device drivers Guide to IP Layer Network Administration with Linux Linux内核源码剖析 —TCP/IP实现 Understand Linux Network Kernel TCP/IP Illustrated, Vol. 2 RFC793, RFC1122, RFC1323, RFC2001, RFC2018, RFC2581 [...]]]></description>
			<content:encoded><![CDATA[<p class="quotecenter"><strong>一点声明</strong><br />
原文链接: <a href="http://www.ecsl.cs.sunysb.edu/elibrary/linux/network/LinuxKernel.pdf" target="_blank">http://www.ecsl.cs.sunysb.edu/elibrary/linux/network/LinuxKernel.pdf</a></p>
<hr />
<p><span style="color: #888888;">译者注: 原文写于2003年,文中描述的不少内容已经发生了改变,在不影响愿意的情况下,我擅自增删了一些内容.</span></p>
<p><span style="color: #888888;">翻译过程中找到的好资料:</span></p>
<ul>
<li><a href="http://vger.kernel.org/%7Edavem/skb.html" target="_blank">How SKBs Work</a></li>
<li><a href="http://heim.ifi.uio.no/%7Epaalh/students/EspenSoegaardPaaby.pdf" target="_blank">Evaluation of TCP retransmission delays</a></li>
<li><a href="http://www.cs.helsinki.fi/research/iwtcp/papers/linuxtcp.pdf" target="_blank">Congestion Control in Linux TCP</a></li>
<li><a href="http://www.ibm.com/developerworks/linux/library/l-linux-networking-stack/" target="_blank">Anatomy of the Linux networking stack &#8212; From sockets to device drivers</a></li>
<li><a href="http://www.linux-ip.net/html/" target="_blank">Guide to IP Layer Network Administration with Linux</a></li>
<li><span style="color: #888888;">Linux内核源码剖析 —TCP/IP实现</span></li>
<li><span style="color: #888888;">Understand Linux Network Kernel</span></li>
<li><span style="color: #888888;">TCP/IP Illustrated, Vol. 2</span></li>
<li><span style="color: #888888;">RFC793, RFC1122, RFC1323, RFC2001, RFC2018, RFC2581</span></li>
</ul>
<p><span style="color: #888888;">translated by ripwu, 个人主页: <a href="http://godorz.info">http://godorz.info</a></span></p>
<hr />
<p>&nbsp;</p>
<h3>sk_buff</h3>
<p><strong>结构体定义</strong><br />
<DIV><DIV><FONT color=#0000ff><B>struct</B></FONT> sk_buff<BR>{&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* These two members must be first. */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> sk_buff <FONT color=#333399>*</FONT> next;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Next buffer in list */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> sk_buff <FONT color=#333399>*</FONT> prev;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Previous buffer in list */</FONT>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> sk_buff_head <FONT color=#333399>*</FONT> list;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* List we are on */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> sock <FONT color=#333399>*</FONT>sk;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Socket we are owned by */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> timeval stamp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Time we arrived */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> net_device <FONT color=#333399>*</FONT>dev;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Device we arrived on/are leaving by */</FONT>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Transport layer(TCP, UDP, ICMP, etc). header */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>union</B></FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> tcphdr <FONT color=#333399>*</FONT>th;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> udphdr <FONT color=#333399>*</FONT>uh;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> icmphdr <FONT color=#333399>*</FONT>icmph;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> igmphdr <FONT color=#333399>*</FONT>igmph;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> iphdr <FONT color=#333399>*</FONT>ipiph;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> spxhdr <FONT color=#333399>*</FONT>spxh;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>char</B></FONT> <FONT color=#333399>*</FONT>raw;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;} h;&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Network layer header (IPv4, IPv6, arp, raw, etc).&nbsp;&nbsp;*/</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>union</B></FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> iphdr <FONT color=#333399>*</FONT>iph;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> ipv6hdr <FONT color=#333399>*</FONT>ipv6h;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> arphdr <FONT color=#333399>*</FONT>arph;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> ipxhdr <FONT color=#333399>*</FONT>ipxh;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>char</B></FONT> <FONT color=#333399>*</FONT>raw;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;} nh;&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Link layer header */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>union</B></FONT>&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> ethhdr <FONT color=#333399>*</FONT>ethernet;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>char</B></FONT> <FONT color=#333399>*</FONT>raw;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;} mac;&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> dst_entry <FONT color=#333399>*</FONT>dst;&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>char</B></FONT> cb[<FONT color=#6e00aa>48</FONT>];&nbsp;&nbsp;&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>int</B></FONT> len;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Length of actual data */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>int</B></FONT> data_len;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>int</B></FONT> csum;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Checksum */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>char</B></FONT> __unused, <FONT color=#008000>/* Dead field, may be reused */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cloned,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* head may be cloned (check refcnt to be sure). */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pkt_type,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Packet class */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ip_summed;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Driver fed us an IP checksum */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;__u32 priority;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Packet queueing priority */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;atomic_t users;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* User count &#8211; see datagram.c,tcp.c */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>short</B></FONT> protocol; <FONT color=#008000>/* Packet protocol from driver. */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>short</B></FONT> security; <FONT color=#008000>/* Security level of packet */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>int</B></FONT> truesize;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Buffer size */</FONT>&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>char</B></FONT> <FONT color=#333399>*</FONT>head;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Head of buffer */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>char</B></FONT> <FONT color=#333399>*</FONT>data;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Data head pointer */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>char</B></FONT> <FONT color=#333399>*</FONT>tail;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* Tail pointer */</FONT>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>unsigned</B></FONT> <FONT color=#0000ff><B>char</B></FONT> <FONT color=#333399>*</FONT>end;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* End pointer */</FONT>&nbsp;<BR>}</DIV></DIV><br />
<strong>布局(Layout)</strong></p>
<p>一个sk_buff对应的内存抽象图示为:</p>
<p><img class="aligncenter size-full wp-image-1077" title="sk_buff" src="http://godorz.info/wp-content/uploads/2011/09/sk_buff.png" alt="" width="361" height="311" />为了实现sk_buff在各层间传输时的零拷贝,采用的是将数据放在内存最后,增减报文头时在内存前面操作,然后重新设置相关指针即可.下图演示了一个UDP数据包在发送时添加UDP报头和IP报头的过程:</p>
<p><img class="aligncenter size-full wp-image-1079" title="sk_buff_modify" src="http://godorz.info/wp-content/uploads/2011/09/sk_buff_modify.png" alt="" width="600" height="343" />sk_buff的实现方式是双向链表.和所有的链表一样,sk_buff含有指向下一元素的next指针和指向前一元素的prev指针,为了更快的知道某个sk_buff元素属于哪一链表,sk_buff还包含一个指向头结点的list指针.<br />
<DIV><DIV><FONT color=#0000ff><B>struct</B></FONT> sk_buff_head {<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#008000>/* These two members must be first. */</FONT><BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> sk_buff <FONT color=#333399>*</FONT> next;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#0000ff><B>struct</B></FONT> sk_buff <FONT color=#333399>*</FONT> prev;<BR>&nbsp;&nbsp;&nbsp;&nbsp;_ _u32 qlen;<BR>&nbsp;&nbsp;&nbsp;&nbsp;spinlock_t lock;<BR>};</DIV></DIV><br />
如上所示,链表的头结点比较特殊,它包括了链表长度qlen和用于同步的自旋锁lock.</p>
<p>sk_buff元素组成的链表结构图如下:</p>
<p><img class="aligncenter size-full wp-image-1078" title="sk_buff_list" src="http://godorz.info/wp-content/uploads/2011/09/sk_buff_list.png" alt="" width="529" height="373" /><strong>创建和销毁</strong></p>
<p>alloc_skb()负责sk_buff的创建,从sk_buff结构体的定义可以看到,当创建一个sk_buff时,总共需要申请两块内存,一块内存存储sk_buff本身(通过kmem_cache_alloc()),另一块内存存储sk_buff.data指向的数据区(通过kmalloc()).示意图如下:</p>
<p><img class="aligncenter size-full wp-image-1080" title="alloc_skb" src="http://godorz.info/wp-content/uploads/2011/09/alloc_skb.png" alt="" width="621" height="382" />其中, Padding的作用是字节对其. skb_shared_info这里不做介绍,详细信息参考&lt;Understand Linux Network Kernel&gt;.</p>
<p>sk_buff的销毁稍微复杂,但原理很简单,这里也不做介绍.</p>
<h3>2. 网络层</h3>
<p>本节介绍底层接收和处理packet(数据包)的流程.</p>
<p>接收packet的过程如下:</p>
<p><strong>1. 网卡接收packet.</strong></p>
<p>这些packet由DMA机制存储在最近使用的网卡的rx_ring中.rx_ring是一种工作于内核态的环形队列,其容量大小依赖于具体的网卡.有一种比较老的网卡,工作方式为轮询(PIO)模式: 由CPU将网卡数据读入内存态内存.</p>
<p><strong>2. 网卡产生中断.</strong></p>
<p>CPU开始执行网卡驱动的ISR代码,在2.4.19内核版本后,工作方式有些区别:</p>
<p><strong>对于2.4.19版本之前(包括2.4.19)的内核:</strong></p>
<p><img class="aligncenter size-full wp-image-1083" title="receiver-old" src="http://godorz.info/wp-content/uploads/2011/09/receiver-old.png" alt="" width="380" height="406" />如上图所示,中断处理程序将调用netif_rx()(net/dev/core.c). netif_rx()将接收到的数据压入被中断CPU的backlog队列,然后调度一个软中断(softirq, 一种内核行为,见<a href="http://tldp.org/HOWTO/KernelAnalysis-HOWTO-5.html" target="_blank"> http://tldp.org/HOWTO/KernelAnalysis-HOWTO-5.html</a> 和  <a href="http://www.netfilter.org/unreliable-guides/kernel-hacking/lk-hacking-guide.html" target="_blank">http://www.netfilter.org/unreliable-guides/kernel-hacking/lk-hacking-guide.html</a> <span style="color: #888888;">译注: 这里实际是指&#8221;下半段&#8221;,与硬中断的&#8221;上半段&#8221;呼应,见&lt;Linux内核设计与实现&gt;</span> <span style="color: #ff0000;">Update[2011-09-06] 译注错误:  软中断是bottom half的扩展,两者不应混为一谈. 操作系统之所以独立分开一个top half的概念,主要是因为中断是在CPU关中断(<a href="http://en.wikipedia.org/wiki/CLI_%28x86_instruction%29" target="_blank">CLI</a>)的情况下运行的,为了不丢失新到的中断信号,操作系统将中断服务程序一分为二,前者(top half)对其执行时间要求比较严格,一般立刻响应中断请求,为保证原子操作,它在关中断情况下执行,后者(bottom half)对时间要求没那么严格,通常在开中断下执行.但是,bottom half有一个致命的缺点,它只能在一个CPU上运行,也就是严格的串行,为了更充分的利用SMP,Linux2.4内核扩展了bottom half,成果便是所谓的&#8221;软中断(softirq)&#8221;机制,软中断最大的优点在于它可以在多CPU上运行,它在设计与实现中自始自终都贯彻了一个思想: &#8220;谁触发,谁执行(Who marks, Who runs),即触发软中断的那个CPU负责执行它所触发的软中断,而且每个 CPU 都由它自己的软中断触发与控制机制.</span>), 由 软中断来负责packet的进一步处理(比如说: TCP/IP 协议的处理). backlog队列的 长度为300个packet大小(/proc/sys/net/core/netdev_max_backlog).</p>
<p>当backlog队列为满时,它转入throttle状态,在此状态等待数据清空,然后重新回到正常状态,正常状态下,backlog允许packet的入队操作.(netif_rx(), net/dev/core.c). 当backlog队列处于throttle状态时, netif_rx()将丢弃新来的packet.  我们可以通过/proc/net/softnet_stats来查看backlog队列的统计信息: 每一行对应一颗CPU,前两列分别是packet量和丢包量,第三列表示backlog队列有多少次进入了throttle状态.</p>
<p><strong>对于2.4.19版本之后的内核:</strong></p>
<p><img class="aligncenter size-full wp-image-1084" title="receiver-new" src="http://godorz.info/wp-content/uploads/2011/09/receiver-new.png" alt="" width="360" height="416" />如上图所示,内核采用的是NAPI新机制(见<a href="http://en.wikipedia.org/wiki/NAPI" target="_blank">http://en.wikipedia.org/wiki/NAPI</a>): 中断处理程序调用netif_rx_schedule()(include/linux/netdevice.h).该函数执行入队的对象不是packet,而是packet的一个引用(reference).(softnet_data&gt;poll_list; include/linux/netdevice.h). 与旧内核机制相同的是,中断处理程序同样会调度一个软中断.为了向后兼容, 在NAPI机制中, backlog被当作像网卡一样可以处理所有到达packet的虚拟设备.内核开发者重写了netif_rx(),该函数将packet压入backlog之后,又将backlog压入CPU的poll_list链表.</p>
<p><strong>3. 软中断执行net_rx_action()(net/core/dev.c).</strong></p>
<p>在这一步中,新老内核处理方式同样有所不同:</p>
<p><strong>对于2.4.19版本之前(包括2.4.19)的内核:</strong></p>
<p>net_rx_action()为backlog队列中的所有packet调用**_rcv()函数(net/ipv4/ip_input.c),这里的**指代不同的packet类型,如ip, arp, bootp等.</p>
<p><strong>对于NAPI:</strong></p>
<p>CPU轮训其poll_list链表中的设备(注: NAPI中backlog为虚拟设备, process_backlog: net/core/dev.c),从设备的rx_ring环形队列中获得全部packet.CPU的轮询是通过 netif_receive_skb()(net/core/dev.c)调用ip_rcv()来完成的.</p>
<p>当一个packet被创建时,内核做了如下工作:</p>
<p style="padding-left: 30px;"><strong>*</strong>IP packet由arp_constructor()创建.每个packet都包含dst field信息,dst提供了路由所需的目的地址,它对应一个output方法,对于IP packet而言,此方法为dev_queue_xmit().</p>
<p style="padding-left: 30px;"><strong>*</strong>内核提供了多种排队原则(queueing disciplines, 简称qdisc).默认的排队原则使用FIFO队列,其缺省长度为100个packet大小.(ether_setup(): dev-&gt;tx_queue_len ; drivers/net/net_init.c),此长度可以通过带txqueuelen选项的ifconfig命令进行设置. 我们无法查看默认FIFO的统计信息,这里提供一个小技巧,通过tc 命令可以设置新的FIFO,以取代缺省的qdisc:</p>
<blockquote>
<ul>
<li>取代缺省的qdisc: tc qdisc add dev eth0 root pfifo limit 100</li>
<li>查看统计信息: tc -s -d qdisc show dev eth0</li>
<li>重新设置缺省qdisc: tc qdisc del dev eth0 root</li>
</ul>
</blockquote>
<p><img class="aligncenter size-full wp-image-1085" title="transmitter" src="http://godorz.info/wp-content/uploads/2011/09/transmitter.png" alt="" width="345" height="368" /><strong>1.</strong>对从IP层(IP layer)传入的packet,调用dev_queue_xmit()(net/core/dev.c).该函数将packet压入外发网卡(output interface, 它由路由决定)相应的qdisc.如果网卡驱动正常, qdisc_restart()(net/sched/sch_generic.c)将处理qdisc中的所有packet;<strong></strong></p>
<p><strong>2.</strong>调用hard_start_xmit().该方法实现于网卡驱动,它将packet压入tx_ring环形队列,然后网卡驱动将通知网卡有数据可发送.<strong></strong></p>
<p><strong>3.</strong> 网卡发送packet并通知CPU. CPU调用net_tx_action()(net/core/dev.c)将packet压入completion_queue,然后调度一个负责释放packet内存的软中断.网卡与CPU之间的通信方式是硬件相关的,这里不作详细介绍.</p>
<h3>3. 传输层</h3>
<p><strong></strong>IP相关文件有:</p>
<ul>
<li>ip_input.c         –  处理到达packet</li>
<li>ip_output.c      –  处理发送packet</li>
<li>ip_forward.c    –  处理由本机路由的packet</li>
</ul>
<p>还有一些模块负责处理 IP packet fragmentation(ip_fragment.c),IP options(ip_options.c), multicast(ipmr.c) 和 IP over IP (ipip.c)</p>
<p>下图描述了packet在IP层的流转路径. 当一个packet到达主机后,如前文所述种种流程,net_rx_action()将它转给ip_rcv()处理. 在经过first netfilter hook后, ip_rcv_finish()验证该packet的目的地是否就是本机.如果是的话, ip_rcv_finish()将该packet传给ip_local_delivery(), 然后由ip_local_delivery()转发给合适的传输层(transport layer)处理函数(tcp, udp, etc).</p>
<p style="text-align: center;"><img class="aligncenter size-full wp-image-1086" title="network-layer-data-path" src="http://godorz.info/wp-content/uploads/2011/09/network-layer-data-path.png" alt="" width="645" height="484" />如果IP packet的目的地址是其他主机,那么处理该packet的当前主机就起了router的角色(这种场景在小型局域网上很常见). 如果主机允许转发packet(通过/proc/sys/net/ipv4/ip_forward查看或设置),那么该packet将由一系列复杂但高效的函数处理.</p>
<p>如果路由表(它是一种hash表)中存在packet的路由信息的话,该packet的行经路径通过ip_route_input()查找,否则,通过ip_route_input_slow(). ip_route_input_slow()调用fib(<a href="http://en.wikipedia.org/wiki/Forwarding_information_base" target="_blank">Forward informationbase</a>, 路由表)族函数,这些函数定义在fib*.c文件中. FIB结构体非常复杂.</p>
<p>如果packet是多播(multicast)数据包,那么内核通过ip_route_input_mc()计算出packet发往的那些设备(该情况下,packet目的地址不变).</p>
<p>在计算出packet的路由信息之后,内核往IP pcaket插入新的目的地址,并将其所属设备信息插入对应的sk_buff结构. 然后,packet被转发(forwarding)函数(ip_forward() 和 ip_forward_finish()).</p>
<p>一个packet也可以从上层发往ip层(通过 TCP or UDP 传输). 处理该packet的第一个函数ip_queue_xmit(),它通过ip_output()将packet发往output part.</p>
<p>output part对packet做了最后的修改. dev_queue_transmit()将packet压入输出队列(output queue),然后通过q-&gt;disc_run()来调用网络调度机制(network scheduler mechanism).该指针指向不同的函数,这取决于内核采用的调度器(默认为FIFO类型,可以通过tc工具进行设置).</p>
<p>调度函数(qdisc_restart() 和 dev_queue_xmit_init())独立于IP层其他函数.</p>
<h3>4. TCP</h3>
<p>本节介绍Linux内核网络协议栈中最为复杂的TCP.</p>
<p><strong>Introduction</strong></p>
<p>TCP相关文件有:</p>
<ul>
<li>tcp_input.c           -  最大的一个文件,它处理网络中到达的packet</li>
<li>tcp_output.c        -  处理将发往网络的packet</li>
<li>tcp.c                         -  TCP代码</li>
<li>tcp_ipv4.c             -  IPv4具体的TCP代码</li>
<li>tcp_timer.c           &#8211; 定时器</li>
<li>tcp.h                         &#8211; 定义TCP结构体</li>
</ul>
<p>TCP数据的处理如下两图,上图处理接收,下图处理发送.</p>
<p><img class="aligncenter size-full wp-image-1087" title="TCP-1" src="http://godorz.info/wp-content/uploads/2011/09/TCP-1.png" alt="" width="596" height="681" /><img class="aligncenter size-full wp-image-1088" title="TCP-2" src="http://godorz.info/wp-content/uploads/2011/09/TCP-2.png" alt="" width="588" height="691" /><strong>TCP Input (mainly tcp_input.c }</strong></p>
<p>TCP input在TCP实现中占了很大一部分.它处理TCP packet的接收,因为TCP实体(TCP entity, 也就是TCP协议栈)可以同时处于接收和发送两种状态,所以这两类代码混杂在了一起.</p>
<p>ip_local_deliver()将packet从IP层发往TCP层.它把packet传给ipproto-&gt;handler,在IPv4的实现中该handler就是tcp_v4_rcv().此函数进一步调用tcp_v4_do_rcv().</p>
<p>tcp_v4_do_rcv()会根据TCP连接(connection)的不同状态调用不同的函数.如果连接已建立(TCP_ESTABLISHED),它会调用tcp_rcv_established(),这是我们接下来会重点介绍的部分. 如果连接状态是TIME_WAIT,它会调用tcp_timewait_process().</p>
<p>对于其他的状态, tcp_v4_do_rcv()统一调用tcp_rcv_state_process().对于SYS_ENT状态的连接,该函数进一步调用tcp_rcv_sysent_state_process().</p>
<p>tcp_rcv_state_process()和tcp_timewait_process()必须初始化TCP结构体,这通过tcp_init_buffer_space()和tcp_init_metrics()完成. tcp_init_metrics()调用tcp_init_cwnd()来初始化其拥塞窗口(congestion window).</p>
<p><strong>tcp_rcv_established()</strong></p>
<p>tcp_rcv_established()有两条分支路径.我们首先介绍慢路径(slow path)分支,因为它简单清晰,另一分支留待后文介绍.</p>
<p><strong>slow path</strong></p>
<p>在RFC中,slow path只要有7步操作:</p>
<ul>
<li>tcp_checksum_complete_user()计算校验码(checksum).如果检验码有误,packet被丢弃.</li>
<li>tcp_paws_discard()负责PAWS(Protection Against Wrapped Sequence Numbers).</li>
<li>STEP 1 &#8211; 检查packet序列号(sequence). 如果序列号不再接收端口中,接收模块(receiver)将 通过tcp_send_dupack()发回一个DUPACK.tcp_send_dupack()通过tcp_dsack_set()设置一个SACK,然后调用tcp_send_ack()进行发送.</li>
<li>STEP 2 &#8211; 检查RST位(th-&gt;rst),如果该位被置位,调用tcp_reset().</li>
<li>STEP 3 &#8211; 检查安全性(security)和优先级(precedence, RFC建议,但内核未实现)</li>
<li>STEP 4 &#8211; 检查SYN位,如果该位被置位,调用tcp_reset()&#8230;</li>
<li>通过tcp_replace_ts_recent()估算RTT(RTTM).</li>
<li>STEP 5 -检查ACK位,如果该位被置位,调用tcp_ack().</li>
<li>STEP 6 -检查URG位,如果该位被置位,调用tcp_urg().</li>
<li>STEP 7 -通过tcp_data_queue()处理packet携带的数据.</li>
<li>通过tcp_data_snd_check()检查是否有数据要发回.该函数将调用TCP发送模块( output sector)的tcp_write_xmit().</li>
<li>Finally, 通过tcp_ack_snd_check()检查是否有ACK要发回.ACK的发回有两种情况: 通过tcp_send_ack()直接发回; 或者通过tcp_send_delayed_ack()延时发回(译注: 这是TCP的延时确认(Delayed ACK)机制,目的为减少分组,可通过/proc/sys/net/ipv4/tcp_delack_min设置),被延时的ACK存储在tcp-&gt;ack.pending.</li>
</ul>
<p><strong>tcp_data_queue()</strong></p>
<p>tcp_data_queue()负责处理packet数据.如果packet顺序到达(所有之前的packet已到达),它将把数据拷贝到tp-&gt;ucopy.iov (skb_copy_datagram_iovec(skb, 0, tp-&gt;ucopy.iov, chunk)).</p>
<p>如果packet并非顺序到达,那么它将通过tcp_ofo_queue()把packet压入乱序队列(out of order queue).</p>
<p>如果乱序队列满,根据RFC 2581文档4.2节, 协议栈将返回一个ACK(tp-&gt;ack.pingpong 被置0,tcp_ack_snd_check()被调用来返回ACK).</p>
<p>packet的到达引起各种反应,这由tcp_event_data_recv()处理. 它首先通过tcp_schedule_ack()调度一个ACK的发回,然后通过tcp_measure_rcv_mss()估算MSS(Maximum Segment Size).在特定的情况下(如: 慢启动), tcp_event_data_recv()选择调用tcp_incr_quickack(),以立刻返回ACK(即不使用延时确认机制).最后, tcp_event_data_recv()通过tcp_grow_window()增大通告窗口(advertised window, <span style="color: #888888;">译注: 其实也就是receive window,即接收窗口,更多参数设定见 <a href="http://140.116.72.80/%7Esmallko/ns2/TCPparameter.htm" target="_blank">http://140.116.72.80/~smallko/ns2/TCPparameter.htm</a></span>).</p>
<p>tcp_data_queue()最终将检查FIN位,如果该位被置位,调用tcp_fin().</p>
<p><strong>tcp_ack()</strong></p>
<p>对发送模块(&#8220;sender&#8221;)而言,它每收到一个ACK,都会调用tcp_ack(),注意这一点不要和接收模块(&#8220;receiver&#8221;)通过tcp_send_ack()调用tcp_write_xmit()发送ACK的行为混淆了.</p>
<p>tcp_ack()首先检查ACK是否比先前收到的ACK较老还是较新,如果较老的话,内核通过执行 uninteresting_ack和old_ack无视之.</p>
<p>如果一切正常, tcp_ack()将通过tcp_ack_update_window()和/或tcp_update_wl()更新发送端的拥塞窗口.</p>
<p>如果ACK可疑(dubious) ,那么通过tcp_fastretrans_alert()进入快速重传(fast retransmit).</p>
<p><strong>tcp_fast_retransmit()</strong></p>
<p>tcp_fast_retransmit_alert()只由tcp_ack()在特定条件下调用.为了更好的理解这些特定条件,我们必须对<br />
NewReno/SACK/FACK/ECN状态有深入的认识.注意这个TCP状态机本身没有什么关系,在这里连接状态几乎可以确定是TCP_ESTABLISHED.</p>
<p>拥塞控制状态有:</p>
<ul>
<li>&#8220;Open&#8221; &#8211; 正常状态,无可疑事件,属于快速路径(fast path).</li>
<li>&#8220;Disorder&#8221; -当发送方检测到DACK(重复确认)或者SACK(selective ACK带选择的ACK, 见<a href="http://simohayha.iteye.com/blog/578744" target="_blank">http://simohayha.iteye.com/blog/578744</a>)时会进入该状态,它可以看成是Open状态,之所以会将&#8221;DisOrder&#8221;从&#8221;Open&#8221;中分离出来,主要是为了将某些处理从快速路径(fast path)移到慢速路径(slow one).<span style="color: #888888;"> (译注: 在这个状态,拥塞窗口不会被调整,每一次新的输入数据包都会触发一个新的端的传输)</span></li>
<li>&#8220;CWR&#8221;(拥塞窗口减小) &#8211; 某些拥塞通知事件(Congestion Notification event)将减小CWND<span style="color: #888888;">(译注: 即congestion window拥塞窗口).</span> 这些事件包括ECN<span style="color: #000000;">(Explicit Congestion Notification显式拥塞通知,见</span><a href="http://en.wikipedia.org/wiki/Explicit_Congestion_Notification" target="_blank">http://en.wikipedia.org/wiki/Explicit_Congestion_Notification</a>), ICMP远端抑制(ICMP source quench, 见<a href="http://en.wikipedia.org/wiki/ICMP_Source_Quench" target="_blank">http://en.wikipedia.org/wiki/ICMP_Source_Quench</a>)和本机设备的拥塞.<span style="color: #808080;">(译注:当接收到一个拥塞通知时,发送方并不立刻减小拥塞窗口,而是每隔一个新收到的ACK减小一段知道拥塞窗口大小减半为止,发送方在减小拥塞窗口的过程中不会有明显的重传. CWR状态可以被Recovery或Loss状态中断.)</span></li>
<li>&#8220;Recovery&#8221; &#8211; 该状态下CWND被减小,正在快速重传(fast-retransmitting). <span style="color: #808080;">(译注: 默认情况下,进入Recovery状态的条件是三个连续的重复ACK.在Recovery状态期间,拥塞窗口的大小每隔一个新到的确认减少一个段,这和CWR状态类似.窗口减小直到刚进入Recovery状态时窗口大小的一半为止.拥塞窗口在恢复状态期间不增大,发送方重传那些被标记为丢失的段,或者根据包守恒原则在新数据上标记前向传输.发送方保持Recovery状态直到所有进入Recovery状态时正在发送的数据段都成功地被确认,之后该发送方恢复Open状态.重传超时有可能中断Recovery状态.)</span></li>
<li>&#8220;Loss&#8221; &#8211; 当RTO到期(RTO timeout)或者SACK拒绝(SACK reneging, <span style="color: #888888;">译注: 表示接收到的ACK的确认已经被先前的SACK确认过</span>),发送方进入Loss状态.CWDN减小而后增大.<span style="color: #808080;"> (译注: Loss状态下所有正在发送的数据段标记为loss,拥塞窗口重置为1,发送方然后按慢启动算法增大拥塞窗口.Loss和Recovery状态的一个主要区别是: 在Loss状态,拥塞窗口在发送方重置为一个段后增大,而Recovery状态下拥塞窗口只能被减小. Loss状态不能被其他的状态中断,因此,发送方只能在所有Loss开始时正在传输的数据都成功得到确认ack后,才能回到Open状态,例如, 快速重传(fast retransmitting)不能在Loss状态期间被触发)</span></li>
</ul>
<p>这些状态保存于tp-&gt;ca_state,对应值分别是TCP_CA_Open, TCP_CA_Disorder, TCP_CA_Cwr, TCP_CA_Recover 和 TCP_CA_Loss.</p>
<p>当收到ACK时状态不为Open,或者收到了奇怪的ACK(如SACK, DUPACK ECN ECE),内核会调用tcp_fastretrans_alert().<span style="color: #808080;"> (译注: 此函数非常重要,它负责拥塞控制的处理,包括处理显式拥塞通知,判断SACK是否虚假,拥塞时记录的SND,NXT被确认时进行撤销,以及当前状态的处理等.详细见<a href="http://research.csc.ncsu.edu/netsrv/sites/default/files/hystart_techreport_2008.pdf" target="_blank"><span style="color: #808080;">http://research.csc.ncsu.edu/netsrv/sites/default/files/hystart_techreport_2008.pdf</span></a>)</span></p>
<p><strong>fast path</strong></p>
<p>在TCP处理过程中,receiver比较简单,因此它经常进入快速路径.</p>
<p><strong>SACKs</strong></p>
<p>Linux TCP协议栈完整实现了SACKS(带选择的ACK).SACK信息存储于tp-&gt;sack_ok field.</p>
<p><strong>quickacks</strong></p>
<p>某些情况下,receiver进入quickack模式,也就是说,延时确认机制(delayed ACK)被禁用.在quickack模式下,连接开始时,tc_rcv_sysent_state_process()将调用tcp_enter_quick_ack_mode().</p>
<p><strong>Timeouts</strong></p>
<p>定时器对TCP的正确工作起到了至关重要的作用.比如说,借助定时器,TCP可以判断packet是否在网络中丢失.</p>
<p><img class="aligncenter size-full wp-image-1089" title="scheduling-a-timeout" src="http://godorz.info/wp-content/uploads/2011/09/scheduling-a-timeout.png" alt="" width="206" height="152" />当发送一个packet时,其重传定时器(retransmit timer)会被设置. tcp_push_pending_frames()通过tcp_check_probe_timer()调度一个软中断(软中断有内核中非网络协议栈相关的代码处理).</p>
<p>定时器的超时事件会产生一个软中断,软中断通过timer_bh()调用run_timer_list(). run_timer_list()将调用timer-&gt;function函数指针指向的tcp_wite_timer(). tcp_wite_timer()进而调用tcp_retransmit_timer(),最终, tcp_wite_timer()调用tcp_enter_loss(). tcp_enter_loss()将拥塞控制状态设为CA_Loss,然后由fastretransmit_alert()重传packet.</p>
<p><img class="aligncenter size-full wp-image-1090" title="timeout" src="http://godorz.info/wp-content/uploads/2011/09/timeout.png" alt="" width="588" height="134" /><strong>ECN</strong></p>
<p>ECN(Explicit Congestion Notification,即显式拥塞通知)代码比较简单, 几乎所有的代码都在/include/net目录下的tcp_ecn.h文件中. tcp_ack()调用TCP_ECN_rcv_ecn_echo()来处理ECN packet.</p>
<p><strong>TCP output</strong></p>
<p>这部分代码(主要在tcp_output.c)负责packet(包括sender发送的数据packet和receiver发回的ACK)的发送.</p>
<h3>5. UDP</h3>
<p>本节简单介绍UDP,它很重要,但比起TCP,其拥塞控制却简单多了.</p>
<p>UDP相关文件:</p>
<ul>
<li>net/ipv4/udp.c</li>
</ul>
<p><img class="aligncenter size-full wp-image-1091" title="UDP" src="http://godorz.info/wp-content/uploads/2011/09/UDP.png" alt="" width="522" height="321" />当packet通过ip_local_delivery()从IP层到达时,它被传给udp_rcv()(该函数角色类似于TCP的tcp_v4_rcv()). udp_rcv()调用sock_put()将packet压入socket queue.packet的传递就到此结束了.内核然后调用inet_recvmsg()(通过recvmsg()系统调用), inet_recvmsg()调用udp_recvmsg(), 而udp_recvmsg()进一步调用skb_rcv_datagram(). skb_rcv_datagram()函数从队列中获得packet,然后据此packet填充用户态将读取的结构体.</p>
<p>当一个packet从用户态到达时<span style="color: #808080;">(译注: 也就是说要发送出去)</span>,处理就更加简单了. inet_sendmsg()调用udp_sendmsg(), udp_sendmsg()通过从sk结构体获取的信息(这些信息在socket被创建和被绑定(bind调用)时被设置于sk中)填充UDP数据报(UDP datagram).</p>
<p>当UDP数据报填充完成之后,数据报被传给ip_build_xmit(), ip_build_xmit()通过ip_build_xmit_slow()创建IP packet.</p>
<p>IP packet创建完成以后, packet被传给ip_output(),正如4 – Network Layer 介绍的一样, ip_output()将packet发往更低层.</p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2011/09/linux-network-kernel/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>我的大学</title>
		<link>http://godorz.info/2011/05/my-undergraduate-year/</link>
		<comments>http://godorz.info/2011/05/my-undergraduate-year/#comments</comments>
		<pubDate>Sun, 08 May 2011 08:34:22 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[My Thoughts]]></category>
		<category><![CDATA[life]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=1030</guid>
		<description><![CDATA[这是一篇很诚实很man show但却毫无营养的自述文,慎入. 也许很少有高中同学知道,尽管成绩一向不错,但实际上我对数学充满了厌恶.高中3年机械式的训练,让我实在无法忍受,于是高考填报志愿时选了一个文科的方向,自以为从此天高云厚,憧憬着大学生活的我完全没有意识到此后的命运弄人带来的打击会有多大. 一分之差被选择了计算机专业,大学的傻逼生涯就此埋下了伏笔.整个大一都是在迷茫中度过,对于在大学第一学期,本应是最有干劲的新生来说,高等代数62分和数学分析60分已经足以说明了我的自暴自弃,虽然如此,此后的我居然一直没在数学之上挂科,这是很难想象的,唯一可能的答案也许是尚有一点点不甘吧.维系着成绩上的苟延残喘之余,所有的郁闷都被我发泄在读书上,我阅读了各种名人的传记,从曹操到希特勒,从艾森豪威尔到巴顿,从切格瓦拉到卡斯特罗,原来,所有人都在自己的方向一步步的努力,这个发现便是大一最大的收获了. 让人很苦的笑话 在拉格朗日下有一座微分方城,旁边有两条溪, 一条叫柯溪,一条叫数学分溪,两条溪又汇成了 解析几河,河边有棵线性代树,树上长满了傅里 叶,很多人就挂在上面了&#8230; 可惜的是,我找不到我的方向,在学习压力巨大的大二,我仍旧找不到前方.曾经的理想已经远去,未来的大门尚未敞开.这篇文章很好的记下了当时的想法.对于数学,我完全无爱,或许,它唯一能带给我快乐的也就只有右边的这个笑话了.我很想改变些什么,但却有心无力,每天浑浑噩噩,做一天和尚撞一天钟,痛苦,挣扎,便是大二全部的关键词. 上了大三,我恋爱了.正如2010总结所描述的那样,大三的前几个月,我终于找回了已经很久未曾体会过的幸福,逛街,看电影,每一天都是如此轻松,每一秒都是如此难忘,我连谈恋爱的时候都不够,又怎么顾得上去厌恶我的专业呢.沉浸,陶醉,如今回过头来想想,正是这几个月无拘无束的生活,让我对计算机系的感觉焕然一新.代码不再枯燥,编程不再无聊,甚至于,当我鬼使神差的捧起APUE时,细细咀嚼,竟然有了一种与巨匠前贤直面交谈的感觉,那一刻我爱上了Unix,我想这就是所谓的顿悟吧.自此之后,我着了魔,疯狂的想弄明白每一句代码背后的故事,地底三万尺,我愿意去深挖,我愿意去探险,这种感觉实在是太奇异了,我很享受在不同的层次间level up和level down,每一次思维上切换都能带来一种快感,如同高空坠落一般,真实,激烈,持久. 我入了门,虽然有点晚,但还不迟,毕竟better late than never.老丘说,努力就是了,于是,在接下来的几个月里,凭着初生牛犊的冲劲,我啃掉了Unix领域不少的经典图书,在此期间,还不忘回头把计算机,算法,网络等本该早已掌握的知识重新打牢,然后,因为无知,所以无畏,我去面试了.此后的事情如在眼前,我很幸运地得以在腾讯实习了4个多月,深感于才疏学浅,如今一直呆在学校里恶补,直到今天下午看到好友们对于毕业纷纷的感概,一时难以自已,便在这里写着这篇文章. Thank you!!! 其实,现在想来,大学四年里我是相当幸运的.首先是学校,虽然我常常不怀好意的称其为中国山寨大学,但实际上,中大是很自由,很轻松的一所大学.即便是某次我很嚣张的质问为什么在google上搜索世界杯一共打多少场比赛都会被重置时,辅导员都没有大动肝火,不过是交份检讨,积极分子除名了事.正是因为学校如此轻松的学风,让我后来可以毫无顾忌的肆意逃课,一心一意学习Linux网络编程.我很幸运,还因为大一时某次邹大牛(yuhan童鞋,尽管你不知道,但就是你了!!)Q群上不经意地说到搞Linux不错,这才让我这菜鸟有了初步印象,时隔两年之后走上了后台开发的道路,而且很幸运地在wenhack童鞋和linuxayn师兄的指点下,找准了入门的方向,从APUE到UNP到TCP/IP到POSA,一路下来,没有经历过多少弯路就越过了门槛.现在想起来,确实有种鬼使神差的感觉.最后,非常感谢给了我实习机会的面试官leo,还有诲人不倦谆谆教诲的导师denny,我真是太幸运了! 我只想要当年那个姑娘 一个苦行僧在柴房修行,邻居的姑娘多加照顾, 两人暗生情愫..姑娘同僧人表白,僧人拒绝了, 因为他有更深的梦想.年复一年,姑娘为人妇为 人母为一抔土,僧人得道.佛祖问,你既已得道, 我满足你一个愿望.僧人默然许久,说:&#8221;我只想 要当年那个姑娘.&#8221; 如果一定要说在大学中学到了什么,好吧,是视野.以前我从不相信什么天才,但是,一个师弟,一个初中就写过带图形界面的操作系统,汇编,c,算法,网络编程无所不通身上爆发者各种闪光点的孩子让我彻底明白了,天才是存在的,而且就在我身边.无数次震撼极大的扩大了我的视野,天外有天,人外有人,深深愧疚于自己的不足的我,一定要努力,像某人说的那样,做一只癞蛤蟆,不做青蛙. 最后,我想起了右边那个故事,回首4年大学生活,曾经浪费的时光再也找不回,但实际上我没有什么后悔的,年少时的错误正是成长的代价,拒绝了错误,也就拒绝了成长.]]></description>
			<content:encoded><![CDATA[<p>这是一篇很诚实很man show但却毫无营养的自述文,慎入.</p>
<p>也许很少有高中同学知道,尽管成绩一向不错,但实际上我对数学充满了厌恶.高中3年机械式的训练,让我实在无法忍受,于是高考填报志愿时选了一个文科的方向,自以为从此天高云厚,憧憬着大学生活的我完全没有意识到此后的命运弄人带来的打击会有多大.</p>
<p>一分之差被选择了计算机专业,大学的傻逼生涯就此埋下了伏笔.整个大一都是在迷茫中度过,对于在大学第一学期,本应是最有干劲的新生来说,高等代数62分和数学分析60分已经足以说明了我的自暴自弃,虽然如此,此后的我居然一直没在数学之上挂科,这是很难想象的,唯一可能的答案也许是尚有一点点不甘吧.维系着成绩上的苟延残喘之余,所有的郁闷都被我发泄在读书上,我阅读了各种名人的传记,从曹操到希特勒,从艾森豪威尔到巴顿,从切格瓦拉到卡斯特罗,原来,所有人都在自己的方向一步步的努力,这个发现便是大一最大的收获了.</p>
<div id="toc" style="float: right; margin: 0pt 0pt 5px 8px; padding: 0pt;">
<div id="toctitle" style="margin: 0pt 0pt 4px; padding: 3px 8px; font-weight: bold; text-align: center; border: 1px solid #d0ddd0; background: none repeat scroll 0% 0% #f0fff0; ">让人很苦的笑话</div>
<div id="tocinside" style="margin: 4px 0pt 0pt; padding: 10px; border: 1px solid #d0ddd0; background: none repeat scroll 0% 0% #f0fff0;">在拉格朗日下有一座微分方城,旁边有两条溪,<br />
一条叫柯溪,一条叫数学分溪,两条溪又汇成了<br />
解析几河,河边有棵线性代树,树上长满了傅里<br />
叶,很多人就挂在上面了&#8230;</div>
</div>
<p>可惜的是,我找不到我的方向,在学习压力巨大的大二,我仍旧找不到前方.曾经的理想已经远去,未来的大门尚未敞开.<a href="http://godorz.info/2009/07/there-is-no-remembrance-of-men-of-old/">这篇文章</a>很好的记下了当时的想法.对于数学,我完全无爱,或许,它唯一能带给我快乐的也就只有右边的这个笑话了.我很想改变些什么,但却有心无力,每天浑浑噩噩,做一天和尚撞一天钟,痛苦,挣扎,便是大二全部的关键词.</p>
<p>上了大三,我恋爱了.正如<a href="http://godorz.info/2011/01/my-2010/">2010总结</a>所描述的那样,大三的前几个月,我终于找回了已经很久未曾体会过的幸福,逛街,看电影,每一天都是如此轻松,每一秒都是如此难忘,我连谈恋爱的时候都不够,又怎么顾得上去厌恶我的专业呢.沉浸,陶醉,如今回过头来想想,正是这几个月无拘无束的生活,让我对计算机系的感觉焕然一新.代码不再枯燥,编程不再无聊,甚至于,当我鬼使神差的捧起APUE时,细细咀嚼,竟然有了一种与巨匠前贤直面交谈的感觉,那一刻我爱上了Unix,我想这就是所谓的顿悟吧.自此之后,我着了魔,疯狂的想弄明白每一句代码背后的故事,地底三万尺,我愿意去深挖,我愿意去探险,这种感觉实在是太奇异了,我很享受在不同的层次间level up和level down,每一次思维上切换都能带来一种快感,如同高空坠落一般,真实,激烈,持久.</p>
<p>我入了门,虽然有点晚,但还不迟,毕竟better late than never.老丘说,努力就是了,于是,在接下来的几个月里,凭着初生牛犊的冲劲,我啃掉了Unix领域不少的经典图书,在此期间,还不忘回头把计算机,算法,网络等本该早已掌握的知识重新打牢,然后,因为无知,所以无畏,我去面试了.此后的事情如在眼前,我很幸运地得以在腾讯实习了4个多月,深感于才疏学浅,如今一直呆在学校里恶补,直到今天下午看到好友们对于毕业纷纷的感概,一时难以自已,便在这里写着这篇文章.</p>
<div id="toc" style="float: left; margin: 0pt 8pt 5px 0px; padding: 0pt;">
<div id="toctitle" style="margin: 0pt 0pt 4px; padding: 3px 8px; font-weight: bold; text-align: center; border: 1px solid #d0ddd0; background: none repeat scroll 0% 0% #f0fff0; ">Thank you!!!</div>
</div>
<p>其实,现在想来,大学四年里我是相当幸运的.首先是学校,虽然我常常不怀好意的称其为中国山寨大学,但实际上,中大是很自由,很轻松的一所大学.即便是某次我很嚣张的质问为什么在google上搜索世界杯一共打多少场比赛都会被重置时,辅导员都没有大动肝火,不过是交份检讨,积极分子除名了事.正是因为学校如此轻松的学风,让我后来可以毫无顾忌的肆意逃课,一心一意学习Linux网络编程.我很幸运,还因为大一时某次邹大牛(yuhan童鞋,尽管你不知道,但就是你了!!)Q群上不经意地说到搞Linux不错,这才让我这菜鸟有了初步印象,时隔两年之后走上了后台开发的道路,而且很幸运地在wenhack童鞋和linuxayn师兄的指点下,找准了入门的方向,从APUE到UNP到TCP/IP到POSA,一路下来,没有经历过多少弯路就越过了门槛.现在想起来,确实有种鬼使神差的感觉.最后,非常感谢给了我实习机会的面试官leo,还有诲人不倦谆谆教诲的导师denny,我真是太幸运了!</p>
<div id="toc" style="float: right; margin: 0pt 0pt 5px 8px; padding: 0pt;">
<div id="toctitle" style="margin: 0pt 0pt 4px; padding: 3px 8px; font-weight: bold; text-align: center; border: 1px solid #d0ddd0; background: none repeat scroll 0% 0% #f0fff0; ">我只想要当年那个姑娘</div>
<div id="tocinside" style="margin: 4px 0pt 0pt; padding: 10px; border: 1px solid #d0ddd0; background: none repeat scroll 0% 0% #f0fff0;">
一个苦行僧在柴房修行,邻居的姑娘多加照顾,<br />
两人暗生情愫..姑娘同僧人表白,僧人拒绝了,<br />
因为他有更深的梦想.年复一年,姑娘为人妇为<br />
人母为一抔土,僧人得道.佛祖问,你既已得道,<br />
我满足你一个愿望.僧人默然许久,说:&#8221;我只想<br />
要当年那个姑娘.&#8221;
</div>
</div>
<p>如果一定要说在大学中学到了什么,好吧,是视野.以前我从不相信什么天才,但是,一个师弟,一个初中就写过带图形界面的操作系统,汇编,c,算法,网络编程无所不通身上爆发者各种闪光点的孩子让我彻底明白了,天才是存在的,而且就在我身边.无数次震撼极大的扩大了我的视野,天外有天,人外有人,深深愧疚于自己的不足的我,一定要努力,像某人说的那样,做一只癞蛤蟆,不做青蛙.</p>
<p>最后,我想起了右边那个故事,回首4年大学生活,曾经浪费的时光再也找不回,但实际上我没有什么后悔的,年少时的错误正是成长的代价,拒绝了错误,也就拒绝了成长.</p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2011/05/my-undergraduate-year/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>I was broken.</title>
		<link>http://godorz.info/2011/04/without-you-i-was-broken/</link>
		<comments>http://godorz.info/2011/04/without-you-i-was-broken/#comments</comments>
		<pubDate>Wed, 06 Apr 2011 08:37:10 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[My Thoughts]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=993</guid>
		<description><![CDATA[爸爸妈妈吵架了. 回想起在家的这几天,沉重的气氛让人眩晕.肃静,冷清取代了往日的欢声笑语,孤独的夜,暴风雨暗暗积攒着能量,秋风刮出了多少感伤,天昏地暗间,一声吵骂打破了最后的安详,在毫无保留地释放着的,是填不平的怒火与绝望.曾经举案齐眉,如今仇深似海,黑白电影般参差破烂的画片,断断续续的在上演,吱嘎吱嘎,末了,一曲绝唱,几许苍凉. 世间再无比这更让人捶胸顿足,徒呼奈何的悲剧了.手心是爸,手背是妈,手心手背,血浓于水.父母,恨不得也说不得,再多的坚强也是徒劳,再多的努力也将付之东流.我的心,碎了,头皮发麻,四肢乏力,已然内伤. 天啊,我已看不清前方. &#8220;Do not go gentle into that good night. Rage, rage against the dying of the light.&#8221; &#8211; Dylan Thomas]]></description>
			<content:encoded><![CDATA[<p>爸爸妈妈吵架了.</p>
<p>回想起在家的这几天,沉重的气氛让人眩晕.肃静,冷清取代了往日的欢声笑语,孤独的夜,暴风雨暗暗积攒着能量,秋风刮出了多少感伤,天昏地暗间,一声吵骂打破了最后的安详,在毫无保留地释放着的,是填不平的怒火与绝望.曾经举案齐眉,如今仇深似海,黑白电影般参差破烂的画片,断断续续的在上演,吱嘎吱嘎,末了,一曲绝唱,几许苍凉.</p>
<p>世间再无比这更让人捶胸顿足,徒呼奈何的悲剧了.手心是爸,手背是妈,手心手背,血浓于水.父母,恨不得也说不得,再多的坚强也是徒劳,再多的努力也将付之东流.我的心,碎了,头皮发麻,四肢乏力,已然内伤.</p>
<p>天啊,我已看不清前方.</p>
<p><span style="color: #888888;">&#8220;Do not go gentle into that good night. Rage, rage against the dying of the light.&#8221; &#8211; Dylan Thomas</span></p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2011/04/without-you-i-was-broken/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>野鸡版delicious推荐系统</title>
		<link>http://godorz.info/2011/03/model-of-delicious-link-recommender/</link>
		<comments>http://godorz.info/2011/03/model-of-delicious-link-recommender/#comments</comments>
		<pubDate>Wed, 30 Mar 2011 10:41:38 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=979</guid>
		<description><![CDATA[日子有点无聊,照着&#60;Programming collective Intelligence&#62;写了一个delicious推荐的小模型(代码),就当作是再学python的hello, world! :p 以后无聊了就跑一下代码,看看有什么值得看的, HoHo~ 运行结果如下: >>> from delicious import * >>> table=initializeUserDict('programming') >>> table['myself']={} >>> fillItems(table) 考虑到国内的网络,冲杯咖啡先吧... >>> from recommendations import * # 对用户username推荐n条网页链接 >>> getRecommendations(table,"username")[0:n] # 输出结果前者为推荐值,后者为链接 [(0.61988282180389409, 'http://html5boilerplate.com/mobile/')] ... deliciousapi deliciousapi是非官方的api,用于从delicious上拿数据,它的接口很简洁,作者博客上有相应的demo,这里不废话. initializeUserDict and fillItems initializeUserDict(tag, count)首先通过deliciousapi.DeliciousAPI().get_urls(&#8216;tag&#8217;)获得某个tag下的count链接,然后通过.get_url(&#8216;url&#8217;)获取该链接相关的信息,e.g. bookmarks,tags.最后收集bookmarks对应的用户到字典user_dict,将其返回.代码如下: def initializeUserDict(tag, count=1): user_dict={} dapi = deliciousapi.DeliciousAPI() for url in dapi.get_urls(tag=tag)[0:count]: for item [...]]]></description>
			<content:encoded><![CDATA[<p>日子有点无聊,照着&lt;Programming collective Intelligence&gt;写了一个delicious推荐的小模型(<a href="http://godorz.info/wp-content/uploads/2011/03/delicious.zip">代码</a>),就当作是再学python的hello, world! :p 以后无聊了就跑一下代码,看看有什么值得看的, HoHo~</p>
<p>运行结果如下:</p>
<pre>
>>> from delicious import *
>>> table=initializeUserDict('programming')
>>> table['myself']={}
>>> fillItems(table) <span style="color: #ff0000;">考虑到国内的网络,冲杯咖啡先吧...</span>
>>> from recommendations import *
# 对用户username推荐n条网页链接
>>> getRecommendations(table,"username")[0:n]
# 输出结果前者为推荐值,后者为链接
[(0.61988282180389409, 'http://html5boilerplate.com/mobile/')]
...
</pre>
<h3>deliciousapi</h3>
<p><a href="http://www.michael-noll.com/projects/delicious-python-api/" target="_blank">deliciousapi</a>是非官方的api,用于从delicious上拿数据,它的接口很简洁,作者博客上有相应的demo,这里不废话.</p>
<h3>initializeUserDict and fillItems</h3>
<p><strong>initializeUserDict(tag, count)</strong>首先通过deliciousapi.DeliciousAPI().get_urls(&#8216;tag&#8217;)获得某个tag下的count链接,然后通过.get_url(&#8216;url&#8217;)获取该链接相关的信息,e.g. bookmarks,tags.最后收集bookmarks对应的用户到字典user_dict,将其返回.代码如下:</p>
<pre class="brush: cpp">
def initializeUserDict(tag, count=1):
   user_dict={}
   dapi = deliciousapi.DeliciousAPI()

   for url in dapi.get_urls(tag=tag)[0:count]:
      for item in dapi.get_url(url).bookmarks:
         user_dict[item[0]]={}
   return user_dict
</pre>
<p><strong>fillIteams(user_dict)</strong>遍历字典user_dict,通过.get_user(user)获取user收集的bookmarks.如果某个url被所有user收藏,那么将user_dict[user][url]置位,否则清零.代码如下:</p>
<pre class="brush: cpp">
def fillItems(user_dict):
   all_items={}
   dapi = deliciousapi.DeliciousAPI()

   for user in user_dict:
      for i in range(3):
         try:
            posts=dapi.get_user(user)
            break
         except:
            print &quot;Fail user &quot;+user+&quot;, retrying&quot;
            time.sleep(4)
      for item in posts.bookmarks:
      	 url=item[0]
      	 user_dict[user][url]=1.0
      	 all_items[url]=1

   for ratings in user_dict.values():
      for item in all_items:
         if item not in ratings:
         	ratings[item]=0.0
</pre>
<p>经过initializeUserDict()和fillItems()两步后,得到的user_dict形如:</p>
<pre>
{'username': {'http://url': 0.0}, {'http://lru': 1.0},
 'nameuser': {'http://url': 0.0}, {'http://lru': 1.0},
  ...
}
</pre>
<h3>getRecommendations</h3>
<p>现在看看最核心的推荐算法,其实很简单:</p>
<pre class="brush: cpp">
def sim_distance(prefs, person1, person2):
   si={}
   for item in prefs[person1]:
      if item in prefs[person2]:
           si[item]=1

   if len(si)==0: return 0

   sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item], 2)
                     for item in prefs[person1] if item in prefs[person2]])

   return 1/(1+sqrt(sum_of_squares))

# gets recommendations for a person by using a weighted average
# of every other user&#039;s rankings
def getRecommendations(prefs, person, similarity=sim_distance):
   totals={}
   simSums={}

   for other in prefs:
      if other==person: continue
      sim=similarity(prefs,person,other)

      if sim&lt;=0: continue

      for item in prefs[other]:
         # only score those haven&#039;t seen yet
         if item not in prefs[person] or prefs[person][item]==0:
            #similarity * score
            totals.setdefault(item,0)
            totals[item]+=prefs[other][item]*sim
            # sum of similarities
            simSums.setdefault(item,0)
            simSums[item]+=sim

   #create the normolized list
   rankings=[(total/simSums[item],item) for item,total in totals.items()]

   # return the sorted list
   rankings.sort()
   rankings.reverse()
   return rankings
</pre>
<p>参数person表示被推荐人,而参数similarity表示计算<a href="http://en.wikipedia.org/wiki/Correlation_and_dependence" target="_blank">Correlation and dependence</a>的函数,默认值为sim_distance,其实也就是个<a href="http://godorz.info/2009/11/heuristics-2/" target="_blank">Euclidean Distance</a>.</p>
<h3>Conclusion and next steps</h3>
<p>小模型很简单,有空时可以完善一下算法.( 如果让C程序员看见如此大的稀疏矩阵,那得有多纠结 :( )</p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2011/03/model-of-delicious-link-recommender/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>小计划</title>
		<link>http://godorz.info/2011/03/a-plan/</link>
		<comments>http://godorz.info/2011/03/a-plan/#comments</comments>
		<pubDate>Tue, 22 Mar 2011 08:05:20 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[My Thoughts]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=922</guid>
		<description><![CDATA[最近太挫了,不知道为什么总是很烦躁,没有多少心思读书,感觉日子过得很快,一眨眼时间就没了.明明知道是焦虑的缘故,可仍旧魂不守舍,静不下来. 毕业之前的这段时间我都打算在学校里度过了.为了抓住时间,定个小计划吧: 学习Linux内核,主要是网络协议栈. 读完lighttpd代码,之后再读点其他代码. 写一个httpd. 彻底入门python. boost? design pattern? 为了SMART一点,我要多写博客,多努力. 最后,多放松,只剩下最后这点自由的日子了,唉~.]]></description>
			<content:encoded><![CDATA[<p>最近太挫了,不知道为什么总是很烦躁,没有多少心思读书,感觉日子过得很快,一眨眼时间就没了.明明知道是焦虑的缘故,可仍旧魂不守舍,静不下来.</p>
<p>毕业之前的这段时间我都打算在学校里度过了.为了抓住时间,定个小计划吧:</p>
<ul>
<li>学习Linux内核,主要是网络协议栈.</li>
<li>读完lighttpd代码,之后再读点其他代码.</li>
<li>写一个httpd.</li>
<li>彻底入门python.</li>
<li>boost? design pattern?</li>
</ul>
<p>为了S<strong>M</strong>ART一点,我要多写博客,多努力.</p>
<p>最后,多放松,只剩下最后这点自由的日子了,唉~.</p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2011/03/a-plan/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>libevent源码浅析: http库</title>
		<link>http://godorz.info/2011/03/the-annotated-libevent-sources-about-http/</link>
		<comments>http://godorz.info/2011/03/the-annotated-libevent-sources-about-http/#comments</comments>
		<pubDate>Mon, 07 Mar 2011 08:27:33 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[libevent]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=907</guid>
		<description><![CDATA[libevent自带了一个http库,用它可以很简单的实现一个http服务器,本文非常简单地分析之. evhttp evhttp库有几个主要的结构体,它们之间的联系非常龌龊: 其中,结构体event, min_heap, evsignal_info, eventop, event_base在前面几篇文章中已经介绍过了,这里不再啰嗦. evbuffer evbuffer用于读或写缓冲,图示为: 和evbuffer有关的外露接口主要是: //从文件读数据到缓冲,读取量为max(howmuch, 4096) int evbuffer_read(struct evbuffer *buf, int fd, int howmuch); //把缓冲写出文件 int evbuffer_write(struct evbuffer *buffer, int fd) evbuffer比较简单,不多介绍. evhttp, evhttp_connection, evhttp_request libevent对成员的命名不太在意,其实evhttp可以看做是echttpsever,它绑定到某个特定端口和地址(socket(), bind()),保存访问该server的连接(通过成员connections,).evhttp_connection是保存连接信息的结构体, evhttp_request表示请求. 看看http库的使用流程: void http_handler(struct evhttp_request *req, void *arg) { struct evbuffer *buf; buf = evbuffer_new(); // 分析请求 char *decode_uri = strdup((char*) [...]]]></description>
			<content:encoded><![CDATA[<p>libevent自带了一个http库,用它可以很简单的实现一个http服务器,本文非常简单地分析之.</p>
<h3>evhttp</h3>
<p>evhttp库有几个主要的结构体,它们之间的联系非常龌龊:</p>
<p><img class="aligncenter size-full wp-image-911" title="evhttp_call_graph" src="http://godorz.info/wp-content/uploads/2011/03/evhttp_call_graph.png" alt="" width="558" height="2282" /></p>
<p>其中,结构体event, min_heap, evsignal_info, eventop, event_base在前面几篇文章中已经介绍过了,这里不再啰嗦.</p>
<h3>evbuffer</h3>
<p>evbuffer用于读或写缓冲,图示为:</p>
<p><img class="aligncenter size-full wp-image-909" title="evbuffer" src="http://godorz.info/wp-content/uploads/2011/03/evbuffer.png" alt="" width="291" height="154" /></p>
<p>和evbuffer有关的外露接口主要是:</p>
<pre class="brush: cpp">
//从文件读数据到缓冲,读取量为max(howmuch, 4096)
int evbuffer_read(struct evbuffer *buf, int fd, int howmuch);

//把缓冲写出文件
int evbuffer_write(struct evbuffer *buffer, int fd)
</pre>
<p>evbuffer比较简单,不多介绍.</p>
<h3>evhttp, evhttp_connection, evhttp_request</h3>
<p>libevent对成员的命名不太在意,其实evhttp可以看做是echttpsever,它绑定到某个特定端口和地址(socket(), bind()),保存访问该server的连接(通过成员connections,).evhttp_connection是保存连接信息的结构体, evhttp_request表示请求.</p>
<p>看看http库的使用流程:</p>
<pre class="brush: cpp">void http_handler(struct evhttp_request *req, void *arg)
{
    struct evbuffer *buf;
    buf = evbuffer_new();

    // 分析请求
    char *decode_uri = strdup((char*) evhttp_request_uri(req));
    struct evkeyvalq http_query;
    evhttp_parse_query(decode_uri, &amp;http_query);
    free(decode_uri);

    // 从http头中获取参数
    const char *request_value = evhttp_find_header(&amp;http_query, &quot;data&quot;);

    // 返回HTTP头部
    evhttp_add_header(req-&gt;output_headers, &quot;Content-Type&quot;, &quot;text/html; charset=UTF-8&quot;);
    evhttp_add_header(req-&gt;output_headers, &quot;Server&quot;, &quot;my_httpd&quot;);
    //evhttp_add_header(req-&gt;output_headers, &quot;Connection&quot;, &quot;keep-alive&quot;);

    evhttp_add_header(req-&gt;output_headers, &quot;Connection&quot;, &quot;close&quot;);

    // 将要输出的值写入输出缓存
    if(request_value != NULL) {
        evbuffer_add_printf(buf, &quot;%s&quot;, request_value);
    } else {
        evbuffer_add_printf(buf, &quot;%s&quot;, &quot;no error.&quot;);
    }

    // 输出
    evhttp_send_reply(req, HTTP_OK, &quot;OK&quot;, buf);

    // 内存释放
    evhttp_clear_headers(&amp;http_query);
    evbuffer_free(buf);
}

int main(int argc, char **argv)
{
    char *host_ip = &quot;0.0.0.0&quot;;
    int host_port = 8080;
    int timeout = 3;

    struct evhttp *httpd;

    event_init();

    //根据host_ip和host_port创建一个addrinfo结构体,然后创建一个socket,绑定到这个socket后,
    //根据这些信息得到得到一个event(回调函数设置为accept_socket),然后将这个event关联到对应的event_base,
    //之后插入到&amp;http-&gt;sockets队列中,然后返回&amp;http
    httpd = evhttp_start(host_ip, host_port);

    if (httpd == NULL) {
        fprintf(stderr, &quot;Error: Unable to listen on %s:%d\n\n&quot;, host_ip, host_port);
        exit(1);
    }

    // 设置请求超时时间
    evhttp_set_timeout(httpd, timeout);

    // 设置请求的处理函数
    evhttp_set_gencb(httpd, http_handler, NULL);

    event_dispatch();

    evhttp_free(httpd);

    return 0;
}
</pre>
<p>[1] 首先看看evhttp_start():</p>
<pre class="brush: cpp">//创建一个evhttp,绑定到端口和地址
struct evhttp * evhttp_start(const char *address, u_short port)
{
	struct evhttp *http = evhttp_new_object();
	evhttp_bind_socket(http, address, port);
	return (http);
}
</pre>
<p>函数evhttp_bind_socket()代码如下:</p>
<pre class="brush: cpp">//根据address和port创建一个非阻塞的socket,
//将其bind后的fd创建一个event(在这里设置好回调函数)后添加到&amp;amp;amp;amp;http-&amp;amp;amp;gt;sockets
int evhttp_bind_socket(struct evhttp *http, const char *address, u_short port)
{
	int fd;
	int res;

	//绑定一个socket
	fd = bind_socket(address, port, 1 /*reuse*/);

	//根据fd创建一个event,设置好回调函数,
        //然后将这个event关联到对应的event_base,并将它插入到&amp;amp;amp;amp;http-&amp;amp;amp;gt;sockets中.
	res = evhttp_accept_socket(http, fd);

	return (res);
}
</pre>
<p>在这里,函数bing_socket()的作用是根据地址和端口创建一个socket,返回bind()后的文件描述符.函数evhttp_accept_socket()的作用在注释中也说明了,其代码如下:</p>
<pre class="brush: cpp">int evhttp_accept_socket(struct evhttp *http, int fd)
{
	struct evhttp_bound_socket *bound;
	struct event *ev;
	int res;

	bound = malloc(sizeof(struct evhttp_bound_socket));
	ev = &amp;bound-&gt;bind_ev;

	/* Schedule the socket for accepting */
	//设置这个ev,回调函数为accept_socket,针对的文件描述符为fd
	event_set(ev, fd, EV_READ | EV_PERSIST, accept_socket, http);

	//将ev关联到&amp;http-&gt;base
	EVHTTP_BASE_SET(http, ev);

	//将ev添加进&amp;http-&gt;base
	res = event_add(ev, NULL);

	//将bound插入到&amp;http-&gt;sockets
	TAILQ_INSERT_TAIL(&amp;http-&gt;sockets, bound, next);
}
</pre>
<p>需要指出的是,在这个函数中,<span style="color: #000000;"><strong>struct event *ev可以看成是服务器struct evhttp的代理,evhttp通过这个ev是否可读来注意到是否有新的连接.</strong>(后文会分析.)</span></p>
<p>[2] 函数evhttp_set_timeout()和evhttp_set_gencb()逻辑比较简单,分别设置超时时间和回调函数.</p>
<p>[3]重头戏来了,函数event_dispatch()负责分发,在前面的文章已经介绍过了,它最终会调用event_base_loop(),分别查看定时器最小堆,信号队列和I/O队列.在http库中,当有一个新的连接时,[1]中已加入到event_base已注册事件队列的事件ev-&gt;fd将变成可读,它被移入已就绪事件队列,然后由函数event_process_active()调用ev的回调函数accept_socket()(回调函数在evhttp_accept_socket()函数中设置).</p>
<p>需要说明的是,<span style="color: #ff0000;"><strong>以下的内容都是在event_base_loop()死循环中被处理的.</strong></span></p>
<p>现在看一下回调函数accept_socket()的代码:</p>
<pre class="brush: cpp">
//作为回调函数,accept 一个 socket
static void accept_socket(int fd, short what, void *arg)
{
	struct evhttp *http = arg;
	struct sockaddr_storage ss;
	socklen_t addrlen = sizeof(ss);
	int nfd;

        //获得accept()后的文件描述符
        nfd = accept(fd, (struct sockaddr *)&amp;ss, &amp;addrlen);

        //设置为非阻塞
	evutil_make_socket_nonblocking(nfd);

        //获得连接
	evhttp_get_request(http, nfd, (struct sockaddr *)&amp;ss, addrlen);
}
</pre>
<p>代码很好懂,看看evhttp_get_request()函数:</p>
<pre class="brush: cpp">
//在回调函数accept_socket中被调用.
//这里传入的参数fd是accept()后返回的描述符
void evhttp_get_request(struct evhttp *http, int fd, struct sockaddr *sa, socklen_t salen)
{
	struct evhttp_connection *evcon;

	//根据fd和sa创建一个evhttp_connection,并将它关联到http-&gt;base.
	evcon = evhttp_get_request_connection(http, fd, sa, salen);

	if (http-&gt;timeout != -1)
		//watch out!!!在这里evcon会被设置超时时间.
		evhttp_connection_set_timeout(evcon, http-&gt;timeout);

	 //将evcon关联到http
	evcon-&gt;http_server = http;

	//将evcon插入到&amp;http-&gt;connections
	TAILQ_INSERT_TAIL(&amp;http-&gt;connections, evcon, next);

	evhttp_associate_new_request_with_connection(evcon);
}
</pre>
<p>跟踪下去看看evhttp_associate_new_request_with_connection()函数:</p>
<pre class="brush: cpp">//初始化一个绑定到evcon的evhttp_request
static int evhttp_associate_new_request_with_connection(struct evhttp_connection *evcon)
{
	struct evhttp *http = evcon-&gt;http_server;
	struct evhttp_request *req;

        //在这里会设置该req的回调函数evhttp_handle_request(),此函数很重要..
	req = evhttp_request_new(evhttp_handle_request, http);

	req-&gt;evcon = evcon;
	req-&gt;flags |= EVHTTP_REQ_OWN_CONNECTION;

	TAILQ_INSERT_TAIL(&amp;evcon-&gt;requests, req, next);

	req-&gt;kind = EVHTTP_REQUEST;

	req-&gt;remote_host = strdup(evcon-&gt;address);

	req-&gt;remote_port = evcon-&gt;port;

	evhttp_start_read(evcon);

	return (0);
}
</pre>
<p>经过这么多层次的函数调用,终于要读数据了,evhttp_start_read()代码:</p>
<pre class="brush: cpp">void evhttp_start_read(struct evhttp_connection *evcon)
{
	/* Set up an event to read the headers */
	if (event_initialized(&amp;evcon-&gt;ev))
		event_del(&amp;evcon-&gt;ev);

	//根据这些参数设置好evcon-&gt;ev.回调函数为evhttp_read()
	event_set(&amp;evcon-&gt;ev, evcon-&gt;fd, EV_READ, evhttp_read, evcon);

        //关联到event_base中
	EVHTTP_BASE_SET(evcon, &amp;evcon-&gt;ev);

	//将该ev插入到event_base中

	//watch out!!!!
	//在这里会设置这个event的超时时间,它将被加入到定时器最小堆中
	//超时之后,该事件会被event_active(),插入到就绪队列中,然后执行其回调函数.

	//evcon-&gt;timeout是在evhttp_get_request()被设置的
	evhttp_add_event(&amp;evcon-&gt;ev, evcon-&gt;timeout, HTTP_READ_TIMEOUT);
	evcon-&gt;state = EVCON_READING_FIRSTLINE;
}
</pre>
<p>可以看到,对于这个连接,evhttp_connection结构体evcon是通过内部成员event *ev来处理的.函数evhttp_start_read()对&amp;evcon-&gt;ev设置好超时时间和回调函数后将它插入到event_base中.</p>
<p>直到这里,回调函数accept_socket()的功能终于完成了.</p>
<p>(3.2) 上一段提到accept_socket()函数最终会调用evhttp_start_read()来设置连接对应的event(&amp;evcon-&gt;ev)的超时时间和回调函数,并将它插入已激活事件队列进行schedule.</p>
<p>在&amp;evcon-&gt;ev超时之后,它会被函数timeout_process()从已激活事件队列移入已就绪事件队列,然后由函数event_process_active()调用它的回调函数,也即是evhttp_read()(此回调函数在函数evhttp_start_read()中设置).代码如下:</p>
<pre class="brush: cpp">//读数据
void evhttp_read(int fd, short what, void *arg)
{
	struct evhttp_connection *evcon = arg;
	//拿到第一个req
	struct evhttp_request *req = TAILQ_FIRST(&amp;evcon-&gt;requests);
	struct evbuffer *buf = evcon-&gt;input_buffer;
	int n, len;

	if (what == EV_TIMEOUT) {
		evhttp_connection_fail(evcon, EVCON_HTTP_TIMEOUT);
		return;
	}

	//从fd读数据到buf
	n = evbuffer_read(buf, fd, -1);
	len = EVBUFFER_LENGTH(buf);

	if (n == -1) {
		if (errno != EINTR &amp;&amp; errno != EAGAIN) {
			event_debug((&quot;%s: evbuffer_read&quot;, __func__));
			evhttp_connection_fail(evcon, EVCON_HTTP_EOF);
		} else {
			evhttp_add_event(&amp;evcon-&gt;ev, evcon-&gt;timeout,
			    HTTP_READ_TIMEOUT);
		}
		return;
	} else if (n == 0) {
		/* Connection closed */
		evhttp_connection_done(evcon);
		return;
	}

	switch (evcon-&gt;state) {
	case EVCON_READING_FIRSTLINE:
		evhttp_read_firstline(evcon, req);
		break;
	case EVCON_READING_HEADERS:
		evhttp_read_header(evcon, req);
		break;
	case EVCON_READING_BODY:
		evhttp_read_body(evcon, req);
		break;
	case EVCON_READING_TRAILER:
		evhttp_read_trailer(evcon, req);
		break;
	case EVCON_DISCONNECTED:
	case EVCON_CONNECTING:
	case EVCON_IDLE:
	case EVCON_WRITING:
	default:
		event_errx(1, &quot;%s: illegal connection state %d&quot;,
			   __func__, evcon-&gt;state);
	}
}
</pre>
<p>代码中的fd其实是evcon-&gt;fd,也就是accept()后返回的文件描述符..</p>
<p>函数evhttp_read()就这么一直读数据下去(可能经过了多次循环,因为在evhttp_accept_socket()函数中被设置了EV_PERSIST标志,所以它不会从已注册时间队列中被移除,而是不断的超时,不断地被调用其回调函数),直到数据读完了<span style="color: #c0c0c0;">(这里经过了好多状态,非常让人不爽的是,libevent官网上连个FSM图都没有,这种体力活我也不会干的,哈哈~)</span>,就调用evhttp_connection_done(),代码如下:</p>
<pre class="brush: cpp">//累个半死终于读完啦
static void evhttp_connection_done(struct evhttp_connection *evcon)
{
	...//省略

        //调用req的回调函数
	(*req-&gt;cb)(req, req-&gt;cb_arg);

}
</pre>
<p>在这里,会调用req的回调函数,也就是在函数evhttp_associate_new_request_with_connection()中设置的evhttp_handle_request(),此回调函数代码为:</p>
<pre class="brush: cpp">//处理请求,在这里会调用http的回调函数http-&gt;gencb
static void evhttp_handle_request(struct evhttp_request *req, void *arg)
{
        ...//一堆无用的噪音

        //由用户指定的回调函数终于显灵了.
	if (http-&gt;gencb) {
		(*http-&gt;gencb)(req, http-&gt;gencbarg);
		return;
	}

}
</pre>
<p>在数据全都读入后,libevent终于终于终于调用了用户指定的回调函数(*http-&gt;gencb).在本文一开始的小例子中,也就是函数http_handler(),要达到这一步可真不容易啊,撒花..</p>
<div class="post-footer"> </div>
<p>由上文提到的种种的繁琐的过程可以看出,libevent对于user来说是很友善的,几句代码就可以实现一个httpd,可以对于developer来说就太恶心了..</p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2011/03/the-annotated-libevent-sources-about-http/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>libevent源码浅析: 定时器和信号</title>
		<link>http://godorz.info/2011/02/the-annotated-libevent-sources-about-timer-and-signal/</link>
		<comments>http://godorz.info/2011/02/the-annotated-libevent-sources-about-timer-and-signal/#comments</comments>
		<pubDate>Mon, 28 Feb 2011 15:17:09 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[libevent]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=903</guid>
		<description><![CDATA[上一篇文章介绍了libevent下基本的I/O事件,这篇文章将讲讲libevent对定时器和信号事件的处理. Timer事件 反应堆event_base包含了一个最小堆min_heap结构体的实例,以此维护注册到这个反应堆实例的定时器事件: struct event_base { //其他成员 struct min_heap timeheap; }; 回顾一下最小堆min_heap: typedef struct min_heap { //p指向一个动态分配的数组,数组元素是event指针. struct event** p; unsigned n, a; // n表示目前保存了多少元素,a表示p指向的内存能够存储event指针的个数 } min_heap_t; 可以看到,它包含一个连续的内存块用于存储定时器事件.针对min_heap的操作主要有: static inline int min_heap_push(min_heap_t* s, struct event* e); static inline struct event* min_heap_pop(min_heap_t* s); 其中,min_heap_push()用于插入节点,min_heap_pop()用于弹出节点.其内部逻辑很简单,不必描述了. 现在看看libevent处理定时器事件的例子: static void timeout_cb(int fd, short event, void *arg) {...} int main [...]]]></description>
			<content:encoded><![CDATA[<p>上一篇文章介绍了libevent下基本的I/O事件,这篇文章将讲讲libevent对定时器和信号事件的处理.</p>
<h3>Timer事件</h3>
<p>反应堆event_base包含了一个最小堆min_heap结构体的实例,以此维护注册到这个反应堆实例的定时器事件:</p>
<pre class="brush: cpp">
struct event_base {
        //其他成员
	struct min_heap timeheap;
};
</pre>
<p>回顾一下最小堆min_heap:</p>
<pre class="brush: cpp">
typedef struct min_heap
{
    //p指向一个动态分配的数组,数组元素是event指针.
    struct event** p;
    unsigned n, a; // n表示目前保存了多少元素,a表示p指向的内存能够存储event指针的个数
} min_heap_t;
</pre>
<p>可以看到,它包含一个连续的内存块用于存储定时器事件.针对min_heap的操作主要有:</p>
<pre class="brush: cpp">
static inline int min_heap_push(min_heap_t* s, struct event* e);
static inline struct event*  min_heap_pop(min_heap_t* s);
</pre>
<p>其中,min_heap_push()用于插入节点,min_heap_pop()用于弹出节点.其内部逻辑很简单,不必描述了.</p>
<p>现在看看libevent处理定时器事件的例子:</p>
<pre class="brush: cpp">
static void timeout_cb(int fd, short event, void *arg) {...}

int main (int argc, char **argv)
{
	struct event timeout;
	struct timeval tv;

        event_init();

	evtimer_set(&amp;timeout, timeout_cb, &amp;timeout);

	evutil_timerclear(&amp;tv);
	tv.tv_sec = 2;
	event_add(&amp;timeout, &amp;tv);

	lasttime = time(NULL);

	event_dispatch();
}
</pre>
<p>首先,和上篇文章例子一样的,event_init()初始化一个event_base(反应堆实例),然后由evtimer_set()设置定时器事件的回调函数,接着event_add()把定时器事件加入反应堆实例中.最后进入event_dispatch()主循环.</p>
<p>在这里,evtimer_set定义如下:</p>
<pre class="brush: cpp">
#define evtimer_set(ev, cb, arg)	event_set(ev, -1, 0, cb, arg)
</pre>
<p>至于event_set(),没有什么好说的,就是对一个event结构体做初始化罢了.</p>
<p>上一篇文章已经从I/O事件的角度介绍了event_add(),这里看看它是如何处理定时器事件的:</p>
<pre class="brush: cpp">
int event_add(struct event *ev, const struct timeval *tv)
{
    struct event_base *base = ev-&gt;ev_base;

    ....//处理IO事件或者信号事件的逻辑.

    //如果tv不为0
    if (tv != NULL)
    {
        event_queue_insert(base, ev, EVLIST_TIMEOUT);
    }
}
</pre>
<p>可以看到,event_add()会把一个定时器事件压入到其对应的反应堆实例下的定时器最小堆timeheap中(&amp;ev-&gt;base.timeheap).</p>
<p>回到event_dispatch(),它会调用event_base_loop(),此函数对定时器事件处理如下:</p>
<pre class="brush: cpp">

//事件主循环
int
event_base_loop(struct event_base *base, int flags)
{
    ...//不必多虑的其他代码

    done = 0;
    while (!done)
    {
	//检查heap中的timer events,将就绪的timer event从heap上删除,并插入到激活链表中,
	//这意味着base-&gt;event_count_active会增加
        timeout_process(base);

	//有就绪事件了
        if (base-&gt;event_count_active)
        {
            //处理就绪事件吧.
            event_process_active(base);
        }
    }
}
</pre>
<p>其中,timeout_process()会将已超时的定时器事件插入到反应堆实例下的已就绪事件队列中,接着由event_process_active()处理已就绪事件.event_process_active()代码在上一篇文章中已经介绍过了,这里看一下timeout_process():</p>
<pre class="brush: cpp">
/时间到~~~
//开始处理base里面的定时器堆里的事件鸟.
//检查heap中的timer events,将就绪的timer event从heap上删除,并插入到激活链表中
void timeout_process(struct event_base *base)
{
    struct timeval now;
    struct event *ev;

   while ((ev = min_heap_top(&amp;base-&gt;timeheap)))
    {
    	//ev超时时间的比现在的时间大,也就是说,这个ev还没有超时,那么while循环结束
        if (evutil_timercmp(&amp;ev-&gt;ev_timeout, &amp;now, &gt;))
            break;

	//else 意味着 evutil_timercmp(&amp;ev-&gt;ev_timeout, &amp;now, &lt;=)为真
	//也就说明定时器最小堆的根超时了

        //从定时器堆删除
        event_del(ev);

	//把它插到激活链表吧.
        event_active(ev, EV_TIMEOUT, 1);
    }
}
</pre>
<h3>Signal事件</h3>
<p>signal事件的处理时libevent中比较难懂的地方,前人之述不详,本文重点讲解之.</p>
<p>反应堆event_base包含了一个<code>evsignal_info</code>结构体的实例,来维护注册到这个反应堆实例的信号事件:</p>
<pre class="brush: cpp">
struct event_base {
        //其他成员
	struct evsignal_info sig;
};
</pre>
<p>这里仔细研究一下evsignal_info结构体的定义:</p>
<pre class="brush: cpp">

struct evsignal_info {

	//为 socket pair 的读 socket向 event_base 注册读事件时使用的 event 结构体
	//这个是所有信号事件共用的.
	struct event ev_signal; 

	//这个也是所有信号事件共用的.
	int ev_signal_pair[2]; 

	//记录ev_signal 事件是否已经注册了
	int ev_signal_added;

	//是否有信号发生的标记
	//只在evsignal_handler()中被修改为1
	volatile sig_atomic_t evsignal_caught;

	//evsigevents[signo]表示注册到信号 signo 的事件链表
	struct event_list evsigevents[NSIG];

	//具体记录每个信号触发的次数,evsigcaught[signo]是记录信号signo被触发的次数
	sig_atomic_t evsigcaught[NSIG];

	//记录了原来的signal处理函数指针,当信号signo注册的event被清空时,需要重新设置其处理函数
	struct sigaction **sh_old;
};
</pre>
<p>要了解evsignal_info为何是这样设计的,首先需要明白int ev_signal_pair[2];的作用.它实际上表示两个文件描述符,在libevent中一个用于写,一个用于读,它们在event_init()是被初始化.好吧,其实更确切点说,event_init()会调用event_base_new(),而event_base_new()调用封装好I/O多路复用技术的结构体eventop实例(&amp;event_base-&gt;evsel)的init函数(&amp;event_base-&gt;evsel),这个init函数会初始化eventop实例的内部数据结构,然后调用evsignal_init()对evsignal_info结构体实例(&amp;event_base-&gt;sig)做初始化.而在初始化实例的过程中,对其内部的ev_signal_pair[2]数组的初始化是通过调用evutil_socketpair()函数来实现的.够了,上面这段话已经够恶心了,图示如下:</p>
<p><img class="aligncenter size-full wp-image-904" title="event_init-call-graph" src="http://godorz.info/wp-content/uploads/2011/02/libevent-call-graph1.png" alt="" width="272" height="442" /></p>
<p>看看evutil_socketpair()代码:</p>
<pre class="brush: cpp">
int
evutil_socketpair(int family, int type, int protocol, int fd[2])
{
#ifndef WIN32
	return socketpair(family, type, protocol, fd);
#else
       ...//山寨一个socketpair函数
}
</pre>
<p>它使用socketpair系统调用创建一对全双工管道(如果有时间的话,可以读一下evutil_socketpair()后半部分的代码,它在WIN32环境下如何山寨了一个socketpair函数,熟悉之可以加深不少理解.).这个全双工管道有什么用呢? 这里先卖个关子,我们看看evsignal_info结构体下的成员struct event ev_signal是如何被初始化的.</p>
<p>evsignal_init()调用event_set()函数,event_set()将&amp;event_base-&gt;sig.ev_signal.ev_fd设置为&amp;event_base-&gt;sig.ev_signal_pair[1],其回调函数为evsignal_cb(). (<strong>[1]</strong>).</p>
<p>至此,铺垫基本上做好了.我们看一个使用libevent处理信号事件的例子吧:</p>
<pre class="brush: cpp">
static void signal_cb(int fd, short event, void *arg) {...}

int main (int argc, char **argv)
{
	/* Initalize the event library */
        event_init();

	struct event signal_int;
	event_set(&amp;signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb, &amp;signal_int);

	event_add(&amp;signal_int, NULL);

	event_dispatch();
}
</pre>
<p>首先是由event_init()创建一个反应堆实例(在此背后,对维护信号事件的结构体evsigal_info的实例(&amp;event_base.sig)如何被初始化在上文已经做了介绍了.),然后由event_set()设置一个事件,将其标志&amp;signal_int.events设为EV_SIGNAL|EV_PERSIST,文件描述符&amp;signal_int.ev_fd设置对应的信号(在例子中是SIGIN,即中断信号,中断下可以用ctrl-c触发).然后设置好这个信号事件对应的回调函数 (<strong>[2]注意,回调函数对应的是信号事件,而非信号.注意与[3]的不同.</strong>).</p>
<p>之后,调用event_add()将信号事件注册到反应堆实例中,event_add()对信号事件的处理如下:</p>
<pre class="brush: cpp">
int event_add(struct event *ev, const struct timeval *tv)
{
    struct event_base *base = ev-&gt;ev_base;
    const struct eventop *evsel = base-&gt;evsel;
    void *evbase = base-&gt;evbase;
    int res = 0;

    //ev-&gt;ev_events表示事件类型
    //如果ev-&gt;ev_events是 读/写/信号 事件,而且ev不在 已注册链表 或 已激活链表,那么调用evbase注册ev事件
    if ((ev-&gt;ev_events &amp; (EV_READ|EV_WRITE|EV_SIGNAL)) &amp;&amp;
            !(ev-&gt;ev_flags &amp; (EVLIST_INSERTED|EVLIST_ACTIVE)))
    {
	//实际执行操作的是evbase
        res = evsel-&gt;add(evbase, ev);

        if (res != -1) //注册成功,把事件ev插入 已注册链表 中
            event_queue_insert(base, ev, EVLIST_INSERTED);
    }
}
</pre>
<p>为了描述方便,我们假定libevent使用的I/O多路复用技术是select,看看select_add()代码吧:</p>
<pre class="brush: cpp">
static int select_add(void *arg, struct event *ev)
{
	if (ev-&gt;ev_events &amp; EV_SIGNAL)
		return (evsignal_add(ev));
}
</pre>
<p>对于信号事件,它转手给evsignal_add()函数处理,evsignal_add()代码如下:</p>
<pre class="brush: cpp">
//将信号事件ev下的描述符ev_fd(也就是信号)添加到&amp;ev-&gt;ev_base-&gt;sig-&gt;evsigevents[ev_fd]队列中
int evsignal_add(struct event *ev)
{
    int evsignal;
    struct event_base *base = ev-&gt;ev_base;
    struct evsignal_info *sig = &amp;ev-&gt;ev_base-&gt;sig;

    //拿到event下的信号标号
    evsignal = EVENT_SIGNAL(ev);

    if (TAILQ_EMPTY(&amp;sig-&gt;evsigevents[evsignal]))
    {
		//设置这个事件对应的信号对应的处理函数

		//watch out!!!!针对的是信号,不是事件
	if (_evsignal_set_handler(
                    base, evsignal, evsignal_handler) == -1)
            return (-1);

	//这里注册的sig本身,而不是信号事件
	//也就是就是说,sig是在真正有信号事件时才注册的.
        if (!sig-&gt;ev_signal_added)
        {
            //注册这个信号对应的事件
            if (event_add(&amp;sig-&gt;ev_signal, NULL))
                return (-1);
            sig-&gt;ev_signal_added = 1;
        }
    }

    //多个事件可能对应同一信号
    TAILQ_INSERT_TAIL(&amp;sig-&gt;evsigevents[evsignal], ev, ev_signal_next);
}
</pre>
<p>evsignal_add()函数先获得信号事件对应的信号,通过_evsignal_set_handler()函数将此信号相应的信号处理函数设置为evsignal_handler(). (<strong>[3]注意,[2]设置的回调函数是针对信号事件的,这里设置的处理函数才是针对信号的.</strong>) 接着,evsignal_add()判断sig-&gt;ev_signal_added是否为0,为0则将&amp;sig-&gt;ev_signal事件注册到反应堆实例中,然后将sig-&gt;ev_signal_added置1。;如果不为0,那么跳过这段代码.需要指出的是,<strong>sig-&gt;ev_signal_added唯一一次被置1就是在这段代码中</strong>,这保证了&amp;sig-&gt;ev_signal事件只被注册到反应堆实例中一次.其实也就是说,只有在第1次有信号事件需要通过event_add()被注册到反应堆实例时,&amp;sig-&gt;ev_signal事件才会被一起注册,这是libevent对&amp;event_base-&gt;sig的延后处理.</p>
<p>&lt;hr/&gt;</p>
<p>接下来,貌似应该讲讲event_dispatch()对信号事件的处理了.且慢,我们回头把 <strong>[1]</strong>, <strong>[2]</strong>, <strong>[3]</strong> 整理一下:</p>
<p>(1) 在调用event_init()新建一个反应堆实例(以base表示)时,evsignal_info结构体(libevent用它来管理信号事件集合) base-&gt;sig被初始化,<strong>base-&gt;sig-&gt;ev_signal的回调函数<span style="color: #ff0000;">总是</span>被设置为evsignal_cb(),而evsignal_cb()是定义在libevent内部的,对libevent用户完全透明</strong>,其代码如下:</p>
<pre class="brush: cpp">
static void
evsignal_cb(int fd, short what, void *arg)
{
    recv(fd, signals, sizeof(signals), 0);
}
</pre>
<p>它从一个文件描述符(后文会看到,这个文件描述符总是&amp;event_base-&gt;sig.ev_signal_pair[1])读1比特的数据.</p>
<p>(2) 在已经通过调用event_init()获得一个反应堆实例后,通过event_set()设置一个信号事件signal_int的文件描述符signal_int.ev_fd(其实对于信号事件而言,ev_fd也就是此信号事件对应的信号),event_set()还设置了这个信号事件的回调函数.很明显,对于同一个信号,可以有不同的信号事件,这些信号事件的回调函数也可以完全不同.<strong>在这里,回调函数是由用户设计的,表示信号被触发时希望作出的反馈函数.</strong></p>
<p>(3) 为了将一个事件(这个事件可以是I/O事件,也可以是定时器事件,也可以是信号事件)注册到反应堆实例中,我们必须调用event_add(),而event_add()通过重重调用,最终由evsignal_add()来完成将信号事件注册.回顾一下evsignal_add():</p>
<p><strong>它通过_evsignal_set_handler<span style="color: #ff0000;">总是</span>将信号事件对应的<span style="color: #ff0000;">信号</span>的处理函数设置为</strong>evsignal_handler(),evsignal_handler()代码如下:</p>
<pre class="brush: cpp">
//通知event_base有信号发生的技巧,往sig.ev_signal_pair[0]写1字节数据
//会设置sig.evsignal_caught = 1,标记有信号产生.
static void evsignal_handler(int sig)
{
    evsignal_base-&gt;sig.evsigcaught[sig]++;
    evsignal_base-&gt;sig.evsignal_caught = 1; //将信号发生标志至1

    send(evsignal_base-&gt;sig.ev_signal_pair[0], &quot;a&quot;, 1, 0);
}
</pre>
<p>它将信号发生标志evsignal_base-&gt;sig.evsignal_caught置1,以此通过libevent有信号发生.然后往&amp;event_base-&gt;sig.ev_signal_pair[0]写1比特数据.</p>
<p>&lt;hr/&gt;</p>
<p>好吧,现在终于可以看看libevent是如何处理信号事件的了:</p>
<p>libevent先进入event_base_loop()主循环,等待已经准备好(可读可写或异常)的事件(通过select_dispatch找出已准备好的文件描述符).当有一个信号产生时,由于这个信号的信号处理函数(总是evsignal_handler())总是会往&amp;event_base-&gt;sig.ev_signal_pair[0]写1比特数据(<strong>这是由操作系统调用的</strong>,对libevent是透明的,对libevent的用户就更加透明了).此时,根据前面的描述,由于ev_signal_pair[0]与ev_signal_pair[1]是一对全双工管道,所以,ev_signal_pair[1]将变得可读.而&amp;event_base-&gt;sig.ev_signal事件的文件描述符正是ev_signal_pair[1],所以libevent可以知道&amp;event_base-&gt;sig.ev_signal事件准备好了.为此,&amp;event_base-&gt;sig.ev_signal事件被移入反应堆实例下的已就绪事件队列.接着在event_base_loop()的后续部分代码中被处理,通过event_process_active()调用其回调函数,也就是evsignal_cb(),从&amp;event_base-&gt;sig.ev_signal_pair[1])读1比特的数据.<strong>[4]我们把信号被捕捉到的这个while()循环记为第1次while()循环.</strong></p>
<p>写到这里,仍然有一个疑惑没有解开,上面都是讲libevent内部定义的&amp;event_base-&gt;sig.ev_signal如何如何,可是我们希望的是自己定义的信号事件signal_int如何如何啊.</p>
<p>答案是,正如(3)描述的那样,在操作系统调用信号处理函数evsignal_handler()时,它会将信号发生标志置1.然后将evsignal_info结构体中用于记录信号被捕捉次数的evsigcaught[id]++,id也就是这个信号.</p>
<p>在第2此while()循环时(参考[4]),它还是调用select_dispatch(),这时,由于信号发生标志为1,所以select_dispatch()会调用函数evsignal_process().select_dispatch()相关代码如下:</p>
<pre class="brush: cpp">

static int
select_dispatch(struct event_base *base, void *arg, struct timeval *tv)
{
        if (base-&gt;sig.evsignal_caught){
		evsignal_process(base);
}
</pre>
<p>evsignal_process()代码如下:</p>
<pre class="brush: cpp">
void evsignal_process(struct event_base *base)
{
    struct evsignal_info *sig = &amp;base-&gt;sig;
    struct event *ev, *next_ev;
    sig_atomic_t ncalls;
    int i;

    base-&gt;sig.evsignal_caught = 0;
    for (i = 1; i &lt; NSIG; ++i)
    {
        ncalls = sig-&gt;evsigcaught[i];
        if (ncalls == 0)
            continue;
        sig-&gt;evsigcaught[i] -= ncalls;

        for (ev = TAILQ_FIRST(&amp;sig-&gt;evsigevents[i]);
                ev != NULL; ev = next_ev)
        {
            next_ev = TAILQ_NEXT(ev, ev_signal_next);
            if (!(ev-&gt;ev_events &amp; EV_PERSIST))
                event_del(ev);

	    //移到已就绪事件队列,ncalls回调函数将会被调用多少次
            event_active(ev, EV_SIGNAL, ncalls);
        }
    }
}
</pre>
<p>总结一下,反应堆结构体event_base有一个数据成员evsignal_info结构体,它维护信号事件集.之所以evsignal_info会有一个event事件成员ev_signal,是因为libevent通过socket pair让操作系统通知自己有信号发生,在信号处理函数中将信号发生标志置1,并使该信号被捕捉的次数自增,然后ev_signal被移到已就绪事件队列,接着被处理.然后libevent检查到信号发生标志已经被置1,遍历所有信号事件,找出信号被捕捉次数不为0的那个信号事件集,将它们移到已就绪事件队列,然后处理之.</p>
<p>以上,就是libevent处理信号事件的逻辑.</p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2011/02/the-annotated-libevent-sources-about-timer-and-signal/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>libevent源码浅析: 事件处理框架</title>
		<link>http://godorz.info/2011/02/the-annotated-libevent-sources-about-event-handling-framework/</link>
		<comments>http://godorz.info/2011/02/the-annotated-libevent-sources-about-event-handling-framework/#comments</comments>
		<pubDate>Sun, 27 Feb 2011 20:04:57 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[libevent]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=897</guid>
		<description><![CDATA[本文将从一个使用libevent的小例子出发,解释libevent处理事件的流程. 例子如下: static void fifo_read(int fd, short event, void *arg) {...} int main (int argc, char **argv) { int socket = open (&#34;/tmp/event.fifo&#34;, O_RDONLY &#124; O_NONBLOCK, 0); fprintf(stdout, &#34;Please write data to %s\n&#34;, fifo); event_init(); struct event evfifo; event_set(&#38;evfifo, socket, EV_READ, fifo_read, &#38;evfifo); event_add(&#38;evfifo, NULL); event_dispatch(); } libevent库的使用方法大体上就像例子展示的那样,先由event_init()得到一个event_base实例(也就是反应堆实例),然后由 event_set()初始化一个event,接着用event_add()将event绑定到event_base,最后调用event_dispatch()进入时间主循环. event_init()和event_set()功能都很简单,它们分别对event_base结构体和event结构体做初始化.我们直接看看event_add(): //为了思路清晰,这里分析的是I/O事件,暂不考虑信号和定时器相关处理代码. //函数将ev注册到ev-&#62;ev_base上,事件类型由ev-&#62;ev_events指明.如果注册成功,ev将被插入到已注册链表中. int event_add(struct event [...]]]></description>
			<content:encoded><![CDATA[<p>本文将从一个使用libevent的小例子出发,解释libevent处理事件的流程.</p>
<p>例子如下:</p>
<pre class="brush: cpp">
static void fifo_read(int fd, short event, void *arg) {...}

int main (int argc, char **argv)
{
	int socket = open (&quot;/tmp/event.fifo&quot;, O_RDONLY | O_NONBLOCK, 0);

	fprintf(stdout, &quot;Please write data to %s\n&quot;, fifo);	

	event_init();

	struct event evfifo;
	event_set(&amp;evfifo, socket, EV_READ, fifo_read, &amp;evfifo);

	event_add(&amp;evfifo, NULL);

	event_dispatch();
}
</pre>
<p>libevent库的使用方法大体上就像例子展示的那样,先由event_init()得到一个event_base实例(也就是反应堆实例),然后由 event_set()初始化一个event,接着用event_add()将event绑定到event_base,最后调用event_dispatch()进入时间主循环.</p>
<p>event_init()和event_set()功能都很简单,它们分别对event_base结构体和event结构体做初始化.我们直接看看event_add():</p>
<pre class="brush: cpp">
//为了思路清晰,这里分析的是I/O事件,暂不考虑信号和定时器相关处理代码.

//函数将ev注册到ev-&gt;ev_base上,事件类型由ev-&gt;ev_events指明.如果注册成功,ev将被插入到已注册链表中.
int event_add(struct event *ev, const struct timeval *tv)
{
	//得到ev对应的反应堆实例event_base
    struct event_base *base = ev-&gt;ev_base;

	//得到libevent选择的已封装的I/O多路复用技术
    const struct eventop *evsel = base-&gt;evsel;

    void *evbase = base-&gt;evbase;
    int res = 0;

    //ev-&gt;ev_events表示事件类型
    //如果ev-&gt;ev_events是 读/写/信号 事件,而且ev不在 已注册队列 或 已就绪队列,
    //那么调用evbase注册ev事件
    if ((ev-&gt;ev_events &amp; (EV_READ|EV_WRITE|EV_SIGNAL)) &amp;&amp;
            !(ev-&gt;ev_flags &amp; (EVLIST_INSERTED|EVLIST_ACTIVE)))
    {

		//实际执行操作的是evbase
        res = evsel-&gt;add(evbase, ev);

		 //注册成功,把事件ev插入已注册队列中
        if (res != -1)
            event_queue_insert(base, ev, EVLIST_INSERTED);
    }

    return (res);
}
</pre>
<p>注释已经很明了了,如果一个事件不在已注册队列或者已激活队列,而且它是I/O事件或者信号事件,那么调用select_add()将ev插入到selectop的内部数据结构中(本文以select为例,下文不再说明.).select_add()代码如下:</p>
<pre class="brush: cpp">
//略去信号处理的相关代码

static int select_add(void *arg, struct event *ev)
{
    struct selectop *sop = arg;

    //如果是读类型事件,把该事件的文件描述符加入到selectop维护的读fd_set集
    //event_readset_in中,并且把该事件插入到读事件队列.
    if (ev-&gt;ev_events &amp; EV_READ)
    {
        FD_SET(ev-&gt;ev_fd, sop-&gt;event_readset_in);
        sop-&gt;event_r_by_fd[ev-&gt;ev_fd] = ev;
    }

    //略去对写事件的处理
}
</pre>
<p>小结一下,结合event_add()代码和select_add()代码,可以看出在调用event_add()时,事件将被插入其对应的反应堆实例event_base的已注册事件队列中,而且还会被加入到selectop维护的内部数据结构中进行监视.</p>
<p>现在可以看看event_dispatch()代码了:</p>
<pre class="brush: cpp">
//略去信号事件和定时器事件处理的相关代码

int event_dispatch(void)
{
    return (event_loop(0));
}

int event_loop(int flags)
{
    return event_base_loop(current_base, flags);
}

//事件主循环
int event_base_loop(struct event_base *base, int flags)
{
    const struct eventop *evsel = base-&gt;evsel;
    void *evbase = base-&gt;evbase;
    struct timeval *tv_p;
    int res, done;

    done = 0;
    while (!done)
    {
	//从定时器最小堆中取出根节点,其时间值作为select最大等待时间
	//如果定时器最小堆没有元素,那么select最大等待时间为0
	timeout_next(base, &amp;tv_p);

	//调用select_dispatch(),它会将已经准备好的事件移到已就绪事件队列中
        res = evsel-&gt;dispatch(base, evbase, tv_p);

	//有就绪事件了,那就处理就绪事件吧.
        if (base-&gt;event_count_active)
            event_process_active(base);
    }
}
</pre>
<p>event_base_loop()先从定时器最小堆中取出根节点作为select的最大等待时间,然后调用select_dispatch()将已经准备好的事件移到已就绪事件队列中,最后调用event_process_active()处理已就绪事件队列.</p>
<pre class="brush: cpp">
//略去信号事件和定时器事件处理的相关代码

static int select_dispatch(struct event_base *base, void *arg, struct timeval *tv)
{
    int res, j;
    struct selectop *sop = arg;

    //根据前面对select_add()的解释,事件fd已被加入到fd_set集中进行监视.
    res = select(sop-&gt;event_fds + 1, sop-&gt;event_readset_out,
                 sop-&gt;event_writeset_out, NULL, tv);

    for (j = 0, res = 0; j &lt;= sop-&gt;event_fds; ++j, res = 0)
    {
        struct event *r_ev = NULL, *w_ev = NULL;

       //找出已经准备好读的事件
        if (FD_ISSET(j, sop-&gt;event_readset_out))
        {
            r_ev = sop-&gt;event_r_by_fd[i];
            res |= EV_READ;
        }

       //将已经准备好读的事件移到已就绪事件队列
        if (r_ev &amp;&amp; (res &amp; r_ev-&gt;ev_events))
            event_active(r_ev, res &amp; r_ev-&gt;ev_events, 1);

	//略去对已经准备好写的事件的处理
    }
}
</pre>
<p>看看在event_base_loop()中被调用的event_process_active()代码:</p>
<pre class="brush: cpp">
static void event_process_active(struct event_base *base)
{
    struct event *ev;
    struct event_list *activeq = NULL;
    int i;
    short ncalls;

    //寻找最高优先级(priority值越小优先级越高)的已就绪事件队列
    for (i = 0; i &lt; base-&gt;nactivequeues; ++i)
    {
        if (TAILQ_FIRST(base-&gt;activequeues[i]) != NULL)
        {
            activeq = base-&gt;activequeues[i];
            break;
        }
    }

    for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq))
    {
	//如果有persist标志,则只从激活队列中移除此事件,
        if (ev-&gt;ev_events &amp; EV_PERSIST)
            event_queue_remove(base, ev, EVLIST_ACTIVE);

        else //否则则从激活事件列表,以及已注册事件中双杀此事件
            event_del(ev);

        ncalls = ev-&gt;ev_ncalls;
        ev-&gt;ev_pncalls = &amp;ncalls;

	//每个事件的回调函数的调用次数
        while (ncalls)
        {
            ncalls--;
            ev-&gt;ev_ncalls = ncalls;

	    //调用回调函数
            (*ev-&gt;ev_callback)((int)ev-&gt;ev_fd, ev-&gt;ev_res, ev-&gt;ev_arg);
        }
    }
}
</pre>
<p>现在,看看这个被阉割的只考虑I/O事件的libevent主循环框架:</p>
<div>
<pre>
event_base_loop:

	while()
	{
		//从定时器最小堆取出select最大等待时间

		//select出已准备事件,将它们移到已就绪事件队列中

		//处理已就绪事件
	}
</pre>
</div>
<hr/>
<p><span style="color: #888888;">这真是篇节能环保的文章啊,哈哈.因为libevent代码太恶心了,描述出来都觉得恶心,有空得拿来重构下..下篇文章会讲讲libevent中非常恶心的信号集成处理.</span></p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2011/02/the-annotated-libevent-sources-about-event-handling-framework/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>libevent源码浅析: 主要的结构体</title>
		<link>http://godorz.info/2011/02/the-annotated-libevent-sources-about-structures/</link>
		<comments>http://godorz.info/2011/02/the-annotated-libevent-sources-about-structures/#comments</comments>
		<pubDate>Thu, 24 Feb 2011 19:33:22 +0000</pubDate>
		<dc:creator>ripwu</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[libevent]]></category>

		<guid isPermaLink="false">http://godorz.info/?p=890</guid>
		<description><![CDATA[About libevent是一个开源的跨平台网络库,属于事件驱动机制,支持多种I/O多路复用技术,从其主页 http://www.monkey.org/~provos/libevent/ 可以看到libevent使用者众多,其中甚至包括 Memcached. 本文参照的版本是主版本号为1的最后一个版本1.4.14(没办法,2.0.1版本代码量都在4W+行以上了, :-( File Tree 先看一下libevent的文件组织, 源文件数目是: $ find . -name &#8220;*\.[hc]&#8221; &#124; wc -l 51 源代码总行数是: $ find . -name &#8220;*\.[hc]&#8221; &#124; xargs wc -l &#124; tail -n1 25562 总计 Structure Call Graph 先送上一张高清无码大图: [虚线表示包含关系,其label表示是通过哪个成员变量来包含另一个结构体的.] 从上图的虚线指向可以看出, event_base 是绝对的核心,它包含了定时器事件最小堆结构 min_heap ,信号事件队列结构 evsignal_info 和I/O事件队列结构 eventqueue ,也就是说,libevent把定时器,I/O,信号这些事件都统一到event_base管理.另外,既然libevent是事件驱动的,那么事件属性也很自然的自成一个结构体了,就是 event . 最后,结构体 eventop 是拿来封装多种I/O多路复用技术的,主要是起隐藏底层系统机制的作用. Tail [...]]]></description>
			<content:encoded><![CDATA[<h3>About</h3>
<p>libevent是一个开源的跨平台网络库,属于事件驱动机制,支持多种I/O多路复用技术,从其主页 <a href="http://www.monkey.org/~provos/libevent/" target="_blank">http://www.monkey.org/~provos/libevent/</a> 可以看到libevent使用者众多,其中甚至包括 <a href="http://www.danga.com/memcached">Memcached</a>.</p>
<p>本文参照的版本是主版本号为1的最后一个版本1.4.14(没办法,2.0.1版本代码量都在4W+行以上了, :-(</p>
<h3>File Tree</h3>
<p>先看一下libevent的文件组织, 源文件数目是:</p>
<div class="comm">$ find . -name  &#8220;*\.[hc]&#8221; | wc -l<br />
51</div>
<p>源代码总行数是:</p>
<div class="comm">$ find . -name  &#8220;*\.[hc]&#8221; | xargs  wc -l | tail -n1<br />
25562 总计</div>
<h3>Structure Call Graph</h3>
<p>先送上一张高清无码大图:</p>
<p><img class="aligncenter size-full wp-image-891" title="libevent-call-graph" src="http://godorz.info/wp-content/uploads/2011/02/libevent-call-graph.png" alt="" width="518" height="1066" /></p>
<p style="text-align: center;"><span style="color: #888888;">[虚线表示包含关系,其label表示是通过哪个成员变量来包含另一个结构体的.]</span></p>
<p>从上图的虚线指向可以看出, event_base 是绝对的核心,它包含了定时器事件最小堆结构 min_heap ,信号事件队列结构 evsignal_info 和I/O事件队列结构 eventqueue ,也就是说,<strong>libevent把定时器,I/O,信号这些事件都统一到event_base管理.</strong>另外,既然libevent是事件驱动的,那么事件属性也很自然的自成一个结构体了,就是 event .</p>
<p>最后,结构体 eventop 是拿来封装多种I/O多路复用技术的,主要是起隐藏底层系统机制的作用.</p>
<h3>Tail Queue</h3>
<p>evsignal_info 和 eventqueue 都是tail queue结构,其定义如下:</p>
<blockquote><p>A tail queue is headed by a pair of pointers, one to the head of the list and the other to the tail of the list. The elements are doubly linked so that an arbitrary element can be removed without a need to traverse the list. New elements can be added to the list before or after an existing element, at the head of the list, or at the end of the list. A tail queue may be traversed in either direction.</p>
<p>tail queue有两个指针,分别指向头部和尾部.它的元素是双向链接的,所以无须遍历整个序列就可以删除任意一个元素,而且,新的元素也可以在任意位置插入,不管是在一个已存在元素的前后,还是在队列头部,还是尾部.一个tail queue可以双向遍历.</p></blockquote>
<p>很自然的,一个tail queue有多种实现方式,在libevent中,使用的是linux下的/usr/include/sys/queue.h,我们看看linux的实现(这个纯宏实现了4种数据结构,任何一行除了注释就是宏,非常牛逼.):</p>
<pre class="brush: cpp">
#define TAILQ_HEAD(name, type)						\
struct name {								\
	struct type *tqh_first;	/* first element */			\
	struct type **tqh_last;	/* addr of last next element */		\
}

#define TAILQ_ENTRY(type)						\
struct {								\
	struct type *tqe_next;	/* next element */			\
	struct type **tqe_prev;	/* address of previous next element */	\
}
</pre>
<p>使用方式如下:</p>
<pre class="brush: cpp">
struct QUEUE_ITEM //定义节点的结构体
{
	int value; //在这里可以定义一系列的数据
	TAILQ_ENTRY(QUEUE_ITEM) entries; //作为队列入口
};   

//定义一个匿名队列的头部,这个队列的节点结构为 QUEUE_ITEM
TAILQ_HEAD(,TAILQ_ITEM) queue_head;
</pre>
<p>从上面对这些宏的使用可以看出,节点的数据结构是自由定义的(废话),但是一定得包含有这个节点相应于队列的入口.当然,我们很容易就可以将一个节点扩展为对应多个队列,只要在这个节点的定义中有多个队列入口成员就可以了.下文将会讲到,事件结构体event就做了这种扩展.</p>
<p><a href="http://www.javaeye.com/topic/292836" target="_blank">这篇文章</a> 对tail queue有比较详细的介绍,强烈建议仔细看完,因为<strong>Tail Queue在libevent中几乎是无处不在的.</strong></p>
<h3>event</h3>
<p>首先看看event的成员:</p>
<pre class="brush: cpp">
//event提供了函数接口,供Reactor在事件发生时调用,以执行相应的事件处理,
//通常它会绑定一个有效的句柄(ev_fd)做为回调函数的参数.
struct event {

	//已注册事件队列入口
	TAILQ_ENTRY (event) ev_next;

	//已激活事件队列入口
	TAILQ_ENTRY (event) ev_active_next;

	//信号事件队列入口
	TAILQ_ENTRY (event) ev_signal_next;

	//表示该event在定时器事件最小堆min_heap的索引
	unsigned int min_heap_idx;	/* for managing timeouts */

	//该事件所属的反应堆实例
	struct event_base *ev_base;

	//对于I/O事件,是绑定的文件描述符; 对于signal事件,是绑定的信号.
	int ev_fd;

	//表示事件类型: I/O,定时器或者信号
	short ev_events;

	//事件就绪执行时,将要调用ev_callback 的次数,通常为1
	short ev_ncalls;

	//该事件的超时时间,在定时器最小堆min_heap操作中作为节点值进行比较.
	struct timeval ev_timeout;

	//该事件的优先级,越小越优先.
	int ev_pri;		/* smaller numbers are higher priority */

	//该事件被激活时的回调函数
	void (*ev_callback)(int, short, void *arg);

	//该事件的标记信息,表示其当前的状态,即它在哪个链表中
	int ev_flags;

	... //其他成员.
};
</pre>
<p>可以看到,一个事件是可以插入到多个队列的,当它与一个反应堆实例(event_base)关联时,这个事件被插入到反应堆实例下的已注册事件队列 event_base -&gt; eventqueue ,当它处于就绪状态时,会被插入到反应堆实例下的已激活事件队列 event_base -&gt; activequeues[id], id = event -&gt; ev_pri .同时,如果此事件是信号事件,那么它会被插入到反应堆结构体下的信号事件结构体下的信号队列 event_base -&gt; evsignal_info -&gt; evsigevents[id], id = event -&gt; ev_fd .</p>
<p>需要指出的,每个事件都保持了一个成员 struct event_base *ev_base; ,它表示该事件属于哪个反应堆实例.</p>
<p>还有一个成员需要注意, short ev_events; ,它表明此事件的事件类型,libevent正是基于此实现对I/O,信号,定时 3种事件的封装的.</p>
<h3>min_heap</h3>
<p>min_heap是存储定时事件的最小堆,它应该是libevent里最简单的结构体了:</p>
<pre class="brush: cpp">
typedef struct min_heap
{
    struct event** p; //p指向一个动态分配的数组,数组元素是event指针.
    unsigned n, a; // n表示目前保存了多少元素,a表示p指向的内存能够存储event指针的个数.
} min_heap_t;
</pre>
<p>之所以会有这个结构体,是因为I/O多路复用机制,比如说select,往往要求一个最大等待时间,而最小堆的根节点表示的就是最小超时时间,所以把根节点时间值传给select作为其最大等待时间就可以了.而在堆中取出根节点复杂度为O(1).</p>
<p>btw,从min_heap的成员p可以看出,libevent使用的是开辟一块连续存储区(即数组)来实现堆的策略.这很容易就可以办到,无非就是shift_up和shift_down操作罢了,但是libevent做了小小的优化,这导致了算法的不清晰,有点得不偿失的感觉,不提为妙.</p>
<h3>eventop</h3>
<p>eventop实现了对系统I/O多路复用机制的封装,这些机制包括 select poll epoll evport kqueue devpoll (别忘了libevent是跨平台的).</p>
<p>看看eventop的成员吧:</p>
<pre class="brush: cpp">
struct eventop {
	const char *name; //表示哪种I/O多路复用机制
	void *(*init)(struct event_base *); //初始化
	int (*add)(void *, struct event *); //注册事件
	int (*del)(void *, struct event *); //删除事件
	int (*dispatch)(struct event_base *, void *, struct timeval *); //事件分发
	void (*dealloc)(struct event_base *, void *); //注销,释放资源

	//是否需要重新初始化
	int need_reinit;
};
</pre>
<p>可以看到,eventop里面包含了5中操作的函数指针,libevent就是通过这一点来实现封装的.不同的机制定义不同的操作,但这些操作的接口却是保持一致的,以select接口为例:</p>
<pre class="brush: cpp">
const struct eventop selectops =
{
    &quot;select&quot;,
    select_init,
    select_add,
    select_del,
    select_dispatch,
    select_dealloc,
    0
};
</pre>
<p>其中,5个select_* 成员都是函数指针,不同机制的函数以static形势封装在不同的文件下<span style="color: #999999;"> </span>,举个例子, static void *<br />
select_init(struct event_base *base) 函数的声明和定义在select.c文件下,而epoll机制的初始化函数 static void *<br />
epoll_init(struct event_base *base) 声明和定义在epoll.c文件下.</p>
<p>好了,做好了这些准备,各机制的信息已经隐藏起来了,那么到底该怎样封装呢? libevent借助了一个static数组来保存这些I/O复用机制的结构体指针,代码如下:</p>
<pre class="brush: cpp">#ifdef HAVE_EVENT_PORTS
extern const struct eventop evportops;
#endif
#ifdef HAVE_SELECT
extern const struct eventop selectops;
#endif
...
#ifdef WIN32
extern const struct eventop win32ops;
#endif

static const struct eventop *eventops[] =
{
#ifdef HAVE_EVENT_PORTS
    &amp;evportops,
#endif
#ifdef HAVE_SELECT
    &amp;selectops, //在select.c中
#endif
...
#ifdef WIN32
    &amp;win32ops,
#endif
    NULL
};
</pre>
<p>其中, HAVE_* 宏是在configure时保存在config.h中的.注意在声明 evportops selectops 等变量时使用的是extern,由此使编译器知道这是外部变量,在别处寻找,或者在链接时寻找其符号名.(在libevent中,是在链接时才寻找到symbol的,封装嘛..)</p>
<p>封装已经ok了,那么程序运行时应该选择哪种I/O多路复用机制呢? libevent很不人性化的一点就在这里,它没有用配置文件或者config.h信息来保证用户可以灵活的选择,而是写死在代码里了(当然,手动修改代码重编译就是唯一的方法了.<span style="color: #999999;">说到这里,还有一点要吐槽的是,libevent用的autoconf,configure文件和makefile文件难懂异常,在这方面完败于手工编写makefile的nginx.</span>). 代码正是上面的 struct eventop *eventops[] 数组,此数组最后一个有效的元素就是libevent选择的机制.选择代码如下:</p>
<pre class="brush: cpp">
//初始化一个反应堆实例
struct event_base *event_base_new(void)
{
    ...
    for (i = 0; eventops[i] &amp;&amp; !base-&gt;evbase; i++)
    {
        //选定I/O多路复用机制
        base-&gt;evsel = eventops[i];
    }
    ...
}
</pre>
<h3>evsignal_info</h3>
<p>evsignal_info 是用来管理信号事件的,代码如下:</p>
<pre class="brush: cpp">
struct evsignal_info {

	//是否有信号发生的标记
	volatile sig_atomic_t evsignal_caught;

	//evsigevents[signo]表示注册到信号 signo 的事件链表
	struct event_list evsigevents[NSIG];

	//具体记录每个信号触发的次数,evsigcaught[signo]是记录信号signo被触发的次数
	sig_atomic_t evsigcaught[NSIG];

	... //其他成员
};
</pre>
<p>其中, struct event_list evsigevents[NSIG]; 成员是一个数组,它的元素 evsigevents[id] 表示注册到信号id的事件链表.</p>
<p>关于evsignal_info 还有很多很多要说的,这留给下篇文章,本文对其描述到此为止.</p>
<h3>event_base</h3>
<p>终于到了最最核心的 event_base 了.秒杀之,代码如下:</p>
<pre class="brush: cpp">
struct event_base {

	//保存I/O机制
	const struct eventop *evsel; 

	//有多少个event
	int event_count;		/* counts number of total events */ 

	//有多少个活动的event
	int event_count_active;	/* counts number of active events */ 

	//存储已就绪事件队列的数组
	struct event_list **activequeues;

        //已就绪事件队列数组的元素个数
	int nactivequeues;

	//保存信号事件队列的结构体
	struct evsignal_info sig;

	//已注册事件队列
	struct event_list eventqueue; 

	//这是定时器事件最小堆
	struct min_heap timeheap;

	...//其他成员
};
</pre>
<p>重要的数据成员的说明已经在注释中给出了.相信我,回头看看前面event的描述吧.</p>
<h3>Conclusion and next steps</h3>
<p>本文介绍了libevent主要的结构体,接下来会分析事件处理框架或者是信号/定时器如何集成到I/O处理中的.我还没决定呢.</p>
]]></content:encoded>
			<wfw:commentRss>http://godorz.info/2011/02/the-annotated-libevent-sources-about-structures/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

