﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><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>C++博客-清幽静谷</title><link>http://www.cppblog.com/zjl-1026-2001/</link><description /><language>zh-cn</language><lastBuildDate>Wed, 08 Apr 2026 05:45:08 GMT</lastBuildDate><pubDate>Wed, 08 Apr 2026 05:45:08 GMT</pubDate><ttl>60</ttl><item><title>罗宋汤</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2010/04/19/112951.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Mon, 19 Apr 2010 00:30:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2010/04/19/112951.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/112951.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2010/04/19/112951.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/112951.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/112951.html</trackback:ping><description><![CDATA[主料：<br>牛肉，土豆3个，胡萝卜2根，洋葱、芹菜、圆白菜、西红柿各适量，西红柿酱少量。<br><br>做法：<br>1、将牛肉洗净切块入锅，锅里加水。煮开以后，撇去浮沫，转小火慢炖两个小时左右。<br><br>2、土豆、洋葱切块，胡萝卜去皮切块，芹菜斜着切段，圆白菜切大片。<br><br>3、将所有的蔬菜都放入锅里，大火烧开后转小火接着炖。<br><br>4、西红柿切丁，另起一锅热油，倒入西红柿丁和西红柿酱翻炒至西红柿丁熟烂以后，倒入<br>汤锅里，加盐调味，再炖一个小时左右即可。
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/112951.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2010-04-19 08:30 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2010/04/19/112951.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>再谈QQ游戏百万人在线的技术实现：负载均衡---大型在线系统实现的关键</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2010/04/16/112755.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Fri, 16 Apr 2010 03:38:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2010/04/16/112755.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/112755.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2010/04/16/112755.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/112755.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/112755.html</trackback:ping><description><![CDATA[本文作者：sodme<br>本文出处：http://blog.csdn.net/sodme<br>声明：本文可以不经作者同意任意转载，但任何对本文的引用都须注明作者、出处及此声明信息。谢谢！！<br><br>　　要了解此篇文章中引用的本人写的另一篇文章，请到以下地址：<br><a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/08/15/93427.html">http://www.cppblog.com/zjl-1026-2001/archive/2009/08/15/93427.html</a><br><br><br>以上的这篇文章是早在去年的时候写的了，当时正在作休闲平台，一直在想着如何实现一个可扩充的支持百万人在线的游戏平台，后来思路有了，就写了那篇总结。文章的意思，重点在于阐述一个百万级在线的系统是如何实施的，倒没真正认真地考察过QQ游戏到底是不是那样实现的。<br><br>　　近日在与业内人士讨论时，提到QQ游戏的实现方式并不是我原来所想的那样，于是，今天又认真抓了一下QQ游戏的包，结果确如这位兄弟所言，QQ游戏的架构与我当初所设想的那个架构相差确实不小。下面，我重新给出QQ百万级在线的技术实现方案，并以此展开，谈谈大型在线系统中的负载均衡机制的设计。<br><br>　　从QQ游戏的登录及游戏过程来看，QQ游戏中，也至少分为三类<a class=mykeyword title=http://www.doserver.net/tag.php?tag=%E6%9C%8D%E5%8A%A1%E5%99%A8 href="http://www.doserver.net/tag.php?tag=%E6%9C%8D%E5%8A%A1%E5%99%A8" target=_blank><u><font color=#810081>服务器</font></u></a>。它们是：<br>　　第一层：登陆/账号服务器(Login Server)，负责验证用户身份、向客户端传送初始信息，从QQ聊天软件的封包常识来看，这些初始信息可能包括&#8220;会话密钥&#8221;此类的信息，以后客户端与后续服务器的通信就使用此会话密钥进行身份验证和信息加密；<br>　　第二层：大厅服务器(估且这么叫吧, Game Hall Server)，负责向客户端传递当前游戏中的所有房间信息，这些房间信息包括：各房间的连接IP，PORT，各房间的当前在线人数，房间名称等等。<br>　　第三层：游戏逻辑服务器(Game Logic Server)，负责处理房间逻辑及房间内的桌子逻辑。<br><br>　　从静态的表述来看，以上的三层结构似乎与我以前写的那篇文章相比并没有太大的区别，事实上，重点是它的工作流程，QQ游戏的通信流程与我以前的设想可谓大相径庭，其设计思想和技术水平确实非常优秀。具体来说，QQ游戏的通信过程是这样的：<br><br>　　1.由Client向Login Server发送账号及密码等登录消息，Login Server根据校验结果返回相应信息。可以设想的是，如果Login Server通过了Client的验证，那么它会通知其它Game Hall Server或将通过验证的消息以及会话密钥放在Game Hall Server也可以取到的地方。总之，Login Server与Game Hall Server之间是可以共享这个校验成功消息的。一旦Client收到了Login Server返回成功校验的消息后，Login Server会主动断开与Client的连接，以腾出socket资源。Login Server的IP信息，是存放在QQGame\config\QQSvrInfo.ini里的。<br><br>　　2.Client收到Login Server的校验成功等消息后，开始根据事先选定的游戏大厅入口登录游戏大厅，各个游戏大厅Game Hall Server的IP及Port信息，是存放在QQGame\Dirconfig.ini里的。Game Hall Server收到客户端Client的登录消息后，会根据一定的策略决定是否接受Client的登录，如果当前的Game Hall Server已经到了上限或暂时不能处理当前玩家登录消息，则由Game Hall Server发消息给Client，以让Client重定向到另外的Game Hall Server登录。重定向的IP及端口信息，本地没有保存，是通过数据包或一定的算法得到的。如果当前的Game Hall Server接受了该玩家的登录消息后，会向该Client发送房间目录信息，这些信息的内容我上面已经提到。目录等消息发送完毕后，Game Hall Server即断开与Client的连接，以腾出socket资源。在此后的时间里，Client每隔30分钟会重新连接Game Hall Server并向其索要最新的房间目录信息及在线人数信息。<br><br>　　3.Client根据列出的房间列表，选择某个房间进入游戏。根据我的抓包结果分析，QQ游戏，并不是给每一个游戏房间都分配了一个单独的端口进行处理。在QQ游戏里，有很多房间是共用的同一个IP和同一个端口。比如，在斗地主一区，前50个房间，用的都是同一个IP和Port信息。这意味着，这些房间，在QQ游戏的服务器上，事实上，可能是同一个程序在处理！！！QQ游戏房间的人数上限是400人，不难推算，QQ游戏单个服务器程序的用户承载量是2万，即QQ的一个游戏逻辑服务器程序最多可同时与2万个玩家保持TCP连接并保证游戏效率和品质，更重要的是，这样可以为腾讯省多少money呀！！！哇哦！QQ确实很牛。以2万的在线数还能保持这么好的游戏品质，确实不容易！QQ游戏的单个服务器程序，管理的不再只是逻辑意义上的单个房间，而可能是许多逻辑意义上的房间。其实，对于服务器而言，它就是一个大区服务器或大区服务器的一部分，我们可以把它理解为一个庞大的游戏地图，它实现的也是分块处理。而对于每一张桌子上的打牌逻辑，则是有一个统一的处理流程，50个房间的50＊100张桌子全由这一个服务器程序进行处理(我不知道QQ游戏的具体打牌逻辑是如何设计的，我想很有可能也是分区域的，分块的)。当然，以上这些只是服务器作的事，针对于客户端而言，客户端只是在表现上，将一个个房间单独罗列了出来，这样作，是为便于玩家进行游戏以及减少服务器的开销，把这个大区中的每400人放在一个集合内进行处理（比如聊天信息，&#8220;向400人广播&#8221;和&#8220;向2万人广播&#8221;，这是完全不同的两个概念）。<br><br>　　4.需要特别说明的一点。进入QQ游戏房间后，直到点击某个位置坐下打开另一个程序界面，客户端的程序，没有再创建新的socket，而仍然使用原来大厅房间客户端跟游戏逻辑服务器交互用的socket。也就是说，这是两个进程共用的同一个socket！不要小看这一点。如果你在创建桌子客户端程序后又新建了一个新的socket与游戏逻辑服务器进行通信，那么由此带来的玩家进入、退出、逃跑等消息会带来非常麻烦的数据同步问题，俺在刚开始的时候就深受其害。而一旦共用了同一个socket后，你如果退出桌子，服务器不涉及释放socket的问题，所以，这里就少了很多的数据同步问题。关于多个进程如何共享同一个socket的问题，请去google以下内容：WSADuplicateSocket。<br><br>　　以上便是我根据最新的QQ游戏抓包结果分析得到的QQ游戏的通信流程，当然，这个流程更多的是客户端如何与服务器之间交互的，却没有涉及到服务器彼此之间是如何通信和作数据同步的。关于服务器之间的通信流程，我们只能基于自己的经验和猜想，得出以下想法：<br><br>　　1.Login Server与Game Hall Server之前的通信问题。Login Server是负责用户验证的，一旦验证通过之后，它要设法让Game Hall Server知道这个消息。它们之前实现信息交流的途径，我想可能有这样几条：a. Login Server将通过验证的用户存放到临时数据库中；b.　Login Server将验证通过的用户存放在内存中，当然，这个信息，应该是全局可访问的，就是说所有QQ的Game Hall Server都可以通过服务器之间的数据包通信去获得这样的信息。<br><br>　　2.Game Hall Server的最新房间目录信息的取得。这个信息，是全局的，也就是整个游戏中，只保留一个目录。它的信息来源，可以由底层的房间服务器逐级报上来，报给谁？我认为就如保存的全局登录列表一样，它报给保存全局登录列表的那个服务器或数据库。<br><br>　　3.在QQ游戏中，同一类型的游戏，无法打开两上以上的游戏房间。这个信息的判定，可以根据全局信息来判定。<br><br>　　以上关于服务器之间如何通信的内容，均属于个人猜想，QQ到底怎么作的，恐怕只有等大家中的某一位进了腾讯之后才知道了。呵呵。不过，有一点是可以肯定的，在整个服务器架构中，应该有一个地方是专门保存了全局的登录玩家列表，只有这样才能保证玩家不会重复登录以及进入多个相同类型的房间。<br><br>　　在前面的描述中，我曾经提到过一个问题：当登录当前Game Hall Server不成功时，QQ游戏服务器会选择让客户端重定向到另位的服务器去登录，事实上，QQ聊天服务器和MSN服务器的登录也是类似的，它也存在登录重定向问题。<br><br>　　那么，这就引出了另外的问题，由谁来作这个策略选择？以及由谁来提供这样的选择资源？这样的处理，便是负责负载均衡的服务器的处理范围了。由QQ游戏的通信过程分析派生出来的针对负责均衡及百万级在线系统的更进一步讨论，将在下篇文章中继续。<br><br>　　在此，特别感谢网友tilly及某位不便透露姓名的网友的讨论，是你们让我决定认真再抓一次包探个究竟。<br><br>　　&lt;未完待续&gt;<br>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/112755.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2010-04-16 11:38 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2010/04/16/112755.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ADSL介绍</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2010/04/16/112750.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Fri, 16 Apr 2010 02:33:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2010/04/16/112750.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/112750.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2010/04/16/112750.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/112750.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/112750.html</trackback:ping><description><![CDATA[<p>&nbsp;</p>
ADSL，全名Asymmetric Digital Subscriber Line，译为非对称数字用户线路，或非对称数字用户环路（Aysmmetric Digital Subscriber Loop）。<br><br>介绍：<br><br>ADSL因为上行（从用户到电信服务提供商方向，如上传动作）和下行（从电信服务提供商到用户的方向，如下载动作）带宽不对称（即上行和下行的速率不相同）因此称为非对称数字用户线路。它采用<a class=mw-redirect title=频分复用 href="http://www.cppblog.com/zh-cn/%E9%A2%91%E5%88%86%E5%A4%8D%E7%94%A8"><font color=#002bb8>频分复用</font></a>技术把普通的电话线分成了电话、上行和下行三个相对独立的信道，从而避免了相互之间的干扰。通常ADSL在不影响正常电话通信的情况下可以提供最高3.5Mbps的上行速度和最高24Mbps的下行速度。<br><br>设备：<br><br>
<p>ADSL是一种<a title=异步传输模式 href="http://www.cppblog.com/zh-cn/%E5%BC%82%E6%AD%A5%E4%BC%A0%E8%BE%93%E6%A8%A1%E5%BC%8F"><font color=#002bb8>异步传输模式</font></a>（ATM）。</p>
<p>在电信服务提供商端，需要将每条开通ADSL业务的电话线路连接在<em>数字用户线路访问多路复用器</em>（DSLAM）上。而在用户端，用户需要使用一个<em>ADSL终端</em>（因为和传统的<a title=调制解调器 href="http://www.cppblog.com/zh-cn/%E8%B0%83%E5%88%B6%E8%A7%A3%E8%B0%83%E5%99%A8"><font color=#002bb8>调制解调器</font></a>（Modem）类似，所以也被称为&#8220;猫&#8221;）来连接电话线路。由于ADSL使用高频信号，所以在两端还都要使用<em>ADSL信号分离器</em>将ADSL数据信号和普通音频电话信号分离出来，避免打电话的时候出现噪音干扰。</p>
<p>通常的ADSL终端有一个电话Line-In，一个<a title=以太网 href="http://www.cppblog.com/zh-cn/%E4%BB%A5%E5%A4%AA%E7%BD%91"><font color=#002bb8>以太网</font></a>口，有些终端集成了ADSL信号分离器，还提供一个连接的Phone接口。</p>
<p>某些ADSL调制解调器使用USB接口与电脑相连，需要在电脑上安装指定的软件以添加虚拟网卡来进行通信。</p>
<br>标准：<br><br><span id=.E4.BC.A0.E8.BE.93.E6.A0.87.E5.87.86 class=mw-headline>传输标准</span>
<p>由于受到传输高频信号的限制，ADSL需要电信服务提供商端接入设备和用户终端之间的距离不能超过5<a title=千米 href="http://www.cppblog.com/zh-cn/%E5%8D%83%E7%B1%B3"><font color=#002bb8>千米</font></a>，也就是用户的电话线连到电话局的距离不能超过5千米。</p>
<p>ADSL设备在传输中需要遵循以下标准之一：</p>
<ul>
    <li>ITU-T G.992.1(G.dmt)
    <ul>
        <li>G.dmt：全速率，下行8Mbps，上行896Kbps </li>
    </ul>
    <li>ITU-T G.992.2(G.lite)
    <ul>
        <li>G.lite：下行1.5Mbps，上行512Kbps </li>
    </ul>
    <li>ITU-T G.994.1(G.hs)
    <ul>
        <li>可变比特率（VBR） </li>
    </ul>
    <li>ANSI T1.413 Issue #2
    <ul>
        <li>下行8Mbps，上行896Kbps </li>
    </ul>
    </li>
</ul>
<p>还有一些更快更新的标准，但是目前还很少有电信服务提供商使用：</p>
<ul>
    <li>ITU G.992.3/4
    <ul>
        <li>ADSL2 下行12Mbps，上行1.0Mbps </li>
    </ul>
    <li>ITU G.992.3/4
    <ul>
        <li>Annex J ADSL2 下行12Mbps，上行3.5Mbps </li>
    </ul>
    <li>ITU G.992.5
    <ul>
        <li>ADSL2+ 下行24Mbps，上行1.0Mbps </li>
    </ul>
    <li>ITU G.992.5
    <ul>
        <li>Annex M ADSL2+ 下行24Mbps，上行3.5Mbps </li>
    </ul>
    </li>
</ul>
<p>当电信服务提供商的设备端和用户终端之间距离小于1.3千米的时候，还可以使用速率更高的<a title=VDSL href="http://www.cppblog.com/zh-cn/VDSL"><font color=#002bb8>VDSL</font></a>，它的速率可以达到下行55.2Mbps，上行19.2Mbps。</p>
<h3>&nbsp;<span id=.E7.BD.91.E7.BB.9C.E7.99.BB.E5.BD.95.E6.A0.87.E5.87.86 class=mw-headline>网络登录标准</span></h3>
<p>ADSL通常提供三种网络登录方式：</p>
<ul>
    <li><a class=mw-redirect title=桥接 href="http://www.cppblog.com/zh-cn/%E6%A1%A5%E6%8E%A5"><font color=#002bb8>桥接</font></a>，直接提供静态<a title=IP href="http://www.cppblog.com/zh-cn/IP"><font color=#002bb8>IP</font></a>
    <li><a class=new title=PPPoA（尚未撰写） href="http://www.cppblog.com/w/index.php?title=PPPoA&amp;action=edit&amp;redlink=1"><font color=#ba0000>PPPoA</font></a>，基于ATM的端对端协议
    <li><a title=PPPoE href="http://www.cppblog.com/zh-cn/PPPoE"><font color=#002bb8>PPPoE</font></a>，基于以太网的端对端协议 </li>
</ul>
<p>后两种通常不提供静态IP，而是动态的给用户分配网络地址。</p>
<br><br>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/112750.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2010-04-16 10:33 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2010/04/16/112750.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转载]GRUB2基础教程</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/18/109959.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Thu, 18 Mar 2010 01:54:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/18/109959.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/109959.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/18/109959.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/109959.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/109959.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: grub2基础教程-修订版&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;<a href='http://www.cppblog.com/zjl-1026-2001/archive/2010/03/18/109959.html'>阅读全文</a><img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/109959.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2010-03-18 09:54 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2010/03/18/109959.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转载]POSIX 线程详解-第3部分</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109419.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Thu, 11 Mar 2010 03:54:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109419.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/109419.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109419.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/109419.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/109419.html</trackback:ping><description><![CDATA[[原文地址]http://www.ibm.com/developerworks/cn/linux/thread/posix_thread3/<br><br>
<blockquote>本文是 POSIX 线程三部曲系列的最后一部分，Daniel 将详细讨论如何使用条件变量。条件变量是 POSIX 线程结构，可以让您在遇到某些条件时&#8220;唤醒&#8221;线程。可以将它们看作是一种线程安全的信号发送。Daniel 使用目前您所学到的知识实现了一个多线程工作组应用程序，本文将围绕着这一示例而进行讨论。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><a name=1><span class=atitle>条件变量详解</span></a></p>
<p>在 <a href="http://www.cppblog.com/developerworks/cn/linux/thread/posix_thread2/index.html"><u><font color=#5c81a7>上一篇文章</font></u></a>结束时，我描述了一个比较特殊的难题：如果线程正在等待某个特定条件发生，它应该如何处理这种情况？它可以重复对互斥对象锁定和解锁，每次都会检查共享数据结构，以查找某个值。但这是在浪费时间和资源，而且这种繁忙查询的效率非常低。解决这个问题的最佳方法是使用 pthread_cond_wait() 调用来等待特殊条件发生。 </p>
<p>了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心，也是最难以理解的部分。</p>
<p>首先，让我们考虑以下情况：线程为查看已链接列表而锁定了互斥对象，然而该列表恰巧是空的。这一特定线程什么也干不了 -- 其设计意图是从列表中除去节点，但是现在却没有节点。因此，它只能：</p>
<p>锁定互斥对象时，线程将调用 pthread_cond_wait(&amp;mycond,&amp;mymutex)。pthread_cond_wait() 调用相当复杂，因此我们每次只执行它的一个操作。</p>
<p>pthread_cond_wait() 所做的第一件事就是同时对互斥对象解锁（于是其它线程可以修改已链接列表），并等待条件 mycond 发生（这样当 pthread_cond_wait() 接收到另一个线程的&#8220;信号&#8221;时，它将苏醒）。现在互斥对象已被解锁，其它线程可以访问和修改已链接列表，可能还会添加项。</p>
<p>此时，pthread_cond_wait() 调用还未返回。对互斥对象解锁会立即发生，但等待条件 mycond 通常是一个阻塞操作，这意味着线程将睡眠，在它苏醒之前不会消耗 CPU 周期。这正是我们期待发生的情况。线程将一直睡眠，直到特定条件发生，在这期间不会发生任何浪费 CPU 时间的繁忙查询。从线程的角度来看，它只是在等待 pthread_cond_wait() 调用返回。</p>
<p>现在继续说明，假设另一个线程（称作&#8220;2 号线程&#8221;）锁定了 mymutex 并对已链接列表添加了一项。在对互斥对象解锁之后，2 号线程会立即调用函数 pthread_cond_broadcast(&amp;mycond)。此操作之后，2 号线程将使所有等待 mycond 条件变量的线程立即苏醒。这意味着第一个线程（仍处于 pthread_cond_wait() 调用中）现在将苏醒。</p>
<p>现在，看一下第一个线程发生了什么。您可能会认为在 2 号线程调用 pthread_cond_broadcast(&amp;mymutex) 之后，1 号线程的 pthread_cond_wait() 会立即返回。不是那样！实际上，pthread_cond_wait() 将执行最后一个操作：重新锁定 mymutex。一旦 pthread_cond_wait() 锁定了互斥对象，那么它将返回并允许 1 号线程继续执行。那时，它可以马上检查列表，查看它所感兴趣的更改。</p>
<br><br>
<p><a name=2><span class=atitle>停止并回顾！</span></a></p>
<p>那个过程非常复杂，因此让我们先来回顾一下。第一个线程首先调用：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    pthread_mutex_lock(&amp;mymutex);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>然后，它检查了列表。没有找到感兴趣的东西，于是它调用：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    pthread_cond_wait(&amp;mycond, &amp;mymutex);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>然后，pthread_cond_wait() 调用在返回前执行许多操作：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>
            pthread_mutex_unlock(&amp;mymutex);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>它对 mymutex 解锁，然后进入睡眠状态，等待 mycond 以接收 POSIX 线程&#8220;信号&#8221;。一旦接收到&#8220;信号&#8221;（加引号是因为我们并不是在讨论传统的 UNIX 信号，而是来自 pthread_cond_signal() 或 pthread_cond_broadcast() 调用的信号），它就会苏醒。但 pthread_cond_wait() 没有立即返回 -- 它还要做一件事：重新锁定 mutex：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>
            pthread_mutex_lock(&amp;mymutex);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>pthread_cond_wait() 知道我们在查找 mymutex &#8220;背后&#8221;的变化，因此它继续操作，为我们锁定互斥对象，然后才返回。</p>
<br><br>
<p><a name=3><span class=atitle>pthread_cond_wait() 小测验</span></a></p>
<p>现在已回顾了 pthread_cond_wait() 调用，您应该了解了它的工作方式。应该能够叙述 pthread_cond_wait() 依次执行的所有操作。尝试一下。如果理解了 pthread_cond_wait()，其余部分就相当容易，因此请重新阅读以上部分，直到记住为止。好，读完之后，能否告诉我在调用 pthread_cond_wait() 之 <em>前</em>，互斥对象必须处于什么状态？pthread_cond_wait() 调用返回之后，互斥对象处于什么状态？这两个问题的答案都是&#8220;锁定&#8221;。既然已经完全理解了 pthread_cond_wait() 调用，现在来继续研究更简单的东西 -- 初始化和真正的发送信号和广播进程。到那时，我们将会对包含了多线程工作队列的 C 代码了如指掌。 </p>
<br><br>
<p><a name=4><span class=atitle>初始化和清除</span></a></p>
<p>条件变量是一个需要初始化的真实数据结构。以下就初始化的方法。首先，定义或分配一个条件变量，如下所示：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    pthread_cond_t mycond;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>然后，调用以下函数进行初始化：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    pthread_cond_init(&amp;mycond,NULL);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>瞧，初始化完成了！在释放或废弃条件变量之前，需要毁坏它，如下所示：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    pthread_cond_destroy(&amp;mycond);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>很简单吧。接着讨论 pthread_cond_wait() 调用。</p>
<br><br>
<p><a name=5><span class=atitle>等待</span></a></p>
<p>一旦初始化了互斥对象和条件变量，就可以等待某个条件，如下所示：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    pthread_cond_wait(&amp;mycond, &amp;mymutex);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>请注意，代码在逻辑上应该包含 mycond 和 mymutex。一个特定条件只能有一个互斥对象，而且条件变量应该表示互斥数据&#8220;内部&#8221;的一种特殊的条件更改。一个互斥对象可以用许多条件变量（例如，cond_empty、cond_full、cond_cleanup），但每个条件变量只能有一个互斥对象。</p>
<br>
<p><a name=6><span class=atitle>发送信号和广播</span></a></p>
<p>对于发送信号和广播，需要注意一点。如果线程更改某些共享数据，而且它想要唤醒所有正在等待的线程，则应使用 pthread_cond_broadcast 调用，如下所示：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    pthread_cond_broadcast(&amp;mycond);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>在某些情况下，活动线程只需要唤醒第一个正在睡眠的线程。假设您只对队列添加了一个工作作业。那么只需要唤醒一个工作程序线程（再唤醒其它线程是不礼貌的！）：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    pthread_cond_signal(&amp;mycond);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>此函数只唤醒一个线程。如果 POSIX 线程标准允许指定一个整数，可以让您唤醒一定数量的正在睡眠的线程，那就更完美了。但是很可惜，我没有被邀请参加会议。</p>
<br><br>
<p><a name=7><span class=atitle>工作组</span></a></p>
<p>我将演示如何创建多线程工作组。在这个方案中，我们创建了许多工作程序线程。每个线程都会检查 wq（&#8220;工作队列&#8221;），查看是否有需要完成的工作。如果有需要完成的工作，那么线程将从队列中除去一个节点，执行这些特定工作，然后等待新的工作到达。</p>
<p>与此同时，主线程负责创建这些工作程序线程、将工作添加到队列，然后在它退出时收集所有工作程序线程。您将会遇到许多 C 代码，好好准备吧！</p>
<br>
<p><a name=8><span class=atitle>队列</span></a></p>
<p>需要队列是出于两个原因。首先，需要队列来保存工作作业。还需要可用于跟踪已终止线程的数据结构。还记得前几篇文章（请参阅本文结尾处的 <a href="#resources"><u><font color=#5c81a7>参考资料</font></u></a>）中，我曾提到过需要使用带有特定进程标识的 pthread_join 吗？使用&#8220;清除队列&#8221;（称作 "cq"）可以解决无法等待 <em>任何</em>已终止线程的问题（稍后将详细讨论这个问题）。以下是标准队列代码。将此代码保存到文件 queue.h 和 queue.c： </p>
<br><a name=N100F4><strong>queue.h</strong></a><br>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>/* queue.h
            ** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
            ** Author: Daniel Robbins
            ** Date: 16 Jun 2000
            */
            typedef struct node {
            struct node *next;
            } node;
            typedef struct queue {
            node *head, *tail;
            } queue;
            void queue_init(queue *myroot);
            void queue_put(queue *myroot, node *mynode);
            node *queue_get(queue *myroot);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br><br><br><a name=N10100><strong>queue.c</strong></a><br>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>/* queue.c
            ** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
            ** Author: Daniel Robbins
            ** Date: 16 Jun 2000
            **
            ** This set of queue functions was originally thread-aware.  I
            ** redesigned the code to make this set of queue routines
            ** thread-ignorant (just a generic, boring yet very fast set of queue
            ** routines).  Why the change?  Because it makes more sense to have
            ** the thread support as an optional add-on.  Consider a situation
            ** where you want to add 5 nodes to the queue.  With the
            ** thread-enabled version, each call to queue_put() would
            ** automatically lock and unlock the queue mutex 5 times -- that's a
            ** lot of unnecessary overhead.  However, by moving the thread stuff
            ** out of the queue routines, the caller can lock the mutex once at
            ** the beginning, then insert 5 items, and then unlock at the end.
            ** Moving the lock/unlock code out of the queue functions allows for
            ** optimizations that aren't possible otherwise.  It also makes this
            ** code useful for non-threaded applications.
            **
            ** We can easily thread-enable this data structure by using the
            ** data_control type defined in control.c and control.h.  */
            #include &lt;stdio.h&gt;
            #include "queue.h"
            void queue_init(queue *myroot) {
            myroot-&gt;head=NULL;
            myroot-&gt;tail=NULL;
            }
            void queue_put(queue *myroot,node *mynode) {
            mynode-&gt;next=NULL;
            if (myroot-&gt;tail!=NULL)
            myroot-&gt;tail-&gt;next=mynode;
            myroot-&gt;tail=mynode;
            if (myroot-&gt;:head==NULL)
            myroot-&gt;head=mynode;
            }
            node *queue_get(queue *myroot) {
            //get from root
            node *mynode;
            mynode=myroot-&gt;head;
            if (myroot-&gt;head!=NULL)
            myroot-&gt;head=myroot-&gt;head-&gt;next;
            return mynode;
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p><a name=9><span class=atitle>data_control 代码</span></a></p>
<p>我编写的并不是线程安全的队列例程，事实上我创建了一个&#8220;数据包装&#8221;或&#8220;控制&#8221;结构，它可以是任何线程支持的数据结构。看一下 control.h：</p>
<br><a name=N10113><strong>control.h</strong></a><br>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include
            typedef struct data_control {
            pthread_mutex_t mutex;
            pthread_cond_t cond;
            int active;
            } data_control;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>现在您看到了 data_control 结构定义，以下是它的视觉表示：</p>
<br><a name=10><strong>所使用的 data_control 结构</strong></a><br><img border=0 alt="" src="http://www.cppblog.com/images/cppblog_com/zjl-1026-2001/control.gif" width=244 height=197 valign="top"> <br>
<p>图像中的锁代表互斥对象，它允许对数据结构进行互斥访问。黄色的星代表条件变量，它可以睡眠，直到所讨论的数据结构改变为止。on/off 开关表示整数 "active"，它告诉线程此数据是否是活动的。在代码中，我使用整数 active 作为标志，告诉工作队列何时应该关闭。以下是 control.c：</p>
<br><a name=N10133><strong>control.c</strong></a><br>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>/* control.c
            ** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
            ** Author: Daniel Robbins
            ** Date: 16 Jun 2000
            **
            ** These routines provide an easy way to make any type of
            ** data-structure thread-aware.  Simply associate a data_control
            ** structure with the data structure (by creating a new struct, for
            ** example).  Then, simply lock and unlock the mutex, or
            ** wait/signal/broadcast on the condition variable in the data_control
            ** structure as needed.
            **
            ** data_control structs contain an int called "active".  This int is
            ** intended to be used for a specific kind of multithreaded design,
            ** where each thread checks the state of "active" every time it locks
            ** the mutex.  If active is 0, the thread knows that instead of doing
            ** its normal routine, it should stop itself.  If active is 1, it
            ** should continue as normal.  So, by setting active to 0, a
            ** controlling thread can easily inform a thread work crew to shut
            ** down instead of processing new jobs.  Use the control_activate()
            ** and control_deactivate() functions, which will also broadcast on
            ** the data_control struct's condition variable, so that all threads
            ** stuck in pthread_cond_wait() will wake up, have an opportunity to
            ** notice the change, and then terminate.
            */
            #include "control.h"
            int control_init(data_control *mycontrol) {
            int mystatus;
            if (pthread_mutex_init(&amp;(mycontrol-&gt;mutex),NULL))
            return 1;
            if (pthread_cond_init(&amp;(mycontrol-&gt;cond),NULL))
            return 1;
            mycontrol-&gt;active=0;
            return 0;
            }
            int control_destroy(data_control *mycontrol) {
            int mystatus;
            if (pthread_cond_destroy(&amp;(mycontrol-&gt;cond)))
            return 1;
            if (pthread_cond_destroy(&amp;(mycontrol-&gt;cond)))
            return 1;
            mycontrol-&gt;active=0;
            return 0;
            }
            int control_activate(data_control *mycontrol) {
            int mystatus;
            if (pthread_mutex_lock(&amp;(mycontrol-&gt;mutex)))
            return 0;
            mycontrol-&gt;active=1;
            pthread_mutex_unlock(&amp;(mycontrol-&gt;mutex));
            pthread_cond_broadcast(&amp;(mycontrol-&gt;cond));
            return 1;
            }
            int control_deactivate(data_control *mycontrol) {
            int mystatus;
            if (pthread_mutex_lock(&amp;(mycontrol-&gt;mutex)))
            return 0;
            mycontrol-&gt;active=0;
            pthread_mutex_unlock(&amp;(mycontrol-&gt;mutex));
            pthread_cond_broadcast(&amp;(mycontrol-&gt;cond));
            return 1;
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p><a name=10><span class=atitle>调试时间</span></a></p>
<p>在开始调试之前，还需要一个文件。以下是 dbug.h：</p>
<br><a name=N10146><strong>dbug.h</strong></a><br>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#define dabort() \
            {  printf("Aborting at line %d in source file %s\n",__LINE__,__FILE__); abort(); }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>此代码用于处理工作组代码中的不可纠正错误。</p>
<p><a name=11><span class=atitle>工作组代码</span></a></p>
<p>说到工作组代码，以下就是：</p>
<br><a name=N1015C><strong>workcrew.c</strong></a><br>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include &lt;stdio.h&gt;
            #include &lt;stdlib.h&gt;
            #include "control.h"
            #include "queue.h"
            #include "dbug.h"
            /* the work_queue holds tasks for the various threads to complete. */
            struct work_queue {
            data_control control;
            queue work;
            } wq;
            /* I added a job number to the work node.  Normally, the work node
            would contain additional data that needed to be processed. */
            typedef struct work_node {
            struct node *next;
            int jobnum;
            } wnode;
            /* the cleanup queue holds stopped threads.  Before a thread
            terminates, it adds itself to this list.  Since the main thread is
            waiting for changes in this list, it will then wake up and clean up
            the newly terminated thread. */
            struct cleanup_queue {
            data_control control;
            queue cleanup;
            } cq;
            /* I added a thread number (for debugging/instructional purposes) and
            a thread id to the cleanup node.  The cleanup node gets passed to
            the new thread on startup, and just before the thread stops, it
            attaches the cleanup node to the cleanup queue.  The main thread
            monitors the cleanup queue and is the one that performs the
            necessary cleanup. */
            typedef struct cleanup_node {
            struct node *next;
            int threadnum;
            pthread_t tid;
            } cnode;
            void *threadfunc(void *myarg) {
            wnode *mywork;
            cnode *mynode;
            mynode=(cnode *) myarg;
            pthread_mutex_lock(&amp;wq.control.mutex);
            while (wq.control.active) {
            while (wq.work.head==NULL &amp;&amp; wq.control.active) {
            pthread_cond_wait(&amp;wq.control.cond, &amp;wq.control.mutex);
            }
            if (!wq.control.active)
            break;
            //we got something!
            mywork=(wnode *) queue_get(&amp;wq.work);
            pthread_mutex_unlock(&amp;wq.control.mutex);
            //perform processing...
            printf("Thread number %d processing job %d\n",mynode-&gt;threadnum,mywork-&gt;jobnum);
            free(mywork);
            pthread_mutex_lock(&amp;wq.control.mutex);
            }
            pthread_mutex_unlock(&amp;wq.control.mutex);
            pthread_mutex_lock(&amp;cq.control.mutex);
            queue_put(&amp;cq.cleanup,(node *) mynode);
            pthread_mutex_unlock(&amp;cq.control.mutex);
            pthread_cond_signal(&amp;cq.control.cond);
            printf("thread %d shutting down...\n",mynode-&gt;threadnum);
            return NULL;
            }
            #define NUM_WORKERS 4
            int numthreads;
            void join_threads(void) {
            cnode *curnode;
            printf("joining threads...\n");
            while (numthreads) {
            pthread_mutex_lock(&amp;cq.control.mutex);
            /* below, we sleep until there really is a new cleanup node.  This
            takes care of any false wakeups... even if we break out of
            pthread_cond_wait(), we don't make any assumptions that the
            condition we were waiting for is true.  */
            while (cq.cleanup.head==NULL) {
            pthread_cond_wait(&amp;cq.control.cond,&amp;cq.control.mutex);
            }
            /* at this point, we hold the mutex and there is an item in the
            list that we need to process.  First, we remove the node from
            the queue.  Then, we call pthread_join() on the tid stored in
            the node.  When pthread_join() returns, we have cleaned up
            after a thread.  Only then do we free() the node, decrement the
            number of additional threads we need to wait for and repeat the
            entire process, if necessary */
            curnode = (cnode *) queue_get(&amp;cq.cleanup);
            pthread_mutex_unlock(&amp;cq.control.mutex);
            pthread_join(curnode-&gt;tid,NULL);
            printf("joined with thread %d\n",curnode-&gt;threadnum);
            free(curnode);
            numthreads--;
            }
            }
            int create_threads(void) {
            int x;
            cnode *curnode;
            for (x=0; x&lt;NUM_WORKERS; x++) {
            curnode=malloc(sizeof(cnode));
            if (!curnode)
            return 1;
            curnode-&gt;threadnum=x;
            if (pthread_create(&amp;curnode-&gt;tid, NULL, threadfunc, (void *) curnode))
            return 1;
            printf("created thread %d\n",x);
            numthreads++;
            }
            return 0;
            }
            void initialize_structs(void) {
            numthreads=0;
            if (control_init(&amp;wq.control))
            dabort();
            queue_init(&amp;wq.work);
            if (control_init(&amp;cq.control)) {
            control_destroy(&amp;wq.control);
            dabort();
            }
            queue_init(&amp;wq.work);
            control_activate(&amp;wq.control);
            }
            void cleanup_structs(void) {
            control_destroy(&amp;cq.control);
            control_destroy(&amp;wq.control);
            }
            int main(void) {
            int x;
            wnode *mywork;
            initialize_structs();
            /* CREATION */
            if (create_threads()) {
            printf("Error starting threads... cleaning up.\n");
            join_threads();
            dabort();
            }
            pthread_mutex_lock(&amp;wq.control.mutex);
            for (x=0; x&lt;16000; x++) {
            mywork=malloc(sizeof(wnode));
            if (!mywork) {
            printf("ouch! can't malloc!\n");
            break;
            }
            mywork-&gt;jobnum=x;
            queue_put(&amp;wq.work,(node *) mywork);
            }
            pthread_mutex_unlock(&amp;wq.control.mutex);
            pthread_cond_broadcast(&amp;wq.control.cond);
            printf("sleeping...\n");
            sleep(2);
            printf("deactivating work queue...\n");
            control_deactivate(&amp;wq.control);
            /* CLEANUP  */
            join_threads();
            cleanup_structs();
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p><a name=12><span class=atitle>代码初排</span></a></p>
<p>现在来快速初排代码。定义的第一个结构称作 "wq"，它包含了 data_control 和队列头。data_control 结构用于仲裁对整个队列的访问，包括队列中的节点。下一步工作是定义实际的工作节点。要使代码符合本文中的示例，此处所包含的都是作业号。</p>
<p>接着，创建清除队列。注释说明了它的工作方式。好，现在让我们跳过 threadfunc()、join_threads()、create_threads() 和 initialize_structs() 调用，直接跳到 main()。所做的第一件事就是初始化结构 -- 这包括初始化 data_controls 和队列，以及激活工作队列。</p>
<p><a name=13><span class=atitle>有关清除的注意事项</span></a></p>
<p>现在初始化线程。如果看一下 create_threads() 调用，似乎一切正常 -- 除了一件事。请注意，我们正在分配清除节点，以及初始化它的线程号和 TID 组件。我们还将清除节点作为初始自变量传递给每一个新的工作程序线程。为什么这样做？</p>
<p>因为当某个工作程序线程退出时，它会将其清除节点连接到清除队列，然后终止。那时，主线程会在清除队列中检测到这个节点（利用条件变量），并将这个节点移出队列。因为 TID（线程标识）存储在清除节点中，所以主线程可以确切知道哪个线程已终止了。然后，主线程将调用 pthread_join(tid)，并联接适当的工作程序线程。如果没有做记录，那么主线程就需要按任意顺序联接工作程序线程，可能是按它们的创建顺序。由于线程不一定按此顺序终止，那么主线程可能会在已经联接了十个线程时，等待联接另一个线程。您能理解这种设计决策是如何使关闭代码加速的吗（尤其在使用几百个工作程序线程的情况下）？</p>
<br>
<p><a name=14><span class=atitle>创建工作</span></a></p>
<p>我们已启动了工作程序线程（它们已经完成了执行 threadfunc()，稍后将讨论此函数），现在主线程开始将工作节点插入工作队列。首先，它锁定 wq 的控制互斥对象，然后分配 16000 个工作包，将它们逐个插入队列。完成之后，将调用 pthread_cond_broadcast()，于是所有正在睡眠的线程会被唤醒，并开始执行工作。此时，主线程将睡眠两秒钟，然后释放工作队列，并通知工作程序线程终止活动。接着，主线程会调用 join_threads() 函数来清除所有工作程序线程。</p>
<p><a name=15><span class=atitle>threadfunc()</span></a></p>
<p>现在来讨论 threadfunc()，这是所有工作程序线程都要执行的代码。当工作程序线程启动时，它会立即锁定工作队列互斥对象，获取一个工作节点（如果有的话），然后对它进行处理。如果没有工作，则调用 pthread_cond_wait()。您会注意到这个调用在一个非常紧凑的 while() 循环中，这是非常重要的。当从 pthread_cond_wait() 调用中苏醒时，决不能认为条件肯定发生了 -- 它 <em>可能</em>发生了，也可能没有发生。如果发生了这种情况，即错误地唤醒了线程，而列表是空的，那么 while 循环将再次调用 pthread_cond_wait()。 </p>
<p>如果有一个工作节点，那么我们只打印它的作业号，释放它并退出。然而，实际代码会执行一些更实质性的操作。在 while() 循环结尾，我们锁定了互斥对象，以便检查 active 变量，以及在循环顶部检查新的工作节点。如果执行完此代码，就会发现如果 wq.control.active 是 0，while 循环就会终止，并会执行 threadfunc() 结尾处的清除代码。</p>
<p>工作程序线程的清除代码部件非常有趣。首先，由于 pthread_cond_wait() 返回了锁定的互斥对象，它会对 work_queue 解锁。然后，它锁定清除队列，添加清除代码（包含了 TID，主线程将使用此 TID 来调用 pthread_join()），然后再对清除队列解锁。此后，它发信号给所有 cq 等待者 (pthread_cond_signal(&amp;cq.control.cond))，于是主线程就知道有一个待处理的新节点。我们不使用 pthread_cond_broadcast()，因为没有这个必要 -- 只有一个线程（主线程）在等待清除队列中的新节点。当它调用 join_threads() 时，工作程序线程将打印关闭消息，然后终止，等待主线程发出的 pthread_join() 调用。</p>
<br>
<p><a name=16><span class=atitle>join_threads()</span></a></p>
<p>如果要查看关于如何使用条件变量的简单示例，请参考 join_threads() 函数。如果还有工作程序线程，join_threads() 会一直执行，等待清除队列中新的清除节点。如果有新节点，我们会将此节点移出队列、对清除队列解锁（从而使工作程序可以添加清除节点）、联接新的工作程序线程（使用存储在清除节点中的 TID）、释放清除节点、减少&#8220;现有&#8221;线程的数量，然后继续。</p>
<br>
<p><a name=17><span class=atitle>结束语</span></a></p>
<p>现在已经到了&#8220;POSIX 线程详解&#8221;系列的尾声，希望您已经准备好开始将多线程代码添加到您自己的应用程序中。有关详细信息，请参阅 <a href="#resources"><u><font color=#5c81a7>参考资料</font></u></a>部分，这部分内容还包含了本文中使用的所有源码的 tar 文件。下一个系列中再见！ </p>
<br>
<p><a name=resources><span class=atitle>参考资料 </span></a></p>
<ul>
    <li>您可以参阅本文在 developerWorks 全球站点上的 <a href="http://www.ibm.com/developerworks/library/l-posix3/index.html?S_TACT=105AGX52&amp;S_CMP=cn-a-l"><u><font color=#5c81a7>英文原文</font></u></a>. <br><br>
    <li>本文中使用的 <a href="http://www.cppblog.com/zjl-1026-2001/admin/thread-3.tar.gz"><u><font color=#5c81a7>源码的 tar 文件</font></u></a>。 <br><br>
    <li>友好的 Linux pthread 在线帮助 ("man -k pthread") 是极好的参考资料。<br><br>
    <li>如果要彻底了解 POSIX 线程，我推荐此书： <a href="http://www.amazon.com/exec/obidos/ASIN/0201633922"><u><font color=#5c81a7><em>Programming with POSIX Threads</em> </font></u></a>，David R. Butenhof (Addison-Wesley, 1997)。据证实，此书是现有最好的讨论 POSIX 线程的书籍。 <br><br>
    <li>W. Richard Stevens 撰写的 <a href="http://search.borders.com/fcgi-bin/db2www/search/search.d2w/Details?&amp;mediaType=Book&amp;prodID=2362607"><u><font color=#5c81a7><em>UNIX Network Programming - Networking APIs: Sockets and XTI</em> </font></u></a>，(Prentice Hall, 1997) 一书还涵盖了 POSIX 线程。这是一本经典著作，但它讨论线程不如上述的 <em>Programming with POSIX Threads</em>那样详细。 <br><br>
    <li>请参考 Daniel 在 <em>developerWorks</em>上发表的 POSIX 线程系列中的前几篇文章：
    <ul>
        <li><a href="http://www.cppblog.com/zjl-1026-2001/posix_thread1/index.html"><u><font color=#5c81a7>POSIX 线程详解</font></u></a>介绍了 POSIX 线程，并演示了如何在代码中使用线程。
        <li><a href="http://www.cppblog.com/zjl-1026-2001/posix_thread2/index.html"><u><font color=#5c81a7>POSIX 线程详解，第 2 部分</font></u></a>演示了如何使用被称为互斥对象的灵巧小玩意，来保护线程代码中共享数据结构的完整性。 </li>
    </ul>
    <br>
    <li>请参阅 Sean Walton 撰写的有关 <a href="http://metalab.unc.edu/pub/Linux/docs/faqs/Threads-FAQ/html/"><u><font color=#5c81a7>Linux 线程</font></u></a>的文档，KB7rfa <br><br>
    <li>请学习亚里桑那大学的 Mark Hays 编写的 POSIX 线程 <a href="http://www.math.arizona.edu/swig/pthreads/threads.html"><u><font color=#5c81a7>教程</font></u></a>。 <br><br>
    <li>请在 <a href="http://hwaci.com/sw/pttcl/pttcl.html"><u><font color=#5c81a7>Pthreads-Tcl 介绍</font></u></a>中查看对 Tcl 的更改，此更改使 Tcl 能够与 POSIX 线程一起使用。 <br><br>
    <li>请访问 <a href="http://members.aa.net/~mtp/PCthreads.html"><u><font color=#5c81a7>LINUX POSIX 和 DCE 线程</font></u></a>主页。 <br><br>
    <li>请参阅 <a href="http://pauillac.inria.fr/~xleroy/linuxthreads/"><u><font color=#5c81a7>LinuxThreads 资料库</font></u></a>。 <br><br>
    <li><a href="http://www.users.itl.net.ua/~prool/proolix.html"><u><font color=#5c81a7>Proolix</font></u></a>是一种简单的遵从 POSIX 标准的基于 i8086+ 的操作系统。 <br></li>
</ul>
<br><br>
<p><a name=author><span class=atitle>关于作者</span></a></p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td colSpan=3><img alt="" src="http://www.ibm.com/i/c.gif" width="100%" height=5></td>
        </tr>
        <tr vAlign=top align=left>
            <td>
            <p>&#160;</p>
            </td>
            <td><img alt="" src="http://www.ibm.com/i/c.gif" width=4 height=5></td>
            <td width="100%">
            <p>Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的总裁兼 CEO，Gentoo 项目的总设计师，MacMillan 出版书籍的撰稿作者，他的著作有： <em>Caldera OpenLinux Unleashed</em>, <em>SuSE Linux Unleashed</em>, 和 <em>Samba Unleashed</em>。Daniel 自二年级起就与计算机某些领域结下不解之缘，那时他首先接触的是 Logo 程序语言，并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 和新出生的女儿 Hadassah 一起共度时光。可通过 <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;&#63;&#99;&#99;&#61;"><u><font color=#5c81a7>drobbins@gentoo.org</font></u></a>与 Daniel 联系。 </p>
            </td>
        </tr>
    </tbody>
</table>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr vAlign=top align=left>
            <td><img alt="" src="http://www.ibm.com/i/c.gif" width=4 height=5></td>
            <td width="100%">
            <p>Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的总裁兼 CEO，Gentoo 项目的总设计师，MacMillan 出版书籍的撰稿作者，他的著作有： <em>Caldera OpenLinux Unleashed</em>, <em>SuSE Linux Unleashed</em>, 和 <em>Samba Unleashed</em>。Daniel 自二年级起就与计算机某些领域结下不解之缘，那时他首先接触的是 Logo 程序语言，并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 和新出生的女儿 Hadassah 一起共度时光。可通过 <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;&#63;&#99;&#99;&#61;"><u><font color=#5c81a7>drobbins@gentoo.org</font></u></a>与 Daniel 联系。 </p>
            </td>
        </tr>
    </tbody>
</table>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr vAlign=top align=left>
            <td><img alt="" src="http://www.ibm.com/i/c.gif" width=4 height=5></td>
            <td width="100%">
            <p>Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的总裁兼 CEO，Gentoo 项目的总设计师，MacMillan 出版书籍的撰稿作者，他的著作有： <em>Caldera OpenLinux Unleashed</em>, <em>SuSE Linux Unleashed</em>, 和 <em>Samba Unleashed</em>。Daniel 自二年级起就与计算机某些领域结下不解之缘，那时他首先接触的是 Logo 程序语言，并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 和新出生的女儿 Hadassah 一起共度时光。可通过 <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;&#63;&#99;&#99;&#61;"><u><font color=#5c81a7>drobbins@gentoo.org</font></u></a>与 Daniel 联系。 </p>
            </td>
        </tr>
    </tbody>
</table>
<p>Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的总裁兼 CEO，Gentoo 项目的总设计师，MacMillan 出版书籍的撰稿作者，他的著作有： <em>Caldera OpenLinux Unleashed</em>, <em>SuSE Linux Unleashed</em>, 和 <em>Samba Unleashed</em>。Daniel 自二年级起就与计算机某些领域结下不解之缘，那时他首先接触的是 Logo 程序语言，并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 和新出生的女儿 Hadassah 一起共度时光。可通过 <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;&#63;&#99;&#99;&#61;"><u><font color=#5c81a7>drobbins@gentoo.org</font></u></a>与 Daniel 联系。 </p>
<br>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/109419.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2010-03-11 11:54 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109419.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转载]POSIX 线程详解-第2部分</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109417.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Thu, 11 Mar 2010 03:46:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109417.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/109417.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109417.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/109417.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/109417.html</trackback:ping><description><![CDATA[原文地址：<a href="http://www.ibm.com/developerworks/cn/linux/thread/posix_thread2/">http://www.ibm.com/developerworks/cn/linux/thread/posix_thread2/</a><br><br>
<blockquote>POSIX 线程是提高代码响应和性能的有力手段。在此三部分系列文章的第二篇中，Daniel Robbins 将说明，如何使用被称为互斥对象的灵巧小玩意，来保护线程代码中共享数据结构的完整性。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><a name=N10043><span class=atitle>互斥我吧！</span></a></p>
<p>在 <a href="http://www.cppblog.com/zjl-1026-2001/posix_thread1/index.html"><u><font color=#5c81a7>前一篇文章中</font></u></a> ，谈到了会导致异常结果的线程代码。两个线程分别对同一个全局变量进行了二十次加一。变量的值最后应该是 40，但最终值却是 21。这是怎么回事呢？因为一个线程不停地&#8220;取消&#8221;了另一个线程执行的加一操作，所以产生这个问题。现在让我们来查看改正后的代码，它使用 <strong>互斥对象</strong>(mutex)来解决该问题： </p>
<br><a name=thread3.c><strong>thread3.c</strong></a><br><br>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include &lt;pthread.h&gt;
            #include &lt;stdlib.h&gt;
            #include &lt;unistd.h&gt;
            #include &lt;stdio.h&gt;
            int myglobal;
            pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
            void *thread_function(void *arg) {
            int i,j;
            for ( i=0; i&lt;20; i++) {
            pthread_mutex_lock(&amp;mymutex);
            j=myglobal;
            j=j+1;
            printf(".");
            fflush(stdout);
            sleep(1);
            myglobal=j;
            pthread_mutex_unlock(&amp;mymutex);
            }
            return NULL;
            }
            int main(void) {
            pthread_t mythread;
            int i;
            if ( pthread_create( &amp;mythread, NULL, thread_function, NULL) ) {
            printf("error creating thread.");
            abort();
            }
            for ( i=0; i&lt;20; i++) {
            pthread_mutex_lock(&amp;mymutex);
            myglobal=myglobal+1;
            pthread_mutex_unlock(&amp;mymutex);
            printf("o");
            fflush(stdout);
            sleep(1);
            }
            if ( pthread_join ( mythread, NULL ) ) {
            printf("error joining thread.");
            abort();
            }
            printf("\nmyglobal equals %d\n",myglobal);
            exit(0);
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p><a name=N1005D><span class=atitle><strong><font size=5>解读一下</font></strong></span></a></p>
<p>如果将这段代码与 <a href="http://www.ibm.com/software/developerworks/library/posix1.html"><u><font color=#5c81a7>前一篇文章</font></u></a> 中给出的版本作一个比较，就会注意到增加了 pthread_mutex_lock() 和 pthread_mutex_unlock() 函数调用。在线程程序中这些调用执行了不可或缺的功能。他们提供了一种 <em>相互排斥</em>的方法（互斥对象即由此得名）。两个线程不能同时对同一个互斥对象加锁。 </p>
<p>互斥对象是这样工作的。如果线程 a 试图锁定一个互斥对象，而此时线程 b 已锁定了同一个互斥对象时，线程 a 就将进入睡眠状态。一旦线程 b 释放了互斥对象（通过 pthread_mutex_unlock() 调用），线程 a 就能够锁定这个互斥对象（换句话说，线程 a 就将从 pthread_mutex_lock() 函数调用中返回，同时互斥对象被锁定）。同样地，当线程 a 正锁定互斥对象时，如果线程 c 试图锁定互斥对象的话，线程 c 也将临时进入睡眠状态。对已锁定的互斥对象上调用 pthread_mutex_lock() 的所有线程都将进入睡眠状态，这些睡眠的线程将&#8220;排队&#8221;访问这个互斥对象。</p>
<p>通常使用 pthread_mutex_lock() 和 pthread_mutex_unlock() 来保护数据结构。这就是说，通过线程的锁定和解锁，对于某一数据结构，确保某一时刻只能有一个线程能够访问它。可以推测到，当线程试图锁定一个未加锁的互斥对象时，POSIX 线程库将同意锁定，而不会使线程进入睡眠状态。</p>
<br><a name=figure1><strong>请看这幅轻松的漫画，四个小精灵重现了最近一次 pthread_mutex_lock() 调用的一个场面。</strong></a><br><img border=0 alt="" src="http://www.cppblog.com/images/cppblog_com/zjl-1026-2001/mutex.gif" width=600 height=280 valign="top"> <br>
<p>图中，锁定了互斥对象的线程能够存取复杂的数据结构，而不必担心同时会有其它线程干扰。那个数据结构实际上是&#8220;冻结&#8221;了，直到互斥对象被解锁为止。pthread_mutex_lock() 和 pthread_mutex_unlock() 函数调用，如同&#8220;在施工中&#8221;标志一样，将正在修改和读取的某一特定共享数据包围起来。这两个函数调用的作用就是警告其它线程，要它们继续睡眠并等待轮到它们对互斥对象加锁。当然，除非在 <em>每个</em> 对特定数据结构进行读写操作的语句前后，都分别放上 pthread_mutex_lock() 和 pthread_mutext_unlock() 调用，才会出现这种情况。<br><br></p>
<p><a name=N1008C><span class=atitle><strong><font size=5>为什么要用互斥对象？</font></strong></span></a></p>
<p>听上去很有趣，但究竟为什么要让线程睡眠呢？要知道，线程的主要优点不就是其具有独立工作、更多的时候是同时工作的能力吗？是的，确实是这样。然而，每个重要的线程程序都需要使用某些互斥对象。让我们再看一下示例程序以便理解原因所在。</p>
<p>请看 thread_function()，循环中一开始就锁定了互斥对象，最后才将它解锁。在这个示例程序中，mymutex 用来保护 myglobal 的值。仔细查看 thread_function()，加一代码把 myglobal 复制到一个局部变量，对局部变量加一，睡眠一秒钟，在这之后才把局部变量的值传回给 myglobal。不使用互斥对象时，即使主线程在 thread_function() 线程睡眠一秒钟期间内对 myglobal 加一，thread_function() 苏醒后也会覆盖主线程所加的值。使用互斥对象能够保证这种情形不会发生。（您也许会想到，我增加了一秒钟延迟以触发不正确的结果。把局部变量的值赋给 myglobal 之前，实际上没有什么真正理由要求 thread_function() 睡眠一秒钟。）使用互斥对象的新程序产生了期望的结果：</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>$ ./thread3
            o..o..o.o..o..o.o.o.o.o..o..o..o.ooooooo
            myglobal equals 40
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>为了进一步探索这个极为重要的概念，让我们看一看程序中进行加一操作的代码：</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>thread_function() 加一代码：
            j=myglobal;
            j=j+1;
            printf(".");
            fflush(stdout);
            sleep(1);
            myglobal=j;
            主线程加一代码：
            myglobal=myglobal+1;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>如果代码是位于单线程程序中，可以预期 thread_function() 代码将完整执行。接下来才会执行主线程代码（或者是以相反的顺序执行）。在不使用互斥对象的线程程序中，代码可能（几乎是，由于调用了 sleep() 的缘故）以如下的顺序执行：</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    thread_function() 线程        主线程
            j=myglobal;
            j=j+1;
            printf(".");
            fflush(stdout);
            sleep(1);                     myglobal=myglobal+1;
            myglobal=j;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>当代码以此特定顺序执行时，将覆盖主线程对 myglobal 的修改。程序结束后，就将得到不正确的值。如果是在操纵指针的话，就可能产生段错误。注意到 thread_function() 线程按顺序执行了它的所有指令。看来不象是 thread_function() 有什么次序颠倒。问题是，同一时间内，另一个线程对同一数据结构进行了另一个修改。</p>
<p><br></p>
<p><a name=N100AD><span class=atitle><strong><font size=5>线程内幕 1</font></strong></span></a></p>
<p>在解释如何确定在何处使用互斥对象之前，先来深入了解一下线程的内部工作机制。请看第一个例子：</p>
<p>假设主线程将创建三个新线程：线程 a、线程 b 和线程 c。假定首先创建线程 a，然后是线程 b，最后创建线程 c。</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    pthread_create( &amp;thread_a, NULL, thread_function, NULL);
            pthread_create( &amp;thread_b, NULL, thread_function, NULL);
            pthread_create( &amp;thread_c, NULL, thread_function, NULL);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>在第一个 pthread_create() 调用完成后，可以假定线程 a 不是已存在就是已结束并停止。第二个 pthread_create() 调用后，主线程和线程 b 都可以假定线程 a 存在（或已停止）。</p>
<p>然而，就在第二个 create() 调用返回后，主线程无法假定是哪一个线程（a 或 b）会首先开始运行。虽然两个线程都已存在，线程 CPU 时间片的分配取决于内核和线程库。至于谁将首先运行，并没有严格的规则。尽管线程 a 更有可能在线程 b 之前开始执行，但这并无保证。对于多处理器系统，情况更是如此。如果编写的代码假定在线程 b 开始执行之前实际上执行线程 a 的代码，那么，程序最终正确运行的概率是 99%。或者更糟糕，程序在您的机器上 100% 地正确运行，而在您客户的四处理器服务器上正确运行的概率却是零。</p>
<p>从这个例子还可以得知，线程库保留了每个单独线程的代码执行顺序。换句话说，实际上那三个 pthread_create() 调用将按它们出现的顺序执行。从主线程上来看，所有代码都是依次执行的。有时，可以利用这一点来优化部分线程程序。例如，在上例中，线程 c 就可以假定线程 a 和线程 b 不是正在运行就是已经终止。它不必担心存在还没有创建线程 a 和线程 b 的可能性。可以使用这一逻辑来优化线程程序。</p>
<p><br></p>
<p><a name=N100C6><span class=atitle><strong><font size=5>线程内幕 2</font></strong></span></a></p>
<p>现在来看另一个假想的例子。假设有许多线程，他们都正在执行下列代码：</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>    myglobal=myglobal+1;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>那么，是否需要在加一操作语句前后分别锁定和解锁互斥对象呢？也许有人会说&#8220;不&#8221;。编译器极有可能把上述赋值语句编译成一条机器指令。大家都知道，不可能"半途"中断一条机器指令。即使是硬件中断也不会破坏机器指令的完整性。基于以上考虑，很可能倾向于完全省略 pthread_mutex_lock() 和 pthread_mutex_unlock() 调用。不要这样做。</p>
<p>我在说废话吗？不完全是这样。首先，不应该假定上述赋值语句一定会被编译成一条机器指令，除非亲自验证了机器代码。即使插入某些内嵌汇编语句以确保加一操作的完整执行――甚至，即使是自己动手写编译器！-- 仍然可能有问题。</p>
<p>答案在这里。使用单条内嵌汇编操作码在单处理器系统上可能不会有什么问题。每个加一操作都将完整地进行，并且多半会得到期望的结果。但是多处理器系统则截然不同。在多 CPU 机器上，两个单独的处理器可能会在几乎同一时刻（或者，就在同一时刻）执行上述赋值语句。不要忘了，这时对内存的修改需要先从 L1 写入 L2 高速缓存、然后才写入主存。（SMP 机器并不只是增加了处理器而已；它还有用来仲裁对 RAM 存取的特殊硬件。）最终，根本无法搞清在写入主存的竞争中，哪个 CPU 将会"胜出"。要产生可预测的代码，应使用互斥对象。互斥对象将插入一道"内存关卡"，由它来确保对主存的写入按照线程锁定互斥对象的顺序进行。</p>
<p>考虑一种以 32 位块为单位更新主存的 SMP 体系结构。如果未使用互斥对象就对一个 64 位整数进行加一操作，整数的最高 4 位字节可能来自一个 CPU，而其它 4 个字节却来自另一 CPU。糟糕吧！最糟糕的是，使用差劲的技术，您的程序在重要客户的系统上有可能不是很长时间才崩溃一次，就是早上三点钟就崩溃。David R. Butenhof 在他的《POSIX 线程编程》（请参阅本文末尾的 <a href="#resources"><u><font color=#5c81a7>参考资料</font></u></a>部分）一书中，讨论了由于未使用互斥对象而将产生的种种情况。 </p>
<p><br></p>
<p><a name=N100E3><span class=atitle><strong><font size=5>许多互斥对象</font></strong></span></a></p>
<p>如果放置了过多的互斥对象，代码就没有什么并发性可言，运行起来也比单线程解决方案慢。如果放置了过少的互斥对象，代码将出现奇怪和令人尴尬的错误。幸运的是，有一个中间立场。首先，互斥对象是用于串行化存取*共享数据*。不要对非共享数据使用互斥对象，并且，如果程序逻辑确保任何时候都只有一个线程能存取特定数据结构，那么也不要使用互斥对象。</p>
<p>其次，如果要使用共享数据，那么在读、写共享数据时都应使用互斥对象。用 pthread_mutex_lock() 和 pthread_mutex_unlock() 把读写部分保护起来，或者在程序中不固定的地方随机使用它们。学会从一个线程的角度来审视代码，并确保程序中每一个线程对内存的观点都是一致和合适的。为了熟悉互斥对象的用法，最初可能要花好几个小时来编写代码，但是很快就会习惯并且*也*不必多想就能够正确使用它们。</p>
<p><br></p>
<p><a name=N100EF><span class=atitle><strong><font size=5>使用调用：初始化</font></strong></span></a></p>
<p>现在该来看看使用互斥对象的各种不同方法了。让我们从初始化开始。在 <a href="#thread3.c"><u><font color=#5c81a7>thread3.c 示例</font></u></a> 中，我们使用了静态初始化方法。这需要声明一个 pthread_mutex_t 变量，并赋给它常数 PTHREAD_MUTEX_INITIALIZER： </p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline bgColor=#c0c0c0>
            <pre class=displaycode>pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>很简单吧。但是还可以动态地创建互斥对象。当代码使用 malloc() 分配一个新的互斥对象时，使用这种动态方法。此时，静态初始化方法是行不通的，并且应当使用例程 pthread_mutex_init()：</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>int pthread_mutex_init( pthread_mutex_t *mymutex, const pthread_mutexattr_t *attr)
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>正如所示，pthread_mutex_init 接受一个指针作为参数以初始化为互斥对象，该指针指向一块已分配好的内存区。第二个参数，可以接受一个可选的 pthread_mutexattr_t 指针。这个结构可用来设置各种互斥对象属性。但是通常并不需要这些属性，所以正常做法是指定 NULL。</p>
<p>一旦使用 pthread_mutex_init() 初始化了互斥对象，就应使用 pthread_mutex_destroy() 消除它。pthread_mutex_destroy() 接受一个指向 pthread_mutext_t 的指针作为参数，并释放创建互斥对象时分配给它的任何资源。请注意， pthread_mutex_destroy() <strong>不会</strong> 释放用来存储 pthread_mutex_t 的内存。释放自己的内存完全取决于您。还必须注意一点，pthread_mutex_init() 和 pthread_mutex_destroy() 成功时都返回零。 </p>
<p><br></p>
<p><a name=N10110><span class=atitle><strong><font size=5>使用调用：锁定</font></strong></span></a></p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>pthread_mutex_lock(pthread_mutex_t *mutex)
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>pthread_mutex_lock() 接受一个指向互斥对象的指针作为参数以将其锁定。如果碰巧已经锁定了互斥对象，调用者将进入睡眠状态。函数返回时，将唤醒调用者（显然）并且调用者还将保留该锁。函数调用成功时返回零，失败时返回非零的错误代码。</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>pthread_mutex_unlock(pthread_mutex_t *mutex)
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>pthread_mutex_unlock() 与 pthread_mutex_lock() 相配合，它把线程已经加锁的互斥对象解锁。始终应该尽快对已加锁的互斥对象进行解锁（以提高性能）。并且绝对不要对您未保持锁的互斥对象进行解锁操作（否则，pthread_mutex_unlock() 调用将失败并带一个非零的 EPERM 返回值）。</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>pthread_mutex_trylock(pthread_mutex_t *mutex)
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>当线程正在做其它事情的时候（由于互斥对象当前是锁定的），如果希望锁定互斥对象，这个调用就相当方便。调用 pthread_mutex_trylock() 时将尝试锁定互斥对象。如果互斥对象当前处于解锁状态，那么您将获得该锁并且函数将返回零。然而，如果互斥对象已锁定，这个调用也不会阻塞。当然，它会返回非零的 EBUSY 错误值。然后可以继续做其它事情，稍后再尝试锁定。</p>
<p><br></p>
<p><a name=N1012B><span class=atitle><strong><font size=5>等待条件发生</font></strong></span></a></p>
<p>互斥对象是线程程序必需的工具，但它们并非万能的。例如，如果线程正在等待共享数据内某个条件出现，那会发生什么呢？代码可以反复对互斥对象锁定和解锁，以检查值的任何变化。同时，还要快速将互斥对象解锁，以便其它线程能够进行任何必需的更改。这是一种非常可怕的方法，因为线程需要在合理的时间范围内频繁地循环检测变化。</p>
<p>在每次检查之间，可以让调用线程短暂地进入睡眠，比如睡眠三秒钟，但是因此线程代码就无法最快作出响应。真正需要的是这样一种方法，当线程在等待满足某些条件时使线程进入睡眠状态。一旦条件满足，还需要一种方法以唤醒因等待满足特定条件而睡眠的线程。如果能够做到这一点，线程代码将是非常高效的，并且不会占用宝贵的互斥对象锁。这正是 POSIX 条件变量能做的事！</p>
<p>而 POSIX 条件变量将是我下一篇文章的主题，其中将说明如何正确使用条件变量。到那时，您将拥有了创建复杂线程程序所需的全部资源，那些线程程序可以模拟工作人员、装配线等等。既然您已经越来越熟悉线程，我将在下一篇文章中加快进度。这样，在下一篇文章的结尾就能放上一个相对复杂的线程程序。说到等到条件产生，下次再见！</p>
<p><br><br></p>
<p><a name=resources><span class=atitle><strong><font size=5>参考资料 </font></strong></span></a></p>
<ul>
    <li>您可以参阅本文在 developerWorks 全球站点上的 <a href="http://www.ibm.com/developerworks/library/l-posix2/index.html?S_TACT=105AGX52&amp;S_CMP=cn-a-l"><u><font color=#5c81a7>英文原文</font></u></a>. <br><br>
    <li>请参阅 <a href="http://metalab.unc.edu/pub/Linux/docs/faqs/Threads-FAQ/html/"><u><font color=#5c81a7>Linux 线程</font></u></a>中的文档，Sean Walton, KB7rfa <br><br>
    <li>POSIX 线程 <a href="http://www.math.arizona.edu/swig/pthreads/threads.html"><u><font color=#5c81a7>教程</font></u></a> ，Mark Hays，亚里桑那大学 <br><br>
    <li>在 <a href="http://hwaci.com/sw/pttcl/pttcl.html"><u><font color=#5c81a7>Pthreads-Tcl 介绍</font></u></a>中，查看对 Tcl 的更改以使其能够使用 POSIX 线程 <br><br>
    <li>使用友好的 Linux pthread 在线帮助 ("man -k pthread")<br><br>
    <li>参考 <a href="http://members.aa.net/~mtp/PCthreads.html"><u><font color=#5c81a7>LINUX POSIX 和 DCE 线程</font></u></a>主页 <br><br>
    <li>查看 <a href="http://pauillac.inria.fr/~xleroy/linuxthreads/"><u><font color=#5c81a7>LinuxThreads 资料库</font></u></a> <br><br>
    <li><a href="http://www.users.itl.net.ua/~prool/proolix.html"><u><font color=#5c81a7>Proolix</font></u></a> ，一种简单遵从 POSIX 标准的操作系统，用于 i8086+，一直在开发中 <br><br>
    <li>阅读 David R. Butenhof 的著作 <a href="http://www.amazon.com/exec/obidos/ASIN/0201633922/o/qid=961544788/sr=8-1/ref=aps_sr_b_1_1/002-2882413-1227240"><u><font color=#5c81a7>POSIX 线程编程指南</font></u></a>，书中讨论了许多问题，其中谈到不使用互斥对象是可能出现的种种情况 <br><br>
    <li>查阅 W. Richard Stevens 的著作 <a href="http://search.borders.com/fcgi-bin/db2www/search/search.d2w/Details?&amp;mediaType=Book&amp;prodID=2362607"><u><font color=#5c81a7>UNIX 网络编程：网络 API：Sockets 和 XTI，第 1 卷</font></u></a> <br></li>
</ul>
<p><br><br></p>
<p><a name=author><span class=atitle><strong><font size=5>关于作者</font></strong></span></a></p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td colSpan=3><strong><font size=5><img alt="" src="http://www.ibm.com/i/c.gif" width="100%" height=5></font></strong></td>
        </tr>
        <tr vAlign=top align=left>
            <td>
            <p><strong><font size=5></font></strong></p>
            </td>
            <td><strong><font size=5><img alt="" src="http://www.ibm.com/i/c.gif" width=4 height=5></font></strong></td>
            <td width="100%">
            <p>Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的总裁兼 CEO， Gentoo 项目的总设计师，多本 MacMillan 出版书籍的作者，包括： <em>Caldera OpenLinux Unleashed</em>、 <em>SuSE Linux Unleashed</em>和 <em>Samba Unleashed</em> 。Daniel 自小学二年级起就与计算机结下不解之缘，那时他首先接触的是 Logo 程序语言，并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 和刚出生的女儿 Hadassah 一起共渡时光。可通过 <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;&#63;&#99;&#99;&#61;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;"><u><font color=#5c81a7>drobbins@gentoo.org</font></u></a> 与 Daniel 取得联系。 </p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的总裁兼 CEO， Gentoo 项目的总设计师，多本 MacMillan 出版书籍的作者，包括： <em>Caldera OpenLinux Unleashed</em>、 <em>SuSE Linux Unleashed</em>和 <em>Samba Unleashed</em> 。Daniel 自小学二年级起就与计算机结下不解之缘，那时他首先接触的是 Logo 程序语言，并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 和刚出生的女儿 Hadassah 一起共渡时光。可通过 <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;&#63;&#99;&#99;&#61;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;"><u><font color=#5c81a7>drobbins@gentoo.org</font></u></a> 与 Daniel 取得联系。 </p>
<p><br><br><br><br><br><br></p>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/109417.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2010-03-11 11:46 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109417.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转载]POSIX 线程详解-第1部分</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109415.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Thu, 11 Mar 2010 03:39:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109415.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/109415.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109415.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/109415.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/109415.html</trackback:ping><description><![CDATA[<blockquote>
<p>[原文地址]<a href="http://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/">http://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/</a></p>
<p><br>POSIX（可移植操作系统接口）线程是提高代码响应和性能的有力手段。在本系列中，Daniel Robbins 向您精确地展示在编程中如何使用线程。其中还涉及大量幕后细节，读完本系列文章，您完全可以运用 POSIX 线程创建多线程程序。</p>
</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><a name=N10043><span class=atitle>线程是有趣的</span></a></p>
<p>了解如何正确运用线程是每一个优秀程序员必备的素质。线程类似于进程。如同进程，线程由内核按时间分片进行管理。在单处理器系统中，内核使用时间分片来模拟线程的并发执行，这种方式和进程的相同。而在多处理器系统中，如同多个进程，线程实际上一样可以并发执行。</p>
<p>那么为什么对于大多数合作性任务，多线程比多个独立的进程更优越呢？这是因为，线程共享相同的内存空间。不同的线程可以存取内存中的同一个变量。所以，程序中的所有线程都可以读或写声明过的全局变量。如果曾用 fork() 编写过重要代码，就会认识到这个工具的重要性。为什么呢？虽然 fork() 允许创建多个进程，但它还会带来以下通信问题: 如何让多个进程相互通信，这里每个进程都有各自独立的内存空间。对这个问题没有一个简单的答案。虽然有许多不同种类的本地 IPC (进程间通信），但它们都遇到两个重要障碍：</p>
<ul>
    <li>强加了某种形式的额外内核开销，从而降低性能。
    <li>对于大多数情形，IPC 不是对于代码的&#8220;自然&#8221;扩展。通常极大地增加了程序的复杂性。 </li>
</ul>
<p>双重坏事: 开销和复杂性都非好事。如果曾经为了支持 IPC 而对程序大动干戈过，那么您就会真正欣赏线程提供的简单共享内存机制。由于所有的线程都驻留在同一内存空间，POSIX 线程无需进行开销大而复杂的长距离调用。只要利用简单的同步机制，程序中所有的线程都可以读取和修改已有的数据结构。而无需将数据经由文件描述符转储或挤入紧窄的共享内存空间。仅此一个原因，就足以让您考虑应该采用单进程/多线程模式而非多进程/单线程模式。</p>
<br>
<p><a name=N1005B><span class=atitle>线程是快捷的</span></a></p>
<p>不仅如此。线程同样还是非常快捷的。与标准 fork() 相比，线程带来的开销很小。内核无需单独复制进程的内存空间或文件描述符等等。这就节省了大量的 CPU 时间，使得线程创建比新进程创建快上十到一百倍。因为这一点，可以大量使用线程而无需太过于担心带来的 CPU 或内存不足。使用 fork() 时导致的大量 CPU 占用也不复存在。这表示只要在程序中有意义，通常就可以创建线程。</p>
<p>当然，和进程一样，线程将利用多 CPU。如果软件是针对多处理器系统设计的，这就真的是一大特性（如果软件是开放源码，则最终可能在不少平台上运行）。特定类型线程程序（尤其是 CPU 密集型程序）的性能将随系统中处理器的数目几乎线性地提高。如果正在编写 CPU 非常密集型的程序，则绝对想设法在代码中使用多线程。一旦掌握了线程编码，无需使用繁琐的 IPC 和其它复杂的通信机制，就能够以全新和创造性的方法解决编码难题。所有这些特性配合在一起使得多线程编程更有趣、快速和灵活。</p>
<br>
<p><a name=N10067><span class=atitle>线程是可移植的</span></a></p>
<p>如果熟悉 Linux 编程，就有可能知道 __clone() 系统调用。__clone() 类似于 fork()，同时也有许多线程的特性。例如，使用 __clone()，新的子进程可以有选择地共享父进程的执行环境（内存空间，文件描述符等）。这是好的一面。但 __clone() 也有不足之处。正如__clone() 在线帮助指出：</p>
<blockquote>&#8220;__clone 调用是特定于 Linux 平台的，不适用于实现可移植的程序。欲编写线程化应用程序（多线程控制同一内存空间），最好使用实现 POSIX 1003.1c 线程 API 的库，例如 Linux-Threads 库。参阅 pthread_create(3thr)。&#8221; </blockquote>
<p>虽然 __clone() 有线程的许多特性，但它是不可移植的。当然这并不意味着代码中不能使用它。但在软件中考虑使用 __clone() 时应当权衡这一事实。值得庆幸的是，正如 __clone() 在线帮助指出，有一种更好的替代方案：POSIX 线程。如果想编写 <strong>可移植的</strong> 多线程代码，代码可运行于 Solaris、FreeBSD、Linux 和其它平台，POSIX 线程是一种当然之选。</p>
<br>
<p><a name=N10079><span class=atitle>第一个线程</span></a></p>
<p>下面是一个 POSIX 线程的简单示例程序：</p>
<br><a name=N10082><strong>thread1.c</strong></a><br><br>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include &lt;pthread.h&gt;
            #include &lt;stdlib.h&gt;
            #include &lt;unistd.h&gt;
            void *thread_function(void *arg) {
            int i;
            for ( i=0; i&lt;20; i++) {
            printf("Thread says hi!\n");
            sleep(1);
            }
            return NULL;
            }
            int main(void) {
            pthread_t mythread;
            if ( pthread_create( &amp;mythread, NULL, thread_function, NULL) ) {
            printf("error creating thread.");
            abort();
            }
            if ( pthread_join ( mythread, NULL ) ) {
            printf("error joining thread.");
            abort();
            }
            exit(0);
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>要编译这个程序，只需先将程序存为 thread1.c，然后输入：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>$ gcc thread1.c -o thread1 -lpthread
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>运行则输入：</p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>$ ./thread1
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=N1009A><span class=atitle>理解 thread1.c</span></a></p>
<p>thread1.c 是一个非常简单的线程程序。虽然它没有实现什么有用的功能，但可以帮助理解线程的运行机制。下面，我们一步一步地了解这个程序是干什么的。main() 中声明了变量 mythread，类型是 pthread_t。pthread_t 类型在 pthread.h 中定义，通常称为&#8220;线程 id&#8221;（缩写为 "tid"）。可以认为它是一种线程句柄。</p>
<p>mythread 声明后（记住 mythread 只是一个 "tid"，或是将要创建的线程的句柄），调用 pthread_create 函数创建一个真实活动的线程。不要因为 pthread_create() 在 "if" 语句内而受其迷惑。由于 pthread_create() 执行成功时返回零而失败时则返回非零值，将 pthread_create() 函数调用放在 if() 语句中只是为了方便地检测失败的调用。让我们查看一下 pthread_create 参数。第一个参数 &amp;mythread 是指向 mythread 的指针。第二个参数当前为 NULL，可用来定义线程的某些属性。由于缺省的线程属性是适用的，只需将该参数设为 NULL。</p>
<p>第三个参数是新线程启动时调用的函数名。本例中，函数名为 thread_function()。当 thread_function() 返回时，新线程将终止。本例中，线程函数没有实现大的功能。它仅将 "Thread says hi!" 输出 20 次然后退出。注意 thread_function() 接受 void * 作为参数，同时返回值的类型也是 void *。这表明可以用 void * 向新线程传递任意类型的数据，新线程完成时也可返回任意类型的数据。那如何向线程传递一个任意参数？很简单。只要利用 pthread_create() 中的第四个参数。本例中，因为没有必要将任何数据传给微不足道的 thread_function()，所以将第四个参数设为 NULL。</p>
<p>您也许已推测到，在 pthread_create() 成功返回之后，程序将包含两个线程。等一等， <strong>两个</strong> 线程？我们不是只创建了一个线程吗？不错，我们只创建了一个进程。但是主程序同样也是一个线程。可以这样理解：如果编写的程序根本没有使用 POSIX 线程，则该程序是单线程的（这个单线程称为&#8220;主&#8221;线程）。创建一个新线程之后程序总共就有两个线程了。 </p>
<p>我想此时您至少有两个重要问题。第一个问题，新线程创建之后主线程如何运行。答案，主线程按顺序继续执行下一行程序（本例中执行 "if (pthread_join(...))"）。第二个问题，新线程结束时如何处理。答案，新线程先停止，然后作为其清理过程的一部分，等待与另一个线程合并或&#8220;连接&#8221;。</p>
<p>现在，来看一下 pthread_join()。正如 pthread_create() 将一个线程拆分为两个， pthread_join() 将两个线程合并为一个线程。pthread_join() 的第一个参数是 tid mythread。第二个参数是指向 void 指针的指针。如果 void 指针不为 NULL，pthread_join 将线程的 void * 返回值放置在指定的位置上。由于我们不必理会 thread_function() 的返回值，所以将其设为 NULL.</p>
<p>您会注意到 thread_function() 花了 20 秒才完成。在 thread_function() 结束很久之前，主线程就已经调用了 pthread_join()。如果发生这种情况，主线程将中断（转向睡眠）然后等待 thread_function() 完成。当 thread_function() 完成后, pthread_join() 将返回。这时程序又只有一个主线程。当程序退出时，所有新线程已经使用 pthread_join() 合并了。这就是应该如何处理在程序中创建的每个新线程的过程。如果没有合并一个新线程，则它仍然对系统的最大线程数限制不利。这意味着如果未对线程做正确的清理，最终会导致 pthread_create() 调用失败。</p>
<br>
<p><a name=N100B8><span class=atitle>无父，无子</span></a></p>
<p>如果使用过 fork() 系统调用，可能熟悉父进程和子进程的概念。当用 fork() 创建另一个新进程时，新进程是子进程，原始进程是父进程。这创建了可能非常有用的层次关系，尤其是等待子进程终止时。例如，waitpid() 函数让当前进程等待所有子进程终止。waitpid() 用来在父进程中实现简单的清理过程。</p>
<p>而 POSIX 线程就更有意思。您可能已经注意到我一直有意避免使用&#8220;父线程&#8221;和&#8220;子线程&#8221;的说法。这是因为 POSIX 线程中不存在这种层次关系。虽然主线程可以创建一个新线程，新线程可以创建另一个新线程，POSIX 线程标准将它们视为等同的层次。所以等待子线程退出的概念在这里没有意义。POSIX 线程标准不记录任何&#8220;家族&#8221;信息。缺少家族信息有一个主要含意：如果要等待一个线程终止，就必须将线程的 tid 传递给 pthread_join()。线程库无法为您断定 tid。</p>
<p>对大多数开发者来说这不是个好消息，因为这会使有多个线程的程序复杂化。不过不要为此担忧。POSIX 线程标准提供了有效地管理多个线程所需要的所有工具。实际上，没有父/子关系这一事实却为在程序中使用线程开辟了更创造性的方法。例如，如果有一个线程称为线程 1，线程 1 创建了称为线程 2 的线程，则线程 1 自己没有必要调用 pthread_join() 来合并线程 2，程序中其它任一线程都可以做到。当编写大量使用线程的代码时，这就可能允许发生有趣的事情。例如，可以创建一个包含所有已停止线程的全局&#8220;死线程列表&#8221;，然后让一个专门的清理线程专等停止的线程加到列表中。这个清理线程调用 pthread_join() 将刚停止的线程与自己合并。现在，仅用一个线程就巧妙和有效地处理了全部清理。<br><br></p>
<p><a name=N100C7><span class=atitle>同步漫游</span></a></p>
<p>现在我们来看一些代码，这些代码做了一些意想不到的事情。thread2.c 的代码如下：</p>
<p><br><a name=N100D0><strong>thread2.c</strong></a><br>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include &lt;pthread.h&gt;
            #include &lt;stdlib.h&gt;
            #include &lt;unistd.h&gt;
            #include &lt;stdio.h&gt;
            int myglobal;
            void *thread_function(void *arg) {
            int i,j;
            for ( i=0; i&lt;20; i++) {
            j=myglobal;
            j=j+1;
            printf(".");
            fflush(stdout);
            sleep(1);
            myglobal=j;
            }
            return NULL;
            }
            int main(void) {
            pthread_t mythread;
            int i;
            if ( pthread_create( &amp;mythread, NULL, thread_function, NULL) ) {
            printf("error creating thread.");
            abort();
            }
            for ( i=0; i&lt;20; i++) {
            myglobal=myglobal+1;
            printf("o");
            fflush(stdout);
            sleep(1);
            }
            if ( pthread_join ( mythread, NULL ) ) {
            printf("error joining thread.");
            abort();
            }
            printf("\nmyglobal equals %d\n",myglobal);
            exit(0);
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p><a name=N100DA><span class=atitle>理解 thread2.c</span></a></p>
<p>如同第一个程序，这个程序创建一个新线程。主线程和新线程都将全局变量 myglobal 加一 20 次。但是程序本身产生了某些意想不到的结果。编译代码请输入：</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>$ gcc thread2.c -o thread2 -lpthread
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>运行请输入：</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>$ ./thread2
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>输出：</p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>$ ./thread2
            ..o.o.o.o.oo.o.o.o.o.o.o.o.o.o..o.o.o.o.o
            myglobal equals 21
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br></p>
<p>非常意外吧！因为 myglobal 从零开始，主线程和新线程各自对其进行了 20 次加一, 程序结束时 myglobal 值应当等于 40。由于 myglobal 输出结果为 21，这其中肯定有问题。但是究竟是什么呢？</p>
<p>放弃吗？好，让我来解释是怎么一回事。首先查看函数 thread_function()。注意如何将 myglobal 复制到局部变量 "j" 了吗? 接着将 j 加一, 再睡眠一秒，然后到这时才将新的 j 值复制到 myglobal？这就是关键所在。设想一下，如果主线程就在新线程将 myglobal 值复制给 j <strong>后</strong> 立即将 myglobal 加一，会发生什么？当 thread_function() 将 j 的值写回 myglobal 时，就覆盖了主线程所做的修改。 </p>
<p>当编写线程程序时，应避免产生这种无用的副作用，否则只会浪费时间（当然，除了编写关于 POSIX 线程的文章时有用）。那么，如何才能排除这种问题呢？</p>
<p>由于是将 myglobal 复制给 j 并且等了一秒之后才写回时产生问题，可以尝试避免使用临时局部变量并直接将 myglobal 加一。虽然这种解决方案对这个特定例子适用，但它还是不正确。如果我们对 myglobal 进行相对复杂的数学运算，而不是简单的加一，这种方法就会失效。但是为什么呢？</p>
<p>要理解这个问题，必须记住线程是并发运行的。即使在单处理器系统上运行（内核利用时间分片模拟多任务）也是可以的，从程序员的角度，想像两个线程是同时执行的。thread2.c 出现问题是因为 thread_function() 依赖以下论据：在 myglobal 加一之前的大约一秒钟期间不会修改 myglobal。需要有些途径让一个线程在对 myglobal 做更改时通知其它线程&#8220;不要靠近&#8221;。我将在下一篇文章中讲解如何做到这一点。到时候见。</p>
<p><br></p>
<p><a name=resources><span class=atitle>参考资料 </span></a></p>
<ul>
    <li>参阅 <a href="http://metalab.unc.edu/pub/Linux/docs/faqs/Threads-FAQ/html/"><u><font color=#5c81a7>Linux threads</font></u></a>中的文档，Sean Walton, KB7rfa <br><br>
    <li>在 <a href="http://hwaci.com/sw/pttcl/pttcl.html"><u><font color=#5c81a7>An Introduction to Pthreads-Tcl</font></u></a> 中，查看对 Tcl 的更改以使其能够使用 POSIX 线程 <br><br>
    <li>使用友好的 Linux pthread 在线帮助 ("man -k pthread")<br><br>
    <li>参考 <a href="http://members.aa.net/~mtp/PCthreads.html"><u><font color=#5c81a7>POSIX and DCE threads for Linux</font></u></a>主页 <br><br>
    <li>查看 <a href="http://pauillac.inria.fr/~xleroy/linuxthreads/"><u><font color=#5c81a7>The LinuxThreads Library</font></u></a> <br><br>
    <li><a href="http://www.users.itl.net.ua/~prool/proolix.html"><u><font color=#5c81a7>Proolix</font></u></a> ，一种简单遵从 POSIX 标准的操作系统，用于 i8086+，一直在开发中 <br><br>
    <li>阅读 David R. Butenhof 的著作 <a href="http://www.amazon.com/exec/obidos/ASIN/0201633922/o/qid=961544788/sr=8-1/ref=aps_sr_b_1_1/002-2882413-1227240"><u><font color=#5c81a7>Programming with POSIX Threads</font></u></a>，书中讨论了许多问题，其中谈到不使用互斥对象是可能出现的种种情况 <br></li>
</ul>
<p><br><br></p>
<p><a name=author><span class=atitle>关于作者</span></a></p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td colSpan=3><img alt="" src="http://www.ibm.com/i/c.gif" width="100%" height=5></td>
        </tr>
        <tr vAlign=top align=left>
            <td>
            <p>&nbsp;</p>
            </td>
            <td><img alt="" src="http://www.ibm.com/i/c.gif" width=4 height=5></td>
            <td width="100%">
            <p>Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的总裁兼 CEO， Gentoo 项目的总设计师，多本 MacMillan 出版书籍的作者，包括： <em>Caldera OpenLinux Unleashed</em>、 <em>SuSE Linux Unleashed</em>和 <em>Samba Unleashed</em> 。Daniel 自小学二年级起就与计算机结下不解之缘，那时他首先接触的是 Logo 程序语言，并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 和刚出生的女儿 Hadassah 一起共渡时光。可通过 <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;&#63;&#99;&#99;&#61;&#100;&#114;&#111;&#98;&#98;&#105;&#110;&#115;&#64;&#103;&#101;&#110;&#116;&#111;&#111;&#46;&#111;&#114;&#103;"><u><font color=#5c81a7>drobbins@gentoo.org</font></u></a> 与 Daniel Robbins 取得联系。 </p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/109415.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2010-03-11 11:39 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2010/03/11/109415.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下用信号量实现对共享内存的访问保护(二)</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108778.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Wed, 03 Mar 2010 05:49:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108778.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/108778.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108778.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/108778.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/108778.html</trackback:ping><description><![CDATA[前面利用Linux中的系统V共享内存机制和semaphore来实现了一个简单的进程级共享内存，但仅仅是一个简单实现，还有很多细节问题没有考虑，比如：<br>1：很多资料中讲到，系统V共享内存是随内核持续的，即使所有访问共享内存的进程都已经正常终止，共享内存仍然存在，在内核引导之前，对该共享内存区域的任何改写操作都将一直保留，除非显式删除共享内存。的确是这样的，系统V机制分配的共享内存将一直保留，若要删除要么重启系统，要么就调用shmctl来显示删除。那么，shmctl怎么使用呢？这在下面将会讲到。<br><br>2：对于两个不同的进程，如何在其中一个进程满足某种条件时唤醒另一个进程呢？下面也会讲到。<br><br>.....................................(待续)
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/108778.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2010-03-03 13:49 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108778.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下用信号量实现对共享内存的访问保护(一)</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108768.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Wed, 03 Mar 2010 03:42:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108768.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/108768.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108768.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/108768.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/108768.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 最近一直在研究多进程间通过共享内存来实现通信的事情，以便高效率地实现对同一数据的访问。本文中对共享内存的实现采用了系统V的机制，我们的重点在于通过信号量来完成对不同进程间共享内存资源的一致性访问，共享内存的具体方法请参见相关资料，这里不再赘述。首先我们先实现最简单的共享内存，一个进程对其更新，另一个进程从中读出数据。同时，通过信号量的PV操作来达到对共享内存资源的保护。思路如下：1.server端...&nbsp;&nbsp;<a href='http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108768.html'>阅读全文</a><img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/108768.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2010-03-03 11:42 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108768.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>什么是集群？哪种群集适合您？</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2009/12/18/103479.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Fri, 18 Dec 2009 09:04:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2009/12/18/103479.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/103479.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2009/12/18/103479.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/103479.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/103479.html</trackback:ping><description><![CDATA[<p>简单的说，集群（cluster）就是一组计算机，它们作为一个整体向用户提供一组网络资源。这些单个的计算机系统就是集群的节点（node）。一个理想的集群是，用户从来不会意识到集群系统底层的节点，在他/她们看来，集群是一个系统，而非多个计算机系统。并且集群系统的管理员可以随意增加和删改集群系统的节点。 </p>
<p>集群并不是一个全新的概念，其实早在七十年代计算机厂商和研究机构就开始了对集群系统的研究和开发。由于主要用于科学工程计算，所以这些系统并不为大家所熟知。直到Linux集群的出现，集群的概念才得以广为传播。集群系统主要分为高可用(High Availability)集群,简称 HA 集群，和高性能计算(High Perfermance Computing)集群，简称 HPC 集群。</p>
<p>通过下面这篇文章我们可以方方面面了解 Linux 集群涉及的硬件和软件。<br><br><br>哪种群集适合您？<br></p>
<br>
<p>2000 年 5 月 01 日</p>
<blockquote>Rawn Shah 作为专家，在 Linux 现有的开放源码和封闭源码集群解决方案方面为您指点迷津。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p>计算 Linux 中集群项目的数量就象计算硅谷中创业公司的数量一样。不象 Windows NT 已经受其自身的封闭环境阻碍，Linux 有大量的集群系统可供选择，适合于不同的用途和需要。但确定应该使用哪一个集群的工作却没有因此变得简单。</p>
<p>问题的部分原因在于术语集群用于不同场合。IT 经理可能关心如何使服务器运行时间更长，或使应用程序运行得更快，而数学家可能更关心在服务器上进行大规模数值计算。两者都需要群集，但是各自需要不同特性的群集。</p>
<p>本文调查了不同形式的集群以及许多实现中的一部分，这些实现可以买到，也可以免费软件形式获得。尽管列出的所有解决方案并不都是开放源码，但是大多数软件都遵循分发 Linux 源码的公共惯例，特别是由于那些实现集群的人还常常希望调整系统性能，以满足需要。</p>
<p><a name=1><span class=atitle>硬件</span></a></p>
<p>集群总是涉及到机器之间的硬件连接。在现今大多数情况下，这只是指&#8220;快速以太网&#8221;网卡和集线器。但在尖端科学领域中，有许多专为集群设计的网络接口卡。</p>
<p>它们包括 Myricom 的 Myrinet、Giganet 的 cLAN 和 IEEE 1596 标准可伸缩一致接口 (SCI)。那些卡的功能不但在群集的节点之间提供高带宽，而且还减少延迟（发送消息所用的时间）。对于在节点间交换状态信息以使其操作保持同步情况，那些延迟是至关重要的。</p>
<br>
<p><a name=2><span class=atitle>Myricom</span></a></p>
<p>Myricom 提供网卡和交换机，其单向互连速度最高可达到 1.28 Gbps。网卡有两种形式，铜线型和光纤型。铜线型 LAN 可以在 10 英尺距离内以全速进行通信，而在长达 60 英尺距离内以半速进行操作。光纤型 Myrinet 可以在 6.25 英里长的单模光纤或者 340 英尺长的多模光纤上全速运行。Myrinet 只提供直接点到点、基于集线器或基于交换机的网络配置，但在可以连接到一起的交换光纤数量方面没有限制。添加交换光纤只会增加节点间的延迟。两个直接连接的节点之间的平均延迟是 5 到 18 微秒，比以太网快得多。</p>
<br>
<p><a name=3><span class=atitle>集群类型</span></a></p>
<p>最常见的三种群集类型包括高性能科学群集、负载均衡群集和高可用性群集。</p>
<br>
<p><a name=4><span class=atitle>科学群集</span></a></p>
<p>通常，第一种涉及为群集开发并行编程应用程序，以解决复杂的科学问题。这是并行计算的基础，尽管它不使用专门的并行超级计算机，这种超级计算机内部由十至上万个独立处理器组成。但它却使用商业系统，如通过高速连接来链接的一组单处理器或双处理器 PC，并且在公共消息传递层上进行通信以运行并行应用程序。因此，您会常常听说又有一种便宜的 Linux 超级计算机问世了。但它实际是一个计算机群集，其处理能力与真的超级计算机相等，通常一套象样的群集配置开销要超过 $100,000。这对一般人来说似乎是太贵了，但与价值上百万美元的专用超级计算机相比还算是便宜的。</p>
<br>
<p><a name=5><span class=atitle>负载均衡群集</span></a></p>
<p>负载均衡群集为企业需求提供了更实用的系统。如名称所暗示的，该系统使负载可以在计算机群集中尽可能平均地分摊处理。该负载可能是需要均衡的应用程序处理负载或网络流量负载。这样的系统非常适合于运行同一组应用程序的大量用户。每个节点都可以处理一部分负载，并且可以在节点之间动态分配负载，以实现平衡。对于网络流量也是如此。通常，网络服务器应用程序接受了太多入网流量，以致无法迅速处理，这就需要将流量发送给在其它节点上运行的网络服务器应用。还可以根据每个节点上不同的可用资源或网络的特殊环境来进行优化。</p>
<br>
<p><a name=6><span class=atitle>高可用性群集</span></a></p>
<p>高可用性群集的出现是为了使群集的整体服务尽可能可用，以便考虑计算硬件和软件的易错性。如果高可用性群集中的主节点发生了故障，那么这段时间内将由次节点代替它。次节点通常是主节点的镜像，所以当它代替主节点时，它可以完全接管其身份，并且因此使系统环境对于用户是一致的。</p>
<p>在群集的这三种基本类型之间，经常会发生混合与交杂。于是，可以发现高可用性群集也可以在其节点之间均衡用户负载，同时仍试图维持高可用性程度。同样，可以从要编入应用程序的群集中找到一个并行群集，它可以在节点之间执行负载均衡。尽管集群系统本身独立于它在使用的软件或硬件，但要有效运行系统时，硬件连接将起关键作用。</p>
<br><br>
<p><a name=7><span class=atitle>Giganet</span></a></p>
<p>Giganet 是用于 Linux 平台的虚拟接口 (VI) 体系结构卡的第一家供应商，提供 cLAN 卡和交换机。VI 体系结构是独立于平台的软件和硬件系统，它由 Intel 开发，用于创建群集。它使用自己的网络通信协议在服务器之间直接交换数据，而不是使用 IP，并且它并不打算成为 WAN 可路由的系统。现在，VI 的未来取决于正在进行的&#8220;系统 I/O 组&#8221;的工作，这个小组本是 Intel 领导的&#8220;下一代 I/O&#8221;小组与 IBM 和 Compaq 领导的&#8220;未来 I/O 小组&#8221;的合并。Giganet 产品当前可以在节点之间提供 1 Gbps 单向通信，最小延迟为 7 微秒。</p>
<br>
<p><a name=8><span class=atitle>IEEE SCI</span></a></p>
<p>IEEE 标准 SCI 的延迟更少（低于 2.5 微秒），并且其单向速度可达到 400 MB／秒 (3.2 Gbps)。SCI 是基于环拓扑的网络系统，不像以太网是星形拓扑。这将使在较大规模的节点之间通信速度更快。更有用的是环面拓扑网络，它在节点之间有许多环形结构。两维环面可以用 n 乘 m 的网格表示，其中在每一行和每一列都有一个环形网络。三维环面也类似，可以用三维立体节点网格表示，每一层上有一个环形网络。密集超级计算并行系统使用环面拓扑网络，为成百上千个节点之间的通信提供相对最快的路径。</p>
<p>大多数操作系统的限制因素不是操作系统或网络接口，而是服务器的内部 PCI 总线系统。几乎所有台式 PC 通常有基本 32-位，33-MHz PCI，并且大多数低端服务器只提供 133 MB／秒 (1 Gbps)，这限制了那些网卡的能力。一些昂贵的高端服务器，如 Compaq Proliant 6500 和 IBM Netfinity 7000 系列，都有 64-位， 66-MHz 网卡，它们能够以四倍速度运行。不幸地是，矛盾是更多公司使用低端的系统，因此大多数供应商最终生产和销售更多低端 PCI 网卡。也有专门的 64-位，66-MHz PCI 网卡，但价格要贵许多。例如，Intel 提供了这种类型的&#8220;快速以太网&#8221;网卡，价格约 $400 到 $500，几乎是普通 PCI 版本价格的 5 倍。</p>
<br><br>
<p><a name=9><span class=atitle>科学群集</span></a></p>
<p>某些并行群集系统可以达到如此高的带宽和低延迟，其原因是它们通常绕过使用网络协议，如 TCP/IP。虽然网际协议对于广域网很重要，但它包含了太多的开销，而这些开销在节点相互已知的封闭网络群集中是不必要的。其实，那些系统中有一部分可以在节点之间使用直接内存访问 (DMA)，它类似于图形卡和其它外围设备在一台机器中的工作方式。因此横跨群集，可以通过任何节点上的任何处理器直接访问一种形式的分布式共享内存。它们也可以使用低开销的消息传递系统，在节点之间进行通信。</p>
<p>消息传递接口 (MPI) 是并行群集系统间消息传递层的最常见实现。MPI 存在几种衍生版本，但在所有情况下，它为开发者访问并行应用程序提供了一个公共 API，这样开发者就不必手工解决如何在群集的节点之间分发代码段。其中一个，Beowulf 系统首先将 MPI 用作公共编程接口。</p>
<p>很难决定使用哪种高性能集群包。许多都提供类似服务，但计算的具体要求才是决定性因素。很多情况下，在那些系统中的研究工作只是解决需求的一半，而且使用那些软件需要集群包开发者的特殊帮助和合作。</p>
<br>
<p><a name=10><span class=atitle>Beowulf</span></a></p>
<p>当谈到 Linux 集群时，许多人的第一反映是 Beowulf。那是最著名的 Linux 科学软件集群系统。没有一个包叫做 Beowulf。实际上，它是一个术语，适用于在 Linux 内核上运行的一组公共软件工具。其中包括流行的软件消息传递 API，如&#8220;消息传送接口&#8221;(MPI) 或&#8220;并行虚拟机&#8221;(PVM)，对 Linux 内核的修改，以允许结合几个以太网接口、高性能网络驱动器，对虚拟内存管理器的更改，以及分布式进程间通信 (DIPC) 服务。公共全局进程标识空间允许使用 DIPC 机制从任何节点访问任何进程。Beowulf 还在节点间支持一系列硬件连通性选件。</p>
<p>Beowulf 可能是考虑 Linux 时注意到的第一个高性能集群系统，这只是因为它的广泛使用和支持。关于这个主题，有许多文档和书籍。Beowulf 与以下一些科学集群系统之间的差异可以是实际的，或者只是在产品名称中有差异。例如，尽管名称不同，Alta Technologies 的 AltaCluster 就是一个 Beowulf 系统。某些供应商，如 ParTec AG，一家德国公司，提供了 Beowulf 模型的衍生版本，以包括其它管理接口和通信协议。</p>
<br>
<p><a name=11><span class=atitle>Giganet cLAN</span></a></p>
<p>Giganet 提供了一种定制的基于硬件的解决方案，它使用非 IP 协议在一个科学群集的节点间进行通信。如前所述，&#8220;虚拟接口&#8221;协议通过除去不少协议的开销，如 IP，以支持服务器间更快的通信。另外，硬件系统可按千兆比特速度运行，并且延迟很短，使它非常适合构建最多达 256 个节点的科学群集。该供应商支持 MPI，这样许多并行应用程序就可以在类似的系统（如 Beowulf）上运行。</p>
<p>它也有 Beowulf 的缺点，即不能用作网络负载共享系统，除非想要编写应用程序来监控和分发在服务器间传送的网络包。<br><br></p>
<p><a name=12><span class=atitle>Legion</span></a></p>
<p>Legion 试图构建一个真正的多计算机系统。这是一个群集，其中每个节点都是一个独立系统，但在用户看来，整个系统只是一台计算机。Legion 设计成支持一台世界范围的计算机，由上百万个主机以及数以万亿计的软件对象组成。在 Legion 中，用户可以创立他们自己的合作小组。</p>
<p>Legion 提供了高性能并行、负载均衡、分布式数据管理和容错性。</p>
<p>Legion 提供了高性能并行、负载均衡、分布式数据管理和容错性。它通过其容错管理和成员节点间的动态重新配置来支持高可用性。它还有一个可扩充核心，该核心可以在出现新的改进和进展时动态替换或升级。系统并不是只接受单一控制，而是可以由任意数量的组织管理，而每个组织都支持整体的自治部分。Legion API 通过其内置的并行性提供了高性能计算。</p>
<p>Legion 需要使用特别编写的软件，以使它可以使用其 API 库。它位于用户计算机操作系统之上，协调本地资源和分布式资源。它自动处理资源调度和安全性，还管理上下文空间以描述和访问整个系统中上亿种可能之外的对象。然而，在每个节点上运行时，不需要使用系统管理员特权，并且可以使用无特权的用户帐号进行工作。这将增加加入 Legion 的节点和用户的灵活性。</p>
<p><br><br></p>
<p><a name=13><span class=atitle>Cplant</span></a></p>
<p>Sandia National Lab 中的 Computational Plant 是一个大规模整体并行群集，用于实现 TeraFLOP（万亿次浮点运算）计算并构建在商业组件上。整个系统由&#8220;可伸缩单元&#8221;组成，这些&#8220;可伸缩单元&#8221;可以划分成适合不同目的（计算、磁盘 I/O、网络 I/O、服务管理）。群集中的每个节点都是一个 Linux 系统，带有专门开发的、提供分区服务的内核级模块。每个分区的功能可以通过装入和卸载内核级模块来修改。</p>
<p>项目分三个阶段完成，开始阶段是原型，有 128 个基于 433-MHz DEC Alpha 21164 的系统，其中每个都有 192 MB RAM 和 2 GB 驱动器，相互之间用 Myrinet 网卡和 8-端口的 SAN 交换机连接。第 1 阶段将它扩充为 400 个基于 21164 的工作站，这些工作站的运行速度为 500 MHz，有 192 MB RAM，没有存储器，用 16-端口的 SAN 交换机以超立方体结构连接起来，并且运行 Red Hat 5.1。当前的第 2 阶段有 592 台基于 DEC 21264 的机器，它们的运行速度为 500 MHz，有 256 MB RAM，没有驱动器。每个节点都使用 64-位，33-MHz PCI Myrinet 网卡，并且仍使用 16-端口交换机以超立方体结构连接。</p>
<p>在 Cplant 上运行的应用程序包括解决稀疏线性系统、流体力学和结构力学中计算系统的优化、分子力学的模拟、线性结构力学的有限元分析，以及并行应用程序的动态负载均衡库。</p>
<p><br></p>
<p><a name=14><span class=atitle>JESSICA 2</span></a></p>
<p>香港大学的系统研究小组有一个基于 Java 的群集，叫做支持 Java 的单系统映像计算体系结构 (JESSICA)，它作为一个中间件层以完成单系统映像的幻想。该层是每个使用分布式共享内存 (DSM) 系统进行通信的节点上运行的所有线程的一个全局线程空间。该项目使用 ThreadMark DSM，但最终将用他们自己创建的 JiaJia Using Migrating-home Protocol (JUMP)。他们使用定制的基于 Java 的 ClusterProbe 软件来管理群集的 50 个节点。</p>
<p><br><br></p>
<p><a name=15><span class=atitle>PARIS</span></a></p>
<p>法国的 IRISA 研究所的&#8220;大规模数字模拟应用程序的编程并行和分布式系统&#8221;(PARIS) 项目提供了几种用于创建 Linux 服务器群集的工具。该项目由三部分组成：群集的资源管理软件、并行编程语言的运行时环境，以及分布式数字模拟的软件工具。</p>
<p>资源管理软件包括用于共享内存、磁盘和处理器资源的 Globelins 分布式系统，及其 Dupleix 和 Mome 分布式共享内存系统。</p>
<p><br><br></p>
<p><a name=16><span class=atitle>负载均衡群集</span></a></p>
<p>负载均衡群集在多节点之间分发网络或计算处理负载。在这种情况下，区别在于缺少跨节点运行的单并行程序。大多数情况下，那种群集中的每个节点都是运行单独软件的独立系统。但是，不管是在节点之间进行直接通信，还是通过中央负载均衡服务器来控制每个节点的负载，在节点之间都有一种公共关系。通常，使用特定的算法来分发该负载。</p>
<p>网络流量负载均衡是一个过程，它检查到某个群集的入网流量，然后将流量分发到各个节点以进行适当处理。它最适合大型网络应用程序，如 Web 或 FTP 服务器。负载均衡网络应用服务要求群集软件检查每个节点的当前负载，并确定哪些节点可以接受新的作业。这最适合运行如数据分析等串行和批处理作业。那些系统还可以配置成关注某特定节点的硬件或操作系统功能：这样，群集中的节点就没有必要是一致的。</p>
<p><br><br></p>
<p><a name=17><span class=atitle>Linux 虚拟服务器</span></a></p>
<p>&#8220;Linux 虚拟服务器&#8221;项目已经实现了许多内核补丁，它们为入网 TCP/IP 流量创建了负载均衡系统。LVS 软件检查入网流量，然后根据负载均衡算法，将流量重定向到一组充当群集的服务器。这允许网络应用程序，如 Web 服务器，在节点群集上运行以支持大量用户。</p>
<p>LVS 支持作为负载均衡服务器直接连接到同一个 LAN 的群集节点，但它还能够以通道传送 IP 包的方式连接到远程服务器。后一种方法包括压缩 IP 包中的均衡请求，这些 IP 信息包从负载均衡服务器直接发送到远程群集节点。尽管 LVS 可以远程支持网站的负载均衡，但它使用的负载均衡算法现在对于虚拟群集中的广域 Web 服务器仍无效。因此，如果 Web 服务器都在同一个 LAN 中，LVS 最好当作负载均衡服务器使用。</p>
<p>负载均衡系统的几种硬件实现比在通用操作系统，如 Linux，上运行得更快。它们包括来自 Alteon 和 Foundry 的硬件，其硬件逻辑和最少操作系统可以在硬件中执行流量管理，并且速度比纯软件快。它们的价格也很高，通常都在 $10,000 以上。如果需要简单和便宜的解决方案，一个有很多内存 (256 MB) 的中等 Linux 系统将会是一个好的负载均衡系统。<br><br></p>
<p><a name=18><span class=atitle>TurboLinux TurboCluster 和 enFuzion</span></a></p>
<p>TurboLinux 有一个产品叫 TurboCluster，它最初以&#8220;Linux 虚拟服务器&#8221;项目开发的内核补丁为基础。因此，它可以得到大部分优点，但它的缺点也与原来的项目一样。TurboLinux 为此还开发了一些工具，用于监控增加产品实用性的群集行为。一家主要供应商的商业支持也使它对于大型网站更具吸引力。</p>
<p>EnFuzion 支持在节点之间实现自动负载均衡和资源共享，而且可以自动重新安排失败的作业。</p>
<p>EnFuzion 是 TurboLinux 即将推出的科学群集产品，它并不基于 Beowulf。但是，它可以支持上百个节点以及许多不同的非 Linux 平台，包括 Solaris、Windows NT、HP-UX、IBM AIX、SGI Irix 和 Tru64。EnFuzion 非常有趣，因为它运行所有现有软件，并且不需要为环境编写定制的并行应用程序。它支持在节点间实现自动负载均衡和资源共享，而且可以自动重新安排失败的作业。</p>
<p><br></p>
<p><a name=19><span class=atitle>Platform Computing 的 LSF 批处理</span></a></p>
<p>Platform Computing 是群集计算领域的老手，现在提供了 Linux 平台上的&#8220;负载均衡设施 (LSF) 批处理&#8221;软件。LSF 批处理允许中央控制器安排作业在群集中任意数量的节点上运行。在概念上，它类似于 TurboLinux enFuzion 软件，并且支持在节点上运行任何类型的应用程序。</p>
<p>这种方法对于群集大小是非常灵活的，因为可以明确选择节点的数量，甚至是运行应用程序的节点。于是，可以将 64 个节点的群集分成更小的逻辑群集，每个逻辑群集都运行自己的批处理应用程序。而且，如果应用程序或节点失败，它可以在其它服务器上重新安排作业。</p>
<p>Platform 的产品在主要 Unix 系统和 Windows NT 上运行。目前，只有它们的 LSF 批处理产品已经移植到 Linux 上。最终，LSF Suite 组件的其余部分也将紧随其后移植到 Linux 上。<br><br></p>
<p><a name=20><span class=atitle>Resonate Dispatch 系列</span></a></p>
<p>Resonate 有一种基于软件的负载均衡方法，类似于 Linux 虚拟服务器。但是，它支持更多特性，以及一些更好的负载均衡算法。例如，使用 Resonate，可以在每个群集节点装入一个代理，以确定该节点当前的系统负载。然后，负载均衡服务器检查每个节点的代理，以确定哪个节点的负载最少，并且将新的流量发送给它。另外，Resonate 还可以使用它的 Global Dispatch 产品更有效地支持地区性分布式服务器。</p>
<p>Resonate 已经在 Red Hat Linux 上彻底测试了该软件，相信它也可以在其它发行版上运行。Resonate 的软件还可以在其它各种平台上运行，包括 Solaris、AIX、Windows NT，并且它还可以在混合环境中进行负载均衡。</p>
<p><br><br></p>
<p><a name=21><span class=atitle>MOSIX</span></a></p>
<p>MOSIX 使用 Linux 内核新版本来实现进程负载均衡集群系统。该群集中，任何服务器或工作站可以按指定加入或离开，即添加到群集的总处理能力，或从中除去。根据其文档，MOSIX 使用自适应进程负载均衡和内存引导算法使整体性能最大化。应用程序进程可以在节点之间抢先迁移，以利用最好的资源，这类似于对称多处理器系统可以在各个处理器之间切换应用程序。</p>
<p>MOSIX 在应用层是完全透明的，并且不需要重新编译或者重新链接到新的库，因为所有一切都发生在内核级上。可以有几种方法将它配置成多用户共享环境群集。所有服务器可以共享一个池，系统可以是群集的一部分，或者群集可以动态地分成几个子群集，每种方法都有不同的用途。Linux 工作站还可以是群集的一部分，可以是固定的，也可以是临时的，或者只是作为批处理作业提交者。作为临时群集节点，工作站可以在其空闲时用于增加群集处理能力。也允许只以批处理方式使用群集，在这种方式中，群集被配置成通过队列接受批处理作业。然后，守护程序取走作业并将它们发送到群集节点进行处理。</p>
<p>MOSIX 的不利之处是它更改 Linux 内核行为的一些核心部分，于是系统级应用程序将不会按期望运行。</p>
<p>除了高性能科学计算，MOSIX 提供了一个有趣的选项，用于以共同设置创建集群环境。通过使用服务器和工作站上的闲置资源，它可以更快更有效地创建和运行应用程序。由于访问了多台服务器，并且可以动态调整群集大小和更改负载均衡规则，它还可以提供高度的服务器可用性。MOSIX 的不利之处是它更改 Linux 内核行为的一些核心部分，于是系统级应用程序将不会按期望运行。要使用网络应用程序时，而该程序使用基于单个服务器地址的套接字连接，MOSIX 通常也会受到限制。这意味着网络应用程序在一个服务器节点上开始运行时，如果 IP 地址与套接字绑定，那么它必须继续在该节点上运行。显然，MOSIX 还正在开始迁移套接字，因此这很快就变成了争论的焦点。</p>
<p><br></p>
<p><a name=22><span class=atitle>高可用性群集</span></a></p>
<p>高可用性 (HA) 群集致力于使服务器系统的运行速度和响应速度尽可能快。它们经常使用在多台机器上运行的冗余节点和服务，用来相互跟踪。如果某个节点失败，它的替补将在几秒钟或更短时间内接管它的职责。因此，对于用户而言，群集永远不会停机。</p>
<p>某些 HA 群集也可以维护节点间冗余应用程序。因此，用户的应用程序将继续运行，即使他或她使用的节点出了故障。正在运行的应用程序会在几秒之内迁移到另一个节点，而所有用户只会察觉到响应稍微慢了一点。但是，这种应用程序级冗余要求将软件设计成具有群集意识的，并且知道节点失败时应该做什么。但对于 Linux，大多数现在还做不到。因为 Linux 系统没有 HA 集群标准，并且也没有公共 API 可供应用程序开发者构建有群集意识的软件。</p>
<p>HA 群集可以执行负载均衡，但通常主服务器运行作业，而系统使辅助服务器保持闲置。辅助服务器通常是主服务器操作系统设置的镜像，尽管硬件本身稍有不同。辅助节点对主服务器进行活动监控或心跳观察，以查看它是否仍在运行。如果心跳计时器没有接收到主服务器的响应，则辅助节点将接管网络和系统身份（如果是 Linux 系统，则是 IP 主机名和地址）。</p>
<p>但是，Linux 在这一领域仍有一点忽略。好消息是有一家著名的供应商正在努力尽快研制高可用性群集，因为它是企业级服务器都必需的功能。</p>
<p><br><br></p>
<p><a name=23><span class=atitle>Linux-HA 项目</span></a></p>
<p>高可用性 Linux 项目，根据其目标声明，旨在为 Linux 提供高可用性解决方案，以通过社区开发成果提高可靠性、可用性和服务能力。Linux 达到高可用性集群时，这是一种试图给予 Linux 与先进的 Unix 系统，如 Solaris、AIX 和 HP/UX，一样具有竞争力的特性。因此，项目的目标是在 2001 年之前达到 Unix 集群比较报告 ( <a href="" cmImpressionSent=" href_cetemp=" ?><u><font color=#5c81a7>http://www.sun.com/clusters/dh.brown.pdf</font></u></a>) 中分析专家组 D. H. Brown 特定功能性级别。 </p>
<p>项目中有可以维护节点间心跳并接管失败节点的 IP 地址的软件。如果一个节点失败，它使用&#8220;伪造冗余 IP&#8221;软件包将失败节点的地址添加到工作节点以承担它的职责。于是，可以在几毫秒时间内自动替换失败的节点。实际使用中，心跳通常在几秒范围内，除非在节点之间有专用网络链接。因此，失败系统中的用户应用程序仍需要在新的节点上重新启动。</p>
<p><a name=24><span class=atitle>无处不在的集群</span></a></p>
<p>对于 Linux，有许多集群系统可供选用。同时，那些项目中有几个是非商业性的，甚至是实验性质的。虽然对学术界和某些组织这也没有形成问题，但大公司通常首选著名供应商的商业支持平台。供应商，如 IBM、SGI、HP 和 Sun，提供了用于在 Linux 中构建科学群集的产品和服务，因为群集很流行，并且可以销售大量的服务器设备。一旦商业机构认为其它形式的集群是可靠的，那些相同的服务器供应商或许会围绕着开放源码集群解决方案创建自己的产品。</p>
<p>Linux 作为服务器平台的重要性依赖于支持大型服务器和服务器群集的能力。这就使它可以与 Sun、HP、IBM 和其它公司的 UNIX 服务器在更高层面上竞争。虽然 Windows NT 和 2000 不支持 Linux 能够支持的集群范围，但是 HA 集群正规方法的可用性以及用于构建有群集意识的 API 也使它能够参与竞争。</p>
<p>如果正在考虑构建一个群集，那么您应当仔细检查那些可能性，并将它们与您的需求做比较。您也许会发现想要实现的目标还不能成为一个完整的解决方案，或许会发现已经有了现成的解决方案。不管是哪种情况，请相信许多现有公司将他们的应用程序托付给进行深度计算并提供大量网页的 Linux 系统群集。集群是一种企业系统服务，已经在 Linux 下成功测试过。尽管新的集群将出现，但选择的多样性正是 Linux 超过其它系统，如 Windows NT，的优势。</p>
<p><a name=author><span class=atitle>关于作者</span></a></p>
<p>
<table border=0 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td colSpan=3><img alt="" src="http://www.ibm.com/i/c.gif" width="100%" height=5></td>
        </tr>
        <tr vAlign=top align=left>
            <td>
            <p>&nbsp;</p>
            </td>
            <td><img alt="" src="http://www.ibm.com/i/c.gif" width=4 height=5></td>
            <td width="100%">
            <p>Rawn Shah 是居住在亚利桑那州图森市的一位独立顾问。他多年来与多平台问题打交道并撰写相关文章，但常常令他不解的是很少有人知道有用的系统工具。</p>
            </td>
        </tr>
    </tbody>
</table>
<br><br></p>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/103479.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2009-12-18 17:04 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/12/18/103479.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下socket编程中的若干问题(持续更新中)</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2009/08/20/93914.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Thu, 20 Aug 2009 07:52:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2009/08/20/93914.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/93914.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2009/08/20/93914.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/93914.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/93914.html</trackback:ping><description><![CDATA[<br>(1) 最近在linux下开发了一个通信服务程序，主要负责与客户端建立连接，转发客户端的消息给后台信息处理模块，同时也将后台的处理结果转发给客户端。<br>由于在windows下已经有了一个相同功能的程序，便做了移植。移植到linux下功能是可以实现的，但发现此程序的cpu利用率非常高。经分析发现是linux下的<br>select调用与windows的select调用的一个区别造成的。<br><br>程序处理流程如下：<br><br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">&nbsp;1</span><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">bool</span><span style="COLOR: #000000">&nbsp;msg_recv_thread(</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">)<br></span><span style="COLOR: #008080">&nbsp;2</span><span style="COLOR: #000000"><img id=Codehighlighter1_27_574_Open_Image onclick="this.style.display='none'; Codehighlighter1_27_574_Open_Text.style.display='none'; Codehighlighter1_27_574_Closed_Image.style.display='inline'; Codehighlighter1_27_574_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_27_574_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_27_574_Closed_Text.style.display='none'; Codehighlighter1_27_574_Open_Image.style.display='inline'; Codehighlighter1_27_574_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_27_574_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"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_27_574_Open_Text><span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">&nbsp;3</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;max&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #008080">&nbsp;4</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;fd_set&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;readfds;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;结果集</span><span style="COLOR: #008000"><br></span><span style="COLOR: #008080">&nbsp;5</span><span style="COLOR: #008000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">struct</span><span style="COLOR: #000000">&nbsp;timeval&nbsp;&nbsp;RevTimeOut;<br></span><span style="COLOR: #008080">&nbsp;6</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;RevTimeOut.tv_sec&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;设定select的超时时间为1s</span><span style="COLOR: #008000"><br></span><span style="COLOR: #008080">&nbsp;7</span><span style="COLOR: #008000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;RevtimeOut.tv_usec&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #008080">&nbsp;8</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top><br></span><span style="COLOR: #008080">&nbsp;9</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">)<br></span><span style="COLOR: #008080">10</span><span style="COLOR: #000000"><img id=Codehighlighter1_228_573_Open_Image onclick="this.style.display='none'; Codehighlighter1_228_573_Open_Text.style.display='none'; Codehighlighter1_228_573_Closed_Image.style.display='inline'; Codehighlighter1_228_573_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_228_573_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_228_573_Closed_Text.style.display='none'; Codehighlighter1_228_573_Open_Image.style.display='inline'; Codehighlighter1_228_573_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_228_573_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"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_228_573_Open_Text><span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">11</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FD_SET(conn_socket,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">readfds);<br></span><span style="COLOR: #008080">12</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;max&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;(max&nbsp;</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">&nbsp;conn_socket)&nbsp;</span><span style="COLOR: #000000">?</span><span style="COLOR: #000000">&nbsp;max&nbsp;:&nbsp;conn_socket;<br></span><span style="COLOR: #008080">13</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;ret&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;select(max</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">readfds,&nbsp;NULL,&nbsp;NULL,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">RevTimeOut);<br></span><span style="COLOR: #008080">14</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top><br></span><span style="COLOR: #008080">15</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">&nbsp;(ret&nbsp;</span><span style="COLOR: #000000">&lt;=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">)<br></span><span style="COLOR: #008080">16</span><span style="COLOR: #000000"><img id=Codehighlighter1_423_455_Open_Image onclick="this.style.display='none'; Codehighlighter1_423_455_Open_Text.style.display='none'; Codehighlighter1_423_455_Closed_Image.style.display='inline'; Codehighlighter1_423_455_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_423_455_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_423_455_Closed_Text.style.display='none'; Codehighlighter1_423_455_Open_Image.style.display='inline'; Codehighlighter1_423_455_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_423_455_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"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_423_455_Open_Text><span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">17</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">continue</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #008080">18</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">19</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top><br></span><span style="COLOR: #008080">20</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">&nbsp;(FD_ISSET(conn_socket,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">readfds)&nbsp;</span><span style="COLOR: #000000">!=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">)<br></span><span style="COLOR: #008080">21</span><span style="COLOR: #000000"><img id=Codehighlighter1_516_552_Open_Image onclick="this.style.display='none'; Codehighlighter1_516_552_Open_Text.style.display='none'; Codehighlighter1_516_552_Closed_Image.style.display='inline'; Codehighlighter1_516_552_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><img id=Codehighlighter1_516_552_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_516_552_Closed_Text.style.display='none'; Codehighlighter1_516_552_Open_Image.style.display='inline'; Codehighlighter1_516_552_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span id=Codehighlighter1_516_552_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"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_516_552_Open_Text><span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">22</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;接受连接请求处理&#8230;&#8230;</span><span style="COLOR: #008000"><br></span><span style="COLOR: #008080">23</span><span style="COLOR: #008000"><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align=top></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">24</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top><br></span><span style="COLOR: #008080">25</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;其他处理&#8230;&#8230;</span><span style="COLOR: #008000"><br></span><span style="COLOR: #008080">26</span><span style="COLOR: #008000"><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif" align=top></span><span style="COLOR: #000000">}</span></span></div>
<br>windows下这样的流程没有问题，但是在linux下，select调用在设定的超时时间内等待时会不断地更新最后一个参数，将其实时更新为离设定的超时时间的时间差，直到这个值被更新为0，即到达超时时间时select函数返回。在上面的程序段中，第一次循环时select的超时参数值为1s，当第一次循环完毕时，RevTimeOut的值已经被变成了0，这样以后的循环就会是无阻塞的，即如果selec没有收到任何的请求便立刻返回，然后继续循环，这样就形成了死循环，从而耗光了cpu。<br><br>将上述程序段中的5-7行移到第13行以前，问题便解决了。<br><br><br>【总结】<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这里涉及到一个编程习惯的问题，本人经验欠缺，在做windows到linux的移植时一直认为既然windows下正确那么linux一定也是正确的，完全没有考虑到两个OS好之间的系统调用方面的区别，导致开始时就搞错了方向，浪费了不少时间。希望大家不要犯我这样的错误。<br></span>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/93914.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2009-08-20 15:52 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/08/20/93914.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>QQ游戏百万人同时在线服务器架构实现(转载)</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2009/08/15/93427.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Sat, 15 Aug 2009 07:29:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2009/08/15/93427.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/93427.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2009/08/15/93427.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/93427.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/93427.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;<a href='http://www.cppblog.com/zjl-1026-2001/archive/2009/08/15/93427.html'>阅读全文</a><img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/93427.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2009-08-15 15:29 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/08/15/93427.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>堆和栈的区别 (转贴)</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/19/83385.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Tue, 19 May 2009 08:53:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/19/83385.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/83385.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/19/83385.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/83385.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/83385.html</trackback:ping><description><![CDATA[<div class=postbody>
<h2>堆和栈的区别&nbsp;(转贴) </h2>
<p>非本人作也!因非常经典,所以收归旗下,与众人阅之!原作者不祥!</p>
<div class=postbody>堆和栈的区别<br>一、预备知识—程序的内存分配<br>一个由c/C++编译的程序占用的内存分为以下几个部分<br>1、栈区（stack）—&nbsp;由编译器自动分配释放&nbsp;，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。<br>2、堆区（heap）&nbsp;—&nbsp;一般由程序员分配释放，&nbsp;若程序员不释放，程序结束时可能由OS回收&nbsp;。注意它与数据结构中的堆是两回事，分配方式倒是类似于链表，呵呵。<br>3、全局区（静态区）（static）—，全局变量和静态变量的存储是放在一块的，初始化的全局变量和静态变量在一块区域，&nbsp;未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。&nbsp;-&nbsp;程序结束后有系统释放&nbsp;<br>4、文字常量区—常量字符串就是放在这里的。&nbsp;程序结束后由系统释放<br>5、程序代码区—存放函数体的二进制代码。<br>二、例子程序&nbsp;<br>这是一个前辈写的，非常详细&nbsp;<br>//main.cpp&nbsp;<br>int&nbsp;a&nbsp;=&nbsp;0;&nbsp;全局初始化区&nbsp;<br>char&nbsp;*p1;&nbsp;全局未初始化区&nbsp;<br>main()&nbsp;<br>{&nbsp;<br>int&nbsp;b;&nbsp;栈&nbsp;<br>char&nbsp;s[]&nbsp;=&nbsp;"abc";&nbsp;栈&nbsp;<br>char&nbsp;*p2;&nbsp;栈&nbsp;<br>char&nbsp;*p3&nbsp;=&nbsp;"123456";&nbsp;123456\0在常量区，p3在栈上。&nbsp;<br>static&nbsp;int&nbsp;c&nbsp;=0；&nbsp;全局（静态）初始化区&nbsp;<br>p1&nbsp;=&nbsp;(char&nbsp;*)malloc(10);&nbsp;<br>p2&nbsp;=&nbsp;(char&nbsp;*)malloc(20);&nbsp;<br>分配得来得10和20字节的区域就在堆区。&nbsp;<br>strcpy(p1,&nbsp;"123456");&nbsp;123456\0放在常量区，编译器可能会将它与p3所指向的"123456"优化成一个地方。&nbsp;<br>}&nbsp;
<p>&#160;</p>
<p><br>二、堆和栈的理论知识&nbsp;<br>2.1申请方式&nbsp;<br>stack:&nbsp;<br>由系统自动分配。&nbsp;例如，声明在函数中一个局部变量&nbsp;int&nbsp;b;&nbsp;系统自动在栈中为b开辟空间&nbsp;<br>heap:&nbsp;<br>需要程序员自己申请，并指明大小，在c中malloc函数&nbsp;<br>如p1&nbsp;=&nbsp;(char&nbsp;*)malloc(10);&nbsp;<br>在C++中用new运算符&nbsp;<br>如p2&nbsp;=&nbsp;(char&nbsp;*)malloc(10);&nbsp;<br>但是注意p1、p2本身是在栈中的。&nbsp;</p>
<p><br>2.2&nbsp;<br>申请后系统的响应&nbsp;<br>栈：只要栈的剩余空间大于所申请空间，系统将为程序提供内存，否则将报异常提示栈溢出。&nbsp;<br>堆：首先应该知道操作系统有一个记录空闲内存地址的链表，当系统收到程序的申请时，&nbsp;<br>会遍历该链表，寻找第一个空间大于所申请空间的堆结点，然后将该结点从空闲结点链表中删除，并将该结点的空间分配给程序，另外，对于大多数系统，会在这块内存空间中的首地址处记录本次分配的大小，这样，代码中的delete语句才能正确的释放本内存空间。另外，由于找到的堆结点的大小不一定正好等于申请的大小，系统会自动的将多余的那部分重新放入空闲链表中。&nbsp;</p>
<p>2.3申请大小的限制&nbsp;<br>栈：在Windows下,栈是向低地址扩展的数据结构，是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的，在WINDOWS下，栈的大小是2M（也有的说是1M，总之是一个编译时就确定的常数），如果申请的空间超过栈的剩余空间时，将提示overflow。因此，能从栈获得的空间较小。&nbsp;<br>堆：堆是向高地址扩展的数据结构，是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的，自然是不连续的，而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见，堆获得的空间比较灵活，也比较大。&nbsp;</p>
<p><br>2.4申请效率的比较：&nbsp;<br>栈由系统自动分配，速度较快。但程序员是无法控制的。&nbsp;<br>堆是由new分配的内存，一般速度比较慢，而且容易产生内存碎片,不过用起来最方便.&nbsp;<br>另外，在WINDOWS下，最好的方式是用VirtualAlloc分配内存，他不是在堆，也不是在栈是直接在进程的地址空间中保留一快内存，虽然用起来最不方便。但是速度快，也最灵活。&nbsp;</p>
<p>2.5堆和栈中的存储内容&nbsp;<br>栈：&nbsp;在函数调用时，第一个进栈的是主函数中后的下一条指令（函数调用语句的下一条可执行语句）的地址，然后是函数的各个参数，在大多数的C编译器中，参数是由右往左入栈的，然后是函数中的局部变量。注意静态变量是不入栈的。&nbsp;<br>当本次函数调用结束后，局部变量先出栈，然后是参数，最后栈顶指针指向最开始存的地址，也就是主函数中的下一条指令，程序由该点继续运行。&nbsp;<br>堆：一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。&nbsp;</p>
<p>2.6存取效率的比较&nbsp;</p>
<p>char&nbsp;s1[]&nbsp;=&nbsp;"aaaaaaaaaaaaaaa";&nbsp;<br>char&nbsp;*s2&nbsp;=&nbsp;"bbbbbbbbbbbbbbbbb";&nbsp;<br>aaaaaaaaaaa是在运行时刻赋值的；&nbsp;<br>而bbbbbbbbbbb是在编译时就确定的；&nbsp;<br>但是，在以后的存取中，在栈上的数组比指针所指向的字符串(例如堆)快。&nbsp;<br>比如：&nbsp;<br>#include&nbsp;<br>void&nbsp;main()&nbsp;<br>{&nbsp;<br>char&nbsp;a&nbsp;=&nbsp;1;&nbsp;<br>char&nbsp;c[]&nbsp;=&nbsp;"1234567890";&nbsp;<br>char&nbsp;*p&nbsp;="1234567890";&nbsp;<br>a&nbsp;=&nbsp;c[1];&nbsp;<br>a&nbsp;=&nbsp;p[1];&nbsp;<br>return;&nbsp;<br>}&nbsp;<br>对应的汇编代码&nbsp;<br>10:&nbsp;a&nbsp;=&nbsp;c[1];&nbsp;<br>00401067&nbsp;8A&nbsp;4D&nbsp;F1&nbsp;mov&nbsp;cl,byte&nbsp;ptr&nbsp;[ebp-0Fh]&nbsp;<br>0040106A&nbsp;88&nbsp;4D&nbsp;FC&nbsp;mov&nbsp;byte&nbsp;ptr&nbsp;[ebp-4],cl&nbsp;<br>11:&nbsp;a&nbsp;=&nbsp;p[1];&nbsp;<br>0040106D&nbsp;8B&nbsp;55&nbsp;EC&nbsp;mov&nbsp;edx,dword&nbsp;ptr&nbsp;[ebp-14h]&nbsp;<br>00401070&nbsp;8A&nbsp;42&nbsp;01&nbsp;mov&nbsp;al,byte&nbsp;ptr&nbsp;[edx+1]&nbsp;<br>00401073&nbsp;88&nbsp;45&nbsp;FC&nbsp;mov&nbsp;byte&nbsp;ptr&nbsp;[ebp-4],al&nbsp;<br>第一种在读取时直接就把字符串中的元素读到寄存器cl中，而第二种则要先把指针值读到edx中，在根据edx读取字符，显然慢了。&nbsp;</p>
<p><br>2.7小结：&nbsp;<br>堆和栈的区别可以用如下的比喻来看出：&nbsp;<br>使用栈就象我们去饭馆里吃饭，只管点菜（发出申请）、付钱、和吃（使用），吃饱了就走，不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作，他的好处是快捷，但是自由度小。&nbsp;<br>使用堆就象是自己动手做喜欢吃的菜肴，比较麻烦，但是比较符合自己的口味，而且自由度大。&nbsp;<br><br><br><br></p>
<p>windows进程中的内存结构</p>
<p><br>在阅读本文之前，如果你连堆栈是什么多不知道的话，请先阅读文章后面的基础知识。&nbsp;</p>
<p>接触过编程的人都知道，高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢？程序又是如何使用这些变量的呢？下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明，默认都使用VC编译的release版。&nbsp;</p>
<p>首先，来了解一下&nbsp;C&nbsp;语言的变量是如何在内存分部的。C&nbsp;语言有全局变量(Global)、本地变量(Local)，静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;</p>
<p>int&nbsp;g1=0,&nbsp;g2=0,&nbsp;g3=0;&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>static&nbsp;int&nbsp;s1=0,&nbsp;s2=0,&nbsp;s3=0;&nbsp;<br>int&nbsp;v1=0,&nbsp;v2=0,&nbsp;v3=0;&nbsp;</p>
<p>//打印出各个变量的内存地址&nbsp;</p>
<p>printf("0x%08x\n",&amp;v1);&nbsp;//打印各本地变量的内存地址&nbsp;<br>printf("0x%08x\n",&amp;v2);&nbsp;<br>printf("0x%08x\n\n",&amp;v3);&nbsp;<br>printf("0x%08x\n",&amp;g1);&nbsp;//打印各全局变量的内存地址&nbsp;<br>printf("0x%08x\n",&amp;g2);&nbsp;<br>printf("0x%08x\n\n",&amp;g3);&nbsp;<br>printf("0x%08x\n",&amp;s1);&nbsp;//打印各静态变量的内存地址&nbsp;<br>printf("0x%08x\n",&amp;s2);&nbsp;<br>printf("0x%08x\n\n",&amp;s3);&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>编译后的执行结果是：&nbsp;</p>
<p>0x0012ff78&nbsp;<br>0x0012ff7c&nbsp;<br>0x0012ff80&nbsp;</p>
<p>0x004068d0&nbsp;<br>0x004068d4&nbsp;<br>0x004068d8&nbsp;</p>
<p>0x004068dc&nbsp;<br>0x004068e0&nbsp;<br>0x004068e4&nbsp;</p>
<p>输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量，g1,g2,g3是全局变量，s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的，但是本地变量和全局变量分配的内存地址差了十万八千里，而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言，可以在逻辑上分成3个部份：代码区，静态数据区和动态数据区。动态数据区一般就是&#8220;堆栈&#8221;。&#8220;栈(stack)&#8221;和&#8220;堆(heap)&#8221;是两种不同的动态数据区，栈是一种线性结构，堆是一种链式结构。进程的每个线程都有私有的&#8220;栈&#8221;，所以每个线程虽然代码一样，但本地变量的数据都是互不干扰。一个堆栈可以通过&#8220;基地址&#8221;和&#8220;栈顶&#8221;地址来描述。全局变量和静态变量分配在静态数据区，本地变量分配在动态数据区，即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。&nbsp;</p>
<p><br>├———————┤低端内存区域&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;动态数据区&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;代码区&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;静态数据区&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤高端内存区域&nbsp;</p>
<p><br>堆栈是一个先进后出的数据结构，栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程，以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定，这些因素有参数的压入规则和堆栈的平衡。windows&nbsp;API的调用规则和ANSI&nbsp;C的函数调用规则是不一样的，前者由被调函数调整堆栈，后者由调用者调整堆栈。两者通过&#8220;__stdcall&#8221;和&#8220;__cdecl&#8221;前缀区分。先看下面这段代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;</p>
<p>void&nbsp;__stdcall&nbsp;func(int&nbsp;param1,int&nbsp;param2,int&nbsp;param3)&nbsp;<br>{&nbsp;<br>int&nbsp;var1=param1;&nbsp;<br>int&nbsp;var2=param2;&nbsp;<br>int&nbsp;var3=param3;&nbsp;<br>printf("0x%08x\n",&#182;m1);&nbsp;//打印出各个变量的内存地址&nbsp;<br>printf("0x%08x\n",&#182;m2);&nbsp;<br>printf("0x%08x\n\n",&#182;m3);&nbsp;<br>printf("0x%08x\n",&amp;var1);&nbsp;<br>printf("0x%08x\n",&amp;var2);&nbsp;<br>printf("0x%08x\n\n",&amp;var3);&nbsp;<br>return;&nbsp;<br>}&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>func(1,2,3);&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>编译后的执行结果是：&nbsp;</p>
<p>0x0012ff78&nbsp;<br>0x0012ff7c&nbsp;<br>0x0012ff80&nbsp;</p>
<p>0x0012ff68&nbsp;<br>0x0012ff6c&nbsp;<br>0x0012ff70&nbsp;</p>
<p><br>├———————┤&lt;—函数执行时的栈顶（ESP）、低端内存区域&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;var&nbsp;1&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;var&nbsp;2&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;var&nbsp;3&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;RET&nbsp;│&nbsp;<br>├———————┤&lt;—&#8220;__cdecl&#8221;函数返回后的栈顶（ESP）&nbsp;<br>│&nbsp;parameter&nbsp;1&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;parameter&nbsp;2&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;parameter&nbsp;3&nbsp;│&nbsp;<br>├———————┤&lt;—&#8220;__stdcall&#8221;函数返回后的栈顶（ESP）&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&lt;—栈底（基地址&nbsp;EBP）、高端内存区域&nbsp;</p>
<p><br>上图就是函数调用过程中堆栈的样子了。首先，三个参数以从又到左的次序压入堆栈，先压&#8220;param3&#8221;，再压&#8220;param2&#8221;，最后压入&#8220;param1&#8221;；然后压入函数的返回地址(RET)，接着跳转到函数地址接着执行（这里要补充一点，介绍UNIX下的缓冲溢出原理的文章中都提到在压入RET后，继续压入当前EBP，然后用当前ESP代替EBP。然而，有一篇介绍windows下函数调用的文章中说，在windows下的函数调用也有这一步骤，但根据我的实际调试，并未发现这一步，这还可以从param3和var1之间只有4字节的间隙这点看出来）；第三步，将栈顶(ESP)减去一个数，为本地变量分配内存空间，上例中是减去12字节(ESP=ESP-3*4，每个int变量占用4个字节)；接着就初始化本地变量的内存空间。由于&#8220;__stdcall&#8221;调用由被调函数调整堆栈，所以在函数返回前要恢复堆栈，先回收本地变量占用的内存(ESP=ESP+3*4)，然后取出返回地址，填入EIP寄存器，回收先前压入参数占用的内存(ESP=ESP+3*4)，继续执行调用者的代码。参见下列汇编代码：&nbsp;</p>
<p>;--------------func&nbsp;函数的汇编代码-------------------&nbsp;</p>
<p>:00401000&nbsp;83EC0C&nbsp;sub&nbsp;esp,&nbsp;0000000C&nbsp;//创建本地变量的内存空间&nbsp;<br>:00401003&nbsp;8B442410&nbsp;mov&nbsp;eax,&nbsp;dword&nbsp;ptr&nbsp;[esp+10]&nbsp;<br>:00401007&nbsp;8B4C2414&nbsp;mov&nbsp;ecx,&nbsp;dword&nbsp;ptr&nbsp;[esp+14]&nbsp;<br>:0040100B&nbsp;8B542418&nbsp;mov&nbsp;edx,&nbsp;dword&nbsp;ptr&nbsp;[esp+18]&nbsp;<br>:0040100F&nbsp;89442400&nbsp;mov&nbsp;dword&nbsp;ptr&nbsp;[esp],&nbsp;eax&nbsp;<br>:00401013&nbsp;8D442410&nbsp;lea&nbsp;eax,&nbsp;dword&nbsp;ptr&nbsp;[esp+10]&nbsp;<br>:00401017&nbsp;894C2404&nbsp;mov&nbsp;dword&nbsp;ptr&nbsp;[esp+04],&nbsp;ecx&nbsp;</p>
<p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;（省略若干代码）&nbsp;</p>
<p>:00401075&nbsp;83C43C&nbsp;add&nbsp;esp,&nbsp;0000003C&nbsp;;恢复堆栈，回收本地变量的内存空间&nbsp;<br>:00401078&nbsp;C3&nbsp;ret&nbsp;000C&nbsp;;函数返回，恢复参数占用的内存空间&nbsp;<br>;如果是&#8220;__cdecl&#8221;的话，这里是&#8220;ret&#8221;，堆栈将由调用者恢复&nbsp;</p>
<p>;-------------------函数结束-------------------------&nbsp;</p>
<p><br>;--------------主程序调用func函数的代码--------------&nbsp;</p>
<p>:00401080&nbsp;6A03&nbsp;push&nbsp;00000003&nbsp;//压入参数param3&nbsp;<br>:00401082&nbsp;6A02&nbsp;push&nbsp;00000002&nbsp;//压入参数param2&nbsp;<br>:00401084&nbsp;6A01&nbsp;push&nbsp;00000001&nbsp;//压入参数param1&nbsp;<br>:00401086&nbsp;E875FFFFFF&nbsp;call&nbsp;00401000&nbsp;//调用func函数&nbsp;<br>;如果是&#8220;__cdecl&#8221;的话，将在这里恢复堆栈，&#8220;add&nbsp;esp,&nbsp;0000000C&#8221;&nbsp;</p>
<p>聪明的读者看到这里，差不多就明白缓冲溢出的原理了。先来看下面的代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;<br>#include&nbsp;&lt;string.h&gt;&nbsp;</p>
<p>void&nbsp;__stdcall&nbsp;func()&nbsp;<br>{&nbsp;<br>char&nbsp;lpBuff[8]="\0";&nbsp;<br>strcat(lpBuff,"AAAAAAAAAAA");&nbsp;<br>return;&nbsp;<br>}&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>func();&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>编译后执行一下回怎么样？哈，&#8220;"0x00414141"指令引用的"0x00000000"内存。该内存不能为"read"。&#8221;，&#8220;非法操作&#8221;喽！"41"就是"A"的16进制的ASCII码了，那明显就是strcat这句出的问题了。"lpBuff"的大小只有8字节，算进结尾的\0，那strcat最多只能写入7个"A"，但程序实际写入了11个"A"外加1个\0。再来看看上面那幅图，多出来的4个字节正好覆盖了RET的所在的内存空间，导致函数返回到一个错误的内存地址，执行了错误的指令。如果能精心构造这个字符串，使它分成三部分，前一部份仅仅是填充的无意义数据以达到溢出的目的，接着是一个覆盖RET的数据，紧接着是一段shellcode，那只要着个RET地址能指向这段shellcode的第一个指令，那函数返回时就能执行shellcode了。但是软件的不同版本和不同的运行环境都可能影响这段shellcode在内存中的位置，那么要构造这个RET是十分困难的。一般都在RET和shellcode之间填充大量的NOP指令，使得exploit有更强的通用性。&nbsp;</p>
<p><br>├———————┤&lt;—低端内存区域&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&lt;—由exploit填入数据的开始&nbsp;<br>│&nbsp;│&nbsp;<br>│&nbsp;buffer&nbsp;│&lt;—填入无用的数据&nbsp;<br>│&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;RET&nbsp;│&lt;—指向shellcode，或NOP指令的范围&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;NOP&nbsp;│&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&lt;—填入的NOP指令，是RET可指向的范围&nbsp;<br>│&nbsp;NOP&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;│&nbsp;<br>│&nbsp;shellcode&nbsp;│&nbsp;<br>│&nbsp;│&nbsp;<br>├———————┤&lt;—由exploit填入数据的结束&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&lt;—高端内存区域&nbsp;</p>
<p><br>windows下的动态数据除了可存放在栈中，还可以存放在堆中。了解C++的朋友都知道，C++可以使用new关键字来动态分配内存。来看下面的C++代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;<br>#include&nbsp;&lt;iostream.h&gt;&nbsp;<br>#include&nbsp;&lt;windows.h&gt;&nbsp;</p>
<p>void&nbsp;func()&nbsp;<br>{&nbsp;<br>char&nbsp;*buffer=new&nbsp;char[128];&nbsp;<br>char&nbsp;bufflocal[128];&nbsp;<br>static&nbsp;char&nbsp;buffstatic[128];&nbsp;<br>printf("0x%08x\n",buffer);&nbsp;//打印堆中变量的内存地址&nbsp;<br>printf("0x%08x\n",bufflocal);&nbsp;//打印本地变量的内存地址&nbsp;<br>printf("0x%08x\n",buffstatic);&nbsp;//打印静态变量的内存地址&nbsp;<br>}&nbsp;</p>
<p>void&nbsp;main()&nbsp;<br>{&nbsp;<br>func();&nbsp;<br>return;&nbsp;<br>}&nbsp;</p>
<p>程序执行结果为：&nbsp;</p>
<p>0x004107d0&nbsp;<br>0x0012ff04&nbsp;<br>0x004068c0&nbsp;</p>
<p>可以发现用new关键字分配的内存即不在栈中，也不在静态数据区。VC编译器是通过windows下的&#8220;堆(heap)&#8221;来实现new关键字的内存动态分配。在讲&#8220;堆&#8221;之前，先来了解一下和&#8220;堆&#8221;有关的几个API函数：&nbsp;</p>
<p>HeapAlloc&nbsp;在堆中申请内存空间&nbsp;<br>HeapCreate&nbsp;创建一个新的堆对象&nbsp;<br>HeapDestroy&nbsp;销毁一个堆对象&nbsp;<br>HeapFree&nbsp;释放申请的内存&nbsp;<br>HeapWalk&nbsp;枚举堆对象的所有内存块&nbsp;<br>GetProcessHeap&nbsp;取得进程的默认堆对象&nbsp;<br>GetProcessHeaps&nbsp;取得进程所有的堆对象&nbsp;<br>LocalAlloc&nbsp;<br>GlobalAlloc&nbsp;</p>
<p>当进程初始化时，系统会自动为进程创建一个默认堆，这个堆默认所占内存的大小为1M。堆对象由系统进行管理，它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间：&nbsp;</p>
<p>HANDLE&nbsp;hHeap=GetProcessHeap();&nbsp;<br>char&nbsp;*buff=HeapAlloc(hHeap,0,8);&nbsp;</p>
<p>其中hHeap是堆对象的句柄，buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢？它的值有什么意义吗？看看下面这段代码吧：&nbsp;</p>
<p>#pragma&nbsp;comment(linker,"/entry:main")&nbsp;//定义程序的入口&nbsp;<br>#include&nbsp;&lt;windows.h&gt;&nbsp;</p>
<p>_CRTIMP&nbsp;int&nbsp;(__cdecl&nbsp;*printf)(const&nbsp;char&nbsp;*,&nbsp;...);&nbsp;//定义STL函数printf&nbsp;<br>/*---------------------------------------------------------------------------&nbsp;<br>写到这里，我们顺便来复习一下前面所讲的知识：&nbsp;<br>(*注)printf函数是C语言的标准函数库中函数，VC的标准函数库由msvcrt.dll模块实现。&nbsp;<br>由函数定义可见，printf的参数个数是可变的，函数内部无法预先知道调用者压入的参数个数，函数只能通过分析第一个参数字符串的格式来获得压入参数的信息，由于这里参数的个数是动态的，所以必须由调用者来平衡堆栈，这里便使用了__cdecl调用规则。BTW，Windows系统的API函数基本上是__stdcall调用形式，只有一个API例外，那就是wsprintf，它使用__cdecl调用规则，同printf函数一样，这是由于它的参数个数是可变的缘故。&nbsp;<br>---------------------------------------------------------------------------*/&nbsp;<br>void&nbsp;main()&nbsp;<br>{&nbsp;<br>HANDLE&nbsp;hHeap=GetProcessHeap();&nbsp;<br>char&nbsp;*buff=HeapAlloc(hHeap,0,0x10);&nbsp;<br>char&nbsp;*buff2=HeapAlloc(hHeap,0,0x10);&nbsp;<br>HMODULE&nbsp;hMsvcrt=LoadLibrary("msvcrt.dll");&nbsp;<br>printf=(void&nbsp;*)GetProcAddress(hMsvcrt,"printf");&nbsp;<br>printf("0x%08x\n",hHeap);&nbsp;<br>printf("0x%08x\n",buff);&nbsp;<br>printf("0x%08x\n\n",buff2);&nbsp;<br>}&nbsp;</p>
<p>执行结果为：&nbsp;</p>
<p>0x00130000&nbsp;<br>0x00133100&nbsp;<br>0x00133118&nbsp;</p>
<p>hHeap的值怎么和那个buff的值那么接近呢？其实hHeap这个句柄就是指向HEAP首部的地址。在进程的用户区存着一个叫PEB(进程环境块)的结构，这个结构中存放着一些有关进程的重要信息，其中在PEB首地址偏移0x18处存放的ProcessHeap就是进程默认堆的地址，而偏移0x90处存放了指向进程所有堆的地址列表的指针。windows有很多API都使用进程的默认堆来存放动态数据，如windows&nbsp;2000下的所有ANSI版本的函数都是在默认堆中申请内存来转换ANSI字符串到Unicode字符串的。对一个堆的访问是顺序进行的，同一时刻只能有一个线程访问堆中的数据，当多个线程同时有访问要求时，只能排队等待，这样便造成程序执行效率下降。&nbsp;</p>
<p>最后来说说内存中的数据对齐。所位数据对齐，是指数据所在的内存地址必须是该数据长度的整数倍，DWORD数据的内存起始地址能被4除尽，WORD数据的内存起始地址能被2除尽，x86&nbsp;CPU能直接访问对齐的数据，当他试图访问一个未对齐的数据时，会在内部进行一系列的调整，这些调整对于程序来说是透明的，但是会降低运行速度，所以编译器在编译程序时会尽量保证数据对齐。同样一段代码，我们来看看用VC、Dev-C++和lcc三个不同编译器编译出来的程序的执行结果：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>int&nbsp;a;&nbsp;<br>char&nbsp;b;&nbsp;<br>int&nbsp;c;&nbsp;<br>printf("0x%08x\n",&amp;a);&nbsp;<br>printf("0x%08x\n",&amp;b);&nbsp;<br>printf("0x%08x\n",&amp;c);&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>这是用VC编译后的执行结果：&nbsp;<br>0x0012ff7c&nbsp;<br>0x0012ff7b&nbsp;<br>0x0012ff80&nbsp;<br>变量在内存中的顺序：b(1字节)-a(4字节)-c(4字节)。&nbsp;</p>
<p>这是用Dev-C++编译后的执行结果：&nbsp;<br>0x0022ff7c&nbsp;<br>0x0022ff7b&nbsp;<br>0x0022ff74&nbsp;<br>变量在内存中的顺序：c(4字节)-中间相隔3字节-b(占1字节)-a(4字节)。&nbsp;</p>
<p>这是用lcc编译后的执行结果：&nbsp;<br>0x0012ff6c&nbsp;<br>0x0012ff6b&nbsp;<br>0x0012ff64&nbsp;<br>变量在内存中的顺序：同上。&nbsp;</p>
<p>三个编译器都做到了数据对齐，但是后两个编译器显然没VC&#8220;聪明&#8221;，让一个char占了4字节，浪费内存哦。&nbsp;</p>
<p><br>基础知识：&nbsp;<br>堆栈是一种简单的数据结构，是一种只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈顶，另一端称为栈底，对堆栈的插入和删除操作被称为入栈和出栈。有一组CPU指令可以实现对进程的内存实现堆栈访问。其中，POP指令实现出栈操作，PUSH指令实现入栈操作。CPU的ESP寄存器存放当前线程的栈顶指针，EBP寄存器中保存当前线程的栈底指针。CPU的EIP寄存器存放下一个CPU指令存放的内存地址，当CPU执行完当前的指令后，从EIP寄存器中读取下一条指令的内存地址，然后继续执行。&nbsp;</p>
<p><br>参考：《Windows下的HEAP溢出及其利用》by:&nbsp;isno&nbsp;<br>《windows核心编程》by:&nbsp;Jeffrey&nbsp;Richter&nbsp;<br><br><br><br></p>
<p>摘要：&nbsp;讨论常见的堆性能问题以及如何防范它们。（共&nbsp;9&nbsp;页）</p>
<p>前言<br>您是否是动态分配的&nbsp;C/C++&nbsp;对象忠实且幸运的用户？您是否在模块间的往返通信中频繁地使用了&#8220;自动化&#8221;？您的程序是否因堆分配而运行起来很慢？不仅仅您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说，&#8220;我的代码真正好，只是堆太慢&#8221;。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题，是很有用的。</p>
<p>什么是堆？<br>（如果您已经知道什么是堆，可以跳到&#8220;什么是常见的堆性能问题？&#8221;部分）</p>
<p>在程序中，使用堆来动态分配和释放对象。在下列情况下，调用堆操作：&nbsp;</p>
<p>事先不知道程序所需对象的数量和大小。</p>
<p><br>对象太大而不适合堆栈分配程序。<br>堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。<br><a href="http://club.5ivb.net/UploadFile/2005311144027byUID16686.gif" target=_blank><img alt="" src="" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border=0 dypop="按此在新窗口浏览图片"></a></p>
<p>GlobalAlloc/GlobalFree：Microsoft&nbsp;Win32&nbsp;堆调用，这些调用直接与每个进程的默认堆进行对话。</p>
<p>LocalAlloc/LocalFree：Win32&nbsp;堆调用（为了与&nbsp;Microsoft&nbsp;Windows&nbsp;NT&nbsp;兼容），这些调用直接与每个进程的默认堆进行对话。</p>
<p>COM&nbsp;的&nbsp;IMalloc&nbsp;分配程序（或&nbsp;CoTaskMemAlloc&nbsp;/&nbsp;CoTaskMemFree）：函数使用每个进程的默认堆。自动化程序使用&#8220;组件对象模型&nbsp;(COM)&#8221;的分配程序，而申请的程序使用每个进程堆。</p>
<p>C/C++&nbsp;运行时&nbsp;(CRT)&nbsp;分配程序：提供了&nbsp;malloc()&nbsp;和&nbsp;free()&nbsp;以及&nbsp;new&nbsp;和&nbsp;delete&nbsp;操作符。如&nbsp;Microsoft&nbsp;Visual&nbsp;Basic&nbsp;和&nbsp;Java&nbsp;等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT&nbsp;创建自己的私有堆，驻留在&nbsp;Win32&nbsp;堆的顶部。</p>
<p>Windows&nbsp;NT&nbsp;中，Win32&nbsp;堆是&nbsp;Windows&nbsp;NT&nbsp;运行时分配程序周围的薄层。所有&nbsp;API&nbsp;转发它们的请求给&nbsp;NTDLL。</p>
<p>Windows&nbsp;NT&nbsp;运行时分配程序提供&nbsp;Windows&nbsp;NT&nbsp;内的核心堆分配程序。它由具有&nbsp;128&nbsp;个大小从&nbsp;8&nbsp;到&nbsp;1,024&nbsp;字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。</p>
<p>在图表的底部是&#8220;虚拟内存分配程序&#8221;，操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。</p>
<p>分配和释放块不就那么简单吗？为何花费这么长时间？</p>
<p>堆实现的注意事项<br>传统上，操作系统和运行时库是与堆的实现共存的。在一个进程的开始，操作系统创建一个默认堆，叫做&#8220;进程堆&#8221;。如果没有其他堆可使用，则块的分配使用&#8220;进程堆&#8221;。语言运行时也能在进程内创建单独的堆。（例如，C&nbsp;运行时创建它自己的堆。）除这些专用的堆外，应用程序或许多已载入的动态链接库&nbsp;(DLL)&nbsp;之一可以创建和使用单独的堆。Win32&nbsp;提供一整套&nbsp;API&nbsp;来创建和使用私有堆。有关堆函数（英文）的详尽指导，请参见&nbsp;MSDN。</p>
<p>当应用程序或&nbsp;DLL&nbsp;创建私有堆时，这些堆存在于进程空间，并且在进程内是可访问的。从给定堆分配的数据将在同一个堆上释放。（不能从一个堆分配而在另一个堆释放。）</p>
<p>在所有虚拟内存系统中，堆驻留在操作系统的&#8220;虚拟内存管理器&#8221;的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下，这些堆是操作系统堆中的层，而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆，而使用虚拟内存函数更利于堆的分配和块的使用。</p>
<p>典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用，堆尝试从前端列表找到一个自由块。如果失败，堆被迫从后端（保留和提交虚拟内存）分配一个大块来满足请求。通用的实现有每块分配的开销，这将耗费执行周期，也减少了可使用的存储空间。</p>
<p>Knowledge&nbsp;Base&nbsp;文章&nbsp;Q10758，&#8220;用&nbsp;calloc()&nbsp;和&nbsp;malloc()&nbsp;管理内存&#8221;&nbsp;（搜索文章编号）,&nbsp;包含了有关这些主题的更多背景知识。另外，有关堆实现和设计的详细讨论也可在下列著作中找到：&#8220;Dynamic&nbsp;Storage&nbsp;Allocation:&nbsp;A&nbsp;Survey&nbsp;and&nbsp;Critical&nbsp;Review&#8221;，作者&nbsp;Paul&nbsp;R.&nbsp;Wilson、Mark&nbsp;S.&nbsp;Johnstone、Michael&nbsp;Neely&nbsp;和&nbsp;David&nbsp;Boles；&#8220;International&nbsp;Workshop&nbsp;on&nbsp;Memory&nbsp;Management&#8221;,&nbsp;作者&nbsp;Kinross,&nbsp;Scotland,&nbsp;UK,&nbsp;1995&nbsp;年&nbsp;9&nbsp;月(<img src="" align=absMiddle border=0><a href="http://www.cs.utexas.edu/users/oops/papers.html" target=_blank><font color=#000000><u>http://www.cs.utexas.edu/users/oops/papers.html</u></font></a>)（英文）。</p>
<p>Windows&nbsp;NT&nbsp;的实现（Windows&nbsp;NT&nbsp;版本&nbsp;4.0&nbsp;和更新版本）&nbsp;使用了&nbsp;127&nbsp;个大小从&nbsp;8&nbsp;到&nbsp;1,024&nbsp;字节的&nbsp;8&nbsp;字节对齐块空闲列表和一个&#8220;大块&#8221;列表。&#8220;大块&#8221;列表（空闲列表[0]）&nbsp;保存大于&nbsp;1,024&nbsp;字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下，&#8220;进程堆&#8221;执行收集操作。（收集是将相邻空闲块合并成一个大块的操作。）收集耗费了额外的周期，但减少了堆块的内部碎片。</p>
<p>单一全局锁保护堆，防止多线程式的使用。（请参见&#8220;Server&nbsp;Performance&nbsp;and&nbsp;Scalability&nbsp;Killers&#8221;中的第一个注意事项,&nbsp;George&nbsp;Reilly&nbsp;所著，在&nbsp;&#8220;MSDN&nbsp;Online&nbsp;Web&nbsp;Workshop&#8221;上（站点：<img src="" align=absMiddle border=0><a href="http://msdn.microsoft.com/workshop/server/iis/tencom.asp" target=_blank><font color=#000000><u>http://msdn.microsoft.com/workshop/server/iis/tencom.asp</u></font></a>（英文）。）单一全局锁本质上是用来保护堆数据结构，防止跨多线程的随机存取。若堆操作太频繁，单一全局锁会对性能有不利的影响。</p>
<p>什么是常见的堆性能问题？<br>以下是您使用堆时会遇到的最常见问题：&nbsp;</p>
<p>分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块，所以运行时分配程序代码会耗费周期寻找较大的空闲块，或从后端分配程序分配新块。</p>
<p><br>释放操作造成的速度减慢。释放操作耗费较多周期，主要是启用了收集操作。收集期间，每个释放操作&#8220;查找&#8221;它的相邻块，取出它们并构造成较大块，然后再把此较大块插入空闲列表。在查找期间，内存可能会随机碰到，从而导致高速缓存不能命中，性能降低。</p>
<p><br>堆竞争造成的速度减慢。当两个或多个线程同时访问数据，而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦；这也是目前多处理器系统遇到的最大问题。当大量使用内存块的应用程序或&nbsp;DLL&nbsp;以多线程方式运行（或运行于多处理器系统上）时将导致速度减慢。单一锁定的使用—常用的解决方案—意味着使用堆的所有操作是序列化的。当等待锁定时序列化会引起线程切换上下文。可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢。&nbsp;<br>竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的，但开销更大的是数据从处理器高速缓存中丢失，以及后来线程复活时的数据重建。</p>
<p>堆破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块，以及块的越界重写等明显问题。（破坏不在本文讨论范围之内。有关内存重写和泄漏等其他细节，请参见&nbsp;Microsoft&nbsp;Visual&nbsp;C++(R)&nbsp;调试文档&nbsp;。）</p>
<p><br>频繁的分配和重分配造成的速度减慢。这是使用脚本语言时非常普遍的现象。如字符串被反复分配，随重分配增长和释放。不要这样做，如果可能，尽量分配大字符串和使用缓冲区。另一种方法就是尽量少用连接操作。<br>竞争是在分配和释放操作中导致速度减慢的问题。理想情况下，希望使用没有竞争和快速分配/释放的堆。可惜，现在还没有这样的通用堆，也许将来会有。</p>
<p>在所有的服务器系统中（如&nbsp;IIS、MSProxy、DatabaseStacks、网络服务器、&nbsp;Exchange&nbsp;和其他）,&nbsp;堆锁定实在是个大瓶颈。处理器数越多，竞争就越会恶化。</p>
<p>尽量减少堆的使用<br>现在您明白使用堆时存在的问题了，难道您不想拥有能解决这些问题的超级魔棒吗？我可希望有。但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够大为改观。如果提前规划堆策略，情况将会大大好转。调整使用堆的方法，减少对堆的操作是提高性能的良方。</p>
<p>如何减少使用堆操作？通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例：</p>
<p>struct&nbsp;ObjectA&nbsp;{<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectA&nbsp;的数据&nbsp;<br>}</p>
<p>struct&nbsp;ObjectB&nbsp;{<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectB&nbsp;的数据&nbsp;<br>}</p>
<p>//&nbsp;同时使用&nbsp;objectA&nbsp;和&nbsp;objectB</p>
<p>//<br>//&nbsp;使用指针&nbsp;<br>//<br>struct&nbsp;ObjectB&nbsp;{<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectA&nbsp;*&nbsp;pObjA;<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectB&nbsp;的数据&nbsp;<br>}</p>
<p>//<br>//&nbsp;使用嵌入<br>//<br>struct&nbsp;ObjectB&nbsp;{<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectA&nbsp;pObjA;<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectB&nbsp;的数据&nbsp;<br>}</p>
<p>//<br>//&nbsp;集合&nbsp;&#8211;&nbsp;在另一对象内使用&nbsp;objectA&nbsp;和&nbsp;objectB<br>//</p>
<p>struct&nbsp;ObjectX&nbsp;{<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectA&nbsp;&nbsp;objA;<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectB&nbsp;&nbsp;objB;<br>}</p>
<p>避免使用指针关联两个数据结构。如果使用指针关联两个数据结构，前面实例中的对象&nbsp;A&nbsp;和&nbsp;B&nbsp;将被分别分配和释放。这会增加额外开销—我们要避免这种做法。</p>
<p><br>把带指针的子对象嵌入父对象。当对象中有指针时，则意味着对象中有动态元素（百分之八十）和没有引用的新位置。嵌入增加了位置从而减少了进一步分配/释放的需求。这将提高应用程序的性能。</p>
<p><br>合并小对象形成大对象（聚合）。聚合减少分配和释放的块的数量。如果有几个开发者，各自开发设计的不同部分，则最终会有许多小对象需要合并。集成的挑战就是要找到正确的聚合边界。</p>
<p><br>内联缓冲区能够满足百分之八十的需要（aka&nbsp;80-20&nbsp;规则）。个别情况下，需要内存缓冲区来保存字符串/二进制数据，但事先不知道总字节数。估计并内联一个大小能满足百分之八十需要的缓冲区。对剩余的百分之二十，可以分配一个新的缓冲区和指向这个缓冲区的指针。这样，就减少分配和释放调用并增加数据的位置空间，从根本上提高代码的性能。</p>
<p><br>在块中分配对象（块化）。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪，例如对一个&nbsp;{名称，值}&nbsp;对的列表，有两种选择：选择一是为每一个&#8220;名称-值&#8221;对分配一个节点；选择二是分配一个能容纳（如五个）&#8220;名称-值&#8221;对的结构。例如，一般情况下，如果存储四对，就可减少节点的数量，如果需要额外的空间数量，则使用附加的链表指针。&nbsp;<br>块化是友好的处理器高速缓存，特别是对于&nbsp;L1-高速缓存，因为它提供了增加的位置&nbsp;—不用说对于块分配，很多数据块会在同一个虚拟页中。</p>
<p>正确使用&nbsp;_amblksiz。C&nbsp;运行时&nbsp;(CRT)&nbsp;有它的自定义前端分配程序，该分配程序从后端（Win32&nbsp;堆）分配大小为&nbsp;_amblksiz&nbsp;的块。将&nbsp;_amblksiz&nbsp;设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用&nbsp;CRT&nbsp;的程序适用。<br>使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面，代码会有点特殊，但如果经过深思熟虑，代码还是很容易管理的。</p>
<p>其他提高性能的技术<br>下面是一些提高速度的技术：&nbsp;</p>
<p>使用&nbsp;Windows&nbsp;NT5&nbsp;堆&nbsp;<br>由于几个同事的努力和辛勤工作，1998&nbsp;年初&nbsp;Microsoft&nbsp;Windows(R)&nbsp;2000&nbsp;中有了几个重大改进：</p>
<p>改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构，防止多线程式的使用。但不幸的是，在高通信量的情况下，堆仍受困于全局锁，导致高竞争和低性能。Windows&nbsp;2000&nbsp;中，锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。</p>
<p><br>使用&nbsp;&#8220;Lookaside&#8221;列表。堆数据结构对块的所有空闲项使用了大小在&nbsp;8&nbsp;到&nbsp;1,024&nbsp;字节（以&nbsp;8-字节递增）的快速高速缓存。快速高速缓存最初保护在全局锁内。现在，使用&nbsp;lookaside&nbsp;列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定，而是使用&nbsp;64&nbsp;位的互锁操作，因此提高了性能。</p>
<p><br>内部数据结构算法也得到改进。<br>这些改进避免了对分配高速缓存的需求，但不排除其他的优化。使用&nbsp;Windows&nbsp;NT5&nbsp;堆评估您的代码；它对小于&nbsp;1,024&nbsp;字节&nbsp;(1&nbsp;KB)&nbsp;的块（来自前端分配程序的块）是最佳的。GlobalAlloc()&nbsp;和&nbsp;LocalAlloc()&nbsp;建立在同一堆上，是存取每个进程堆的通用机制。如果希望获得高的局部性能，则使用&nbsp;Heap(R)&nbsp;API&nbsp;来存取每个进程堆，或为分配操作创建自己的堆。如果需要对大块操作，也可以直接使用&nbsp;VirtualAlloc()&nbsp;/&nbsp;VirtualFree()&nbsp;操作。</p>
<p>上述改进已在&nbsp;Windows&nbsp;2000&nbsp;beta&nbsp;2&nbsp;和&nbsp;Windows&nbsp;NT&nbsp;4.0&nbsp;SP4&nbsp;中使用。改进后，堆锁的竞争率显著降低。这使所有&nbsp;Win32&nbsp;堆的直接用户受益。CRT&nbsp;堆建立于&nbsp;Win32&nbsp;堆的顶部，但它使用自己的小块堆，因而不能从&nbsp;Windows&nbsp;NT&nbsp;改进中受益。（Visual&nbsp;C++&nbsp;版本&nbsp;6.0&nbsp;也有改进的堆分配程序。）</p>
<p>使用分配高速缓存&nbsp;<br>分配高速缓存允许高速缓存分配的块，以便将来重用。这能够减少对进程堆（或全局堆）的分配/释放调用的次数，也允许最大限度的重用曾经分配的块。另外，分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。</p>
<p>典型地，自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设计成一套固定大小（如&nbsp;32&nbsp;字节、64&nbsp;字节、128&nbsp;字节等）。这一个很好的策略，但这种自定义堆分配程序丢失与分配和释放的对象相关的&#8220;语义信息&#8221;。&nbsp;</p>
<p>与自定义堆分配程序相反，&#8220;分配高速缓存&#8221;作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外，它们还能够保留大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化，这些参数表示并发级别、对象大小和保持在空闲列表中的元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池（不超过指定的阀值）并使用私有保护锁。合在一起，分配高速缓存和私有锁减少了与主系统堆的通信量，因而提供了增加的并发、最大限度的重用和较高的可伸缩性。</p>
<p>需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动，将释放分配对象的池，从而提高性能。</p>
<p>可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一，这种关系可以用来减少内存分配。</p>
<p>分配高速缓存也起到了调试助手的作用，帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名，甚至能够找到确切的失败的调用者。</p>
<p>MP&nbsp;堆&nbsp;<br>MP&nbsp;堆是对多处理器友好的分布式分配的程序包，在&nbsp;Win32&nbsp;SDK（Windows&nbsp;NT&nbsp;4.0&nbsp;和更新版本）中可以得到。最初由&nbsp;JVert&nbsp;实现，此处堆抽象建立在&nbsp;Win32&nbsp;堆程序包的顶部。MP&nbsp;堆创建多个&nbsp;Win32&nbsp;堆，并试图将分配调用分布到不同堆，以减少在所有单一锁上的竞争。</p>
<p>本程序包是好的步骤&nbsp;—一种改进的&nbsp;MP-友好的自定义堆分配程序。但是，它不提供语义信息和缺乏统计功能。通常将&nbsp;MP&nbsp;堆作为&nbsp;SDK&nbsp;库来使用。如果使用这个&nbsp;SDK&nbsp;创建可重用组件，您将大大受益。但是，如果在每个&nbsp;DLL&nbsp;中建立这个&nbsp;SDK&nbsp;库，将增加工作设置。</p>
<p>重新思考算法和数据结构&nbsp;<br>要在多处理器机器上伸缩，则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问，&#8220;我能用不同的数据结构完成此工作吗？&#8221;例如，如果在应用程序初始化时加载了只读项的列表，这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎片，从而增强性能。</p>
<p>减少需要的小对象的数量减少堆分配程序的负载。例如，我们在服务器的关键处理路径上使用五个不同的对象，每个对象单独分配和释放。一起高速缓存这些对象，把堆调用从五个减少到一个，显著减少了堆的负载，特别当每秒钟处理&nbsp;1,000&nbsp;个以上的请求时。</p>
<p>如果大量使用&#8220;Automation&#8221;结构，请考虑从主线代码中删除&#8220;Automation&nbsp;BSTR&#8221;，或至少避免重复的&nbsp;BSTR&nbsp;操作。（BSTR&nbsp;连接导致过多的重分配和分配/释放操作。）</p>
<p>摘要<br>对所有平台往往都存在堆实现，因此有巨大的开销。每个单独代码都有特定的要求，但设计能采用本文讨论的基本理论来减少堆之间的相互作用。&nbsp;</p>
<p>评价您的代码中堆的使用。</p>
<p><br>改进您的代码，以使用较少的堆调用：分析关键路径和固定数据结构。</p>
<p><br>在实现自定义的包装程序之前使用量化堆调用成本的方法。</p>
<p><br>如果对性能不满意，请要求&nbsp;OS&nbsp;组改进堆。更多这类请求意味着对改进堆的更多关注。</p>
<p><br>要求&nbsp;C&nbsp;运行时组针对&nbsp;OS&nbsp;所提供的堆制作小巧的分配包装程序。随着&nbsp;OS&nbsp;堆的改进，C&nbsp;运行时堆调用的成本将减小。</p>
<p><br>操作系统（Windows&nbsp;NT&nbsp;家族）正在不断改进堆。请随时关注和利用这些改进。<br>Murali&nbsp;Krishnan&nbsp;是&nbsp;Internet&nbsp;Information&nbsp;Server&nbsp;(IIS)&nbsp;组的首席软件设计工程师。从&nbsp;1.0&nbsp;版本开始他就设计&nbsp;IIS，并成功发行了&nbsp;1.0&nbsp;版本到&nbsp;4.0&nbsp;版本。Murali&nbsp;组织并领导&nbsp;IIS&nbsp;性能组三年&nbsp;(1995-1998),&nbsp;从一开始就影响&nbsp;IIS&nbsp;性能。他拥有威斯康星州&nbsp;Madison&nbsp;大学的&nbsp;M.S.和印度&nbsp;Anna&nbsp;大学的&nbsp;B.S.。工作之外，他喜欢阅读、打排球和家庭烹饪。<br><br><br><br><img src="" align=absMiddle border=0><a href="http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835" target=_blank><font color=#000000><u>http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835</u></font></a><br>我在学习对象的生存方式的时候见到一种是在堆栈(stack)之中，如下&nbsp;&nbsp;<br>CObject&nbsp;&nbsp;object;&nbsp;&nbsp;<br>还有一种是在堆(heap)中&nbsp;&nbsp;如下&nbsp;&nbsp;<br>CObject*&nbsp;&nbsp;pobject=new&nbsp;&nbsp;CObject();&nbsp;&nbsp;<br>&nbsp;<br>请问&nbsp;&nbsp;<br>（1）这两种方式有什么区别？&nbsp;&nbsp;<br>（2）堆栈与堆有什么区别？？&nbsp;&nbsp;<br>&nbsp;<br>&nbsp;<br>---------------------------------------------------------------&nbsp;&nbsp;<br>&nbsp;<br>1)&nbsp;&nbsp;about&nbsp;&nbsp;stack,&nbsp;&nbsp;system&nbsp;&nbsp;will&nbsp;&nbsp;allocate&nbsp;&nbsp;memory&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp;&nbsp;instance&nbsp;&nbsp;of&nbsp;&nbsp;object&nbsp;&nbsp;automatically,&nbsp;&nbsp;and&nbsp;&nbsp;to&nbsp;&nbsp;the <br>&nbsp;heap,&nbsp;&nbsp;you&nbsp;&nbsp;must&nbsp;&nbsp;allocate&nbsp;&nbsp;memory&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp;&nbsp;instance&nbsp;&nbsp;of&nbsp;&nbsp;object&nbsp;&nbsp;with&nbsp;&nbsp;new&nbsp;&nbsp;or&nbsp;&nbsp;malloc&nbsp;&nbsp;manually.&nbsp;&nbsp;<br>2)&nbsp;&nbsp;when&nbsp;&nbsp;function&nbsp;&nbsp;ends,&nbsp;&nbsp;system&nbsp;&nbsp;will&nbsp;&nbsp;automatically&nbsp;&nbsp;free&nbsp;&nbsp;the&nbsp;&nbsp;memory&nbsp;&nbsp;area&nbsp;&nbsp;of&nbsp;&nbsp;stack,&nbsp;&nbsp;but&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp; <br>heap,&nbsp;&nbsp;you&nbsp;&nbsp;must&nbsp;&nbsp;free&nbsp;&nbsp;the&nbsp;&nbsp;memory&nbsp;&nbsp;area&nbsp;&nbsp;manually&nbsp;&nbsp;with&nbsp;&nbsp;free&nbsp;&nbsp;or&nbsp;&nbsp;delete,&nbsp;&nbsp;else&nbsp;&nbsp;it&nbsp;&nbsp;will&nbsp;&nbsp;result&nbsp;&nbsp;in&nbsp;&nbsp;memory <br>leak.&nbsp;&nbsp;<br>3)栈内存分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限。&nbsp;&nbsp;<br>4）堆上分配的内存可以有我们自己决定，使用非常灵活。&nbsp;&nbsp;<br>---------------------------------------------------------------&nbsp;&nbsp;</p>
</div>
</div>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/83385.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2009-05-19 16:53 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/05/19/83385.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>详解函数调用约定</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/15/83062.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Fri, 15 May 2009 09:30:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/15/83062.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/83062.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/15/83062.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/83062.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/83062.html</trackback:ping><description><![CDATA[<br>在编写windows程序时，我们经常发现一些函数的前面带有WINAPI等的关键字(不知道这样描述是否准确，请明白的读者联系本人更正<img height=20 src="http://www.cppblog.com/Emoticons/QQ/13.gif" width=20 border=0>)，如windows的消息响应函数定义如下：<br><br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">1</span>&nbsp;<span style="COLOR: #000000">LRESULT&nbsp;CALLBACK&nbsp;WndProc&nbsp;(HWND,&nbsp;UINT,&nbsp;WPARAM,&nbsp;LPARAM)&nbsp;;</span></div>
<br>这里的LRESULT在windows中被定义为long型，而CALLBACK则被定义成了__stacall，仔细看了一下，在WINDEF.H中还包含如下定义：<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">1</span><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;CALLBACK&nbsp;&nbsp;&nbsp;&nbsp;__stdcall</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">2</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;WINAPI&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__stdcall</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">3</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;WINAPIV&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__cdecl</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">4</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;APIENTRY&nbsp;&nbsp;&nbsp;&nbsp;WINAPI</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">5</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;APIPRIVATE&nbsp;&nbsp;__stdcall</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">6</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PASCAL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__stdcall</span></div>
<br>那么，这里的__stacall、__cdecl到底是什么意思呢，又有什么作用呢？我经过查找相关资料对其有了些许浅显的了解，这里与大家一起分享。<br><br>我们知道，在C语言中假设我们有这样一个函数定义：<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;function_add(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a,&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b);</span></div>
那么只要用<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">1</span><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;x&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">,&nbsp;y&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">5</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #008080">2</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;result&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;function_add(x,&nbsp;y);</span></div>
这样的方式就可以对函数进行调用了。但是，在计算机中，当高级语言程序被编译成计算机可以识别的机器码时，有一个问题就凸现出来：在CPU中，计算机没有办法知道一个函数调用需要多少个参数、这些参数是什么样的，也没有硬件可以保存这些参数。也就是说，计算机并不知道应该怎么给这个函数传递参数，传递参数的工作必须由函数调用者和函数本身来协调。为此，计算机提供了一种被称为栈的数据结构来支持函数的参数传递。<br><br>栈是一种先进后出的数据结构，栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶的上方向堆栈中加入数据，这个操作被称为压栈(Push)，压栈以后，栈顶自动变成新加入数据项的位置，栈顶指针也随之修改。用户也可以从堆栈中取出栈顶元素，这个操作被称为弹出栈(pop)，弹出栈以后，栈顶的下一个元素变成栈顶，栈顶指针随之修改。<br><br>函数调用时，调用者依次把参数压栈，然后调用函数，函数被调用以后，在堆栈中取出数据，并进行计算。函数计算结束以后，或者调用者、或者函数本身修改堆栈，使堆栈恢复原状。问题的关键就在这里，到底应该如何清除栈呢？<br><br>函数调用需要进行参数传递，在参数传递过程中有两个很重要的问题必须得到明确说明：<br>&nbsp;&nbsp;&nbsp; 1. 当参数个数多于一个时，按照什么样的顺序把参数压入栈中<br>&nbsp;&nbsp;&nbsp; 2. 函数调用后，由谁来负责把堆栈恢复原状<br><br>在高级语言中，函数调用约定就是用来说明这两个问题的。常见的函数调用约定有：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stdcall<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cdecl<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fastcall<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; thiscall<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; naked call<br><br>下面一一进行介绍。<br><br>一、stdcall调用约定<br><br>stdcall，也可写作__stdcall，很多时候被称为pascal调用约定，因为pascal是早期很常见的一种教学用计算机程序设计语言，其语法严谨，使用的函数调用约定就是stdcall。几乎我们写的每一个WINDOWS API函数都是__stdcall类型的。在Microsoft C++系列的C/C++编译器中，常常用PASCAL宏来声明这个调用约定，类似的宏还有WINAPI和CALLBACK(如文章开头引用的在WINDEF.H头文件中的定义)。<br><br>stdcall调用约定声明的语法为（以前面的function_add函数为例）：<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;__stdcall&nbsp;function_add(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a,</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b);</span></div>
<br>stdcall调用约定意味着：<br>(1) 参数从右向左压入堆栈<br>(2) 函数自身修改堆栈<br>(3) 函数名自动加前导的下划线，后面紧跟一个@符号，其后紧跟着参数的大小<br><br>以上述这个函数为例，参数b首先被压栈，然后是参数a，函数调用function_add(1, 2)调用处翻译成汇编语言将变成：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;// 第二个参数入栈<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 第一个参数入栈<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; function_add&nbsp;&nbsp;&nbsp;&nbsp;// 调用参数，注意此时自动把cs:eip入栈<br><br>而对于函数自身，则可翻译为：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ebp　　　　　　&nbsp;&nbsp;&nbsp;// 保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov　&nbsp;&nbsp;&nbsp;ebp, esp　　　　&nbsp;&nbsp;&nbsp; // 保存堆栈指针<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov　&nbsp;&nbsp;&nbsp;eax,[ebp + 8H]　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 堆栈中ebp指向位置之前依次保存有ebp, cs:eip, a, b, ebp +8指向a<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add　&nbsp;&nbsp;&nbsp; eax,[ebp + 0CH]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 堆栈中ebp + 12处保存了b<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov　&nbsp;&nbsp;&nbsp;esp, ebp　　　　&nbsp;&nbsp; //&nbsp; 恢复esp<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop　&nbsp;&nbsp;&nbsp; ebp<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8<br><br>而在编译时，这个函数的名字被翻译成<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#64;&#56;"><font color=#336699>_function@8</font></a>
<p>　　注意不同编译器会插入自己的汇编代码以提供编译的通用性，但是大体代码如此。其中在函数开始处保留esp到ebp中，在函数结束恢复是编译器常用的方法。</p>
<p>　　从函数调用看，2和1依次被push进堆栈，而在函数中又通过相对于ebp(即刚进函数时的堆栈指针）的偏移量存取参数。函数结束后，ret 8表示清理8个字节的堆栈，函数自己恢复了堆栈。<br><br>由于不同的编译器产生栈的方式不尽相同，调用者就不一定能够正常的完成堆栈的清除工作，但函数本身自己可以解决清除工作，所以，在跨平台的程序开发中的函数调用，我们通常都使用__stdcall约定，windows下的绝大多数函数也都是stdcall调用。既然如此，为什么还需要__cdecl呢？别着急，接着往下看。<br><br>二、cdecl调用约定<br><br>cdecl，也可写作__cdecl，又称为C调用约定，是C/C++语言和MFC程序默认缺省的调用约定，它的定义语法是：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;function&nbsp;(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a&nbsp;,</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b)　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">不加修饰就是C调用约定</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;__cdecl&nbsp;function(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a,</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b)&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">明确指出C调用约定<br></span></div>
<p><br>采用__cdecl约定时，函数参数按照从右到左的顺序入栈，并且由调用函数者把参数弹出栈以清理堆栈。因此，实现可变参数的函数只能使用该调用约定。由于这种变化，C调用约定允许函数的参数的个数是不固定的，这也是C语言的一大特色。同时，由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码，所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。 <br><br>对于前面的function函数，使用cdecl后的汇编码变成： <br><br>调用处<br>　　push&nbsp;&nbsp; 1<br>　　push&nbsp;&nbsp; 2<br>　　call&nbsp;&nbsp;&nbsp;&nbsp; function<br>　　add　esp, 8　　　　　 // 注意：这里调用者在恢复堆栈</p>
<p>　　被调用函数_function处<br>　　push&nbsp;&nbsp;&nbsp; ebp　　　　　　// 保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复<br>　　mov&nbsp;&nbsp;&nbsp; ebp,esp　　　　 // 保存堆栈指针<br>　　mov　eax,[ebp + 8H]　 // 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a<br>　　add　eax,[ebp + 0CH]&nbsp;&nbsp;&nbsp;&nbsp;// 堆栈中ebp + 12处保存了b<br>　　mov　esp,ebp　　　　 // 恢复esp<br>　　pop　ebp<br>　　ret　　　　　　　　　//&nbsp; 注意，这里没有修改堆栈</p>
<p><br>不写了，累得慌，呵呵 转载两篇文章吧<br><br></p>
<p>__stdcall,__cdecl,_cdecl,_stdcall,。__fastcall,_fastcall 区别简介&nbsp;</p>
<p>1.</p>
<p>今天写线程函数时，发现msdn中对ThreadProc的定义有要求：DWORD WINAPI ThreadProc(LPVOID lpParameter); </p>
<p>不解为什么要用WINAPI宏定义，查了后发现下面的定义。于是乎需要区别__stdcall和__cdecl两者的区别； #define CALLBACK __stdcall<br>#define WINAPI __stdcall<br>#define WINAPIV __cdecl<br>#define APIENTRY WINAPI<br>#define APIPRIVATE __stdcall<br>#define PASCAL __stdcall<br>#define cdecl _cdecl<br>#ifndef CDECL<br>#define CDECL _cdecl<br>#endif </p>
<p>几乎我们写的每一个WINDOWS API函数都是__stdcall类型的，首先，需要了解两者之间的区别： WINDOWS的函数调用时需要用到栈（STACK，一种先入后出的存储结构）。当函数调用完成后，栈需要清楚，这里就是问题的关键，如何清除？？ 如果我们的函数使用了_cdecl，那么栈的清除工作是由调用者，用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题，不同的编译器产生栈的方式不尽相同，那么调用者能否正常的完成清除工作呢？答案是不能。 如果使用__stdcall，上面的问题就解决了，函数自己解决清除工作。所以，在跨（开发）平台的调用中，我们都使用__stdcall（虽然有时是以WINAPI的样子出现）。那么为什么还需要_cdecl呢？当我们遇到这样的函数如fprintf()它的参数是可变的，不定长的，被调用者事先无法知道参数的长度，事后的清除工作也无法正常的进行，因此，这种情况我们只能使用_cdecl。到这里我们有一个结论，如果你的程序中没有涉及可变参数，最好使用__stdcall关键字。</p>
<p>2.</p>
<p>__cdecl,__stdcall是声明的函数调用协议.主要是传参和弹栈方面的不同.一般c++用的是__cdecl,windows里大都用的是__stdcall(API) <br><br>__cdecl是C/C++和MFC程序默认使用的调用约定，也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdecl约定时，函数参数按照从右到左的顺序入栈，并且由调用函数者把参数弹出栈以清理堆栈。因此，实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码，所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。 <br>__stdcall调用约定用于调用Win32 API函数。采用__stdcall约定时，函数参数按照从右到左的顺序入栈，被调用的函数在返回前清理传送参数的栈，函数参数个数固定。由于函数体本身知道传进来的参数个数，因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。__stdcall可以写成_stdcall。 <br>__fastcall约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节（DWORD）的参数分别放在ECX和EDX寄存器，其余的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall</p>
<p>3.</p>
<p style="TEXT-INDENT: 2em">__stdcall:</p>
<p style="TEXT-INDENT: 2em">_stdcall 调用约定相当于16位动态库中经常使用的PASCAL调用约定。</p>
<p style="TEXT-INDENT: 2em">
<table cellSpacing=0 cellPadding=0 width=160 align=left border=0>
    <tbody>
        <tr>
            <td>&nbsp;</td>
        </tr>
    </tbody>
</table>
在32位的VC++5.0中PASCAL调用约定不再被支持（实际上它已被定义为__stdcall。除了__pascal外，__fortran和__syscall也不被支持），取而代之的是__stdcall调用约定。两者实质上是一致的，即函数的参数自右向左通过栈传递，被调用的函数在返回前清理传送参数的内存栈，但不同的是函数名的修饰部分（关于函数名的修饰部分在后面将详细说明）。</p>
<p style="TEXT-INDENT: 2em">_stdcall是Pascal程序的缺省调用方式，通常用于Win32 Api中，函数采用从右到左的压栈方式，自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀，在函数名后加上"@"和参数的字节数。</p>
<p style="TEXT-INDENT: 2em">_cdecl:</p>
<p style="TEXT-INDENT: 2em">_cdecl c调用约定, 按从右至左的顺序压参数入栈，由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的（正因为如此，实现可变参数的函数只能使用该调用约定）。另外，在函数名修饰约定方面也有所不同。</p>
<p style="TEXT-INDENT: 2em">_cdecl是C和C＋＋程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码，所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。</p>
<p style="TEXT-INDENT: 2em">__fastcall:</p>
<p style="TEXT-INDENT: 2em">__fastcall调用约定是"人"如其名，它的主要特点就是快，因为它是通过寄存器来传送参数的（实际上，它用ECX和EDX传送前两个双字（DWORD）或更小的参数，剩下的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的内存栈），在函数名修饰约定方面，它和前两者均不同。</p>
<p style="TEXT-INDENT: 2em">_fastcall方式的函数采用寄存器传递参数，VC将函数编译后会在函数名前面加上"@"前缀，在函数名后加上"@"和参数的字节数。</p>
<p style="TEXT-INDENT: 2em">thiscall:</p>
<p style="TEXT-INDENT: 2em">thiscall仅仅应用于"C++"成员函数。this指针存放于CX寄存器，参数从右到左压。thiscall不是关键词，因此不能被程序员指定。</p>
<p style="TEXT-INDENT: 2em">naked call:</p>
<p style="TEXT-INDENT: 2em">采用1-4的调用约定时，如果必要的话，进入函数时编译器会产生代码来保存ESI，EDI，EBX，EBP寄存器，退出函数时则产生代码恢复这些寄存器的内容。</p>
<p style="TEXT-INDENT: 2em">naked call不产生这样的代码。naked call不是类型修饰符，故必须和_declspec共同使用。</p>
<p style="TEXT-INDENT: 2em">另附:</p>
<p style="TEXT-INDENT: 2em">关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前，也可以在编译环境的Setting...\C/C++ \Code Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时，直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd，即__cdecl。</p>
<p style="TEXT-INDENT: 2em">要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定，至于函数名修饰约定，可以通过其它方法模仿。还有一个值得一提的是WINAPI宏，Windows.h支持该宏，它可以将出函数翻译成适当的调用约定，在WIN32中，它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。</p>
<p style="TEXT-INDENT: 2em">名字修饰约定 <br><br>1、修饰名(Decoration name) <br>&#8220;C&#8221;或者&#8220;C++&#8221;函数在内部（编译和链接）通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的，如在模块定义文件里头指定输出&#8220;C++&#8221;重载函数、构造函数、析构函数，又如在汇编代码里调用&#8220;C&#8221;&#8221;或&#8220;C++&#8221;函数等。 <br><br>修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。 <br><br>2、名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。函数名修饰约定随编译种类和调用约定的不同而不同，下面分别说明。 <br><br>a、C编译时函数名修饰约定规则： <br><br>__stdcall调用约定在输出函数名前加上一个下划线前缀，后面加上一个&#8220;@&#8221;符号和其参数的字节数，格式为_functionname@number。 <br><br>__cdecl调用约定仅在输出函数名前加上一个下划线前缀，格式为_functionname。</p>
<p style="TEXT-INDENT: 2em">__fastcall调用约定在输出函数名前加上一个&#8220;@&#8221;符号，后面也是一个&#8220;@&#8221;符号和其参数的字节数，格式为@functionname@number。 <br><br>它们均不改变输出函数名中的字符大小写，这和PASCAL调用约定不同，PASCAL约定输出的函数名无任何修饰且全部大写。 <br><br>b、C++编译时函数名修饰约定规则： <br><br>__stdcall调用约定： <br>1、以&#8220;?&#8221;标识函数名的开始，后跟函数名； <br>2、函数名后面以&#8220;@@YG&#8221;标识参数表的开始，后跟参数表； <br>3、参数表以代号表示： <br>X--void ， <br>D--char， <br>E--unsigned char， <br>F--short， <br>H--int， <br>I--unsigned int， <br>J--long， <br>K--unsigned long， <br>M--float， <br>N--double， <br>_N--bool， <br>.... <br>PA--表示指针，后面的代号表明指针类型，如果相同类型的指针连续出现，以&#8220;0&#8221;代替，一个&#8220;0&#8221;代表一次重复； <br>4、参数表的第一项为该函数的返回值类型，其后依次为参数的数据类型,指针标识在其所指数据类型前； <br>5、参数表后以&#8220;@Z&#8221;标识整个名字的结束，如果该函数无参数，则以&#8220;Z&#8221;标识结束。 <br><br>其格式为&#8220;?functionname@@YG*****@Z&#8221;或&#8220;?functionname@@YG*XZ&#8221;，例如 <br>int Test1（char *var1,unsigned long）-----&#8220;?Test1@@YGHPADK@Z&#8221; <br>void Test2（） -----&#8220;?Test2@@YGXXZ&#8221; <br><br>__cdecl调用约定： <br>规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的&#8220;@@YG&#8221;变为&#8220;@@YA&#8221;。 <br><br>__fastcall调用约定： <br>规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的&#8220;@@YG&#8221;变为&#8220;@@YI&#8221;。 <br>VC++对函数的省缺声明是&#8220;__cedcl&#8220;,将只能被C/C++调用. <br><br>CB在输出函数声明时使用4种修饰符号 <br>//__cdecl <br>cb的默认值，它会在输出函数名前加_，并保留此函数名不变，参数按照从右到左的顺序依次传递给栈，也可以写成_cdecl和cdecl形式。 <br>//__fastcall <br>她修饰的函数的参数将尽肯呢感地使用寄存器来处理，其函数名前加@，参数按照从左到右的顺序压栈； <br>//__pascal <br>它说明的函数名使用Pascal格式的命名约定。这时函数名全部大写。参数按照从左到右的顺序压栈； <br>//__stdcall <br>使用标准约定的函数名。函数名不会改变。使用__stdcall修饰时。参数按照由右到左的顺序压栈，也可以是_stdcall；</p>
<p style="TEXT-INDENT: 2em">VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用. </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">注意：</p>
<p style="TEXT-INDENT: 2em">1、_beginthread需要__cdecl的线程函数地址，_beginthreadex和CreateThread需要__stdcall的线程函数地址。</p>
<p style="TEXT-INDENT: 2em">2、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义：</p>
<p style="TEXT-INDENT: 2em">&nbsp;#define CALLBACK __stdcall</p>
<p style="TEXT-INDENT: 2em">&nbsp;#define WINAPI　 __stdcall</p>
<p style="TEXT-INDENT: 2em">3、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);</p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp; typedef int (__cdecl*FunPointer)(int a, int b);</p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp; 修饰符的书写顺序如上。</p>
<p style="TEXT-INDENT: 2em">4、extern "C"的作用：如果Add(int a, int b)是在c语言编译器编译，而在c++文件使用，则需要在c++文件中声明：extern "C" Add(int a, int b)，因为c编译器和c++编译器对函数名的解释不一样（c++编译器解释函数名的时候要考虑函数参数，这样是了方便函数重载，而在c语言中不存在函数重载的问题），使用extern "C"，实质就是告诉c++编译器，该函数是c库里面的函数。如果不使用extern "C"则会出现链接错误。</p>
<p style="TEXT-INDENT: 2em">一般象如下使用：</p>
<p style="TEXT-INDENT: 2em">#ifdef _cplusplus </p>
<p style="TEXT-INDENT: 2em">#define EXTERN_C extern "C"</p>
<p style="TEXT-INDENT: 2em">#else</p>
<p style="TEXT-INDENT: 2em">#define EXTERN_C extern</p>
<p style="TEXT-INDENT: 2em">#endif</p>
<p style="TEXT-INDENT: 2em">#ifdef _cplusplus </p>
<p style="TEXT-INDENT: 2em">extern "C"{</p>
<p style="TEXT-INDENT: 2em">#endif </p>
<p style="TEXT-INDENT: 2em">&nbsp;EXTERN_C int func(int a, int b); </p>
<p style="TEXT-INDENT: 2em">#ifdef _cplusplus </p>
<p style="TEXT-INDENT: 2em">} </p>
<p style="TEXT-INDENT: 2em">#endif</p>
<p style="TEXT-INDENT: 2em">5、MFC提供了一些宏，可以使用AFX_EXT_CLASS来代替__declspec(DLLexport)，并修饰类名，从而导出类，AFX_API_EXPORT来修饰函数，AFX_DATA_EXPORT来修饰变量</p>
<p style="TEXT-INDENT: 2em">AFX_CLASS_IMPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_API_IMPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_DATA_IMPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_CLASS_EXPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_API_EXPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_DATA_EXPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_EXT_CLASS：#ifdef _AFXEXT </p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;AFX_CLASS_EXPORT</p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #else</p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;AFX_CLASS_IMPORT</p>
<p style="TEXT-INDENT: 2em">6、DLLMain负责初始化(Initialization)和结束(Termination)工作，每当一个新的进程或者该进程的新的线程访问DLL时，或者访问DLL的每一个进程或者线程不再使用DLL或者结束时，都会调用DLLMain。但是，使用TerminateProcess或TerminateThread结束进程或者线程，不会调用DLLMain。</p>
<p style="TEXT-INDENT: 2em">7、一个DLL在内存中只有一个实例</p>
<p style="TEXT-INDENT: 2em">DLL程序和调用其输出函数的程序的关系：</p>
<p style="TEXT-INDENT: 2em">1)、DLL与进程、线程之间的关系</p>
<p style="TEXT-INDENT: 2em">DLL模块被映射到调用它的进程的虚拟地址空间。</p>
<p style="TEXT-INDENT: 2em">DLL使用的内存从调用进程的虚拟地址空间分配，只能被该进程的线程所访问。</p>
<p style="TEXT-INDENT: 2em">DLL的句柄可以被调用进程使用；调用进程的句柄可以被DLL使用。</p>
<p style="TEXT-INDENT: 2em">DLLDLL可以有自己的数据段，但没有自己的堆栈，使用调用进程的栈，与调用它的应用程序相同的堆栈模式。</p>
<p style="TEXT-INDENT: 2em">2)、关于共享数据段</p>
<p style="TEXT-INDENT: 2em">DLL定义的全局变量可以被调用进程访问；DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量，则需要使用同步机制；对一个DLL的变量，如果希望每个使用DLL的线程都有自己的值，则应该使用线程局部存储(TLS，Thread Local Strorage)。<br></p>
<p><br><br></p>
<h2><strong>论函数调用约定</strong></h2>
<p><br>在C语言中，假设我们有这样的一个函数：<br>　　<br>　　int function(int a,int b)<br>　　<br>　　调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是，当高级语言被编译成计算机可以识别的机器码时，有一个问题就凸现出来：在CPU中，计算机没有办法知道一个函数调用需要多少个、什么样的参数，也没有硬件可以保存这些参数。也就是说，计算机不知道怎么给这个函数传递参数，传递参数的工作必须由函数调用者和函数本身来协调。为此，计算机提供了一种被称为栈的数据结构来支持参数传递。</p>
<p>　　栈是一种先进后出的数据结构，栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项（被称为栈顶）。用户可以在栈顶上方向栈中加入数据，这个操作被称为压栈(Push)，压栈以后，栈顶自动变成新加入数据项的位置，栈顶指针也随之修改。用户也可以从堆栈中取走栈顶，称为弹出栈(pop)，弹出栈后，栈顶下的一个元素变成栈顶，栈顶指针随之修改。</p>
<p>　　函数调用时，调用者依次把参数压栈，然后调用函数，函数被调用以后，在堆栈中取得数据，并进行计算。函数计算结束以后，或者调用者、或者函数本身修改堆栈，使堆栈恢复原装。</p>
<p>　　在参数传递中，有两个很重要的问题必须得到明确说明：<br>　　<br>　　当参数个数多于一个时，按照什么顺序把参数压入堆栈 <br>　　函数调用后，由谁来把堆栈恢复原装 <br>　　在高级语言中，通过函数调用约定来说明这两个问题。常见的调用约定有： </p>
<p>　　stdcall <br>　　cdecl <br>　　fastcall <br>　　thiscall <br>　　naked call</p>
<p>&#160;</p>
<p>　　stdcall调用约定<br>　　stdcall很多时候被称为pascal调用约定，因为pascal是早期很常见的一种教学用计算机程序设计语言，其语法严谨，使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中，常常用PASCAL宏来声明这个调用约定，类似的宏还有WINAPI和CALLBACK。</p>
<p>　　stdcall调用约定声明的语法为(以前文的那个函数为例）：<br>　　<br>　　int __stdcall function(int a,int b)<br>　　<br>　　stdcall的调用约定意味着：1）参数从右向左压入堆栈，2）函数自身修改堆栈 3)函数名自动加前导的下划线，后面紧跟一个@符号，其后紧跟着参数的尺寸</p>
<p>　　以上述这个函数为例，参数b首先被压栈，然后是参数a，函数调用function(1,2)调用处翻译成汇编语言将变成：</p>
<p>　　push 2　　　　　　　 第二个参数入栈<br>　　push 1　　　　　　　 第一个参数入栈<br>　　call function　　　　调用参数，注意此时自动把cs:eip入栈</p>
<p>　　而对于函数自身，则可以翻译为： <br>　　push ebp　　　　　　 保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复<br>　　mov　ebp, esp　　　　保存堆栈指针<br>　　mov　eax,[ebp + 8H]　堆栈中ebp指向位置之前依次保存有ebp, cs:eip, a, b, ebp +8指向a<br>　　add　eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b<br>　　mov　esp, ebp　　　　恢复esp<br>　　pop　ebp<br>　　ret　8</p>
<p>　　而在编译时，这个函数的名字被翻译成<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#64;&#56;"><font color=#336699>_function@8</font></a> </p>
<p>　　注意不同编译器会插入自己的汇编代码以提供编译的通用性，但是大体代码如此。其中在函数开始处保留esp到ebp中，在函数结束恢复是编译器常用的方法。</p>
<p>　　从函数调用看，2和1依次被push进堆栈，而在函数中又通过相对于ebp(即刚进函数时的堆栈指针）的偏移量存取参数。函数结束后，ret 8表示清理8个字节的堆栈，函数自己恢复了堆栈。</p>
<p>　　<br>　　cdecl调用约定<br>　　cdecl调用约定又称为C调用约定，是C语言缺省的调用约定，它的定义语法是：</p>
<p>　　int function (int a ,int b)　//不加修饰就是C调用约定<br>　　int __cdecl function(int a,int b)//明确指出C调用约定</p>
<p>　　在写本文时，出乎我的意料，发现cdecl调用约定的参数压栈顺序是和stdcall是一样的，参数首先由右向左压入堆栈。所不同的是，函数本身不清理堆栈，调用者负责清理堆栈。由于这种变化，C调用约定允许函数的参数的个数是不固定的，这也是C语言的一大特色。对于前面的function函数，使用cdecl后的汇编码变成： </p>
<p>　　调用处<br>　　push 1<br>　　push 2<br>　　call function<br>　　add　esp, 8　　　　　注意：这里调用者在恢复堆栈</p>
<p>　　被调用函数_function处<br>　　push ebp　　　　　　 保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复<br>　　mov　ebp,esp　　　　 保存堆栈指针<br>　　mov　eax,[ebp + 8H]　堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a<br>　　add　eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b<br>　　mov　esp,ebp　　　　 恢复esp<br>　　pop　ebp<br>　　ret　　　　　　　　　注意，这里没有修改堆栈</p>
<p>　　MSDN中说，该修饰自动在函数名前加前导的下划线，因此函数名在符号表中被记录为_function，但是我在编译时似乎没有看到这种变化。</p>
<p>　　由于参数按照从右向左顺序压栈，因此最开始的参数在最接近栈顶的位置，因此当采用不定个数参数时，第一个参数在栈中的位置肯定能知道，只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来，就可以使用不定参数，例如对于CRT中的sprintf函数，定义为： <br>　　int sprintf(char* buffer,const char* format,...)<br>　　由于所有的不定参数都可以通过format确定，因此使用不定个数的参数是没有问题的。</p>
<p>　　fastcall<br>　　fastcall调用约定和stdcall类似，它意味着： <br>　　<br>　　函数的第一个和第二个DWORD参数（或者尺寸更小的）通过ecx和edx传递，其他参数通过从右向左的顺序压栈 <br>　　被调用函数清理堆栈 <br>　　函数名修改规则同stdcall <br>　　其声明语法为：int fastcall function(int a, int b)</p>
<p>　　thiscall<br>　　thiscall是唯一一个不能明确指明的函数修饰，因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针，因此必须特殊处理，thiscall意味着： </p>
<p>　　参数从右向左入栈 <br>　　如果参数个数确定，this指针通过ecx传递给被调用者；如果参数个数不确定，this指针在所有参数压栈后被压入堆栈。对参数个数不定的，调用者清理堆栈，否则函数自己清理堆栈为了说明这个调用约定，定义如下类和使用代码： </p>
<p>　　class A<br>　　{<br>　　public:<br>　　　 int function1(int a,int b);<br>　　　 int function2(int a,...);<br>　　};</p>
<p>　　int A::function1 (int a,int b)<br>　　{<br>　　　 return a+b;<br>　　}</p>
<p>　　#include &lt;stdarg.h&gt;<br>　　int A::function2(int a,...)<br>　　{<br>　　　 va_list ap;<br>　　　 va_start(ap,a);<br>　　　 int i;<br>　　　 int result = 0;<br>　　　 for(i = 0 ; i &lt; a ; i ++)<br>　　　 {<br>　　　　　result += va_arg(ap,int);<br>　　　 }<br>　　　 return result;<br>　　}</p>
<p>　　void callee()<br>　　{<br>　　　 A a;<br>　　　 a.function1(1, 2);<br>　　　 a.function2(3, 1, 2, 3);<br>　　}</p>
<p>callee函数被翻译成汇编后就变成： <br>　　//函数function1调用<br>　　00401C1D　 push　　　　2<br>　　00401C1F　 push　　　　1<br>　　00401C21　 lea　　　　 ecx,[ebp-8]<br>　　00401C24　 call　　　　function1　　　　　注意，这里this没有被入栈</p>
<p>　　//函数function2调用<br>　　00401C29　 push　　　　3<br>　　00401C2B　 push　　　　2<br>　　00401C2D　 push　　　　1<br>　　00401C2F　 push　　　　3<br>　　00401C31　 lea　　　　 eax, [ebp-8]　　　 这里引入this指针<br>　　00401C34　 push　　　　eax<br>　　00401C35　 call　　　　function2<br>　　00401C3A　 add　　　　 esp, 14h<br>　　<br>　　可见，对于参数个数固定情况下，它类似于stdcall，不定时则类似cdecl</p>
<p>　　naked call<br>　　这是一个很少见的调用约定，一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码，更特殊的是，你不能用return返回返回值，只能用插入汇编返回结果。这一般用于实模式驱动程序设计，假设定义一个求和的加法程序，可以定义为： </p>
<p>　　__declspec(naked) int　add(int a,int b)<br>　　{<br>　　　 __asm mov eax,a<br>　　　 __asm add eax,b<br>　　　 __asm ret <br>　　}</p>
<p>　　注意，这个函数没有显式的return返回值，返回通过修改eax寄存器实现，而且连退出函数的ret指令都必须显式插入。上面代码被翻译成汇编以后变成： </p>
<p>　　mov eax,[ebp+8]<br>　　add eax,[ebp+12]<br>　　ret 8</p>
<p>　 注意这个修饰是和__stdcall及cdecl结合使用的，前面是它和cdecl结合使用的代码，对于和stdcall结合的代码，则变成： </p>
<p>　　__declspec(naked) int __stdcall function(int a,int b)<br>　 {<br>　　　　__asm mov eax,a<br>　　　　__asm add eax,b<br>　　　　__asm ret 8　　　　//注意后面的8<br>　　}</p>
<p>　　至于这种函数被调用，则和普通的cdecl及stdcall调用函数一致。</p>
<p>　　函数调用约定导致的常见问题<br>　　如果定义的约定和使用的约定不一致，则将导致堆栈被破坏，导致严重问题，下面是两种常见的问题： </p>
<p>　　函数原型声明和函数体定义不一致 <br>　　DLL导入函数时声明了不同的函数约定 <br>　　以后者为例，假设我们在dll种声明了一种函数为： </p>
<p>　　__declspec(dllexport) int func(int a,int b);//注意，这里没有stdcall，使用的是cdecl<br>　　使用时代码为： </p>
<p>　　typedef int (*WINAPI DLLFUNC)func(int a,int b);<br>　　hLib = LoadLibrary(...);</p>
<p>　　DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定<br>　　result = func(1,2);//导致错误</p>
<p>　　由于调用者没有理解WINAPI的含义错误的增加了这个修饰，上述代码必然导致堆栈被破坏，MFC在编译时插入的checkesp函数将告诉你，堆栈被破坏</p>
<p><br><br><br><br><br><br><br><br>&nbsp;</p>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/83062.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2009-05-15 17:30 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/05/15/83062.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何创建自己的dll</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2009/04/28/81304.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Tue, 28 Apr 2009 04:10:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2009/04/28/81304.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/81304.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2009/04/28/81304.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/81304.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/81304.html</trackback:ping><description><![CDATA[<pre>前几天有个朋友问道这个问题，结果因为以前从没搞过这个，对vs2005也不熟悉，竟花了2个小时才搞定，<img height=20 src="http://www.cppblog.com/Emoticons/QQ/07.gif" width=20 border=0>。</pre>
<pre>特地拿来与大家分享，希望能给像我这样的菜鸟们一些帮助，O(&#8745;_&#8745;)O</pre>
<pre><span style="COLOR: #ff0000">【第一步】创建自己的dll</span></pre>
<pre>1.打开vs2005，选择菜单【File-New-Project】，在弹出对话框中选择[Visual C++]下的[Win32]-[Win32 Console Application]，输入工程名后确认。</pre>
<pre>2.在弹出的对话框中选择[next]，在Application Settiongs中选择Application type为Dll，Additional options选择Empty project，然后点Finish。</pre>
<pre>这时就创建了一个空的可以生成dll文件的工程。</pre>
<pre>3.在工程中添加一个头文件(这里为dll_test.h)，在头文件中写入如下内容：</pre>
<pre>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">&nbsp;1</span>&nbsp;<span style="COLOR: #000000">#ifndef&nbsp;_DLL_TUTORIAL_H<br></span><span style="COLOR: #008080">&nbsp;2</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;_DLL-TUTORIAL_H</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;3</span>&nbsp;<span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;4</span>&nbsp;<span style="COLOR: #000000">#include</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">iostream</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;5</span>&nbsp;<span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;6</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#if</span><span style="COLOR: #000000">&nbsp;defined&nbsp;DLL_EXPORT</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;7</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;</span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DECLDIR&nbsp;_declspec(dllexport)</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;8</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#else</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;9</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;</span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DECLDIR&nbsp;_declspec(dllimport)</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">10</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#endif</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">11</span>&nbsp;<span style="COLOR: #000000"><br></span><span style="COLOR: #008080">12</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">extern</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">C</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">13</span>&nbsp;<span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">14</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;DECLDIR int Add(int a, int b);<br>15&nbsp;&nbsp;&nbsp;DECLDIR void Function(void);<br></span><span style="COLOR: #008080">16</span>&nbsp;<span style="COLOR: #000000">}<br>17<br>18 #endif</span></div>
</pre>
<pre>这里要说明的是：</pre>
<pre>在VC中有两个方法来导出dll中定义的函数：</pre>
<pre>  (1) 使用__declspec,这是一个Microsoft定义的关键字。</pre>
<pre>  (2) 创建一个模板定义文件(Module-Definition File，即.DEF)。</pre>
<pre>  第一种方法稍稍比第二种方法简单，在这里我们使用的是第一种方法。</pre>
<pre>    __declspec(dllexport)函数的作用是导出函数符号到在你的Dll中的一个存储类里去。</pre>
<pre>当下面一行被定义时我定义DECLDIR宏来运行这个函数。</pre>
<pre>    #define DLL_EXPORT</pre>
<pre>在此情况下你将导出函数Add(int a,int b)和Function().</pre>
<pre>4.创建一个源文件(名字为dll_test.cpp)，内容如下：</pre>
<pre>&nbsp;
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">&nbsp;1</span>&nbsp;<span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">iostream</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;2</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DLL_EXPORT</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;3</span>&nbsp;<span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">dll_test.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;4</span>&nbsp;<span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;5</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">extern</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">C</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;6</span>&nbsp;<span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">&nbsp;7</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;定义了（DLL中的）所有函数</span><span style="COLOR: #008000"><br></span><span style="COLOR: #008080">&nbsp;8</span>&nbsp;<span style="COLOR: #008000"></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;DECLDIR&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;Add(&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a,&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b&nbsp;)<br></span><span style="COLOR: #008080">&nbsp;9</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br></span><span style="COLOR: #008080">10</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">(&nbsp;a&nbsp;</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">&nbsp;b&nbsp;);<br></span><span style="COLOR: #008080">11</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}<br></span><span style="COLOR: #008080">12</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;<br></span><span style="COLOR: #008080">13</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;DECLDIR&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;Function(&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;)<br></span><span style="COLOR: #008080">14</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br></span><span style="COLOR: #008080">15</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;std::cout&nbsp;</span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">DLL&nbsp;Called!</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;std::endl;<br></span><span style="COLOR: #008080">16</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}<br></span><span style="COLOR: #008080">17</span>&nbsp;<span style="COLOR: #000000">}<br></span><span style="COLOR: #008080">18</span>&nbsp;<span style="COLOR: #000000"></span></div>
</pre>
<pre><span style="COLOR: #ff0000">【第二步】使用创建好的DLL</span></pre>
<pre>现在已经创建了DLL，那么如何在一个应用程序中使用它呢？</pre>
<pre>当DLL被生成后，它创建了一个.dll文件和一个.lib，这两个都是使用dll时需要用到的。</pre>
<pre>在具体介绍之前先看一下dll的链接方式。</pre>
<pre>(1)隐式连接</pre>
<pre>这里有两个方法来载入一个DLL，一个方法是只链接到.lib文件，并将.dll文件放到要使用这个DLL的项目路径中。</pre>
<pre>因此，创建一个新的空的Win32控制台项目并添加一个源文件。将我们创建好的DLL放入与新项目相同的目录下。同时我们还必须链接到dll_test.lib文件。</pre>
<pre>可以在项目属性中设置，也可以在源程序中用下面的语句来链接：
#pragma comment(lib, "dll_test.lib")</pre>
<pre>最后，我们还要在新的win32控制台项目中包含前面的dll_test.h头文件。可以把这个头文件放到新建win32控制台项目的目录中然后在程序中加入语句：
#include "dll_test.h"</pre>
<pre>新项目代码如下：</pre>
<pre>#include&lt;iostream&gt;</pre>
<pre>#include "DLLTutorial.h"</pre>
<pre>int main()</pre>
<pre>{</pre>
<pre>  Function();</pre>
<pre>  std::cout&lt;&lt; Add(32, 56)&lt;&lt; endl;</pre>
<pre>  return 0;</pre>
<pre>}</pre>
<pre>(2)显示链接</pre>
<pre>稍微复杂一点的加载DLL的方法需要用到函数指针和一些Windows函数。但是，通过这种载入DLL的方法，不需要DLL的.lib文件或头文件，而只需要DLL即可。</pre>
<pre>下面列出一些代码：</pre>
<pre>/****************************************************************/
#include &lt;iostream&gt;
#include &lt;windows.h&gt;
typedef int (*AddFunc)(int,int);
typedef void (*FunctionFunc)();</pre>
<pre>int main()</pre>
<pre>{
AddFunc _AddFunc;</pre>
<pre>   FunctionFunc _FunctionFunc;</pre>
<pre>   HINSTANCE hInstLibrary = LoadLibrary("DLL_Tutorial.dll");</pre>
<pre>   if (hInstLibrary == NULL)
{
FreeLibrary(hInstLibrary);
}</pre>
<pre>   _AddFunc = (AddFunc)GetProcAddress(hInstLibrary, "Add");</pre>
<pre>   _FunctionFunc = (FunctionFunc)GetProcAddress(hInstLibrary, "Function");</pre>
<pre>   if ((_AddFunc == NULL) || (_FunctionFunc == NULL))
{
FreeLibrary(hInstLibrary);
}</pre>
<pre>   std::cout &lt;&lt; _AddFunc(23, 43) &lt;&lt; std::endl;</pre>
<pre>   _FunctionFunc();</pre>
<pre>   std::cin.get();</pre>
<pre>   FreeLibrary(hInstLibrary);</pre>
<pre>   return(1);
}
/*******************************************************************/</pre>
<pre>首先可以看到，这里包括进了windows.h头文件，同时去掉了对dll_test.h头文件的包含。原因很简单：因为windows.h包含了一些Windows函数，</pre>
<pre>它也包含了一些将会用到的Windows特定变量。可以去掉DLL的头文件，因为当使用这个方法载入DLL时并不需要其头文件。</pre>
<pre>下面你会看到：以下面形式的一小块古灵精怪的代码:</pre>
<pre>    typedef int (*AddFunc)(int,int);
typedef void (*FunctionFunc)();</pre>
<pre>    这是函数指针。因为这是一个关于DLL的自学指南，深入探究函数指针超出了本指南的范围；因此，现在我们只把它们当作DLL包含的函数的别名。</pre>
<pre>    我喜欢在尾部用&#8220;Func&#8221;命名之。(int,int)部分是这个函数的参数部分，比如，Add函数要获得两个整数；因此，你需要它们</pre>
<pre>（译注：指(int,int)部分）作为函数指针的参数。Function函数没有参数，因此你让它为空。main()部分中的前面两行是声明函数指针以使得你可</pre>
<pre>以认为它们等同于DLL内部的函数。我只是喜欢预先定义它们。</pre>
<pre>      一个HINSTANCE是一个Windows数据类型：是一个实例的句柄；在此情况下，这个实例将是这个DLL。你可以通过使用函数LoadLibrary()获得DLL的</pre>
<pre>实例，它获得一个名称作为参数。</pre>
<pre>     在调用LoadLibrary函数后，你必需查看一下函数返回是否成功。你可以通过检查HINSTANCE是否等于NULL（在Windows.h中定义为0或Windows.h包</pre>
<pre>含的一个头文件）来查看其是否成功。如果其等于NULL，该句柄将是无效的，并且你必需释放这个库。换句话说，你必需释放DLL获得的内存。</pre>
<pre>      如果函数返回成功，你的HINSTANCE就包含了指向DLL的句柄。一旦你获得了指向DLL的句柄，你现在可以从DLL中重新获得函数。</pre>
<pre>     为了这样作，你必须使用函数GetProcAddress()，它将DLL的句柄（你可以使用HINSTANCE）和函数的名称作为参数。你可以让函数指针获得由</pre>
<pre>GetProcAddress()返回的值，同时你必需将GetProcAddress()转换为那个函数定义的函数指针。举个例子，对于Add()函数，你必需将GetProcAddress()</pre>
<pre>转换为AddFunc；这就是它知道参数及返回值的原因。现在，最好先确定函数指针是否等于NULL以及它们拥有DLL的函数。</pre>
<pre>     这只是一个简单的if语句；如果其中一个等于NULL，你必需如前所述释放库。一旦函数指针拥有DLL的函数，你现在就可以使用它们了，但是这里有一个</pre>
<pre>需要注意的地方：你不能使用函数的实际名称；你必需使用函数指针来调用它们。在那以后，所有你需要做的是释放库如此而已。</pre>
<pre>     现在你知道了DLL的一些基本知识。你知道如何创建它们，你也知道如何用两种不同的方法链接它们。这里仍然有更多的东西需要我们学习，但我把它们留给你们自己探索了和更棒的作者来写了。
</pre>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/81304.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2009-04-28 12:10 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/04/28/81304.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>