<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>技术</title><link>http://blog.vckbase.com/localvar/category/710.html</link><description>技术</description><managingEditor>局部变量</managingEditor><dc:language>zh-CHS</dc:language><generator>.Text Version 0.958.2004.214</generator><item><dc:creator>局部变量</dc:creator><title>解决了一个困惑很久的bug</title><link>http://blog.vckbase.com/localvar/archive/2009/01/08/36194.html</link><pubDate>Thu, 08 Jan 2009 08:58:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2009/01/08/36194.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/36194.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2009/01/08/36194.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/36194.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/36194.html</trackback:ping><description>&lt;p&gt;让这个bug困扰了很久，前一段太忙只找了个临时解决方案而没有追究原因，今天终于把它搞清楚了。由于测试时只在多cpu系统上出现，我甚至一度怀疑它是cpu的bug&lt;img title="大汗" alt="大汗" src="http://vckbase.com/bbs/image/emimg/010.gif"&gt; 。&lt;/p&gt; &lt;p&gt;两个c/s结构的网络通讯程序，服务器端使用完成端口模型，客户端使用阻塞模型，双方以一种客户端发送命令，服务器端处理，然后返回应答的方式通讯。问题出在服务器端。以下是服务器端代码的大致处理逻辑：&lt;/p&gt;&lt;pre&gt;&lt;span style="color: #0000ff"&gt;long&lt;/span&gt; &lt;span style="color: #0000ff"&gt;volatile&lt;/span&gt; g_busy = 0;
&lt;span style="color: #0000ff"&gt;void&lt;/span&gt; iocp_thread()
{
	&lt;span style="color: #0000ff"&gt;while&lt;/span&gt;( GetQueuedCompletionStatus() )
	{
		&lt;span style="color: #0000ff"&gt;if&lt;/span&gt;( InterlockedCompareExchange( &amp;amp;g_busy, 1, 0 ) != 0 )
			WSASend( "&lt;span style="color: #8b0000"&gt;服务器忙&lt;/span&gt;" );
		&lt;span style="color: #008000"&gt;// 处理命令&lt;/span&gt;
		ProcessCommand();
		WSASend( "&lt;span style="color: #8b0000"&gt;应答信息&lt;/span&gt;" );
		InterlockedExchange( &amp;amp;g_busy, 0 );
	}
}&lt;/pre&gt;
&lt;p&gt;其中ProcessCommand需要互斥运行（这是简化的逻辑，实际上有很多不同的命令，有些需要互斥，有些可以并行，否则就没必要用完成端口了），并且需要一定的时间才能处理完毕。为了避免多个客户端同时执行命令，导致所有的iocp线程都等在那，我把g_busy当成了一个锁，第一个线程可以成功进入，其它的都直接向客户端返回&amp;#8220;服务器忙&amp;#8221;。&lt;/p&gt;
&lt;p&gt;程序一直都运行的很好，直到有一天把服务器程序装到了一台有双核cpu的机器上。我发现，如果让客户端连续发送命令，即收到上一条命令的应答后立即发送下一条命令，就会随机的返回&amp;#8220;服务器忙&amp;#8221;，而这时只有一个客户端连接上去，按照我设想的逻辑是不可能出这种情况的。检查了半天代码，没觉得有什么问题，调试吧，又遇到了另一个难题，海森堡的测不准原理起作用了，做的工作太多问题就消失了，做的太少又得不到什么有价值的信息。搞得我很是头疼。&lt;/p&gt;
&lt;p&gt;今天再次看这个问题，突然想到：它肯定和线程切换相关，所以我应该记录下每次处理命令的线程的ID，这样出错时就可以看看上次成功执行命令的那个线程在干什么了。方法正确了，问题也就迎刃而解了，我发现，出问题时，上一个线程的WSASend居然还没有返回，也就是说，客户端已经收到应答并发送了下一条命令，服务器端也收到了命令并准备处理，但上一条命令的应答却还没有完全发送完成，难怪出错了！&lt;/p&gt;
&lt;p&gt;总结经验教训，感觉自己一开始被两点给误导了，一是实际程序中的WSARecv/WSASend藏的比较深，没这么明显，所以没注意到。二是当时粗略检查代码觉得没问题，就把主要精力放在ProcessCommand上了，由于我把它里面一段访问sql server的代码注释掉以后，问题就不出了，所以还看了半天atl oledb的源码，最后精疲力尽，其它事情又比较多就放弃了。&lt;/p&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/36194.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>发布一个小工具：EasyDump</title><link>http://blog.vckbase.com/localvar/archive/2009/01/06/36183.html</link><pubDate>Tue, 06 Jan 2009 08:33:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2009/01/06/36183.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/36183.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2009/01/06/36183.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/36183.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/36183.html</trackback:ping><description>&lt;P&gt;为了分析用户使用过程中出现的软件Bug，经常需要.dmp文件的帮助。一般我们会用WinDbg或adplus制作这个文件，可这两个工具都有点&amp;#8220;太难&amp;#8221;了，往往要费九牛二虎之力才能教会用户。而让程序在崩溃时自动转储或用Dr. Watson转储虽然使用简单，却只能做崩溃转储，对死锁之类的情况则无能为力。&lt;/P&gt;
&lt;P&gt;所以我决定自己写一个小工具降低一下制作.dmp文件的难度，也就有了今天发布的这个EasyDump（轻松转储）。代码和可执行文件都放到google code（也是刚注册的，尝试一下:)）上去了，大家可以到&lt;A title=http://code.google.com/p/easytools/ href="http://code.google.com/p/easytools/"&gt;http://code.google.com/p/easytools/&lt;/A&gt;下载。&lt;/P&gt;
&lt;P&gt;程序还没有很好的测试过，如果有bug的话，应该可以直接在项目主页上报告。另外下一步考虑增加三个功能：首先是异常过滤，因为first chance异常太多了！如果选择了生成.dmp的话，一秒钟可能就有十个甚至更多的文件，设置了异常过滤后，可以把一些不关心的异常屏蔽掉，不生成文件。其次是如果没有second chance的话，就把first chance的文件直接删掉，也有助于减少不必要的文件。第三是界面的国际化，也发布个英文版什么的。&lt;/P&gt;
&lt;P&gt;2009.01.08: 自动删除first chance文件的功能已经实现.&lt;BR&gt;2009.01.22: 异常过滤功能已经实现.&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/36183.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>命令行下进行数字签名</title><link>http://blog.vckbase.com/localvar/archive/2008/11/18/35679.html</link><pubDate>Tue, 18 Nov 2008 03:26:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/11/18/35679.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/35679.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/11/18/35679.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/35679.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/35679.html</trackback:ping><description>&lt;P&gt;网上介绍数字签名的文章，大多使用signtool的signwizard命令实现，这种方式虽说简单，却需要人为干预，不能自动执行。msdn上说signtool的sign命令可以在命令行中完成签名，但描述的相当模糊，试了半天，终于找到了它的使用方法，一共执行四条命令即可，前三条一次性执行，最后生成一个个人证书(pfx)，最后一条用于实际签名，可以放在post build event中去自动执行。&lt;/P&gt;
&lt;P&gt;1. makecert生成x.509证书和私钥, 会弹出界面要求输入两次密码, 我输的是123, 其中localvar studio是公司名&lt;BR&gt;makecert /sv sign.pvk /n "CN=localvar studio" sign.cer&lt;/P&gt;
&lt;P&gt;2. 把x.509证书转换为Software Publisher Certificate&lt;BR&gt;cert2spc sign.cer sign.spc&lt;/P&gt;
&lt;P&gt;3. 把pvk转换为pfx, 例子中的123是私钥密码&lt;BR&gt;pvk2pfx -pvk sign.pvk -pi 123 -spc sign.spc -pfx sign.pfx&lt;/P&gt;
&lt;P&gt;4. 签名, 稍微调整一下，就能写在post build event里了，123是密码&lt;BR&gt;signtool sign /f sign.pfx /p 123 test.exe&lt;/P&gt;
&lt;P&gt;上面的例子只是演示签名过程，由于证书是本机做出来的，所以签了名也没用，用户那看到的仍然是&amp;#8220;未知发行商&amp;#8221;。向证书颁发机构申请真正的证书时，能直接得到.spc和.pvk文件，所以就不用执行前两步了。&lt;/P&gt;
&lt;P&gt;PS: 证书颁发机构真是坐地收钱呀，几秒钟生成个证书，每年就收好几千。&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/35679.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>_tfopen指定文件编码后程序崩溃</title><link>http://blog.vckbase.com/localvar/archive/2008/11/03/35543.html</link><pubDate>Mon, 03 Nov 2008 04:58:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/11/03/35543.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/35543.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/11/03/35543.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/35543.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/35543.html</trackback:ping><description>vs05和08的crt增加了一点功能, 使用fopen(_wfopen)时可以指定文件的编码, 但我发现这个功能好像有很多bug, 会导致程序崩溃。&lt;BR&gt;我是使用下面的形式打开文件的:&lt;BR&gt;TCHAR buf[1024];&lt;BR&gt;FILE* fp = _tfopen( _T(&amp;#8220;a.txt&amp;#8221;) , _T(&amp;#8221;rt,ccs=UNICODE&amp;#8221;) );&lt;BR&gt;_fgetts( buf, _countof(buf), fp );&lt;BR&gt;按msdn的说法，这时fopen会根据文件的bom自动判断文件的编码, 并保证buf中字符的编码总是我希望的那一种。&lt;BR&gt;可是这个程序在使用mbcs并打开unicode编码的文件时会崩溃, 考虑到我的程序只发布unicode版本, 所以忍了，啥也不说。&lt;BR&gt;但这两天发现, UNICODE版本在fgets时也会崩溃, 方法是新建一个excel文件然后重命名为a.txt。&lt;BR&gt;&lt;BR&gt;我仔细读了两天msdn，并测试了各种形式，感觉不像是我的错误。&lt;BR&gt;在网上没找到类似的描述, 所以记下来，也许有人会碰到同样的问题。&lt;img src ="http://blog.vckbase.com/localvar/aggbug/35543.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>多线程和函数里的静态变量</title><link>http://blog.vckbase.com/localvar/archive/2008/05/29/33945.html</link><pubDate>Thu, 29 May 2008 01:33:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/05/29/33945.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/33945.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/05/29/33945.html#Feedback</comments><slash:comments>19</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/33945.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/33945.html</trackback:ping><description>&lt;DIV&gt;试试下面这段代码的输出是什么?&lt;/DIV&gt;
&lt;TABLE style="BORDER-COLLAPSE: collapse" borderColor=#999999 cellSpacing=0 cellPadding=0 width="75%" bgColor=#f1f1f1 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P style="MARGIN: 5px; LINE-HEIGHT: 150%"&gt;&lt;CODE&gt;&lt;SPAN style="COLOR: #000000"&gt;&lt;SPAN style="COLOR: #0000cc"&gt;#&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;include&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;stdio&lt;SPAN style="COLOR: #0000cc"&gt;.&lt;/SPAN&gt;h&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;#&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;include&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;process&lt;SPAN style="COLOR: #0000cc"&gt;.&lt;/SPAN&gt;h&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;#&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;include&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;windows&lt;SPAN style="COLOR: #0000cc"&gt;.&lt;/SPAN&gt;h&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;class&lt;/SPAN&gt; foo&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;public&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;:&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foo&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;printf&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"before sleep\n"&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;Sleep&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; 1000 &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;printf&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"after sleep\n"&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;void&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;test&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;printf&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"in test\n"&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;foo&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt; bar&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;static&lt;/SPAN&gt; foo a&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;return&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;&amp;amp;&lt;/SPAN&gt;a&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;unsigned&lt;/SPAN&gt; __stdcall thread&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000ff"&gt;void&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foo&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt; p &lt;SPAN style="COLOR: #0000cc"&gt;=&lt;/SPAN&gt; bar&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;p&lt;SPAN style="COLOR: #0000cc"&gt;-&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;test&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;return&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;int&lt;/SPAN&gt; _cdecl main&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000ff"&gt;int&lt;/SPAN&gt; argc&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000ff"&gt;char&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt; argv &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;for&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000ff"&gt;int&lt;/SPAN&gt; i &lt;SPAN style="COLOR: #0000cc"&gt;=&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt; i &lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt; 10&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;+&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;+&lt;/SPAN&gt;i &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;uintptr_t&lt;/SPAN&gt; t &lt;SPAN style="COLOR: #0000cc"&gt;=&lt;/SPAN&gt; _beginthreadex&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;NULL&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; thread&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;NULL&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;NULL&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CloseHandle&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;HANDLE&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;t &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;Sleep&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; 5000 &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;return&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/CODE&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;BR&gt;不知道C/C++标准有什么规定没有, 但粗看起来好像是编译器的问题呀。我用的是vc8，谁帮忙测测别的编译器。&lt;BR&gt;根据星星的建议，把输出贴出来，如下：&lt;BR&gt;before sleep&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;after sleep&lt;BR&gt;in test&lt;BR&gt;这里的问题是至少有10个中的9个线程没有等对象初始化完成，就已经调用对象的方法了，这肯定是不对的。我大概看了一下反汇编的结果，实际上还可能出现构造函数被调用多次的情况。&lt;BR&gt;要解决这个问题，在编译器的层次上要容易一点。如果是在用户程序的层次上，则麻烦的多，因为这类方法都会涉及到另一个静态变量的初始化。&lt;img src ="http://blog.vckbase.com/localvar/aggbug/33945.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>使用SVN实现版本号自增</title><link>http://blog.vckbase.com/localvar/archive/2008/05/20/33718.html</link><pubDate>Tue, 20 May 2008 01:12:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/05/20/33718.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/33718.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/05/20/33718.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/33718.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/33718.html</trackback:ping><description>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在《介绍一下SVN》一文中，我提到了自动递增版本号的功能，现在就来具体说明一下实现方法。虽然标题中说的是&amp;#8220;使用SVN&amp;#8221;，但我们实际用的是SVN的客户端工具TortoiseSVN中的SubWCRev程序。另外文中的例子也使用了Visual Studio的SVN插件VisualSVN，它并非必须，用了方便一些，不用也行。我平时主要使用C/C++语言，但考虑C#有更大的用户群，我的示例项目也采用了C#。
&lt;H3&gt;&lt;/H3&gt;
&lt;H4&gt;1.&amp;nbsp;生成一个名为autover的项目&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;注意项目的Properties文件夹下有一个名为AssemblyInfo.cs的文件，autover程序的版本号就写在它里面。&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image001.jpg"&gt; 
&lt;H4&gt;2.&amp;nbsp;创建模板文件&lt;/H4&gt;
&lt;P&gt;在windows的资源管理器中进入Properties文件夹，把AssemblyInfo.cs文件复制一份，命名为AssemblyInfo.template.cs，并把它加入到项目中来。&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image002.jpg"&gt; 
&lt;H4&gt;3.&amp;nbsp;修改AssemblyInfo.template.cs的属性&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AssemblyInfo.template.cs文件是用来自动生成版本号的模板文件，它不应该被编译，所以我们要把它的Build Action改成None，如下图所示：&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image003.jpg"&gt; 
&lt;H4&gt;4.&amp;nbsp;修改AssemblyInfo.template.cs的内容&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在这个文件中，你能找到如下两行代码： &lt;BR&gt;&lt;FONT color=#0000ff&gt;[assembly: AssemblyVersion( "1.0.0.0" )] &lt;BR&gt;[assembly: AssemblyFileVersion( "1.0.0.0" )] &lt;BR&gt;&lt;/FONT&gt;其中的&amp;#8220;1.0.0.0&amp;#8221;就是程序的版本号，它使用的是&amp;#8220;主版本号.次版本号.内部版本号.修订号&amp;#8221;的形式。前三个改成你自己需要的数字，最后一个改成&amp;#8220;$WCREV$&amp;#8221;，改完之后应该是类似下面的样子： &lt;BR&gt;&lt;FONT color=#0000ff&gt;[assembly: AssemblyVersion( "1.0.0.$WCREV$" )] &lt;BR&gt;[assembly: AssemblyFileVersion( "1.0.0.$WCREV$" )]&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在这个文件的最后，你还应该加上下面两段代码，它们可以检测出有本地修改（修改了但没有提交）的代码和有混合版本的代码。 &lt;BR&gt;&lt;FONT color=#0000ff&gt;#if $WCMIXED?true:false$ &lt;BR&gt;#if DEBUG &lt;BR&gt;#warning mixed update revisions founded &lt;BR&gt;#else &lt;BR&gt;#error mixed update revisions founded &lt;BR&gt;#endif &lt;BR&gt;#endif &lt;BR&gt;&lt;BR&gt;#if $WCMODS?true:false$ &lt;BR&gt;#if DEBUG &lt;BR&gt;#warning local modification founded &lt;BR&gt;#else &lt;BR&gt;#error local modification founded &lt;BR&gt;#endif &lt;BR&gt;#endif&lt;/FONT&gt;&lt;/P&gt;
&lt;H4&gt;5.&amp;nbsp;修改项目属性&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在项目属性的Build Event页的Pre-build event command line中输入： &lt;BR&gt;&lt;FONT color=#0000ff&gt;"%ProgramFiles%\TortoiseSVN\bin\SubWCRev.exe" $(SolutionDir) $(ProjectDir)Properties\AssemblyInfo.template.cs $(ProjectDir)Properties\AssemblyInfo.cs -f &lt;BR&gt;&lt;/FONT&gt;注意，这里我们必须保证TortoiseSVN安装到了默认路径上。在多人参加的项目中这应该是强制性的要求，否则，大家安装的路径都不一样，甲机器上能用的配置，到了乙机器上可能就不行了。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;然后，你可能还需要将Publish页中的Automatically increment revision with each publish选项关掉（我不确定这步是否必须）。C#可以自己递增版本号，但它生成的版本号和代码库中的代码没有对应关系，我个人觉得意义不大。并且它还可能会把我们的版本自增机制搞乱。所以应该关掉。&lt;/P&gt;
&lt;H4&gt;6.&amp;nbsp;把项目加入版本库&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;使用VisualSVN的Add solution to Subversion命令把项目加入SVN，但不要提交。&lt;/P&gt;
&lt;H4&gt;7.&amp;nbsp;从SVN中排除AssemblyInfo.cs文件&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;每次编译时，这个文件都会基于AssemblyInfo.template.cs重新生成，所以没必要加入版本库。这步做完之后就可以提交整个项目了。&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image004.jpg"&gt; 
&lt;H4&gt;8.&amp;nbsp;编译&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;编译完成后，看一下生成的autover.exe文件的版本信息，本例中是1.0.0.1。随便改点什么，提交，重新编译，你会发现它自动变成了1.0.0.2，也就是程序的修订号总是与生成它的代码的修订号一致。这样，当程序出问题后，我们通过这个数字就能轻松得到生成它的那一版代码了。&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image005.jpg"&gt; 
&lt;H4&gt;9.&amp;nbsp;其它问题&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;第8步中大家做完修改后再编译时可能会看到警告或错误信息，说代码有本地修改或混合版本。这就是第4步中，在AssemblyInfo.template.cs文件最后加的两段代码的作用，它们检测代码是否都已经提交了并且版本是否一致，一旦发现问题就会在调试版中生成警告信息，在发布版中生成错误信息。使用这种方法，我们可以基本消除发布的程序的版本和代码的版本出现不一致的可能性。去掉这两个错误或警告的方法也很简单，把代码整体提交或更新一下就行了。&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/33718.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>转载：extern "C"</title><link>http://blog.vckbase.com/localvar/archive/2008/03/10/32879.html</link><pubDate>Mon, 10 Mar 2008 01:16:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/03/10/32879.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/32879.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/03/10/32879.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/32879.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/32879.html</trackback:ping><description>&lt;A href="http://www.newsmth.net/bbstcon.php?board=CPlusPlus&amp;amp;gid=218259"&gt;原文&lt;/A&gt;&lt;BR&gt;本以为很简单，仔细阅读了一下 C++ 标准，发现内容还不少。总结了一下。&lt;BR&gt;&lt;BR&gt;要点：&lt;BR&gt;&lt;BR&gt;函数类型，函数名，变量名具有语言链接性，language linkage。&lt;BR&gt;&lt;BR&gt;语言链接性可能会影响到名字以及调用约定等，由实现决定。&lt;BR&gt;&lt;BR&gt;C++ 默认的语言连接性是 C++ 语言链接性。&lt;BR&gt;&lt;BR&gt;语言链接性仅作用于函数类型，函数名，变量名。&lt;BR&gt;&lt;BR&gt;不同语言链接性的函数类型是不同的类型，即便其余的地方都相同。&lt;BR&gt;&lt;BR&gt;语言链接性用链接性规格（linkage-specification）来声明，分有无大括号两种形式。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern string-literal { declaration-seq opt }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern string-literal declaration;&lt;BR&gt;&lt;BR&gt;所有的实现都必须支持 "C" 和 "C++" 链接性。&lt;BR&gt;&lt;BR&gt;链接性规格允许嵌套，此时最内层的那个起作用，但是并不建立作用域。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { extern "C++" { class A{}; } }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { class B:A{};}&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;因为不是作用域，B 可以看到 A&lt;BR&gt;&lt;BR&gt;如果C链接性施加到C++类成员和成员函数的类型，忽略C链接性，但是其余的地方依然有效，比如成员函数的参数。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;比如：&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { class A{ void f(void (*p)()){} }; }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;A 不是函数和变量，没有语言链接性&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;f 是 C++ 成员函数，忽略所指定的 C 链接性，如果在外层没指定别的，就是&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;C++链接性。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;p 具有 C 链接性。&lt;BR&gt;&lt;BR&gt;除了C++链接性的函数外，同一个函数不带链接性规格的声明的函数不能早于带的。&lt;BR&gt;如果前面声明了带链接性规格的形式，后面又出现了不带的形式，不受影响。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;比如 extern "C" void foo(); void foo(); 是可以的，反过来不行。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;但是 extern void foo(); extern "C++" void foo(); 可以。&lt;BR&gt;&lt;BR&gt;特定名字的 C 语言链接性的函数最多只能有一个，即便放在不同的 namespace 里，&lt;BR&gt;也是同一个函数，变量也是如此。这样的函数或变量也不能重复定义。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;namespace A { extern "C" void f(); }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;namespace B { extern "C" void f(); }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;两个 f 是同一个&lt;BR&gt;&lt;BR&gt;有链接性规格的函数具有外部链接性。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;因此&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" void f();&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;static void f();&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;是错误的。&lt;BR&gt;&lt;BR&gt;链接性规格，带大括号的形式，不影响里面的声明是声明还是定义。&lt;BR&gt;单个声明的形式，视为 extern 限定符。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" int i; &amp;nbsp; &amp;nbsp; &amp;nbsp; 是声明；&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { int i; } &amp;nbsp; 是定义；&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { extern int i; } 又是声明。&lt;BR&gt;&lt;BR&gt;单个声明形式的连接性规格，不能再指定存储类别。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" static void f(); // error&lt;BR&gt;&lt;BR&gt;其他说明：&lt;BR&gt;因为语言链接性只跟链接性有关，因此 C 语言链接性的函数也可以有默认参数，&lt;BR&gt;函数的接口可以有引用。&lt;BR&gt;&lt;BR&gt;标准 C++ 库中的符号默认是 C++ 链接性。&lt;BR&gt;C++ 用的标准 C 库中的外部链接性符号可以是 C 或者 C++ 语言链接性，推荐后者。&lt;BR&gt;任何带有连续两个下划线的名字保留给实现用作同时具有 C 和 C++ 链接性的名字。&lt;BR&gt;&lt;BR&gt;标准 C 库中的任何函数签名都保留给实现用来做同时具有 C 和 C++ 链接性的名字。&lt;BR&gt;&lt;BR&gt;因为不同语言连接性的相同签名的函数类型算作不同类型，所以可以相互重载，不算同&lt;BR&gt;一个函数。&lt;BR&gt;//commented by ctrlz&lt;BR&gt;//这里应该是ill-formed. &lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" int atexit(void (*f)(void)) &amp;nbsp; // 这里的 f 是 C 链接性&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C++" int atexit(void (*f)(void)) // 这里的 f 是 C++ 链接性&lt;BR&gt;&lt;BR&gt;类似的有两个原型的函数还有 bsearch，qsort 等带函数指针类型的参数的函数。&lt;BR&gt;&lt;BR&gt;以上都是 C++ 标准里的理论，落实到实践上，为了实现方便，很多编译器都把 C 和 &lt;BR&gt;C++ 链接性的函数的类型视为相同的类型，此时的链接性仅仅影响生成的名字，或者&lt;BR&gt;还可能影响异常规格，比如把 extern "C" 的函数默认视为 throw() 的等，并不影响&lt;BR&gt;调用约定。同样也为了实现的方便，C++ 所用的 C 库中的符号也都是 C 语言链接性的，&lt;BR&gt;因此这样的情况下，atexit, bsearch, qsort 等在这些编译器搭配的库中也就只有一种&lt;BR&gt;原型了。&lt;BR&gt;&lt;BR&gt;这种情况下，同样签名的 C 和 C++ 链接性的函数类型就算做同种类型了，下边的程序&lt;BR&gt;编译就会失败：&lt;BR&gt;//commented by ctrlz.&lt;BR&gt;//ill-formed&lt;BR&gt;void f(void (*p)())&lt;BR&gt;{&lt;BR&gt;}&lt;BR&gt;extern "C" void f(void (*q)())&lt;BR&gt;{&lt;BR&gt;}&lt;BR&gt;&lt;BR&gt;VC 和 gcc 在这一点上都是不符合标准的。&lt;BR&gt;&lt;BR&gt;链接性不应该影响函数的类型，就像普通函数的返回类型不影响函数的类型一样，从重载解&lt;BR&gt;析的角度看，如果接受以上的行为，势必影响重载解析的过程。&lt;img src ="http://blog.vckbase.com/localvar/aggbug/32879.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>翻译：sqlite中原子提交的实现</title><link>http://blog.vckbase.com/localvar/archive/2008/02/13/32581.html</link><pubDate>Wed, 13 Feb 2008 01:47:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/02/13/32581.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/32581.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/02/13/32581.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/32581.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/32581.html</trackback:ping><description>最近在实现一个类似数据库事务操作的东西，找到了&lt;A href="http://www.sqlite.org/atomiccommit.html"&gt;这篇关于sqlite事务实现的文章&lt;/A&gt;，觉得还不错。由于网上相关的中文资料很少，所以决定把它翻译过来。不过，等我翻译完了之后，发现有人已经&lt;A href="http://chensheng.net/p/sqlite/auto_commit_zh_cn.html"&gt;先我一步完成了&lt;/A&gt;，我对比了一下这两个译本，自认为我的翻译质量更高一点，故仍有必要把它也发布出来。 
&lt;H3&gt;1. 引言&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;像SQLITE这样支持事务的数据库的一个重要特性是&amp;#8220;原子提交&amp;#8221;。原子提交意味着，一个事务中的所有修改动作要么全都发生，要么一个都不发生。有了原子提交，对一个数据库文件不同部分的多次写操作，就会像瞬间同时完成了一样。当然，现实中的存储器硬件会把写操作串行化，并且写每个扇区都会花上那么一小段时间，所以，绝对意义上的&amp;#8220;瞬间同时完成&amp;#8221;是不可能的。但SQLITE的原子提交逻辑还是让整个过程看起来像那么回事。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE保证，即使事务执行过程中发生了操作系统崩溃或掉电，整个事务也是原子的。本文描述了SQLITE实现原子提交时所采用的技术。 
&lt;H3&gt;2. 对硬件的假设&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;虽然有的时候会使用闪存，但下文中，我们将把存储设备称为&amp;#8220;磁盘&amp;#8221;。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们假设对磁盘的写操作是以&amp;#8220;扇区&amp;#8221;为单位的，也就是说不可能直接对磁盘进行小于一个扇区的修改，要想进行这类修改，你必须把整个扇区读进内存，进行所需的修改，然后再把整个扇区写回去。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;对真正&amp;#8220;磁盘&amp;#8221;来说，读写操作的最小单位都是一个扇区；但闪存有些不同，它们的最小读单位一般远小于最小写单位。SQLITE只关心最小写单位，所以，在本文中，我们说&amp;#8220;扇区&amp;#8221;的时候，指的是向存储器中写数据时的最小数据量。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3.3.14版之前，SQLITE在任何情况下都认为一个扇区的大小是512字节，有一个编译期选项能改变这个值，但从未有人用更大一些的值测试过相关代码。直到不久以前，把这个值定为512都是合理的，因为所有的磁盘驱动器都在内部使用512字节的扇区。但最近，有人把磁盘扇区的大小提升到了4096字节，而且，闪存的扇区一般也是大于512字节的。由于这些原因，从3.3.14版开始，SQLITE的操作系统接口层提供了一种可以从文件系统获取真实扇区大小的方法。不过，到目前为止（3.5.0版），这一方法仍然只是返回一个硬编码的512字节，因为不论是win32系统还是unix系统，都没有一个标准的机制来获得实际的值。但这种方法给了嵌入式设备的提供商们根据实际情况进行调整的能力，也让我们未来在win32和unix上给出一个更有意义的实现成了可能。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE并不假设对扇区的写操作是原子的，它仅假设这种写是&amp;#8220;线性&amp;#8221;的。所谓线性是指：写一个扇区时，硬件总是从扇区一端开始，一个字节一个字节的写到另一端结束，中间不会后退，硬件可以从头向尾写，也可以从尾向头写。如果掉电发生时只写到了扇区的中间，则可能出现扇区一部分修改了而另一部分没被修改的情况。SQLITE在这里做的一个关键假设是：只要扇区被修改了，那么它的第一个字节和最后一个字节中的至少一个会被修改，也就是说，硬件绝不会从中间开始向两端写。我们不清楚这个假设是否总是对的，但它看起来是合理的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在上一段中，我们说&amp;#8220;SQLITE没有假设写扇区是原子的&amp;#8221;。默认情况下，这是正确的，但在3.5.0版中，我们增加了一个叫做&amp;#8220;虚拟文件系统（VFS）&amp;#8221;的接口，它是SQLITE和底层文件系统通讯的唯一路径。代码中包含了用于unix和windows的默认VFS实现，同时提供了一种在运行时创建新VFS实现的机制。在这个新的VFS接口中有一个称为&amp;#8220;xDeviceCharacteristics&amp;#8221;的方法，它通过询问文件系统来判断文件系统是否支持某些特性。如果文件系统支持某个特性，SQLITE就会试着利用这个特性进行某种优化。默认的xDeviceCharacteristics不会指出文件系统支持原子的写扇区操作，所以与此相关的优化都是关闭的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设操作系统会缓冲写操作，并且写操作会在数据被真正写到磁盘上之前返回。SQLITE还假设写操作会被操作系统记录下来。因此，SQLITE会在关键点上执行&amp;#8220;flush&amp;#8221;或&amp;#8220;fsync&amp;#8221;，并假设&amp;#8220;flush&amp;#8221;和&amp;#8220;fsync&amp;#8221;会等所有正在进行的&amp;#8220;写操作&amp;#8221;真正执行完毕后才返回。在某些版本的windows和unix上，&amp;#8220;flush&amp;#8221;和&amp;#8220;fsync&amp;#8221;原语会被打断，这非常不幸，在这些系统上，如果提交的过程中发生了掉电，SQLITE的数据库有可能崩溃掉，而SQLITE自己则对此无能为力。SQLITE假设操作系统能像广告宣传的那样完美，如果事实并非如此，你只好祈求老天保佑不要经常掉电了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设文件增长时，新增加的部分最初包含的是垃圾数据，然后它们会被实际的数据覆盖掉。换句话说，SQLITE假设文件大小的变化发生在文件内容变化之前。这是个悲观的假设，为了保证在从&amp;#8220;文件大小改变&amp;#8221;开始到&amp;#8220;文件内容写完&amp;#8221;为止的这段时间内，系统掉电不会导致数据库崩溃，SQLITE要做一些额外的工作。VFS的xDeviceCharacteristics也可能会指出文件系统总是先写数据后更新文件的大小，这种情况下，SQLITE可以跳过一些过于小心的数据库保护操作，从而减少一次提交所需的磁盘I/O数量。但目前windows和unix上的VFS实现都没有做这个假设。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设文件删除是原子的，至少从用户程序的角度来看要是这样。也就是说，如果SQLITE要删除一个文件，并且删除的过程中掉电了，那么电力恢复后，文件要么不能从文件系统中找到，要么它的内容和删除之前一模一样。如果文件还能从文件系统中找到，但内容被修改或清空了，那么数据库极有可能会崩溃。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设检测由宇宙射线、热噪声、驱动程序bug等引起的位错误（bit error）是操作系统和硬件的责任。SQLITE没有在数据库文件中增加任何冗余信息来检测或纠正这类问题。SQLITE假设它所读的数据与它上次所写的数据总是完全相同。 
&lt;H3&gt;3. 单文件提交&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们先来从整体上看看SQLITE在一个单独的数据库文件上操作时，要保证事务提交的原子性需要哪些步骤。为防止掉电时文件被破坏，文件格式在设计时也有相应考虑，相关细节和多数据库提交技术将在后续章节讨论。 
&lt;H4&gt;3.1. 初始状态&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;下图给出了数据库连接刚刚打开时计算机的状态。图的最右侧是存储在磁盘上的数据，每个小格代表一个扇区，蓝色表示扇区存储的是原始数据；图的中间部分是操作系统的缓存，在当前的例子中，缓存是&amp;#8220;冷&amp;#8221;的，所以它的每个格都没有着色；最左侧是使用SQLITE的进程（译注：本文的作者可能更喜欢unix，所以在windows上，原文中的部分&amp;#8220;进程&amp;#8221;用&amp;#8220;线程&amp;#8221;替换一下会更好，我没有做这种替换，故需要您在阅读过程中结合上下文判断&amp;#8220;进程&amp;#8221;的具体含义）的内存，数据库连接刚刚创建，还没有读任何数据，所以用户的内存空间中什么也没有。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-0.gif"&gt; 
&lt;H4&gt;3.2. 获取一个&amp;#8220;读锁&amp;#8221;&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE写数据库之前，必须先读，这样它才能知道数据库中已经有些什么了。即使是单纯的追加数据，SQLITE也要先从sqlite_master表中读出数据库的表结构，从而知道如何去解析INSERT语句，以及新数据应该保存到文件的哪个位置。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;读操作的第一步是获取一个数据库文件的&amp;#8220;共享锁&amp;#8221;。这个共享锁允许两个或多个数据库连接同时读数据库文件，但不许其他数据库连接写这个文件。这个锁非常重要，因为，如果在读数据的过程中另一个连接写了数据，我们就可能读到一个新数据和旧数据的混合体，这会让其他连接的写操作失去原子性。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;请注意，共享锁是操作系统的磁盘缓存实现的，而不是磁盘本身。一般来说，文件锁仅仅是操作系统内核中的一些标志（细节取决于具体操作系统的接口层）。所以，当系统崩溃或掉电后，这个锁就自动消失了。并且，通常情况下，创建这个锁的进程退出后，锁也会自动消失。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-1.gif"&gt; 
&lt;H4&gt;3.3. 从数据库中读数据&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;获得共享锁后，我们开始从数据库文件中读出数据。在这个例子中，由于我们假设最初的缓存是&amp;#8220;冷&amp;#8221;的，所以要先把数据从磁盘读到操作系统的缓存，再把它们从缓存复制到用户空间。后续的读操作，由于部分或全部数据可能已经在缓存中了，或许就只需要从缓存复制到用户空间这一步了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;一般情况下，我们不会需要数据库文件的所有页（译注：页是SQLITE对数据进行缓冲的最小单位，但本文中有时它和扇区是一个意思，请注意结合上下文区分），所以我们读的只是它的一个子集。本例中，我们的数据库文件有8个页，而我们需要的是其中的3个。一个真实的数据库可能有数千个页，但每次查询要访问的一般只是其中很小的一部分。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-2.gif"&gt; 
&lt;H4&gt;3.4. 获取一个预定（Reserved）锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在对数据库做任何修改之前，SQLITE需要获得一个预定锁。预定锁和共享锁很像，它们都允许其他进程读数据库文件。并且，预定锁也可以和多个共享锁共存。但是，一个数据库文件某一时刻只能有一个预定锁，也就是只允许一个进程有写数据的意图。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;预定锁的目的是告诉整个系统：有一个进程要在不久的将来修改数据库文件了，但它目前还没有任何实际行动。由于仅仅是个&amp;#8220;意图&amp;#8221;，其他进程还可以继续自己的读操作，但是它们不能也有这个意图了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-3.gif"&gt; 
&lt;H4&gt;3.5. 创建回滚日志（Journal）文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在任何实质性的修改之前，SQLITE还需要创建一个独立的回滚日志文件，并把所有要被替换的数据库页的原始内容写到这个文件中去。实际上，日志文件将保存将数据库文件恢复到原始状态所需的全部信息。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;日志文件有一个不大的文件头（图中用绿色表示），它记录了数据库文件的原始大小。如果数据库文件因为修改变大了，我们仍然可以凭它来获得文件的原始大小。数据库页和它们的对应的页号会被放在一起写到日志文件中去。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;创建新文件时，大多数操作系统（windows、linux、macOSX等）并不会立即向磁盘写数据。新文件一开始只存在于操作系统的缓存中，直到操作系统有空闲的时候，它才会真的去在磁盘上创建这个文件。这种方式让用户觉得文件创建非常快，起码比真的去做磁盘I/O快多了。在下图中，为了表示这一情形，我们只在操作系统缓存中画了这个日志文件。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-4.gif"&gt; 
&lt;H4&gt;3.6. 在用户空间中修改数据库&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;数据库页的原始内容保存到日志文件后，就可以在用户空间中修改了。每个数据库连接有一份私有的用户空间拷贝，所以这些修改只会被当前的连接看到，其他连接看到的仍然是操作系统缓存中未被修改的内容。在这种情况下，虽然有一个进程正在对数据库进行修改，其他进程仍然可以继续读数据库的原始内容。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-5.gif"&gt; 
&lt;H4&gt;3.7. 把日志文件&amp;#8220;刷&amp;#8221;到磁盘&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;下一步是把回滚日志文件的内容刷到具有持久性的存储器上。后面你会看到，这是让数据库能够在掉电情况下存活的关键之一。它可能要花不少时间，因为往持久性存储器上写东西一般是很慢的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;这一步通常比仅仅把回滚日志刷到磁盘上复杂的多。在大多数平台上，你要刷（flush或fsync）两次才行。第一次是日志文件的基本内容。然后修改日志文件的头部，以反应日志文件中实际的页面数。接着刷第二次，把文件头刷上去。至于为什么要修改文件头并多刷一次，我们将在后续章节讨论。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-6.gif"&gt; 
&lt;H4&gt;3.8. 获取一个独占锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;为了对数据库文件进行真正的修改，我们需要一个独占锁。获取这个锁需要两步，首先是获取一个待决（Pending）锁，然后再把它提升为独占锁。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;待决锁允许其他已经有了共享锁的进程继续读数据库文件，但它不允许创建新的共享锁。设计它的目的是为了避免一大堆读进程把写进程给饿到。系统中可能会有几十甚至上百个进程想读数据库文件，每个这样的进程都要经历一个&amp;#8220;获得共享锁、读数据、释放锁&amp;#8221;的过程。如果很多进程都想读同一个数据库文件，那么一个极有可能现象是：新进程总是在已有的进程释放共享锁之前获得一个新的共享锁。这样一来，数据库文件就上就总有共享锁了，要写数据的进程可能会一直没有机会得到自己的独占锁。通过禁止创建新的共享锁，待决锁解决了这个问题，已有的共享锁会逐渐被释放，最终，当它们全部被释放后，待决锁就可以升级到独占锁了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-7.gif"&gt; 
&lt;H4&gt;3.9. 更新数据库文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;一旦获得独占锁，就可以保证没有其他进程在读这个数据库文件了，这时更新它就是安全的了。一般来说，这里的更新只会影响到操作系统磁盘缓存这一层，而不会影响磁盘上的物理文件。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-8.gif"&gt; 
&lt;H4&gt;3.10. 把变化刷到存储器&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;为了把数据库的变化写到持久性存储器，我们还要再刷一次。这也是保证数据库在掉电情况下不崩溃的关键。当然，向磁盘或闪存写数据实在是太慢了，这一步和3.7节中的刷日志文件加在一起会消耗掉SQLITE一次事务提交的绝大部分时间。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-9.gif"&gt; 
&lt;H4&gt;3.11. 删除日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;把所有变化都安全的写到存储器上以后，回滚日志文件就可以删除了。这是提交事务的那个时间点。如果掉电或系统崩溃发生在这之前，后面将要介绍的恢复过程会让数据库文件回到修改之前的状态，就好像什么都没发生过一样。如果掉电或系统崩溃发生在日志文件被删除之后，那么所有的修改都会生效。所以，SQLITE对数据库的修改全部有效还是全部无效，实际上是取决于这个日志文件是否存在。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;删除文件不一定真的是原子操作，但从用户程序的角度来看，它却好像总是原子的。进程总可以询问操作系统&amp;#8220;这个文件存在吗？&amp;#8221;并等到是或否的回答。如果事务提交过程中发生了掉电，SQLITE就会问操作系统是否存在回滚日志文件，存在则事务是不完整的，需要回滚，不存在则说明事务确实成功提交了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE事务的实现依赖于回滚日志文件是否存在和用户程序眼中的原子的文件删除。所以，事务也是一个原子操作。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-A.gif"&gt; 
&lt;H4&gt;3.12. 释放锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;最后一步是释放独占锁，这样其他进程就又能访问数据库文件了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在下图中，我们看到，用户空间中的数据在锁被释放后就清除了。如果是较早版本的SQLITE，这是实际情况。但从最近几版开始，SQLITE不这么做了，因为下个操作可能还会用到它们。比起从操作系统的缓存或磁盘中读数据来，重用这些已经在本地内存中的数据的性能要高得多。再次使用它们之前，我们要先得到一个共享锁，然后再检查一下在我们没有锁的这段时间内是否有别的进程修改了数据库文件。数据库的第一页有一个计数器，每次对数据库进行修改时都会递增它。检查这个计数器，就能知道数据库是否被别的进程修改过了。如果修改过，就必须清除用户空间中的数据并把新数据读进来。但更大的可能是没有任何修改，这样就可以重用原有的数据，从而大幅提高效率。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-B.gif"&gt; 
&lt;H3&gt;4. 回滚&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;原子提交看起来是瞬间完成的，但很明显，前面介绍的过程需要一定的时间才能完成。如果在提交过程中电源被切断，为了让整个过程看起来是瞬时的，我们必须回滚那些不完整的修改，并把数据库恢复到事务开始之前的状态。 
&lt;H4&gt;4.1. 如果出了问题&amp;#8230;&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;假设掉电发生在3.10节所讲的那一步，也就是把数据库变化刷到磁盘中去的时侯。电力恢复后，情况可能会像下图所示的那样。我们要修改三页数据，但只成功完成了一页，有一页只写了一部分，另一页则一点都没写。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;电力恢复后日志文件是完整的，这是个关键。3.7节中的操作就是为了保证在对数据文件做任何改变之前回滚日志的所有内容已经安全的写到持久性存储器中去了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-0.gif"&gt; 
&lt;H4&gt;4.2. &amp;#8220;热的&amp;#8221;回滚日志&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;任何进程第一次访问数据库文件之前，必须获得一个3.2节中描述的共享锁。然后，如果发现还有一个日志文件，SQLITE就会检查这个回滚日志是不是&amp;#8220;热的&amp;#8221;。我们必须回放热日志文件，从而把数据库恢复到一致的状态。只有在一个程序正在提交事务时发生掉电或崩溃的情况下，才会出现热日志文件。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;日志文件在符合以下所有条件时才是热的： &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&lt;LI&gt;日志文件是存在的 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&lt;LI&gt;日志文件不是空文件 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&lt;LI&gt;数据库文件上没有预定锁 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&lt;LI&gt;日志文件头中没有主日志文件的文件名，或者，如果有主日志文件名的话，主日志文件是存在的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;热日志文件告诉我们：之前有进程试图提交一个事务，但由于某种原因，这个提交没有完成。也就是说：数据库处于一种不一致的状态，使用之前必须修复（回滚）。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-1.gif"&gt; 
&lt;H4&gt;4.3. 获取数据库上的独占锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;处理热日志的第一步是获得数据库文件上的独占锁，这可以防止两个或更多的进程同时回放一个热日志。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-2.gif"&gt; 
&lt;H4&gt;4.4. 回滚不完整的修改&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;获得了独占锁，进程就有权力修改数据库文件了。它从日志中读出页面的原有内容，然后把它们分别写回到其在数据库文件中的原始位置上去。前面说过，日志文件的头部记录了数据库文件在事务开始前的大小，如果修改让数据库文件变大了，SQLITE会使用这一信息把文件截断到原始大小。这一步结束之后，数据库文件就应该和事务开始前一样大，并且包含和那时完全一样的数据了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-3.gif"&gt; 
&lt;H4&gt;4.5. 删除热日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;日志中的所有信息都回放到数据库文件，并将数据库文件刷到磁盘（回滚时可能会再次掉电）以后，就可以删除热日志文件了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-4.gif"&gt; 
&lt;H4&gt;4.6. 继续前进，就像那个中断了的事务根本没发生过一样&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;回滚的最后一步是把独占锁降级为共享锁。此后，数据库的状态看起来就像那个中断了的事务根本没有开始过一样了。由于整个回滚过程是完全自动、透明的，使用SQLITE的那个程序根本就不会知道有一个事务中断并回滚了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-5.gif"&gt; 
&lt;H3&gt;5. 多文件提交&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;通过ATTACH DATABASE命令，SQLITE允许一个数据库连接使用多个数据库文件。当在一个事务中修改多个文件时，所有文件都会被原子的更新。换句话说，或者所有文件都会被更新，或者一个也不会被更新。在多个文件上实现原子提交比在单个文件上实现更复杂，本章将解释SQLITE是如何做到这一点的。 
&lt;H4&gt;5.1. 每个数据库一个日志&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;当一个事务涉及了多个数据库文件时，每个数据库都有自己回滚日志，并且对它们的锁也是各自独立的。下图展示了三个数据库文件在一个事务中被修改的情况，它所描述的状态相当于单文件事务在第3.6节中的状态。每个数据库文件有各自的预定锁，它们将要被修改的那些页的原始内容已经写进回滚日志了，但还没有刷到磁盘上。用户内存中的数据已经被修改了，不过数据库文件本身还没有任何变化。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;相比之前，下图做了一些简化。在这张图上，蓝色仍然代表原始数据，粉红色仍然代表新数据。但上面没有画出回滚日志和数据库的页，并且也没有明确区分操作系统缓存中的数据和磁盘上的数据。所有这些在这张图上仍然适用，不过即使把它们画出来我们也学不到什么新的东西，所以，为了缩小图幅，我们把它们省略掉了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-0.gif"&gt; 
&lt;H4&gt;5.2. 主日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;多文件提交中的下一步是创建一个&amp;#8220;主日志文件&amp;#8221;。这个文件的名字是最初的数据库文件名（也就是用sqlite3_open()打开的那个数据库，而不是之后附加上来的那些）加上后缀&amp;#8220;-mjHHHHHHHH&amp;#8221;。其中HHHHHHHH是一个32位16进制随机数，每次生成新的主日志文件时，它都会不同。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; （注意：上面一段中用来生成主日志文件名的方法是3.5.0版中使用的方法。这个方法并没有规范化，也不是SQLITE对外接口的一部分，在未来版本中，我们可能会修改它。） &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 主日志中没有与原始数据库页面内容相关的信息，它里面保存的是所有参与到这个事务中的回滚日志文件的完整路径。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 主日志生成完毕后，会被立即刷到磁盘上，中间没有任何别的操作。在unix系统上，主日志所在的目录，也会被同步一下，以确保掉电后它也会出现在这个目录下。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-1.gif"&gt; 
&lt;H4&gt;5.3. 更新回滚日志文件头&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;下一步是把主日志的路径记录到回滚日志的文件头中去，回滚日志创建时在文件头预留了相应的空间。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;主日志路径写到回滚日志文件头之前和之后，要分别把回滚日志的内容往磁盘上刷一次。这可能有些效率损失，但非常重要，而且，幸运的是，刷第二次时一般只有一页（最开始的那页）数据有变化，所以整个操作可能并没有想象的那么慢。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;这个操作大致相当于单文件提交时的第7步，也就是第3.7节中的内容。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-2.gif"&gt; 
&lt;H4&gt;5.4. 更新数据库文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;把回滚日志刷到磁盘上后，就可以安全的更新数据库文件了。我们需要获得所有数据库文件上的独占锁，然后写数据，并把这些数据刷到磁盘上去。这一步相当于单文件提交时的第8、9和10步。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-3.gif"&gt; 
&lt;H4&gt;5.5. 删除主日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;下一步是删除主日志文件，这是多文件事务被实际提交的时间点。它相当于单文件提交时的第11步，也就是删除日志文件的那一步。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;如果掉电或系统崩溃发生在这之后，重启时，即使存在回滚日志文件，事务也不会被回滚。这里的区别在于回滚日志的文件头里面有主日志的路径。SQLITE只认为文件头中没有主日志文件路径的回滚日志（单文件提交的情况）或主日志文件仍然存在的回滚日志是&amp;#8220;热的&amp;#8221;，并且只会回放热的回滚日志。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-4.gif"&gt; 
&lt;H4&gt;5.6. 清理回滚日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;最后是删除所有的回滚日志文件，释放独占锁以便其他进程发现数据的变化。这一步对应的是单文件提交时的第12步。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;由于事务已经提交了，所以删除这些文件在时间上并不是非常紧迫。当前的实现是删除一个日志文件，并释放其对应的数据库文件上的独占锁，然后再接着处理下一个。今后，我们可能把它改成先删除所有日志文件，再释放独占锁。这里，只要保证删除日志文件在前，释放其对应的锁在后就行，文件被删除的顺序或锁被释放的顺序并不重要。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-5.gif"&gt; 
&lt;H3&gt;6. 提交中的更多细节&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;第3章从总体上介绍了SQLITE原子提交的实现方法，但漏掉了几个重要的细节，本章将对它们进行一些补充说明。 
&lt;H4&gt;6.1. 总是日志中记录整个扇区&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在把数据库页面的原始内容写进回滚日志时，即使页面比扇区小，SQLITE也会把完整的扇区写进去。从前，SQLITE中的扇区大小是硬编码的512字节，而最小页面也是512字节，所以不会有什么问题。但从3.3.14版开始，SQLITE也支持扇区大小超过512字节的存储器了，所以，从这一版起，当某个扇区中的任何页面被写进日志时，这个扇区中的其它页面也会被一同写进去。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;掉电可能在写扇区时发生，总是记录整个扇区可以在这种情况下保证数据库不被破坏。例如，我们假设每个扇区有四个页面，现在2号页面被修改了，为了把变化写入这个页面，底层硬件，因为它只能写完整的扇区，也会把1、3、4号页面重新写一遍，如果写操作被打断，这三个页面的数据可能就不对了。为了避免这种情况，必须把扇区中的所有页面写到回滚日志中去。 
&lt;H4&gt;6.2. 日志文件中的垃圾数据&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;向日志文件末尾追加数据时，SQLITE一般悲观的假设文件系统会先用垃圾数据把文件撑大，再用正确的数据覆盖这些垃圾。换句话说，SQLITE假设文件体积先变大，之后才是写入实际内容。如果掉电发生在文件已经变大但数据还未写入时，回滚日志中就会包含垃圾数据。电力恢复后，另一个SQLITE进程会发现这个日志文件，并试图恢复它，这就有可能把垃圾数据拷贝到数据库文件，进而对其造成破坏。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;为对付这个问题，SQLITE建立了两道防线。首先，SQLITE在回滚日志的文件头中记录了实际的页面数。这个数字一开始是0，所以，在回放一个不完整的回滚日志时，SQLITE会发现文件中没有包含任何页面，也就不会对数据库做任何修改。提交之前，回滚日志会被刷到磁盘上，以保证其中没有任何垃圾。之后，文件头中的页面数才会被改成实际的数值。文件头总是保存在一个单独的扇区去，所以，如果在覆盖它或把它刷到磁盘上时发生掉电，其它页面是不会被破坏的。注意回滚日志要往磁盘上刷两次：第一次是写页面的原始内容，第二次是写文件头中的页面数。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;上一段描述的是同步选项设置为&amp;#8220;full&amp;#8221;（PRAGMA synchronous=FULL）时的情形，这也是默认的设置。不过，当同步选项低于&amp;#8220;normal&amp;#8221;时，SQLITE只会刷一次日志文件，也就是修改完页面数后的那一次。由于（大于0的）页面数可能先于其它数据到达磁盘，这样做有一定的风险。SQLITE假设文件系统会记录写请求，所以即使先写数据后写页面数，页面数也可能会先被磁盘记录下来。所以，作为第二道防线，SQLITE在日志文件中为每页数据都记录了一个32位的校验码。回滚日志文件时，SQLITE会检查这个校验码，一旦发现错误，就会放弃回滚操作。要注意的是，校验码无法完全保证页面数据的正确性，数据有错误但校验码正确的概率虽然极小，却不是零.。不过，校验码机制至少让类似的事情看起来不那么容易发生了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在同步选项设置为&amp;#8220;full&amp;#8221;时，就没有必要用校验码了，我们只在同步选项低于&amp;#8220;normal&amp;#8221;时才需要它。然而，鉴于校验码是无害的，故不管同步选项如何设置，它们总是出现在回滚日志中的。 
&lt;H4&gt;6.3. 提交之前的缓存溢出&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;第三章描述的过程假设提交之前所有的数据库变化都能保存在内存中。一般来说就是这样的，但特殊情况也会出现。这时，数据库变化会在事务提交之前用完用户缓存，需要把缓存中的内容提前写入数据库才行。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;操作之前，数据库连接处于第3.6步时的状态：原始页面的内容已经保存到回滚日志了，修改后的页面位于用户内存中。为了回收缓存，SQLITE执行第3.7到3.9步，也就是把回滚日志刷到磁盘上，获取独占锁，然后把变化写入数据库。但后续步骤在事务真正提交之前都有所不同。SQLITE会在日志文件的最后追加一个文件头（使用一个单独的扇区），独占锁继续保留，而执行流程将跳到第3.6步。当事务提交或再次回收缓存时，将重复执行第3.7和3.9步（由于第一次回收缓存时获得了独占锁且一直没有释放，3.8步将被跳过）。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;把预定锁提升为独占锁将降低并发度，额外的刷磁盘操作也非常慢，所以回收缓存会严重影响系统效率。因此，只要有可能，SQLITE就不会使用它。 
&lt;H3&gt;7. 优化&lt;/H3&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;对程序的性能分析显示，在绝大多数系统和绝大多数情况下，SQLITE把绝大部分时间消耗在了磁盘I/O上。所以，减少磁盘I/O的数量是最有可能大幅提升效率的方法。本章将介绍SQLITE在保证原子提交的前提下，为减少磁盘I/O而使用的一些技术。 
&lt;H4&gt;7.1. 在事务之间保持缓存数据&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在3.12节中，我们说过当释放共享锁时会丢弃所有已经在用户缓存中的数据库信息。之所以这样做，是因为没有共享锁的时候其他进程能够随意修改数据库文件的内容，从而导致已经缓存的数据过时。所以，每当一个新事务开始时，SQLITE都必须重新读一次以前读过的东西。这个操作并不像大家想象的那么糟糕，因为要重新读的数据极有可能仍在操作系统的缓存中，所谓的&amp;#8220;重读&amp;#8221;一般仅仅是把数据从内核空间拷贝到用户空间而已。不过，即使如此，也是需要一些时间的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;从3.3.14版开始，我们在SQLITE中增加了一个机制来避免不必要的重读。这些版本中，释放共享锁后，用户缓存的页面继续保留。等到SQLITE启动下一个事务并获得共享锁后，它会检查是否有其他进程修改了数据库文件。如果自上次释放锁后有修改，用户缓存会被清空并重读。但一般不会有任何修改，所以用户缓存仍然有效，这样很多不必要的读操作就被避免了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;为了判断数据库文件是否被修改，SQLITE在文件头（第24到27字节）中使用了一个计数器，每个修改操作都会递增它。释放数据库锁之前，SQLITE会记下这个计数器的值，等到再次获得锁以后，它比较记录的值和实际的值，相同则重用已有的缓存数据，不同则清空缓存并重读。 
&lt;H4&gt;7.2. 独占访问模式&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;自3.3.14版开始，SQLITE中增加了&amp;#8220;独占访问模式&amp;#8221;。在这种模式下，SQLITE会在事务提交后继续保留独占锁。这样一来，其他进程就不能访问数据库了。不过，由于大多数的部署方案都只有一个进程访问数据库，所以一般不会有什么问题。独占访问模式让以下三个减少磁盘I/O的方法成为了可能： 
&lt;OL&gt;
&lt;LI&gt;除了第一个事务，不必每次递增数据库文件头中的计数器。这通常意味着在数据库文件和回滚日志中各自少刷一次1号页面。 
&lt;LI&gt;因为没有别的进程能访问数据库，所以没必要每次启动事务时检查计数器和清空用户缓存。 
&lt;LI&gt;事务结束后可以截断（译注：把文件长度设置为0字节）回滚日志文件，而不是删除它。在很多操作系统上，截断比删除快的多。 &lt;/LI&gt;&lt;/OL&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;第三项优化，也就是用截断代替删除，并不要求一直拥有独占锁。理论上说，总是实现它，而不是只在独占访问模式下实现它是可能的，也许我们会在未来版本中让其成为现实。不过，到目前为止（3.5.0版），这项优化仍然只在独占访问模式下有效。 
&lt;H4&gt;7.3. 不记录空闲页面&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;从数据库中删除数据时，那些不再使用的页面会被加到&amp;#8220;空闲页表&amp;#8221;里去。之后的插入操作将首先使用这些页面，而不是扩大数据库文件。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;一些空闲页面中也有重要数据，比如说其他空闲页面的位置等等。但大多数空闲页面的内容没有用，我们把这些页面称为&amp;#8220;叶页&amp;#8221;。修改叶页的内容对数据库没有任何影响。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;由于叶页的内容没用，SQLITE不会把它们在提交过程的第3.5步中记录到回滚日志里去。也就是说，修改叶页，但不在回滚过程中恢复它们对数据库无害。同样的，一个新叶页的内容既不会在第3.9步中写入数据库也不会在第3.3步中被读出来。在数据库文件有空闲空间时，这项优化大幅减少了磁盘I/O的数量。 
&lt;H4&gt;7.4. .单页更新和原子扇区写&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;从3.5.0版开始，新的VFS接口包含了一个名叫xDeviceCharacteristics的方法，它可以报告底层存储器是否支持一些特性。这些特性中，有一个是&amp;#8220;原子扇区写&amp;#8221;。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们前面说过，SQLITE假设写扇区是线性的，而不是原子的。线性写从扇区的一端开始，逐字节写到另一端结束。如果在线性写的中间发生掉电，则可能扇区的一端被修改了，另一端却保持不变。但在原子写的情况下，扇区或者被完全更新了，或者完全没有变化。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们相信大多数现在磁盘驱动器实现了原子扇区写。掉电时，驱动器使用电容中的电能和（或）盘片旋转的动能完成正在进行的操作。然而，在系统写调用与磁盘电子元件之间存在太多的层次，所以我们在Unix和windows的默认VFS实现上做了一个保守的假设，认为写扇区不是原子的。另一方面，能对其使用的文件系统有更多发言权的设备厂商，如果它们的硬件确实支持原子扇区写，也许会选择打开xDeviceCharacteristics中的这个选项。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;当写扇区是原子的、数据库页面和扇区一样大，而且数据库的变化只涉及到一个页面时，SQLITE会跳过整个记日志和同步过程，直接把修改后的页面写到数据库文件上。数据库文件第一页上的修改计数器也会独立修改，因为即使在更新它之前掉电也是无害的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;译注：个人认为，如果硬件不支持原子扇区写，是无法在软件层次上实现绝对意义上的原子提交的。 
&lt;H4&gt;7.5. 支持安全追加的文件系统&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3.5.0版加入的另一项优化措施是基于文件系统的&amp;#8220;安全追加&amp;#8221;功能的。SQLITE假设向文件（特别是回滚日志文件）追加数据时，文件大小的改变早于文件内容增加。所以，如果掉电发生在文件变大之后，数据写完之前，文件中就会包含垃圾数据。也可以通过VFS中的xDeviceCharacteristics方法指出文件系统支持&amp;#8220;安全追加&amp;#8221;功能，这意味着内容的增加早于大小的改变，所以掉电或系统崩溃不可能向日志文件中引入垃圾。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;文件系统支持安全追加时，SQLITE总是在日志文件头的页面数字段中填入-1，表示回滚时要处理的页面数应该根据日志文件的大小自动计算。这个-1不会被修改，所以提交时，我们可以不用单独刷一次日志文件的第一页。而且，当回收缓存时，也没有必要在日志文件末尾再写一个新的文件头了，我们只要继续在已有的日志文件上追加新页面即可。 
&lt;H3&gt;8. 对原子提交的测试&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们作为SQLITE的开发者，对其在掉电和系统崩溃时的健壮性充满自信，因为，我们的自动测试过程在模拟的掉电故障下，对它的恢复能力进行了非常多的检测。我们把这种模拟的故障称为&amp;#8220;崩溃测试&amp;#8221;。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;崩溃测试使用了一个修改过的VFS，以便模拟掉电或崩溃时可能出现的各种文件系统错误。它可以模拟出没有完整写入的扇区、因为写操作没有完成而包含垃圾数据的页面、顺序错误的写操作等，这些错误在测试场景的各个路径点上都会出现。崩溃测试不停地执行事务，让模拟的掉电或系统崩溃发生在各个不同的时刻，造成各种不同的数据损坏。在模拟的崩溃事件发生之后，测试程序重新打开数据库，检测事务是否完全完成或者（看起来）根本没有启动，也就是数据库是否处于一个一致的状态。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE的崩溃测试帮助我们发现了恢复机制中的很多小问题（现在都已经修复了）。其中的一部分非常隐晦，单单通过代码检查和分析可能是发现不了的。这些经验让SQLITE的开发者相信：那些没有使用类似崩溃测试的数据库系统，非常有可能包含在系统崩溃或掉电时导致数据库损坏的BUG。 
&lt;H3&gt;9. 可能发生的问题&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;虽然SQLITE的原子提交机制本身是健壮的，但它却有可能被恶意的对手或不那么完善的操作系统实现给打垮。本章将介绍几个可能在掉电或系统崩溃时导致数据库损坏的情形。 
&lt;H4&gt;9.1. 有问题的锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE使用文件系统的锁来保证某一时刻只有一个进程和数据库连接可以修改数据库。文件系统的锁机制是在VFS层实现的，并且在每种操作系统上都有所不同。SQLITE自身的正确性依赖于这个实现的正确性。如果它出了问题，导致两个或更多进程能同时修改一个数据库文件，肯定会严重损坏数据库。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;有人向我们报告说windows的网络文件系统和（Unix的，译注）NFS的锁都有些问题。我们验证不了这些报告，但是考虑到在网络文件系统上实现一个正确的锁的难度，我们也无法否定它们。由于网络文件系统的效率也很低，所以我们建议你最好是避免在其上使用SQLITE。如果一定要这么做的话，请考虑使用一个附加的锁机制来保证即使文件系统自身的锁机制不起作用时，也不会出现多个进程同时写一个数据库文件的情况。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;苹果Mac OS X计算机上预装的SQLITE进行了一个扩展，可以在苹果支持的所有网络文件系统上使用一个替代的加锁策略。只要所有进程使用统一的方式访问数据库文件，这个扩展就工作的很好。但不幸的是，这些加锁机制是相互独立的，如果一个进程用AFP锁，另一个用点文件（dot-file）锁，那这两个进程就可能发生冲突，因为AFP锁并不能禁止点文件锁，反之亦然。 
&lt;H4&gt;9.2. 不完整的刷磁盘操作&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在第3.7节和3.10节中你已经看到，SQLITE要把系统缓存刷到磁盘上。在unix系统上，这是用fsync()系统调用来完成的，windows上则是用FlushFileBuffers()。可是，我们收到的报告显示，很多系统上的这些接口没有广告宣传的那么好。我们听说，在一些windows版本上，通过修改注册表，可以完全禁用FlushFileBuffers()；而linux的某些历史版本中的fsync仅仅是个什么也不干的空操作。我们还知道，即使是在FlushFileBuffers()或fsync()可以正常工作的系统上，IDE磁盘控制器也经常会在数据仍处在自己的缓存中时，撒谎说数据已经到达磁盘表面了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在苹果的系统上，如果你把fullsync选项打开（PRAGMA fullsync=ON），它可以保证数据确实刷到磁盘上了。Fullsync本身就很慢，而fullsync的实现还需要重置磁盘控制器，这会让其他根本不相关的磁盘I/O也变慢，所以我们不建议你这样做。 
&lt;H4&gt;9.3. 文件删除只完成了一半&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设从用户程序的角度看文件删除是原子操作。如果删除文件时掉电，电力恢复后，SQLITE期望这个文件或者不存在，或者是一个完整的、和删除前一模一样的文件。如果操作系统做不到这一点，事务就有可能不是原子的。 
&lt;H4&gt;9.4. 文件中的垃圾&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE的数据库文件是普通的文件，其它用户程序也可以打开它并任意的往里面写数据，一些流氓程序就可能这样做。垃圾数据的来源也可能是操作系统或磁盘控制器的BUG，尤其是那些会在掉电时触发的BUG。对此类问题，SQLITE无能为力。 
&lt;H4&gt;9.5. 删除或重命名热日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;如果发生了掉电或崩溃，并且生成了热日志文件，那么，在另一个SQLITE进程打开它和数据库文件并完成回滚之前，这两个文件的名字绝对不能改变。在第4.2步时，SQLITE会在打开的数据库文件所在的目录下，寻找热日志文件，这个文件的名字是从数据库文件名派生而来的。所以，只要这两个文件中的任何一个被移走或改名，就会找不到热日志，也就不会进行回滚。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们认为SQLITE恢复过程的失败模式一般是这样的：发生了掉电；电力恢复后，一位好心的用户或者系统管理员开始清点损失；他们发现有一个名为&amp;#8220;important.data&amp;#8221;的文件，他们可能很熟悉这个文件，所以没有对其进行任何操作；但崩溃后，磁盘上还有一个名为&amp;#8220;important.data-journal&amp;#8221;的热日志文件，用户把它删除了，因为他们认为这个文件是系统中的垃圾。防止此类事件的唯一方法可能就是加强用户教育了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;如果有多个链接（硬链接或符号链接）指向一个数据库文件，那么生成的日志文件会依据打开数据库文件时使用链接名来命名。如果发生了崩溃，并且下次打开数据库时使用了另一个链接，则也会因为找不到热日志文件而不进行回滚。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;某些时候，掉电会导致文件系统出错，以致新更改的文件名无法记录，这时，文件就会被移动到&amp;#8220;/lost+found&amp;#8221;目录下。为防止此类错误，SQLITE会在同步日志文件的同时，打开并同步一下这个文件所在的目录。但是，一些八竿子打不着的程序，在数据库文件所在目录下创建其他文件的操作，也可能会导致文件被移动到&amp;#8220;/lost+found&amp;#8221;里去，这是SQLITE控制不了的，所以SQLITE对它也没什么办法。如果你正在使用此类名字空间易被损坏的文件系统（我们相信大多数现代的日志文件系统没有此问题），我们建议你把SQLITE的数据库文件放在单独的子目录中。 
&lt;H3&gt;10. 总结和展望&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;不论是过去还是现在，总有人能发现一些SQLITE原子提交机制的失败模式，开发者也不得不为此做一些补丁。但这类事情发生的已经越来越少了，失败模式也变得越来越隐晦。不过，如果藉此认为SQLITE的原子提交逻辑已经无懈可击了，肯定是相当愚蠢的。开发者们能承诺的只是尽量快速的修复新发现的BUG。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;同时，我们也在寻找新的方法来优化这个提交机制。在Linux、MacOSX和windows上，当前的VFS实现都做了悲观的假设。也许在与一些熟悉这些系统工作原理的专家交流之后，我们能放宽一些限制，让它跑得更快些。特别的，我们猜测大部分现代文件系统已经具有了&amp;#8220;安全追加&amp;#8221;和&amp;#8220;原子扇区写&amp;#8221;这两个特性，但在确认之前，我们仍会保守的做最坏假设。&lt;/LI&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/32581.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>再记自己的两个常识性错误</title><link>http://blog.vckbase.com/localvar/archive/2008/01/08/31689.html</link><pubDate>Tue, 08 Jan 2008 03:58:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/01/08/31689.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/31689.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/01/08/31689.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/31689.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/31689.html</trackback:ping><description>&lt;P&gt;1. WSAStartup只要每个进程调用一次就行了&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 不知为什么, 几年以来，我一直认为要为每个使用网络的线程调一次. 直到今天才发现弄错了, 按说我一直是仔细阅读msdn的, 唉! 不过为每个线程调一次只是多余的, 并不是错误的, 也许这就是我一直没有注意到它的原因吧.&lt;BR&gt;2. do while循环中的continue会跳到哪里&lt;/P&gt;
&lt;DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid"&gt;
&lt;DIV&gt;&lt;SPAN style="COLOR: #0000ff"&gt;do&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN id=Codehighlighter1_3_42_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"&gt;&lt;/SPAN&gt;&lt;SPAN id=Codehighlighter1_3_42_Open_Text&gt;&lt;SPAN style="COLOR: #000000"&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;//&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;&amp;nbsp;①&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;&lt;BR&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;i&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;++&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;continue&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;//&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;&amp;nbsp;②&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;&lt;BR&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;}&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;while&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;(&amp;nbsp;i&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;10&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;);&lt;/SPAN&gt;&lt;/DIV&gt;&lt;/DIV&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 一直认为是①, 今天正在写的程序出错了才发现是②. 老天保佑以前的程序不出错吧&lt;IMG height=20 src="/Emoticons/QQ/02.gif" width=20 border=0&gt;. 这个错误一直没发现的原因有两点，一是我用do while循环比较少, 里面有continue的更少; 二是自己偷懒了, 想当然了, 其实以前怀疑过它的结果的, 但觉得①更符合逻辑就没有深究.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 犯了错误总是比较郁闷的, 不过能在一个上午认识到这样两个错误，也算收获不小了。&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/31689.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>Windows下配置SVN 1.4.5 + APACHE 2.2.6使用域认证</title><link>http://blog.vckbase.com/localvar/archive/2007/12/20/31427.html</link><pubDate>Thu, 20 Dec 2007 06:28:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2007/12/20/31427.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/31427.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2007/12/20/31427.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/31427.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/31427.html</trackback:ping><description>&lt;DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其实在网上搜索这个主题，已经有很多文章了，而且Subversion和TortoiseSVN的文档上也有相关介绍。但在我自己配置的过程中，发现它们好像都不完全对。所以我觉得有必要把自己摸索的过程写出来，供大家参考。不过已经有那么多&amp;#8220;前车之鉴&amp;#8221;了，我的方法是否真的有用，只能靠老天保佑了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 要想配置成功，首先要保证Apache、Svn和mod_auth_sspi这几个模块的版本是匹配的。我最开始就是在这上面栽的跟头。Apache有很多个版本（以2.0.x和2.2.x最常见），作为对应，每个版本的svn都有一些子版本与其匹配。例如1.4.5版的svn就有针对2.0.x和2.2.x的两个子版本。不幸的是，网上搜到的svn下载链接多是指向针对Apache 2.0.x的那个子版本，当把它用在最新版（目前是2.2.6）的Apache上时，出问题就是必然的了。实际上，当使用2.2.x版的Apache时，我们应该到&lt;A href="http://subversion.tigris.org/servlets/ProjectDocumentList?expandFolder=91&amp;amp;folderID=9246" target=_blank&gt;这里&lt;/A&gt;，点击左侧的文件夹&lt;A href="http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=8100&amp;amp;expandFolder=8100&amp;amp;folderID=9246" target=_blank&gt;Windows Apache 2.2.x&lt;/A&gt;（等以后有了新版的apache，可能就是其它对应的文件夹了），然后在右侧的文件列表中下载对应得svn（我下载的是&lt;A href="http://subversion.tigris.org/downloads/1.4.5-win32/apache-2.2/svn-win32-1.4.5.zip" target=_blank&gt;svn-win32-1.4.5.zip&lt;/A&gt;）。mod_auth_sspi我们也下载针对2.2.x版apache的那个就可以了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 以下是我的安装配置过程，它是针对apache2.2.6和svn1.4.5的，如果你用的是其它版本，可能一些细节上会有所不同。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 分别安装apache和svn（svn就是把压缩包解开就行），然后把svn\bin文件夹下的mod_dav_svn.so、mod_authz_svn.so、libdb44.dll和intl3_svn.dll拷贝到apache的modules文件夹下，mod_auth_sspi中的mod_auth_sspi.so也拷贝到那去。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后是修改apache的配置文件httpd.conf，经过我的试验，最后确定使用下面配置文件就行了（其中背景标红的内容你可能需要根据你的实际情况进行修改）。&lt;/DIV&gt;
&lt;TABLE style="BORDER-COLLAPSE: collapse" borderColor=#999999 cellSpacing=0 cellPadding=0 width="75%" bgColor=#f1f1f1 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P style="MARGIN: 5px; LINE-HEIGHT: 150%"&gt;&lt;CODE&gt;&lt;SPAN style="COLOR: #000000"&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ThreadsPerChild&lt;/SPAN&gt; 250&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;MaxRequestsPerChild&lt;/SPAN&gt; 0&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ServerRoot&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"&lt;FONT style="BACKGROUND-COLOR: #ff0000" color=#000000&gt;C:/Program Files/Apache Software Foundation/Apache2.2&lt;/FONT&gt;"&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ServerName&lt;/SPAN&gt; &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;svnserver.mydomain.net&lt;SPAN style="COLOR: #0000cc"&gt;:&lt;/SPAN&gt;8080&lt;/FONT&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ServerSignature&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;Off&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ServerTokens&lt;/SPAN&gt; Prod&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;DocumentRoot&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"htdocs"&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Listen&lt;/SPAN&gt; &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;8080&lt;/FONT&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; sspi_auth_module modules/mod_auth_sspi.so&lt;BR&gt;#&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; auth_basic_module modules/mod_auth_basic.so&lt;BR&gt;#&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; auth_digest_module modules/mod_auth_digest.so&lt;BR&gt;#&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; authn_file_module modules/mod_authn_file.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; authz_svn_module modules/mod_authz_svn.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; dir_module modules/mod_dir.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; deflate_module modules/mod_deflate.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; mime_module modules/mod_mime.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; setenvif_module modules/mod_setenvif.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; dav_module modules/mod_dav.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; dav_svn_module modules/mod_dav_svn.so&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Directory&lt;/SPAN&gt; /&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;Options&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;FollowSymLinks&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AllowOverride&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;None&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Directory&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;IfModule&lt;/SPAN&gt; dir_module&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;DirectoryIndex&lt;/SPAN&gt; index.html&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;IfModule&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ErrorLog&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"&lt;FONT style="BACKGROUND-COLOR: #ff0000" color=#000000&gt;e:/svn/server.log&lt;/FONT&gt;"&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LogLevel&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;error&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;DefaultType&lt;/SPAN&gt; text/plain&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;IfModule&lt;/SPAN&gt; mime_module&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;TypesConfig&lt;/SPAN&gt; conf/mime.types&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AddType&lt;/SPAN&gt; application/x-compress .Z&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AddType&lt;/SPAN&gt; application/x-gzip .gz .tgz&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AddType&lt;/SPAN&gt; application/x-x509-&lt;SPAN style="COLOR: #ff0000"&gt;ca&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;-&lt;/SPAN&gt;cert .crt&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AddType&lt;/SPAN&gt; application/x-pkcs7-crl .crl&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;IfModule&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&lt;FONT color=#000000&gt;# 注意&amp;#8220;/svn/&amp;#8221;中最后的斜杠是必须的, 否则列不出版本库列表&lt;BR&gt;# 访问时的url也要带着它, 想要去掉它可搜索RedirectMatch&lt;/FONT&gt;&lt;BR&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Location&lt;/SPAN&gt; &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;/svn/&lt;/FONT&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# configure SVN&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;DAV&lt;/SPAN&gt; svn&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SVNListParentPath &lt;SPAN style="COLOR: #ff0000"&gt;on&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 版本库的根目录&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SVNParentPath &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;e&lt;SPAN style="COLOR: #0000cc"&gt;:&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;svn&lt;/FONT&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 权限控制文件&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AuthzSVNAccessFile &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;e&lt;SPAN style="COLOR: #0000cc"&gt;:&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;svn/authz&lt;/FONT&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 认证时的提示信息(中文不好使)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AuthName&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"&lt;FONT style="BACKGROUND-COLOR: #ff0000" color=#000000&gt;My Subversion&lt;/FONT&gt;"&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 使用域认证&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AuthType&lt;/SPAN&gt; SSPI&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIAuth &lt;SPAN style="COLOR: #ff0000"&gt;On&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIAuthoritative &lt;SPAN style="COLOR: #ff0000"&gt;On&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 指定使用那个域&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIDomain &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;mydomain.net&lt;/FONT&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 是否省略掉用户id的域名部分(好像只是影响svn的一些日志记录)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIOmitDomain &lt;SPAN style="COLOR: #ff0000"&gt;On&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 是否允许非IE客户端(必须打开)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIOfferBasic &lt;SPAN style="COLOR: #ff0000"&gt;On&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 基本认证(非域认证方式)具有更高的优先级?&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIBasicPreferred &lt;SPAN style="COLOR: #ff0000"&gt;Off&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 用户名大小写&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIUsernameCase lower&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 用户必须通过认证&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;Require&lt;/SPAN&gt; valid-&lt;SPAN style="COLOR: #0000ff"&gt;user&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Location&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/CODE&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后如果大家觉得手工编辑那个权限控制文件（authz）很麻烦的话，也有一个取巧的办法，就是使用visualsvn server，虽然它目前还不支持域认证，但是我们可以借用它的权限管理界面。操作如下（假设版本库的根目录是e:\svn，并且权限控制文件的名字这时必须用authz）：&lt;BR&gt;1）&amp;nbsp;按前面的操作安装好apache和svn，但不要启动apache&lt;BR&gt;2）&amp;nbsp;把e:\svn改名为e:\svn1&lt;BR&gt;3）&amp;nbsp;&lt;A href="http://www.visualsvn.com/server" target=_blank&gt;下载&lt;/A&gt;并安装visualsvn server，安装时指定版本库根目录为e:\svn&lt;BR&gt;4）&amp;nbsp;停掉并禁用visualsvn server的服务（VisualSVNServer），删除e:\svn&lt;BR&gt;5）&amp;nbsp;把e:\svn1的名字改回e:\svn&lt;BR&gt;6）&amp;nbsp;启动apache&lt;BR&gt;7）&amp;nbsp;启动visualsvn server的管理界面，把要使用这个版本库的所有人的域帐号都添加到它的用户列表中去（密码不会被实际使用，随便设或留空都行）。&lt;BR&gt;8）&amp;nbsp;万事ok了，设置权限吧！&lt;BR&gt;
&lt;DIV&gt;&lt;BR&gt;ps: 2008-05-23&lt;BR&gt;tortoisesvn(1.4.8版)文档中关于使用多认证源的描述中有一个错误，其中的&lt;FONT color=#ff0000&gt;AuthAthoritative&lt;/FONT&gt;&lt;FONT color=#000000&gt;和&lt;/FONT&gt;&lt;FONT color=#ff0000&gt;AuthAuthoritative&lt;FONT color=#000000&gt;都应该改成&lt;/FONT&gt;&lt;FONT color=#ff0000&gt;AuthBasicAuthoritative&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT color=#000000&gt;。另外，多认证源还要求域用户登录时必须用&amp;#8220;domain\user&amp;#8221;的形式，只输user部分就会用其他认证方式。所以，如果你按我前面的描述用了visual svn server，增加多认证源后，域用户的密码就千万不要留空了，因为那样不用密码就能登录了。&lt;BR&gt;&lt;/FONT&gt;&lt;/DIV&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/31427.html" width = "1" height = "1" /&gt;</description></item></channel></rss>