﻿<?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++博客-一路向北-文章分类-Linux</title><link>http://www.cppblog.com/deane/category/13181.html</link><description>                    追逐梦想，永不停步......</description><language>zh-cn</language><lastBuildDate>Fri, 17 Feb 2012 15:57:52 GMT</lastBuildDate><pubDate>Fri, 17 Feb 2012 15:57:52 GMT</pubDate><ttl>60</ttl><item><title>Linux 信号 (转)</title><link>http://www.cppblog.com/deane/articles/165309.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Fri, 10 Feb 2012 09:49:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/165309.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/165309.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/165309.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/165309.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/165309.html</trackback:ping><description><![CDATA[<p><br />信号与进程是分不开的，而把信号与进程的笔记分开来写，是因为我觉得这个信号太难搞懂了，特别是APUE信号这一章还把信号结合历史来介绍弄的我云里雾里。&nbsp;</p>
<p>信号本质上是在软件层次上对中断机制的一种模拟，他有几种产生方式和处理方式(APUE有介绍)，下面带着疑惑从几个角度对信号进行介绍&nbsp;</p>
<p><br /></p>
<p><strong>（一）&nbsp;&nbsp;&nbsp;&nbsp;站在进程的角度</strong></p>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p align="left"><strong>进程发现和接受信号</strong></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p align="left">我们知道，信号是异步的，一个进程不可能等待信号的到来，也不知道信号会到来，那么，进程是如何发现和接受信号呢？实际上，信号的接收不是由用户进程来完成的，而是由内核代理。当一个进程P2向另一个进程P1发送信号后，内核接受到信号，并将其放在P1的信号队列当中。当P1再次陷入内核态时，会检查信号队列，并根据相应的信号调取相应的信号处理函数</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>Task _struct 是进程控制块(PCB),详见&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://oss.org.cn/kernel-book/ch04/4.3.htm">http://oss.org.cn/kernel-book/ch04/4.3.htm</a></p></blockquote></blockquote>
<p>&nbsp;<img alt="" src="http://www.spongeliu.com/wp-content/uploads/2010/10/Screenshot.png" /></p>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p align="left"><strong>信号检测和响应时机</strong></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p align="left">刚才我们说，当P1再次陷入内核时，会检查信号队列。那么，P1什么时候会再次陷入内核呢？陷入内核后在什么时机会检测信号队列呢？</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p align="left">1.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当前进程由于系统调用、中断或异常而进入系统空间以后，从系统空间返回到用户空间的前夕。</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p align="left">2.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当前进程在内核中进入睡眠以后刚被唤醒的时候（必定是在系统调用中），或者由于不可忽略信号的存在而提前返回到用户空间</p></blockquote></blockquote>
<p align="left"><br /></p>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p align="left"><strong>进入信号处理函数</strong></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p align="left">&nbsp;<img alt="" src="http://img.ddvip.com/2007_03/1173688691_ddvip_875.gif" /></p></blockquote>
<p align="left">&nbsp;</p>
<p><strong><br /></strong></p>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>对于sigprocmask 会进入内核空间、pause需要从进入睡眠这两者都符合检测处理信号函数的条件，所以存在忽略信号的情况，而APUE讲sigsuspend的之后真是晦涩难懂，其实他主要做的工作就是等待一个中断然后执行相应的handle处理</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>所以我感觉例子中的</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>sigsuspend(&amp;zeromask)；</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>sigprocmask(SIG_SETMASK, &amp;oldmask,NULL)；</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>是不是可以直接替换为</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>Sigsuspend(&amp;oldmask)</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>因为测试情况难以出现，这里只是个人理解并未得到验证</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>&nbsp;</p></blockquote></blockquote>
<p>&nbsp;</p>
<p><strong>（二）&nbsp;&nbsp;站在信号自身的角度</strong></p>
<p>&nbsp;</p>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p><strong>信号生命周期:</strong></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>&nbsp;对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说，可以分为三个重要的阶段，这三个阶段由四个重要事件来刻画：</p></blockquote></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>1.信号诞生；</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>2.信号在进程中注册完毕；</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>3.信号在进程中的注销完毕；</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>4.信号处理函数执行完毕。</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>相邻两个事件的时间间隔构成信号生命周期的一个阶段。</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>&nbsp;<img alt="" src="http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/3.gif" /></p></blockquote></blockquote>
<p><strong>&nbsp; &nbsp; 详细描述各个生命周期</strong></p>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p><strong>1. &nbsp; &nbsp;&nbsp;信号"诞生"。</strong></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>信号的诞生指的是触发信号的事件发生（如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等）。&nbsp;</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p><strong>2.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;信号在目标进程中"注册"。</strong></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>这里注册定义不是由signal或者sigaction实现的，而是说信号发生之后内核中自动对信号的注册保存。</p>
<p>进程的task_struct结构中有关于本进程中未决信号的数据成员：&nbsp;</p>
<p>struct sigpending pending;</p>
<p>struct sigpending</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp; struct sigqueue *head, **tail;</p>
<p>&nbsp;&nbsp;&nbsp; sigset_t signal;</p>
<p>};</p>
<p>第一、第二个成员分别指向一个sigqueue类型的结构链（称之为"未决信号信息链"）的首尾，第三个成员是进程中所有未决信号集，信息链中的每个sigqueue结构体刻画一个特定信号所携带的信息，并指向下一个sigqueue结构:</p>
<p>struct sigqueue</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp; struct sigqueue *next;</p>
<p>&nbsp;&nbsp;&nbsp; siginfo_t info;</p>
<p>};</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>&nbsp;&nbsp;&nbsp; 信号在进程中注册指的就是信号值加入到进程的未决信号集中（sigpending结构的第二个成员sigset_t signal），并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中，表明进程已经知道这些信号的存在，但还没来得及处理，或者该信号被进程阻塞。</p>
<p>注：&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 当一个实时信号发送给一个进程时，不管该信号是否已经在进程中注册，都会被再注册一次，因此，信号不会丢失，因此，实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构（进程每收到一个实时信号，都会为它分配一个结构来登记该信号信息，并把该结构添加在未决信号链尾，即所有诞生的实时信号都会在目标进程中注册）；</p>
<p>当一个非实时信号发送给一个进程时，如果该信号已经在进程中注册，则该信号将被丢弃，造成信号丢失。因此，非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中，至多占有一个sigqueue结构。一个非实时信号诞生后，（1）、如果发现相同的信号已经在目标结构中注册，则不再注册，对于进程来说，相当于不知道本次信号发生，信号丢失；（2）、如果进程的未决信号中没有相同信号，则在进程中注册自己。在APUE的不可靠信号章节中需要每次重新声明sinal_hanle函数，这个是说的以前Unix系统的处理，现在可靠不可靠就是上面所说的实时与注册次数的区别。</p></blockquote>
<p><br /></p>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p><strong>3.信号在进程中的注销。</strong></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>在目标进程执行过程中，会检测是否有信号等待处理（每次从系统空间返回到用户空间时都做这样的检查）。（&#8220;sigprocmask返回前，也至少会将其中一个未决且未阻塞的信号递送给进程&#8221;）如果存在未决信号等待处理且该信号没有被进程阻塞，则在运行相应的信号处理函数前，进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集中删除对于实时与非实时信号是不同的。对于非实时信号来说，由于在未决信号信息链中最多只占用一个sigqueue结构，因此该结构被释放后，应该把信号在进程未决信号集中删除（信号注销完毕）；而对于实时信号来说，可能在未决信号信息链中占用多个sigqueue结构，因此应该针对占用gqueue结构的数目区别对待：如果只占用一个sigqueue结构（进程只收到该信号一次），则应该把信号在进程的未决信号集中删除（信号注销完毕）。否则，不在进程的未决信号集中删除该信号（信号注销完毕）。进程在执行信号相应处理函数之前，首先要把信号在进程中注销。</p></blockquote>
<p><br /></p>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p><strong>4.信号生命终止。</strong></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>进程注销信号后，立即执行相应的信号处理函数，执行完毕后，信号的本次发送对进程的影响彻底结束。&nbsp;</p>
<p><br /></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>注：&nbsp;</p>
<p>1）信号注册与否，与发送信号的函数（如kill()或sigqueue()等）以及信号安装函数（signal()及sigaction()）无关，只与信号值有关（信号值小于SIGRTMIN的信号最多只注册一次，信号值在SIGRTMIN及SIGRTMAX之间的信号，只要被进程接收到就被注册）。</p>
<p>2）在信号被注销到相应的信号处理函数执行完毕这段时间内，如果进程又收到同一信号多次，则对实时信号来说，每一次都会在进程中注册；而对于非实时信号来说，无论收到多少次信号，都会视为只收到一个信号，只在进程中注册一次。</p></blockquote>
<p>&nbsp;</p>
<p><strong>（三）&nbsp;&nbsp;进程和信号两者的角度来看</strong></p>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>实际执行信号的处理动作称为信号递达（Delivery），信号从产生到递达之间的状态，称为信号未决（Pending）。进程可以选择阻塞（Block）某个信号。被阻塞的信号产生时将保持在未决状态，直到进程解除对此信号的阻塞，才执行递达的动作。注意，阻塞和忽略是不同的，只要信号被阻塞就不会递达，而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的：</p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p><img alt="信号在内核中的表示示意图" src="http://learn.akae.cn/media/images/signal.internal.png" /></p></blockquote>
<blockquote style="border-right: medium none; padding-right: 0px; border-top: medium none; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0px 40px; border-left: medium none; padding-top: 0px; border-bottom: medium none">
<p>&nbsp;每个信号都有两个标志位分别表示阻塞和未决，还有一个函数指针表示处理动作。信号产生时，内核在进程控制块中设置该信号的未决标志，直到信号递达才清除该标志。在上图的例子中，</p>
<p>1.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SIGHUP信号未阻塞也未产生过，当它递达时执行默认处理动作。</p>
<p>2.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SIGINT信号产生过，但正在被阻塞，所以暂时不能递达。虽然它的处理动作是忽略，但在没有解除阻塞之前不能忽略这个信号，因为进程仍有机会改变处理动作之后再解除阻塞。</p>
<p>3.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SIGQUIT信号未产生过，一旦产生SIGQUIT信号将被阻塞，它的处理动作是用户自定义函数sighandler。</p></blockquote>
<p>&nbsp;</p><br /><img src ="http://www.cppblog.com/deane/aggbug/165309.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2012-02-10 17:49 <a href="http://www.cppblog.com/deane/articles/165309.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转】IO - 同步，异步，阻塞，非阻塞 （亡羊补牢篇） </title><link>http://www.cppblog.com/deane/articles/165219.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Thu, 09 Feb 2012 05:52:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/165219.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/165219.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/165219.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/165219.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/165219.html</trackback:ping><description><![CDATA[同步（synchronous） IO和异步（asynchronous） IO，阻塞（blocking） IO和非阻塞（non-blocking）IO分别是什么，到底有什么区别？这个问题其实不同的人给出的答案都可能不同，比如wiki，就认为asynchronous IO和non-blocking IO是一个东西。这其实是因为不同的人的知识背景不同，并且在讨论这个问题的时候上下文(context)也不相同。所以，为了更好的回答这个问题，我先限定一下本文的上下文。<br />本文讨论的背景是Linux环境下的network IO。<br />本文最重要的参考文献是Richard Stevens的&#8220;<strong>UNIX&#174; Network Programming Volume 1, Third Edition: The Sockets Networking</strong> &#8221;，6.2节&#8220;<strong>I/O Models</strong> &#8221;，Stevens在这节中详细说明了各种IO的特点和区别，如果英文够好的话，推荐直接阅读。Stevens的文风是有名的深入浅出，所以不用担心看不懂。本文中的流程图也是截取自参考文献。 
<p>Stevens在文章中一共比较了五种IO Model：<br />&nbsp;&nbsp;&nbsp; blocking IO<br />&nbsp;&nbsp;&nbsp; nonblocking IO<br />&nbsp;&nbsp;&nbsp; IO multiplexing<br />&nbsp;&nbsp;&nbsp; signal driven IO<br />&nbsp;&nbsp;&nbsp; asynchronous IO<br />由于signal driven IO在实际中并不常用，所以我这只提及剩下的四种IO Model。<br /><br />再说一下IO发生时涉及的对象和步骤。<br />对于一个network IO (这里我们以read举例)，它会涉及到两个系统对象，一个是调用这个IO的process (or thread)，另一个就是系统内核(kernel)。当一个read操作发生时，它会经历两个阶段：<br />1 等待数据准备 (Waiting for the data to be ready)<br />2 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)<br />记住这两点很重要，因为这些IO Model的区别就是在两个阶段上各有不同的情况。</p>
<p><strong>blocking IO</strong> <br />在linux中，默认情况下所有的socket都是blocking，一个典型的读操作流程大概是这样：</p>
<p><span><img class="blogimg" src="mhtml:file://E:\linux doc\IO - 同步，异步，阻塞，非阻塞.mht!http://hiphotos.baidu.com/penzo/pic/item/a82845cae26094c0c8176842.jpg" border="0" small="0"  alt="" /><br /></span></p>
<p>当用户进程调用了recvfrom这个系统调用，kernel就开始了IO的第一个阶段：准备数据。对于network io来说，很多时候数据在一开始还没有到达（比如，还没有收到一个完整的UDP包），这个时候kernel就要等待足够的数据到来。而在用户进程这边，整个进程会被阻塞。当kernel一直等到数据准备好了，它就会将数据从kernel中拷贝到用户内存，然后kernel返回结果，用户进程才解除block的状态，重新运行起来。<br />所以，blocking IO的特点就是在IO执行的两个阶段都被block了。</p>
<p><strong>non-blocking IO</strong></p>
<p>linux下，可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时，流程是这个样子：</p>
<p><span><img class="blogimg" style="width: 580px; height: 320px" height="320" src="mhtml:file://E:\linux doc\IO - 同步，异步，阻塞，非阻塞.mht!http://hiphotos.baidu.com/penzo/pic/item/24369c08f7b72ed30b7b8254.jpg" width="580" border="0" small="0"  alt="" /><br /></span></p>
<p>从图中可以看出，当用户进程发出read操作时，如果kernel中的数据还没有准备好，那么它并不会block用户进程，而是立刻返回一个error。从用户进程角度讲 ，它发起一个read操作后，并不需要等待，而是马上就得到了一个结果。用户进程判断结果是一个error时，它就知道数据还没有准备好，于是它可以再次发送read操作。一旦kernel中的数据准备好了，并且又再次收到了用户进程的system call，那么它马上就将数据拷贝到了用户内存，然后返回。<br />所以，用户进程其实是需要不断的主动询问kernel数据好了没有。</p>
<p><strong>IO multiplexing</strong></p>
<p>IO multiplexing这个词可能有点陌生，但是如果我说select，epoll，大概就都能明白了。有些地方也称这种IO方式为event driven IO。我们都知道，select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket，当某个socket有数据到达了，就通知用户进程。它的流程如图：</p>
<p><span><img class="blogimg" style="width: 580px; height: 310px" height="310" src="mhtml:file://E:\linux doc\IO - 同步，异步，阻塞，非阻塞.mht!http://hiphotos.baidu.com/penzo/pic/item/0ae1f77f0f91344e29388a59.jpg" width="580" border="0" small="0"  alt="" /><br /></span></p>
<p>当用户进程调用了select，那么整个进程会被block，而同时，kernel会&#8220;监视&#8221;所有select负责的socket，当任何一个socket中的数据准备好了，select就会返回。这个时候用户进程再调用read操作，将数据从kernel拷贝到用户进程。<br />这个图和blocking IO的图其实并没有太大的不同，事实上，还更差一些。因为这里需要使用两个system call (select 和 recvfrom)，而blocking IO只调用了一个system call (recvfrom)。但是，用select的优势在于它可以同时处理多个connection。（多说一句。所以，如果处理的连接数不是很高的话，使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好，可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快，而是在于能处理更多的连接。）<br />在IO multiplexing Model中，实际中，对于每一个socket，一般都设置成为non-blocking，但是，如上图所示，整个用户的process其实是一直被block的。只不过process是被select这个函数block，而不是被socket IO给block。</p>
<p><strong>Asynchronous I/O</strong></p>
<p>linux下的asynchronous IO其实用得很少。先看一下它的流程：</p>
<p><span><img class="blogimg" src="mhtml:file://E:\linux doc\IO - 同步，异步，阻塞，非阻塞.mht!http://hiphotos.baidu.com/penzo/pic/item/93205a82c4c079d6f703a624.jpg" border="0" small="0"  alt="" /><br /></span></p>
<p>用户进程发起read操作之后，立刻就可以开始去做其它的事。而另一方面，从kernel的角度，当它受到一个asynchronous read之后，首先它会立刻返回，所以不会对用户进程产生任何block。然后，kernel会等待数据准备完成，然后将数据拷贝到用户内存，当这一切都完成之后，kernel会给用户进程发送一个signal，告诉它read操作完成了。</p>
<p>到目前为止，已经将四个IO Model都介绍完了。现在回过头来回答最初的那几个问题：blocking和non-blocking的区别在哪，synchronous IO和asynchronous IO的区别在哪。<br />先回答最简单的这个：blocking vs non-blocking。前面的介绍中其实已经很明确的说明了这两者的区别。调用blocking IO会一直block住对应的进程直到操作完成，而non-blocking IO在kernel还准备数据的情况下会立刻返回。</p>
<p>在说明synchronous IO和asynchronous IO的区别之前，需要先给出两者的定义。Stevens给出的定义（其实是POSIX的定义）是这样子的：<br />&nbsp;&nbsp;&nbsp; <strong>A synchronous I/O operation causes the requesting process to be blocked until that <span style="color: #ff0000">I/O operation</span> completes;<br />&nbsp;&nbsp;&nbsp; An asynchronous I/O operation does not cause the requesting process to be blocked;</strong> <br />两者的区别就在于synchronous IO做&#8221;IO operation&#8221;的时候会将process阻塞。按照这个定义，之前所述的blocking IO，non-blocking IO，IO multiplexing都属于synchronous IO。有人可能会说，non-blocking IO并没有被block啊。这里有个非常&#8220;狡猾&#8221;的地方，定义中所指的&#8221;IO operation&#8221;是指真实的IO操作，就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候，如果kernel的数据没有准备好，这时候不会block进程。但是，当kernel中数据准备好的时候，recvfrom会将数据从kernel拷贝到用户内存中，这个时候进程是被block了，在这段时间内，进程是被block的。而asynchronous IO则不一样，当进程发起IO 操作之后，就直接返回再也不理睬了，直到kernel发送一个信号，告诉进程说IO完成。在这整个过程中，进程完全没有被block。</p>
<p>各个IO Model的比较如图所示：</p>
<p><span><img class="blogimg" style="width: 580px; height: 308px" height="308" src="mhtml:file://E:\linux doc\IO - 同步，异步，阻塞，非阻塞.mht!http://hiphotos.baidu.com/penzo/pic/item/7ee9648f8192a7a8f11f3620.jpg" width="580" border="0" small="0"  alt="" /><br /></span></p>
<p>经过上面的介绍，会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中，虽然进程大部分时间都不会被block，但是它仍然要求进程去主动的check，并且当数据准备完成以后，也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人（kernel）完成，然后他人做完后发信号通知。在此期间，用户进程不需要去检查IO操作的状态，也不需要主动的去拷贝数据。<br /><br /><span style="color: #0000ff">最后，再举几个不是很恰当的例子来说明这四个IO Model:<br />有A，B，C，D四个人在钓鱼：<br />A用的是最老式的鱼竿，所以呢，得一直守着，等到鱼上钩了再拉杆；<br />B的鱼竿有个功能，能够显示是否有鱼上钩，所以呢，B就和旁边的MM聊天，隔会再看看有没有鱼上钩，有的话就迅速拉杆；<br />C用的鱼竿和B差不多，但他想了一个好办法，就是同时放好几根鱼竿，然后守在旁边，一旦有显示说鱼上钩了，它就将对应的鱼竿拉起来；<br />D是个有钱人，干脆雇了一个人帮他钓鱼，一旦那个人把鱼钓上来了，就给D发个短信。</span></p>
<p><span style="color: #0000ff">原帖地址：<a>http://blog.csdn.net/historyasamirror/archive/2010/07/31/5778378.aspx</a></span></p><img src ="http://www.cppblog.com/deane/aggbug/165219.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2012-02-09 13:52 <a href="http://www.cppblog.com/deane/articles/165219.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入了解epoll (转)</title><link>http://www.cppblog.com/deane/articles/165218.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Thu, 09 Feb 2012 05:48:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/165218.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/165218.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/165218.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/165218.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/165218.html</trackback:ping><description><![CDATA[<br />
<div class="article_content">
<p><span onmouseup="NewHighlight(event)" class="wenzhang_con" id="articlecontent" style="width: 740px"></span></p>
<div class="articleContent" id="articleBody"><span style="font-size: small; color: #000000; font-family: 宋体"><font size="2">一、 介绍<br />Epoll 是一种高效的管理socket的模型，相对于select和poll来说具有更高的效率和易用性。传统的select以及poll的效率会因为 socket数量的线形递增而导致呈二次乃至三次方的下降，而epoll的性能不会随socket数量增加而下降。标准的linux-2.4.20内核不支持epoll，需要打patch。本文主要从linux-2.4.32和linux-2.6.10两个内核版本介绍epoll。<br />二、 Epoll的使用<br />epoll用到的所有函数都是在头文件sys/epoll.h中声明的，下面简要说明所用到的数据结构和函数：<br />所用到的数据结构<br />typedef union epoll_data {<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; void ptr;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; int fd;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; __uint32_t u32;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; __uint64_t u64;<br />&nbsp;&nbsp; &nbsp;&nbsp; } epoll_data_t;<br /><br />&nbsp;&nbsp; &nbsp;&nbsp; struct epoll_event {<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; __uint32_t events; &nbsp;&nbsp; / Epoll events /<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; epoll_data_t data; &nbsp;&nbsp; / User data variable /<br />&nbsp;&nbsp; &nbsp;&nbsp; };<br />结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件，其中epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据，例如一个client连接到服务器，服务器通过调用accept函数可以得到于这个client对应的socket文件描述符，可以把这文件描述符赋给epoll_data的fd字段以便后面的读写操作在这个文件描述符上进行。epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件可能的取值为：EPOLLIN ：表示对应的文件描述符可以读；<br />EPOLLOUT：表示对应的文件描述符可以写；<br />EPOLLPRI：表示对应的文件描述符有紧急的数据可读<br />EPOLLERR：表示对应的文件描述符发生错误；<br />EPOLLHUP：表示对应的文件描述符被挂断；<br />EPOLLET：表示对应的文件描述符设定为edge模式；<br />所用到的函数：<br />1、epoll_create函数<br />&nbsp;&nbsp;&nbsp; 函数声明：int epoll_create(int size)<br />&nbsp;&nbsp;&nbsp; 该函数生成一个epoll专用的文件描述符，其中的参数是指定生成描述符的最大范围。在linux-2.4.32内核中根据size大小初始化哈希表的大小，在linux2.6.10内核中该参数无用，使用红黑树管理所有的文件描述符，而不是hash。<br />2、epoll_ctl函数<br />&nbsp;&nbsp;&nbsp; 函数声明：int epoll_ctl(int epfd, int op, int fd, struct epoll_event event)<br />&nbsp;&nbsp;&nbsp; 该函数用于控制某个文件描述符上的事件，可以注册事件，修改事件，删除事件。<br />&nbsp;&nbsp;&nbsp; 参数：epfd：由 epoll_create 生成的epoll专用的文件描述符；<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; op：要进行的操作例如注册事件，可能的取值<br />EPOLL_CTL_ADD 注册、<br />EPOLL_CTL_MOD 修改、<br />EPOLL_CTL_DEL 删除<br />fd：关联的文件描述符；<br />event：指向epoll_event的指针；<br />如果调用成功返回0,不成功返回-1<br />3、epoll_wait函数<br />函数声明:int epoll_wait(int epfd,struct epoll_event&nbsp;&nbsp; events,int maxevents,int timeout)<br />该函数用于轮询I/O事件的发生；<br />参数：<br />epfd:由epoll_create 生成的epoll专用的文件描述符；<br />epoll_event:用于回传代处理事件的数组；<br />maxevents:每次能处理的事件数；<br />timeout:等待I/O事件发生的超时值（ms）；-1永不超时，直到有事件产生才触发，0立即返回。<br />返回发生事件数。-1有错误。<br /><br />举一个简单的例子：<br /><br />C/C++ codeint main()<br />{<br />&nbsp;&nbsp;&nbsp; //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件<br />&nbsp;&nbsp;&nbsp; struct epoll_event ev,events[20];<br /><br />&nbsp;&nbsp;&nbsp; epfd=epoll_create(10000); //创建epoll句柄<br />&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp; listenfd = socket(AF_INET, SOCK_STREAM, 0);<br />&nbsp;&nbsp;&nbsp; //把socket设置为非阻塞方式<br />&nbsp;&nbsp;&nbsp; setnonblocking(listenfd);<br />&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp; bzero(&amp;serveraddr, sizeof(serveraddr));<br />&nbsp;&nbsp;&nbsp; serveraddr.sin_family = AF_INET;<br />&nbsp;&nbsp;&nbsp; serveraddr.sin_addr.s_addr = INADDR_ANY;<br />&nbsp;&nbsp;&nbsp; serveraddr.sin_port=htons(SERV_PORT);<br />&nbsp;&nbsp;&nbsp; bind(listenfd,(struct sockaddr )&amp;serveraddr, sizeof(serveraddr));<br />&nbsp;&nbsp;&nbsp; listen(listenfd, 255);<br /><br />&nbsp;&nbsp;&nbsp; //设置与要处理的事件相关的文件描述符<br />&nbsp;&nbsp;&nbsp; ev.data.fd=listenfd;<br />&nbsp;&nbsp;&nbsp; //设置要处理的事件类型<br />&nbsp;&nbsp;&nbsp; ev.events=EPOLLIN;<br />&nbsp;&nbsp;&nbsp; //注册epoll事件<br />&nbsp;&nbsp;&nbsp; epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&amp;ev);<br /><br />&nbsp;&nbsp;&nbsp; for ( ; ; )<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp; &nbsp;&nbsp; //等待epoll事件的发生<br />&nbsp;&nbsp; &nbsp;&nbsp; nfds=epoll_wait(epfd,events,20,1000);<br />&nbsp;&nbsp; &nbsp;&nbsp; //处理所发生的所有事件<br />&nbsp;&nbsp; &nbsp;&nbsp; for(i=0;i&lt;nfds;++i)<br />&nbsp;&nbsp; &nbsp;&nbsp; {<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; if(events</font></span> <span style="font-size: small; color: #000000; font-family: 宋体"><font size="2">.data.fd==listenfd)<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; {<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; connfd = accept(listenfd,(struct sockaddr )&amp;clientaddr, &amp;clilen);<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(connfd&lt;0)<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; perror("connfd&lt;0");<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; setnonblocking(connfd);<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //设置用于读操作的文件描述符<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ev.data.fd=connfd;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //设置用于注测的读操作事件<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ev.events=EPOLLIN|EPOLLET;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //注册event<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&amp;ev);<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; }<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; else if(events</font></span> <span style="font-size: small; color: #000000; font-family: 宋体"><font size="2">.events&amp;EPOLLIN)<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; {<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; read_socket(events</font></span> <span style="font-size: small; color: #000000; font-family: 宋体"><font size="2">.data.fd);<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ev.data.fd=events</font></span> <span style="font-size: small; color: #000000; font-family: 宋体"><font size="2">.data.fd;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ev.events=EPOLLIN|EPOLLOUT|EPOLLET;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&amp;ev);<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; }<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; else if(events</font></span> <span style="font-size: small; color: #000000; font-family: 宋体"><font size="2">.events&amp;EPOLLOUT)<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; {<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; write_socket(events</font></span> <span style="font-size: small; color: #000000; font-family: 宋体"><font size="2">.data.fd);<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ev.data.fd=events</font></span> <span style="color: #000000"><span style="font-size: small; font-family: 宋体"><font size="2">.data.fd;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ev.events=EPOLLIN|EPOLLET; //ET模式<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&amp;ev);<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; }<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; else<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; {<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; perror("other event");<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; }<br />&nbsp;&nbsp; &nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />}<br /><br /><br />Epoll的ET模式与LT模式<br />ET（Edge Triggered）与LT（Level Triggered）的主要区别可以从下面的例子看出<br />eg：<br />1． 标示管道读者的文件句柄注册到epoll中；<br />2． 管道写者向管道中写入2KB的数据；<br />3． 调用epoll_wait可以获得管道读者为已就绪的文件句柄；<br />4． 管道读者读取1KB的数据<br />5． 一次epoll_wait调用完成<br />如果是ET模式，管道中剩余的1KB被挂起，再次调用epoll_wait，得不到管道读者的文件句柄，除非有新的数据写入管道。如果是LT模式，只要管道中有数据可读，每次调用epoll_wait都会触发。<br /><br />另一点区别就是设为ET模式的文件句柄必须是非阻塞的。<br />三、 Epoll的实现<br />Epoll 的源文件在/usr/src/linux/fs/eventpoll.c，在module_init时注册一个文件系统 eventpoll_fs_type，对该文件系统提供两种操作poll和release，所以epoll_create返回的文件句柄可以被poll、 select或者被其它epoll epoll_wait。对epoll的操作主要通过三个系统调用实现：<br />1． sys_epoll_create<br />2． sys_epoll_ctl<br />3． sys_epoll_wait<br />下面结合源码讲述这三个系统调用。<br />1.1 long sys_epoll_create (int size)<br />该系统调用主要分配文件句柄、inode以及file结构。在linux-2.4.32内核中，使用hash保存所有注册到该epoll的文件句柄，在该系统调用中根据size大小分配hash的大小。具体为不小于size，但小于2size的2的某次方。最小为2的9次方（512），最大为2的17次方（128 x 1024）。在linux-2.6.10内核中，使用红黑树保存所有注册到该epoll的文件句柄，size参数未使用。<br />1.2 long sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event event)<br />1． 注册句柄 op = EPOLL_CTL_ADD<br />注册过程主要包括：<br />A．将fd插入到hash（或rbtree）中，如果原来已经存在返回-EEXIST，<br />B．给fd注册一个回调函数，该函数会在fd有事件时调用，在该函数中将fd加入到epoll的就绪队列中。<br />C．检查fd当前是否已经有期望的事件产生。如果有，将其加入到epoll的就绪队列中，唤醒epoll_wait。<br /><br />2． 修改事件 op = EPOLL_CTL_MOD<br />修改事件只是将新的事件替换旧的事件，然后检查fd是否有期望的事件。如果有，将其加入到epoll的就绪队列中，唤醒epoll_wait。<br /><br />3． 删除句柄 op = EPOLL_CTL_DEL<br />将fd从hash（rbtree）中清除。<br />1.3 long sys_epoll_wait(int epfd, struct epoll_event events, int maxevents,int timeout)<br />如果epoll的就绪队列为空，并且timeout非0，挂起当前进程，引起CPU调度。<br />如果epoll的就绪队列不空，遍历就绪队列。对队列中的每一个节点，获取该文件已触发的事件，判断其中是否有我们期待的事件，如果有，将其对应的epoll_event结构copy到用户events。<br /><br />revents = epi-&gt;file-&gt;f_op-&gt;poll(epi-&gt;file, NULL);<br />epi-&gt;revents = revents &amp; epi-&gt;event.events;<br />if (epi-&gt;revents) {<br />&#8230;&#8230;<br />copy_to_user;<br />&#8230;&#8230;<br />}<br />需要注意的是，在LT模式下，把符合条件的事件copy到用户空间后，还会把对应的文件重新挂接到就绪队列。所以在LT模式下，如果一次epoll_wait某个socket没有read/write完所有数据，下次epoll_wait还会返回该socket句柄。<br />四、 使用epoll的注意事项<br />1. ET模式比LT模式高效，但比较难控制。<br />2. 如果某个句柄期待的事件不变，不需要EPOLL_CTL_MOD，但每次读写后将该句柄modify一次有助于提高稳定性，特别在ET模式。<br />3. socket关闭后最好将该句柄从epoll中delete（EPOLL_CTL_DEL），虽然epoll自身有处理，但会使epoll的hash的节点数增多，影响搜索hash的速度。<br />&nbsp;&nbsp;<br />Q：网络服务器的瓶颈在哪？<br />A：IO效率。<br /><br />在大家苦苦的为在线人数的增长而导致的系统资源吃紧上的问题正在发愁的时候，Linux 2.6内核中提供的System Epoll为我们提供了一套完美的解决方案。传统的select以及poll的效率会因为在线人数的线形递增而导致呈二次乃至三次方的下降，这些直接导致了网络服务器可以支持的人数有了个比较明显的限制。<br /><br />自从Linux提供了/dev/epoll的设备以及后来2.6内核中对/dev /epoll设备的访问的封装（System Epoll）之后，这种现象得到了大大的缓解，如果说几个月前，大家还对epoll不熟悉，那么现在来说的话，epoll的应用已经得到了大范围的普及。<br /><br />那么究竟如何来使用epoll呢？其实非常简单。<br />通过在包含一个头文件#include 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。<br /><br />首先通过create_epoll(int maxfds)来创建一个epoll的句柄，其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄，之后的所有操作将通过这个句柄来进行操作。在用完之后，记得用close()来关闭这个创建出来的epoll句柄。<br /><br />之后在你的网络主循环里面，每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口，看哪一个可以读，哪一个可以写了。基本的语法为：<br />nfds = epoll_wait(kdpfd, events, maxevents, -1);<br />其中kdpfd为用epoll_create创建之后的句柄，events是一个epoll_event的指针，当epoll_wait这个函数操作成功之后，epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时，为0的时候表示马上返回，为-1的时候表示一直等下去，直到有事件范围，为任意正整数的时候表示等这么长的时间，如果一直没有事件，则范围。一般如果网络主循环是单独的线程的话，可以用-1来等，这样可以保证一些效率，如果是和主逻辑在同一个线程的话，则可以用0来保证主循环的效率。<br /><br />epoll_wait范围之后应该是一个循环，遍利所有的事件：<br /><br />C/C++ codefor(n = 0; n &lt; nfds; ++n) {<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; if(events[n].data.fd == listener) { //如果是主socket的事件的话，则表示有新连接进入了，进行新连接的处理。<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; client = accept(listener, (struct sockaddr ) &amp;local,<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &amp;addrlen);<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(client &lt; 0){<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; perror("accept");<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; continue;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; setnonblocking(client); // 将新连接置于非阻塞模式<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ev.events = EPOLLIN | EPOLLET; // 并且将新连接也加入EPOLL的监听队列。<br />注意，这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听，如果有写操作的话，这个时候epoll是不会返回事件的，如果要对写操作也监听的话，应该是EPOLLIN | EPOLLOUT | EPOLLET<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ev.data.fd = client;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &amp;ev) &lt; 0) {<br />//&nbsp;&nbsp; 设置好event之后，将这个新的event通过epoll_ctl加入到epoll的监听队列里面，这里用EPOLL_CTL_ADD来加一个新的 epoll事件，通过EPOLL_CTL_DEL来减少一个epoll事件，通过EPOLL_CTL_MOD来改变一个事件的监听方式。<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; fprintf(stderr, "epoll set insertion error: fd=d0,<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; client);<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; return -1;<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; }<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; else // 如果不是主socket的事件的话，则代表是一个用户socket的事件，则来处理这个用户socket的事情，比如说read(fd,xxx)之类的，或者一些其他的处理。<br />&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; do_use_fd(events[n].data.fd);<br />}<br /><br /><br /><br />对，epoll的操作就这么简单，总共不过4个API：epoll_create, epoll_ctl, epoll_wait和close。</font></span> </span></div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>Linux 2.6内核中提高网络I/O性能的新方法 <br /><br />1、为什么select是落后的？ <br />首先，在Linux内核中，select所用到的FD_SET是有限的，即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数，在我用的2.6.15-25-386内核中，该值是1024，搜索内核源代码得到： <br />include/linux/posix_types.h:#define __FD_SETSIZE 1024 <br />也就是说，如果想要同时检测1025个句柄的可读状态是不可能用select实现的。或者同时检测1025个句柄的可写状态也是不可能的。 <br />其次，内核中实现select是用轮询方法，即每次检测都会遍历所有FD_SET中的句柄，显然，select函数执行时间与FD_SET中的句柄个数有一个比例关系，即select要检测的句柄数越多就会越费时。 <br />当然，在前文中我并没有提及poll方法，事实上用select的朋友一定也试过poll，我个人觉得select和poll大同小异，个人偏好于用select而已。 <br />2、2.6内核中提高I/O性能的新方法epoll <br />epoll是什么？按照man手册的说法：是为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用：epoll_create(2)， epoll_ctl(2)， epoll_wait(2)。 <br />当然，这不是2.6内核才有的，它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44) <br /><br />epoll的优点 <br />&lt;1&gt;支持一个进程打开大数目的socket描述符(FD) <br />select 最不能忍受的是一个进程所打开的FD是有一定限制的，由FD_SETSIZE设置，默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核，不过资料也同时指出这样会带来网络效率的下降，二是可以选择多进程的解决方案(传统的 Apache方案)，不过虽然linux上面创建进程的代价比较小，但仍旧是不可忽视的，加上进程间数据同步远比不上线程间同步的高效，所以也不是一种完美的方案。不过 epoll则没有这个限制，它所支持的FD上限是最大可以打开文件的数目，这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右，具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。 <br /><br />&lt;2&gt;IO效率不随FD数目增加而线性下降 <br />传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合，不过由于网络延时，任一时间只有部分的socket是"活跃"的，但是select/poll每次调用都会线性扫描全部的集合，导致效率呈现线性下降。但是epoll不存在这个问题，它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么，只有"活跃"的socket才会主动的去调用 callback函数，其他idle状态socket则不会，在这点上，epoll实现了一个"伪"AIO，因为这时候推动力在os内核。在一些 benchmark中，如果所有的socket基本上都是活跃的---比如一个高速LAN环境，epoll并不比select/poll有什么效率，相反，如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。 <br />&lt;3&gt;使用mmap加速内核与用户空间的消息传递。<br />这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间，如何避免不必要的内存拷贝就很重要，在这点上，epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话，一定不会忘记手工 mmap这一步的。 <br />&lt;4&gt;内核微调 <br />这一点其实不算epoll的优点了，而是整个linux平台的优点。也许你可以怀疑linux平台，但是你无法回避linux平台赋予你微调内核的能力。比如，内核TCP/IP协议栈使用内存池管理sk_buff结构，那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小 --- 通过echo XXXX&gt;/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度)，也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。 <br />epoll的使用 <br />令人高兴的是，2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多，所以，大部分情况下，强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。<br />LT(level triggered)是缺省的工作方式，并且同时支持block和no-block socket.在这种做法中，内核告诉你一个文件描述符是否就绪了，然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作，内核还是会继续通知你的，所以，这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表． <br />ET (edge-triggered)是高速工作方式，只支持no-block socket。在这种模式下，当描述符从未就绪变为就绪时，内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪，并且不会再为那个文件描述符发送更多的就绪通知，直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如，你在发送，接收或者接收请求，或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误）。但是请注意，如果一直不对这个fd作IO操作(从而导致它再次变成未就绪)，内核不会发送更多的通知(only once),不过在TCP协议中，ET模式的加速效用仍需要更多的benchmark确认。 <br />epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用，具体用法请参考http://www.xmailserver.org/linux-patches/nio-improve.html ， <br />在http://www.kegel.com/rn/也有一个完整的例子，大家一看就知道如何使用了 <br />Leader/follower模式线程pool实现，以及和epoll的配合</p>
<p>在Linux上开发网络服务器的一些相关细节:poll与epoll <br />　　随着2.6内核对epoll的完全支持，网络上很多的文章和示例代码都提供了这样一个信息：使用epoll代替传统的 poll能给网络服务应用带来性能上的提升。但大多文章里关于性能提升的原因解释的较少，这里我将试分析一下内核（2.6.21.1）代码中poll与 epoll的工作原理，然后再通过一些测试数据来对比具体效果。 POLL： <br /><br />先说poll，poll或select为大部分Unix/Linux程序员所熟悉，这俩个东西原理类似，性能上也不存在明显差异，但select对所监控的文件描述符数量有限制，所以这里选用poll做说明。<br />poll是一个系统调用，其内核入口函数为sys_poll，sys_poll几乎不做任何处理直接调用do_sys_poll，do_sys_poll的执行过程可以分为三个部分：<br />1，将用户传入的pollfd数组拷贝到内核空间，因为拷贝操作和数组长度相关，时间上这是一个O（n）操作，这一步的代码在do_sys_poll中包括从函数开始到调用do_poll前的部分。 <br />2，查询每个文件描述符对应设备的状态，如果该设备尚未就绪，则在该设备的等待队列中加入一项并继续查询下一设备的状态。查询完所有设备后如果没有一个设备就绪，这时则需要挂起当前进程等待，直到设备就绪或者超时，挂起操作是通过调用schedule_timeout执行的。设备就绪后进程被通知继续运行，这时再次遍历所有设备，以查找就绪设备。这一步因为两次遍历所有设备，时间复杂度也是O（n），这里面不包括等待时间。相关代码在do_poll函数中。 <br />3，将获得的数据传送到用户空间并执行释放内存和剥离等待队列等善后工作，向用户空间拷贝数据与剥离等待队列等操作的的时间复杂度同样是O（n），具体代码包括do_sys_poll函数中调用do_poll后到结束的部分。 <br />EPOLL： <br />接下来分析epoll，与poll/select不同，epoll不再是一个单独的系统调用，而是由epoll_create/epoll_ctl/epoll_wait三个系统调用组成，后面将会看到这样做的好处。<br />先来看sys_epoll_create(epoll_create对应的内核函数），这个函数主要是做一些准备工作，比如创建数据结构，初始化数据并最终返回一个文件描述符（表示新创建的虚拟epoll文件），这个操作可以认为是一个固定时间的操作。 <br />epoll是做为一个虚拟文件系统来实现的，这样做至少有以下两个好处：<br />1，可以在内核里维护一些信息，这些信息在多次epoll_wait间是保持的，比如所有受监控的文件描述符。 <br />2， epoll本身也可以被poll/epoll; <br />具体epoll的虚拟文件系统的实现和性能分析无关，不再赘述。 <br />在sys_epoll_create中还能看到一个细节，就是epoll_create的参数size在现阶段是没有意义的，只要大于零就行。 <br /><br />接着是sys_epoll_ctl(epoll_ctl对应的内核函数），需要明确的是每次调用sys_epoll_ctl只处理一个文件描述符，这里主要描述当op为EPOLL_CTL_ADD时的执行过程，sys_epoll_ctl做一些安全性检查后进入ep_insert，ep_insert里将 ep_poll_callback做为回掉函数加入设备的等待队列（假定这时设备尚未就绪），由于每次poll_ctl只操作一个文件描述符，因此也可以认为这是一个O(1)操作 <br /><br />ep_poll_callback函数很关键，它在所等待的设备就绪后被系统回掉，执行两个操作： <br /><br />1，将就绪设备加入就绪队列，这一步避免了像poll那样在设备就绪后再次轮询所有设备找就绪者，降低了时间复杂度，由O（n）到O（1）; <br />2，唤醒虚拟的epoll文件; <br />最后是sys_epoll_wait，这里实际执行操作的是ep_poll函数。该函数等待将进程自身插入虚拟epoll文件的等待队列，直到被唤醒（见上面ep_poll_callback函数描述），最后执行ep_events_transfer将结果拷贝到用户空间。由于只拷贝就绪设备信息，所以这里的拷贝是一个O(1）操作。<br />还有一个让人关心的问题就是epoll对EPOLLET的处理，即边沿触发的处理，粗略看代码就是把一部分水平触发模式下内核做的工作交给用户来处理，直觉上不会对性能有太大影响，感兴趣的朋友欢迎讨论。 <br />POLL/EPOLL对比： <br />表面上poll的过程可以看作是由一次epoll_create/若干次epoll_ctl/一次epoll_wait/一次close等系统调用构成，实际上epoll将poll分成若干部分实现的原因正是因为服务器软件中使用poll的特点（比如Web服务器）： <br />1，需要同时poll大量文件描述符; <br />2，每次poll完成后就绪的文件描述符只占所有被poll的描述符的很少一部分。 <br />3，前后多次poll调用对文件描述符数组（ufds）的修改只是很小; <br />传统的poll函数相当于每次调用都重起炉灶，从用户空间完整读入ufds，完成后再次完全拷贝到用户空间，另外每次poll都需要对所有设备做至少做一次加入和删除等待队列操作，这些都是低效的原因。 <br /><br />epoll将以上情况都细化考虑，不需要每次都完整读入输出ufds，只需使用epoll_ctl调整其中一小部分，不需要每次epoll_wait都执行一次加入删除等待队列操作，另外改进后的机制使的不必在某个设备就绪后搜索整个设备数组进行查找，这些都能提高效率。另外最明显的一点，从用户的使用来说，使用epoll不必每次都轮询所有返回结果已找出其中的就绪部分，O（n）变O（1），对性能也提高不少。 <br /><br />此外这里还发现一点，是不是将epoll_ctl改成一次可以处理多个fd（像semctl那样）会提高些许性能呢？特别是在假设系统调用比较耗时的基础上。不过关于系统调用的耗时问题还会在以后分析。</p>
<p>POLL/EPOLL测试数据对比： <br />测试的环境：我写了三段代码来分别模拟服务器，活动的客户端，僵死的客户端，服务器运行于一个自编译的标准2.6.11内核系统上，硬件为 PIII933，两个客户端各自运行在另外的PC上，这两台PC比服务器的硬件性能要好，主要是保证能轻易让服务器满载，三台机器间使用一个100M交换机连接。 <br />服务器接受并poll所有连接，如果有request到达则回复一个response，然后继续poll。<br />活动的客户端（Active Client）模拟若干并发的活动连接，这些连接不间断的发送请求接受回复。<br />僵死的客户端（zombie）模拟一些只连接但不发送请求的客户端，其目的只是占用服务器的poll描述符资源。 <br />测试过程：保持10个并发活动连接，不断的调整僵并发连接数，记录在不同比例下使用poll与epoll的性能差别。僵死并发连接数根据比例分别是：0，10，20，40，80，160，320，640，1280，2560，5120，10240。 <br />下图中横轴表示僵死并发连接与活动并发连接之比，纵轴表示完成40000次请求回复所花费的时间，以秒为单位。红色线条表示poll数据，绿色表示 epoll数据。可以看出，poll在所监控的文件描述符数量增加时，其耗时呈线性增长，而epoll则维持了一个平稳的状态，几乎不受描述符个数影响。 <br />在监控的所有客户端都是活动时，poll的效率会略高于epoll（主要在原点附近，即僵死并发连接为0时，图上不易看出来），究竟epoll实现比poll复杂，监控少量描述符并非它的长处。</p></div><img src ="http://www.cppblog.com/deane/aggbug/165218.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2012-02-09 13:48 <a href="http://www.cppblog.com/deane/articles/165218.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>加速Linux程序编译</title><link>http://www.cppblog.com/deane/articles/165217.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Thu, 09 Feb 2012 05:45:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/165217.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/165217.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/165217.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/165217.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/165217.html</trackback:ping><description><![CDATA[<br />项目越来越大，每次需要重新<a onclick="javascript:tagshow(event, '%E7%BC%96%E8%AF%91');" href="javascript:;" target="_self"><u><strong>编译</strong></u></a>整个项目都是一件很浪费时间的事情。Research了一下，找到以下可以帮助提高速度的方法，总结一下。 
<p>　　tmpfs</p>
<p>　　有人说在<span class="channel_keylink"><a onclick="javascript:tagshow(event, 'Windows');" href="javascript:;" target="_self"><u><strong>Windows</strong></u></a></span>下用了RAMDisk把一个项目编译时间从4.5小时减少到了5分钟，也许这个数字是有点夸张了，不过粗想想，把<a onclick="javascript:tagshow(event, '%E6%96%87%E4%BB%B6');" href="javascript:;" target="_self"><u><strong>文件</strong></u></a>放到<a onclick="javascript:tagshow(event, '%E5%86%85%E5%AD%98');" href="javascript:;" target="_self"><u><strong>内存</strong></u></a>上做编译应该是比在磁盘上快多了吧，尤其如果编译器需要生成很多临时文件的话。</p>
<p>　　这个做法的实现成本最低，在<a onclick="javascript:tagshow(event, 'Linux');" href="javascript:;" target="_self"><u><strong>Linux</strong></u></a>中，直接mount一个tmpfs就可以了。而且对所编译的工程没有任何要求，也不用改动编译环境。</p>
<p>　　mount -t tmpfs tmpfs ~/build -o size=1G</p>
<p>　　用2.6.32.2的Linux Kernel来<span class="channel_keylink">测试</span>一下编译速度：</p>
<p>　　用物理磁盘：40分16秒</p>
<p>　　用tmpfs：39分56秒</p>
<p>　　呃&#8230;&#8230;没什么变化。看来编译慢很大程度上瓶颈并不在IO上面。但对于一个实际项目来说，编译过程中可能还会有打包等IO密集的操作，所以只要可能，用tmpfs是有益无害的。当然对于大项目来说，你需要有足够的内存才能负担得起这个tmpfs的开销。</p>
<p>　　make -j</p>
<p>　　既然IO不是瓶颈，那CPU就应该是一个影响编译速度的重要因素了。</p>
<p>　　用make -j带一个<a onclick="javascript:tagshow(event, '%E5%8F%82%E6%95%B0');" href="javascript:;" target="_self"><u><strong>参数</strong></u></a>，可以把项目在进行并行编译，比如在一台双核的机器上，完全可以用make -j4，让make最多允许4个编译<a onclick="javascript:tagshow(event, '%E5%91%BD%E4%BB%A4');" href="javascript:;" target="_self"><u><strong>命令</strong></u></a>同时执行，这样可以更有效的利用CPU资源。</p>
<p>　　还是用Kernel来<span class="channel_keylink">测试</span>：</p>
<p>　　用make： 40分16秒</p>
<p>　　用make -j4：23分16秒</p>
<p>　　用make -j8：22分59秒</p>
<p>　　由此看来，在多核CPU上，适当的进行并行编译还是可以明显提高编译速度的。但并行的任务不宜太多，一般是以CPU的核心数目的两倍为宜。</p>
<p>　　不过这个方案不是完全没有cost的，如果项目的Makefile不规范，没有正确的<a onclick="javascript:tagshow(event, '%E8%AE%BE%E7%BD%AE');" href="javascript:;" target="_self"><u><strong>设置</strong></u></a>好依赖关系，并行编译的结果就是编译不能正常进行。如果依赖关系设置过于保守，则可能本身编译的可并行度就下降了，也不能取得最佳的效果。</p>
<p>　　ccache</p>
<p>　　ccache用于把编译的中间结果进行缓存，以便在再次编译的时候可以节省时间。这对于玩Kernel来说实在是再好不过了，因为经常需要修改一些Kernel的代码，然后再重新编译，而这两次编译大部分东西可能都没有发生变化。对于平时<a onclick="javascript:tagshow(event, '%E5%BC%80%E5%8F%91');" href="javascript:;" target="_self"><u><strong>开发</strong></u></a>项目来说，也是一样。为什么不是直接用make所支持的增量编译呢？还是因为现实中，因为Makefile的不规范，很可能这种&#8220;聪明&#8221;的方案根本不能正常工作，只有每次make clean再make才行。</p>
<p>　　安装完ccache后，可以在/usr/local/bin下建立gcc，g++，c++，cc的symbolic link，链到/usr/bin/ccache上。总之确认<a onclick="javascript:tagshow(event, '%E7%B3%BB%E7%BB%9F');" href="javascript:;" target="_self"><u><strong>系统</strong></u></a>在调用gcc等命令时会调用到ccache就可以了（通常情况下/usr/local /bin会在PATH中排在/usr/bin前面）。</p>
<p>　　继续测试：</p>
<p>　　用ccache的第一次编译(make -j4)：23分38秒</p>
<p>　　用ccache的第二次编译(make -j4)：8分48秒</p>
<p>　　用ccache的第三次编译(修改若干配置，make -j4)：23分48秒</p>
<p>　　看来修改配置（我改了CPU类型...）对ccache的影响是很大的，因为基本头文件发生变化后，就导致所有缓存<a onclick="javascript:tagshow(event, '%E6%95%B0%E6%8D%AE');" href="javascript:;" target="_self"><u><strong>数据</strong></u></a>都无效了，必须重头来做。但如果只是修改一些.c文件的代码，ccache的效果还是相当明显的。而且使用ccache对项目没有特别的依赖，布署成本很低，这在日常工作中很实用。</p>
<p>　　可以用ccache -s来查看cache的使用和命中情况：</p>
<p>　　cache directory&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /home/lifanxi/.ccachecache hit&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; 7165cache miss&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 14283called for link&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 71not a C/<span class="channel_keylink">C++</span>file&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 120no input file&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3045files in cache&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 28566cache size&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; 81.7 Mbytesmax cache size&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 976.6 Mbytes</p>
<p>　　可以看到，显然只有第二编次译时cache命中了，cache miss是第一次和第三次编译带来的。两次cache占用了81.7M的磁盘，还是完全可以接受的。</p>
<p>　　distcc</p>
<p>　　一台机器的能力有限，可以联合多台电脑一起来编译。这在公司的日常开发中也是可行的，因为可能每个开发人员都有自己的开发编译环境，它们的编译器版本一般是一致的，公司的网络也通常具有较好的<a onclick="javascript:tagshow(event, '%E6%80%A7%E8%83%BD');" href="javascript:;" target="_self"><u><strong>性能</strong></u></a>。这时就是distcc大显身手的时候了。</p>
<p>　　使用distcc，并不像想象中那样要求每台电脑都具有完全一致的环境，它只要求源代码可以用make -j并行编译，并且参与分布式编译的电脑系统中具有相同的编译器。因为它的原理只是把预处理好的源文件分发到多台计算机上，预处理、编译后的目标文件的链接和其它除编译以外的工作仍然是在发起编译的主控电脑上完成，所以只要求发起编译的那台机器具备一套完整的编译环境就可以了。</p>
<p>　　distcc安装后，可以启动一下它的服务：</p>
<p>　　/usr/bin/distccd&nbsp; --daemon --allow 10.64.0.0/16</p>
<p>　　默认的3632端口允许来自同一个网络的distcc连接。</p>
<p>　　然后设置一下DISTCC_HOSTS环境变量，设置可以参与编译的机器列表。通常localhost也参与编译，但如果可以参与编译的机器很多，则可以把localhost从这个列表中去掉，这样本机就完全只是进行预处理、分发和链接了，编译都在别的机器上完成。因为机器很多时，localhost的处理负担很重，所以它就不再&#8220;兼职&#8221;编译了。</p>
<p>　　export DISTCC_HOSTS="localhost 10.64.25.1 10.64.25.2 10.64.25.3"</p>
<p>　　然后与ccache类似把g++，gcc等常用的命令链接到/usr/bin/distcc上就可以了。</p>
<p>　　在make的时候，也必须用-j参数，一般是参数可以用所有参用编译的计算机CPU内核总数的两倍做为并行的任务数。</p>
<p>　　同样测试一下：</p>
<p>　　一台双核计算机，make -j4：23分16秒</p>
<p>　　两台双核计算机，make -j4：16分40秒</p>
<p>　　两台双核计算机，make -j8：15分49秒</p>
<p>　　跟最开始用一台双核时的23分钟相比，还是快了不少的。如果有更多的计算机加入，也可以得到更好的效果。</p>
<p>　　在编译过程中可以用distccmon-text来查看编译任务的分配情况。distcc也可以与ccache同时使用，通过设置一个环境变量就可以做到，非常方便。</p>
<p>　　总结一下：</p>
<p>　　tmpfs： 解决IO瓶颈，充分利用本机内存资源</p>
<p>　　make -j： 充分利用本机计算资源</p>
<p>　　distcc： 利用多台计算机资源</p>
<p>　　ccache： 减少重复编译相同代码的时间</p>
<p>　　这些工具的好处都在于布署的成本相对较低，综合利用这些工具，就可以轻轻松松的节省相当可观的时间。上面介绍的都是这些工具最基本的<a onclick="javascript:tagshow(event, '%E7%94%A8%E6%B3%95');" href="javascript:;" target="_self"><u><strong>用法</strong></u></a>，更多的用法可以参考它们各自的man page。</p><img src ="http://www.cppblog.com/deane/aggbug/165217.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2012-02-09 13:45 <a href="http://www.cppblog.com/deane/articles/165217.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> Linux下Gcc生成和使用静态库和动态库详解（转）</title><link>http://www.cppblog.com/deane/articles/165216.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Thu, 09 Feb 2012 05:43:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/165216.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/165216.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/165216.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/165216.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/165216.html</trackback:ping><description><![CDATA[<span class="Apple-style-span" style="word-spacing: 0px; font: medium Simsun; text-transform: none; color: rgb(0,0,0); text-indent: 0px; white-space: normal; letter-spacing: normal; border-collapse: separate; orphans: 2; widows: 2; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px"><span class="Apple-style-span" style="font-size: 14px; line-height: 26px; font-family: Arial; text-align: left"> 
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-size: 18px; color: rgb(0,0,0)"><strong><br />一、基本概念</strong></span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.1什么是库</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">在windows平台和linux平台下都大量存在着库。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">本质上来说库是一种可执行代码的二进制形式，可以被操作系统载入内存执行。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">由于windows和linux的平台不同（主要是编译器、汇编器和连接器的不同），因此二者库的二进制是不兼容的。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">本文仅限于介绍linux下的库。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.2库的种类</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">linux下的库有两种：静态库和共享库（动态库）。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">二者的不同点在于代码被载入的时刻不同。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">静态库的代码在编译过程中已经被载入可执行程序，因此体积较大。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">共享库的代码是在可执行程序运行时才载入内存的，在编译过程中仅简单的引用，因此代码体积较小。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.3库存在的意义</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">库是别人写好的现有的，成熟的，可以复用的代码，你可以使用但要记得遵守许可协议。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">现实中每个程序都要依赖很多基础的底层库，不可能每个人的代码都从零开始，因此库的存在意义非同寻常。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">共享库的好处是，不同的应用程序如果调用相同的库，那么在内存里只需要有一份该共享库的实例。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.4库文件是如何产生的在linux下</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">静态库的后缀是.a，它的产生分两步</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">Step 1.由源文件编译生成一堆.o，每个.o里都包含这个编译单元的符号表</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">Step 2.ar命令将很多.o转换成.a，成为静态库</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">动态库的后缀是.so，它由gcc加特定参数编译产生。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">具体方法参见后文实例。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.5库文件是如何命名的，有没有什么规范</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">在linux下，库文件一般放在/usr/lib和/lib下，</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">静态库的名字一般为libxxxx.a，其中xxxx是该lib的名称</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">动态库的名字一般为libxxxx.so.major.minor，xxxx是该lib的名称，major是主版本号，&nbsp;minor是副版本号</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.6如何知道一个可执行程序依赖哪些库</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">ldd命令可以查看一个可执行程序依赖的共享库，</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">例如# ldd /bin/lnlibc.so.6</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">=&gt; /lib/libc.so.6 (0&#215;40021000)/lib/ld-linux.so.2</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">=&gt; /lib/ld- linux.so.2 (0&#215;40000000)</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">可以看到ln命令依赖于libc库和ld-linux库</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.7可执行程序在执行的时候如何定位共享库文件</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">当系统加载可执行代码时候，能够知道其所依赖的库的名字，但是还需要知道绝对路径。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">此时就需要系统动态载入器(dynamic linker/loader)</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">对于elf格式的可执行程序，是由ld-linux.so*来完成的，它先后搜索elf文件的&nbsp;DT_RPATH段&#8212;环境变量LD_LIBRARY_PATH&#8212;/etc/ld.so.cache文件列表&#8212;/lib/,/usr/lib目录找到库文件后将其载入内存</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">如：export LD_LIBRARY_PATH=&#8217;pwd&#8217;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">将当前文件目录添加为共享目录</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.8在新安装一个库之后如何让系统能够找到他</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">如果安装在/lib或者/usr/lib下，那么ld默认能够找到，无需其他操作。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">如果安装在其他目录，需要将其添加到/etc/ld.so.cache文件中，步骤如下</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1.编辑/etc/ld.so.conf文件，加入库文件所在目录的路径</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">2.运行ldconfig，该命令会重建/etc/ld.so.cache文件</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><br /></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">二、用gcc生成静态和动态链接库的示例</span></p><span style="font-family: 宋体"><strong></strong></span>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">我们通常把一些公用函数制作成函数库，供其它程序使用。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">函数库分为静态库和动态库两种。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">静态库在程序编译时会被连接到目标代码中，程序运行时将不再需要该静态库。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">动态库在程序编译时并不会被连接到目标代码中，而是在程序运行是才被载入，因此在程序运行时还需要动态库存在。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">本文主要通过举例来说明在Linux中如何创建静态库和动态库，以及使用它们。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">为了便于阐述，我们先做一部分准备工作。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.1准备好测试代码hello.h、hello.c和main.c；</strong></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">hello.h(见程序1)为该函数库的头文件。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">hello.c(见程序2)是函数库的源程序，其中包含公用函数hello，该函数将在屏幕上输出"Hello XXX!"。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">main.c(见程序3)为测试库文件的主程序，在主程序中调用了公用函数hello。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;程序1: hello.h</p>
<table cellspacing="0" cellpadding="0" width="95%" bgcolor="#f1f1f1" border="1">
<tbody>
<tr>
<td>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">#ifndef HELLO_H<span class="Apple-converted-space">&nbsp;</span><br />#define HELLO_H<span class="Apple-converted-space">&nbsp;</span><br />&nbsp;&nbsp;<br />void hello(const char *name);<span class="Apple-converted-space">&nbsp;</span><br />&nbsp;&nbsp;<br />#endif</p></td></tr></tbody></table><br />
<div>程序2：hello.c 
<table cellspacing="0" cellpadding="0" width="95%" bgcolor="#f1f1f1" border="1">
<tbody>
<tr>
<td>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">#include &lt;stdio.h&gt;<span class="Apple-converted-space">&nbsp;</span><br />void hello(const char *name) {&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;printf("Hello %s!\n", name);<span class="Apple-converted-space">&nbsp;</span><br />}</p></td></tr></tbody></table><br /></div>
<div><span style="font-family: 宋体">程序3：main.c</span> 
<table cellspacing="0" cellpadding="0" width="95%" bgcolor="#f1f1f1" border="1">
<tbody>
<tr>
<td>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">#include "hello.h"<span class="Apple-converted-space">&nbsp;</span><br />&nbsp;int main()<span class="Apple-converted-space">&nbsp;</span><br />&nbsp;{<span class="Apple-converted-space">&nbsp;</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hello("everyone");<span class="Apple-converted-space">&nbsp;</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 0;<span class="Apple-converted-space">&nbsp;</span><br />&nbsp;}</p></td></tr></tbody></table>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.2问题的提出</strong></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">注意：这个时候，我们编译好的hello.o是无法通过gcc &#8211;o&nbsp;编译的，这个道理非常简单，</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">hello.c是一个没有main函数的.c程序，因此不够成一个完整的程序，如果使用gcc &#8211;o&nbsp;编译并连接它，GCC将报错。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">无论静态库，还是动态库，都是由.o文件创建的。因此，我们必须将源程序hello.c通过gcc先编译成.o文件。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">这个时候我们有三种思路：</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">1）&nbsp;&nbsp;通过编译多个源文件，直接将目标代码合成一个.o文件。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">2）&nbsp;&nbsp;通过创建静态链接库libmyhello.a，使得main函数调用hello函数时可调用静态链接库。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">3）&nbsp;&nbsp;通过创建动态链接库libmyhello.so，使得main函数调用hello函数时可调用静态链接库。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.3思路一：编译多个源文件</strong></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在系统提示符下键入以下命令得到</span><span style="font-family: 宋体">hello.o</span><span style="font-family: 宋体">文件。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -c hello.c</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">为什么不使用</span><span style="font-family: 宋体">gcc&#8211;o hello hello.cpp&nbsp;</span><span style="font-family: 宋体">这个道理我们之前已经说了，使用</span><span style="font-family: 宋体">-c</span><span style="font-family: 宋体">是什么意思呢？这涉及到</span><span style="font-family: 宋体">gcc&nbsp;</span><span style="font-family: 宋体">编译选项的常识。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们通常使用的</span><span style="font-family: 宋体">gcc &#8211;o&nbsp;</span><span style="font-family: 宋体">是将</span><span style="font-family: 宋体">.c</span><span style="font-family: 宋体">源文件编译成为一个可执行的二进制代码<span style="color: rgb(240,0,0)">(-o选项其实是制定输出文件文件名，如果不加-c选项，gcc默认会编译连接生成可执行文件，文件的名称有-o选项指定</span>)，这包括调用作为</span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">内的一部分真正的</span><span style="font-family: 宋体">C</span><span style="font-family: 宋体">编译器（</span><span style="font-family: 宋体">ccl</span><span style="font-family: 宋体">），以及调用</span><span style="font-family: 宋体">GNU C</span><span style="font-family: 宋体">编译器的输出中实际可执行代码的外部</span><span style="font-family: 宋体">GNU</span><span style="font-family: 宋体">汇编器（as）和连接器工具（ld）。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">而</span><span style="font-family: 宋体">gcc &#8211;c</span><span style="font-family: 宋体">是使用</span><span style="font-family: 宋体">GNU</span><span style="font-family: 宋体">汇编器将源文件转化为目标代码之后就结束，在这种情况下,只调用了C编译器（ccl）和汇编器（as），而连接器(ld)并没有被执行，所以输出的目标文件不会包含作为</span><span style="font-family: 宋体">Linux</span><span style="font-family: 宋体">程序在被装载和执行时所必须的包含信息，但它可以在以后被连接到一个程序。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们运行</span><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命令看看是否生存了</span><span style="font-family: 宋体">hello.o</span><span style="font-family: 宋体">文件。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.c hello.h hello.o main.c</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在</span><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命令结果中，我们看到了</span><span style="font-family: 宋体">hello.o</span><span style="font-family: 宋体">文件，本步操作完成。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">同理编译</span><span style="font-family: 宋体">main</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#gcc &#8211;c main.c</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">将两个文件链接成一个</span><span style="font-family: 宋体">.o</span><span style="font-family: 宋体">文件。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#gcc &#8211;o hello hello.o main.o</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">运行</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Hello everyone!</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">完成</span><span style="font-family: 宋体">^ ^!</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.4思路二：静态链接库</strong></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">下面我们先来看看如何创建静态库，以及使用它。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">静态库文件名的命名规范是以</span><span style="font-family: 宋体">lib</span><span style="font-family: 宋体">为前缀，紧接着跟静态库名</span><span style="font-family: 宋体">，扩展名为</span><span style="font-family: 宋体">.a</span><span style="font-family: 宋体">。例如：我们将创建的静态库名为</span><span style="font-family: 宋体">myhello</span><span style="font-family: 宋体">，则静态库文件名就是</span><span style="font-family: 宋体">libmyhello.a</span><span style="font-family: 宋体">。在创建和使用静态库时，需要注意这点。</span><span style="font-family: 宋体">创建静态库用</span><span style="font-family: 宋体">ar</span><span style="font-family: 宋体">命令</span><span style="font-family: 宋体">。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在系统提示符下键入以下命令将创建静态库文件</span><span style="font-family: 宋体">libmyhello.a</span><span style="font-family: 宋体">。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ar rcs libmyhello.a hello.o</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们同样运行</span><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命令查看结果：</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.c&nbsp;&nbsp;hello.h hello.o libmyhello.a main.c</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命令结果中有</span><span style="font-family: 宋体">libmyhello.a</span><span style="font-family: 宋体">。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">静态库制作完了，如何使用它内部的函数呢？只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明，然后在用</span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">命令生成目标文件时指明静态库名，</span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">将会从静态库中将公用函数连接到目标文件中。注意，</span><span style="color: rgb(240,0,0)"><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">会在静态库名前加上前缀</span><span style="font-family: 宋体">lib</span><span style="font-family: 宋体">，然后追加扩展名</span><span style="font-family: 宋体">.a</span><span style="font-family: 宋体">得到的静态库文件名来查找静态库文件,因此，我们在写需要连接的库时，只写名字就可以，如lib<span style="color: rgb(0,0,0)">myhello</span>.a的库，只写:-l<span style="color: rgb(0,0,0)">myhello</span></span></span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在程序</span><span style="font-family: 宋体">3:main.c</span><span style="font-family: 宋体">中，我们包含了静态库的头文件</span><span style="font-family: 宋体">hello.h</span><span style="font-family: 宋体">，然后在主程序</span><span style="font-family: 宋体">main</span><span style="font-family: 宋体">中直接调用公用函数</span><span style="font-family: 宋体">hello</span><span style="font-family: 宋体">。下面先生成目标程序</span><span style="font-family: 宋体">hello</span><span style="font-family: 宋体">，然后运行</span><span style="font-family: 宋体">hello</span><span style="font-family: 宋体">程序看看结果如何。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -o hello main.c -static -L. -lmyhello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Hello everyone!</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们删除静态库文件试试公用函数</span><span style="font-family: 宋体">hello</span><span style="font-family: 宋体">是否真的连接到目标文件</span><span style="font-family: 宋体">&nbsp;hello</span><span style="font-family: 宋体">中了。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># rm libmyhello.a</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">rm: remove regular file `libmyhello.a'? y</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">Hello everyone!</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">程序照常运行，静态库中的公用函数已经连接到目标文件中了。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"><span style="font-family: 宋体">静态链接库的一个缺点是，如果我们同时运行了许多程序，并且它们使用了同一个库函数，这样，在内存中会大量拷贝同一库函数。这样，就会浪费很多珍贵的内存和存储空间。使用了共享链接库的Linux就可以避免这个问题。</span></span></p><span style="font-family: 宋体"></span>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">共享函数库和静态函数在同一个地方，只是后缀有所不同。比如，在一个典型的Linux系统，标准的共享数序函数库是/usr/lib/libm.so。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="color: rgb(240,0,0); font-family: 宋体">当一个程序使用共享函数库时，在连接阶段并不把函数代码连接进来，而只是链接函数的一个引用。当最终的函数导入内存开始真正执行时，函数引用被解析，共享函数库的代码才真正导入到内存中。这样，共享链接库的函数就可以被许多程序同时共享，并且只需存储一次就可以了。共享函数库的另一个优点是，它可以独立更新，与调用它的函数毫不影响。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong>2.5思路三、动态链接库（共享函数库）</strong></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们继续看看如何在</span><span style="font-family: 宋体">Linux</span><span style="font-family: 宋体">中创建动态库。我们还是从</span><span style="font-family: 宋体">.o</span><span style="font-family: 宋体">文件开始。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">动态库文件名命名规范和静态库文件名命名规范类似，也是在动态库名增加前缀</span><span style="font-family: 宋体">lib</span><span style="font-family: 宋体">，但其文件扩展名为</span><span style="font-family: 宋体">.so</span><span style="font-family: 宋体">。例如：我们将创建的动态库名为</span><span style="font-family: 宋体">myhello</span><span style="font-family: 宋体">，则动态库文件名就是</span><span style="font-family: 宋体">libmyhello.so</span><span style="font-family: 宋体">。用</span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">来创建动态库。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在系统提示符下键入以下命令得到动态库文件</span><span style="font-family: 宋体">libmyhello.so</span><span style="font-family: 宋体">。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -shared -fPIC -o libmyhello.so hello.o</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">&nbsp;</span><span style="font-family: 宋体">&#8220;PIC&#8221;</span><span style="font-family: 宋体">命令行标记告诉</span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">产生的代码不要包含对函数和变量具体内存位置的引用，这是因为现在还无法知道使用该消息代码的应用程序会将它连接到哪一段内存地址空间。这样编译出的</span><span style="font-family: 宋体">hello.o</span><span style="font-family: 宋体">可以被用于建立共享链接库。建立共享链接库只需要用</span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">的</span><span style="font-family: 宋体">&#8221;-shared&#8221;</span><span style="font-family: 宋体">标记即可。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们照样使用</span><span style="font-family: 宋体">ls</span><span style="font-family: 宋体">命令看看动态库文件是否生成。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.cpp hello.h hello.o libmyhello.so main.cpp</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">调用动态链接库编译目标文件。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在程序中使用动态库和使用静态库完全一样，也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明，然后在用</span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">命令生成目标文件时指明动态库名进行编译。我们先运行</span><span style="font-family: 宋体">gcc</span><span style="font-family: 宋体">命令生成目标文件，再运行它看看结果。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">如果直接用如下方法进行编译，并连接：</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -o hello main.c -L. -lmyhello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">（使用</span><span style="font-family: 宋体">&#8221;-lmyhello&#8221;</span><span style="font-family: 宋体">标记来告诉</span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">驱动程序在连接阶段引用共享函数库</span><span style="font-family: 宋体">libmyhello.so</span><span style="font-family: 宋体">。</span><span style="font-family: 宋体">&#8221;-L.&#8221;</span><span style="font-family: 宋体">标记告诉</span><span style="font-family: 宋体">GCC</span><span style="font-family: 宋体">函数库可能位于当前目录。否则</span><span style="font-family: 宋体">GNU</span><span style="font-family: 宋体">连接器会查找标准系统函数目录:<span style="font-size: 16px">它先后搜索1.elf文件的&nbsp;DT_RPATH段&#8212;2.环境变量LD_LIBRARY_PATH&#8212;3./etc/ld.so.cache文件列表&#8212;4./lib/,/usr/lib目录找到库文件后将其载入内存,但是我们生成的共享库在当前文件夹下，并没有加到上述的4个路径的任何一个中，因此，执行后会出现错误）</span></span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">错误提示，找不到动态库文件</span><span style="font-family: 宋体">libmyhello.so</span><span style="font-family: 宋体">。程序在运行时，会在</span><span style="font-family: 宋体">/usr/lib</span><span style="font-family: 宋体">和</span><span style="font-family: 宋体">/lib</span><span style="font-family: 宋体">等目录中查找需要的动态库文件。若找到，则载入动态库，否则将提示类似上述错误而终止程序运行。有多种方法可以解决，</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">（1）我们将文件</span><span style="font-family: 宋体">&nbsp;libmyhello.so</span><span style="font-family: 宋体">复制到目录</span><span style="font-family: 宋体">/usr/lib</span><span style="font-family: 宋体">中，再试试。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># mv libmyhello.so /usr/lib</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">成功！</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">（2）既然连接器会搜寻LD_LIBRARY_PATH所指定的目录，那么我们可以将这个环境变量设置成当前目录：</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">先执行：</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">export LD_LIBRARY_PATH=$(pwd)</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">再执行：</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">./hello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">成功！</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">（3）</span></p><span style="font-family: 宋体"></span>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">执行：&nbsp;&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">ldconfig&nbsp;&nbsp; /usr/zhsoft/lib&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp; 注:&nbsp;&nbsp; 当用户在某个目录下面创建或拷贝了一个动态链接库,若想使其被系统共享,可以执行一下"<span style="color: rgb(240,0,0)">ldconfig&nbsp;&nbsp; 目录名</span>"这个命令.此命令的功能在于让ldconfig将指定目录下的动态链接库被系统共享起来,意即:在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库.本例让系统共享了/usr/zhsoft/lib目录下的动态链接库.该命令会重建/etc/ld.so.cache文件</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">成功！</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">下面的这个错误我没有遇到，不过也记录下，给遇到的人：</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;<span style="font-family: 宋体">{ &nbsp;</span><span style="font-family: 宋体">这步后我没有成功,报错内容如下：</span><span style="font-family: 宋体">/hello: error while loading shared libraries: /usr/lib/libmyhello.so: cannot restore segment prot after reloc: Permission denied</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">google了一下，发现是</span><span style="font-family: 宋体">SELinux搞的鬼，解决办法有两个：</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">1.<br />&nbsp;&nbsp;&nbsp; chcon -t texrel_shlib_t &nbsp;&nbsp;</span><span style="font-family: 宋体">/usr/lib/libmyhello.so</span><span style="font-family: 宋体"><br />&nbsp;&nbsp;&nbsp; (chcon -t texrel_shlib_t "你不能share的库的绝对路径")</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">2.<br />&nbsp;&nbsp;&nbsp; #vi /etc/sysconfig/selinux file<br />&nbsp;&nbsp;&nbsp; 或者用<br />&nbsp;&nbsp;&nbsp; #gedit /etc/sysconfig/selinux file<br />&nbsp;&nbsp;&nbsp; 修改SELINUX=disabled<br />&nbsp;&nbsp;&nbsp; 重启</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">}</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥￥</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">这也进一步说明了动态库在程序运行时是需要的。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">可以查看程序执行时调用动态库的过程：</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ldd hello<br />执行 test,可以看到它是如何调用动态库中的函数的。<br />[pin@localhost 20090505]$ ldd hello<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux-gate.so.1 =&gt; (0x00110000)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libmyhello.so =&gt; /usr/lib/libmyhello.so (0x00111000)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.so.6 =&gt; /lib/libc.so.6 (0x00859000)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /lib/ld-linux.so.2 (0x0083a000)</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">我们回过头看看，发现使用静态库和使用动态库编译成目标程序使用的gcc命令完全一样，<br />那当静态库和动态库同名时，gcc命令会使用哪个库文件呢？抱着对问题必究到底的心情，<br />来试试看。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">先删除除.c和.h外的所有文件，恢复成我们刚刚编辑完举例程序状态。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># rm -f hello hello.o /usr/lib/libmyhello.so</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.c hello.h main.c</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">在来创建静态库文件libmyhello.a和动态库文件libmyhello.so。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -c hello.c</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ar&nbsp;rcs libmyhello.a hello.o</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -shared -fPCI -o libmyhello.so hello.o</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ls</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">hello.c hello.h hello.o libmyhello.a libmyhello.so main.c</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">通过上述最后一条ls命令，可以发现静态库文件libmyhello.a和动态库文件libmyhello.so都已经生成，并都在当前目录中。然后，我们运行gcc命令来使用函数库myhello生成目标文件hello，并运行程序 hello。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># gcc -o hello main.c -L. -lmyhello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"># ./hello</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">./hello: error while loading shared libraries: libmyhello.so: cannot open shar<br />ed object file: No such file or directory</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">#</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="color: rgb(240,0,0); font-family: 宋体">从程序hello运行的结果中很容易知道，当静态库和动态库同名时， gcc命令将优先使用动态库。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="color: rgb(0,0,255)"><strong><span style="font-family: 宋体">Note:</span></strong></span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong><span style="font-family: 宋体">编译参数解析</span></strong></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 黑体"><br /></span><span style="font-family: 宋体">最主要的是GCC命令行的一个选项:<br />-shared 该选项指定生成动态连接库（让连接器生成T类型的导出符号表，有时候也生成弱连接W类型的导出符号），不用该标志外部程序无法连接。相当于一个可执行文件<br />l -fPIC：表示编译为位置独立的代码，不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要，而不能达到真正代码段共享的目的。<br />l -L.：表示要连接的库在当前目录中<br />l -ltest：编译器查找动态连接库时有隐含的命名规则，即在给出的名字前面加上lib，后面加上.so来确定库的名称<br />l LD_LIBRARY_PATH：这个环境变量指示动态连接器可以装载动态库的路径。<br />l 当然如果有root权限的话，可以修改/etc/ld.so.conf文件，然后调用 /sbin/ldconfig来达到同样的目的，不过如果没有root权限，那么只能采用输出LD_LIBRARY_PATH的方法了。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体">调用动态库的时候有几个问题会经常碰到，有时，明明已经将库的头文件所在目录 通过 &#8220;-I&#8221; include进来了，库所在文件通过 &#8220;-L&#8221;参数引导，并指定了&#8220;-l&#8221;的库名，但通过ldd命令察看时，就是死活找不到你指定链接的so文件，这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。<br /></span></p><br />
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 宋体"><strong>静态库链接时搜索路径顺序：</strong></span><br /><br /><span style="font-family: 宋体">1. ld会去找GCC命令中的参数-L<br /><br />2. 再找gcc的环境变量LIBRARY_PATH<br /><br />3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><br /></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><strong><span style="font-family: 宋体">动态链接时、执行时搜索路径顺序:</span></strong></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><br /><span style="font-family: 宋体">1. &nbsp;编译目标代码时指定的动态库搜索路径；<br /><br />2. &nbsp;环境变量LD_LIBRARY_PATH指定的动态库搜索路径；<br /><br />3. &nbsp;配置文件/etc/ld.so.conf中指定的动态库搜索路径；<br /><br />4. 默认的动态库搜索路径/lib；<br /><br />5. 默认的动态库搜索路径/usr/lib。</span></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><br /></p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px"><span style="font-family: 黑体"><strong>有关环境变量：</strong></span><br /><br /><span style="font-family: 宋体">LIBRARY_PATH环境变量：指定程序静态链接库文件搜索路径<br /><br />LD_LIBRARY_PATH环境变量：指定程序动态链接库文件搜索路径</span></p></div></span></span><br /><img src ="http://www.cppblog.com/deane/aggbug/165216.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2012-02-09 13:43 <a href="http://www.cppblog.com/deane/articles/165216.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境进程间通信  共享内存（下）</title><link>http://www.cppblog.com/deane/articles/139057.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Fri, 21 Jan 2011 12:59:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/139057.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/139057.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/139057.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/139057.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/139057.html</trackback:ping><description><![CDATA[<p><br>系统调用mmap()通过映射一个普通文件实现共享内存。系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说，每个共享内存区域对应特殊文件系统shm中的一个文件（这是通过shmid_kernel结构联系起来的），后面还将阐述。</p>
<p><a name=1><span class=atitle><strong><font size=5>1、系统V共享内存原理</font></strong></span></a></p>
<p>进程间需要共享的数据被放在一个叫做IPC共享内存区域的地方，所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。系统V共享内存通过shmget获得或创建一个IPC共享内存区域，并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区，初始化该共享内存区相应的shmid_kernel结构注同时，还将在特殊文件系统shm中，创建并打开一个同名文件，并在内存中建立起该文件的相应dentry及inode结构，新打开的文件不属于任何一个进程（任何进程都可以访问该共享内存区）。所有这一切都是系统调用shmget完成的。</p>
<p>注：每一个共享内存区都有一个控制结构struct shmid_kernel，shmid_kernel是共享内存区域中非常重要的一个数据结构，它是存储管理和文件系统结合起来的桥梁，定义如下：</p>
<table cellSpacing=0 cellPadding=0 width="70%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>struct shmid_kernel /* private to the kernel */
            {
            struct kern_ipc_perm	shm_perm;
            struct file *		shm_file;
            int			id;
            unsigned long		shm_nattch;
            unsigned long		shm_segsz;
            time_t			shm_atim;
            time_t			shm_dtim;
            time_t			shm_ctim;
            pid_t			shm_cprid;
            pid_t			shm_lprid;
            };
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>该结构中最重要的一个域应该是shm_file，它存储了将被映射文件的地址。每个共享内存区对象都对应特殊文件系统shm中的一个文件，一般情况下，特殊文件系统shm中的文件是不能用read()、write()等方法访问的，当采取共享内存的方式把其中的文件映射到进程地址空间后，可直接采用访问内存的方式对其访问。</p>
<p>这里我们采用[1]中的图表给出与系统V共享内存相关数据结构：</p>
<br><img height=409 alt="" src="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/image001.jpg" width=580 border=0> <br>
<p>正如消息队列和信号灯一样，内核通过数据结构struct ipc_ids shm_ids维护系统中的所有共享内存区域。上图中的shm_ids.entries变量指向一个ipc_id结构数组，而每个ipc_id结构数组中有个指向kern_ipc_perm结构的指针。到这里读者应该很熟悉了，对于系统V共享内存区来说，kern_ipc_perm的宿主是shmid_kernel结构，shmid_kernel是用来描述一个共享内存区域的，这样内核就能够控制系统中所有的共享区域。同时，在shmid_kernel结构的file类型指针shm_file指向文件系统shm中相应的文件，这样，共享内存区域就与shm文件系统中的文件对应起来。</p>
<p>在创建了一个共享内存区域后，还要将它映射到进程地址空间，系统调用shmat()完成此项功能。由于在调用shmget()时，已经创建了文件系统shm中的一个同名文件与共享内存区域相对应，因此，调用shmat()的过程相当于映射文件系统shm中的同名文件过程，原理与mmap()大同小异。</p>
<div class=ibm-alternate-rule>
<hr>
</div>
<p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html#ibm-pcon"><font color=#4c6e94><strong>回页首</strong></font></a></p>
<p><a name=2><span class=atitle><strong><font size=5>2、系统V共享内存API</font></strong></span></a></p>
<p>对于系统V共享内存，主要有以下几个API：shmget()、shmat()、shmdt()及shmctl()。</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include &lt;sys/ipc.h&gt;
            #include &lt;sys/shm.h&gt;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>shmget（）用来获得共享内存区域的ID，如果不存在指定的共享区域就创建相应的区域。shmat()把共享内存区域映射到调用进程的地址空间中去，这样，进程就可以方便地对共享区域进行访问操作。shmdt()调用用来解除进程对共享内存区域的映射。shmctl实现对共享内存区域的控制操作。这里我们不对这些系统调用作具体的介绍，读者可参考相应的手册页面，后面的范例中将给出它们的调用方法。</p>
<p>注：shmget的内部实现包含了许多重要的系统V共享内存机制；shmat在把共享内存区域映射到进程空间时，并不真正改变进程的页表。当进程第一次访问内存映射区域访问时，会因为没有物理页表的分配而导致一个缺页异常，然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的页表。</p>
<div class=ibm-alternate-rule>
<hr>
</div>
<p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html#ibm-pcon"><font color=#4c6e94><strong>回页首</strong></font></a></p>
<p><a name=3><span class=atitle><strong><font size=5>3、系统V共享内存限制</font></strong></span></a></p>
<p>在/proc/sys/kernel/目录下，记录着系统V共享内存的一下限制，如一个共享内存区的最大字节数shmmax，系统范围内最大共享内存区标识符数shmmni等，可以手工对其调整，但不推荐这样做。</p>
<p>在[2]中，给出了这些限制的测试方法，不再赘述。</p>
<div class=ibm-alternate-rule>
<hr>
</div>
<p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html#ibm-pcon"><font color=#4c6e94><strong>回页首</strong></font></a></p>
<p><a name=4><span class=atitle><strong><font size=5>4、系统V共享内存范例</font></strong></span></a></p>
<p>本部分将给出系统V共享内存API的使用方法，并对比分析系统V共享内存机制与mmap()映射普通文件实现共享内存之间的差异，首先给出两个进程通过系统V共享内存通信的范例：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>/***** testwrite.c *******/
            #include &lt;sys/ipc.h&gt;
            #include &lt;sys/shm.h&gt;
            #include &lt;sys/types.h&gt;
            #include &lt;unistd.h&gt;
            typedef struct{
            char name[4];
            int age;
            } people;
            main(int argc, char** argv)
            {
            int shm_id,i;
            key_t key;
            char temp;
            people *p_map;
            char* name = "/dev/shm/myshm2";
            key = ftok(name,0);
            if(key==-1)
            perror("ftok error");
            shm_id=shmget(key,4096,IPC_CREAT);
            if(shm_id==-1)
            {
            perror("shmget error");
            return;
            }
            p_map=(people*)shmat(shm_id,NULL,0);
            temp='a';
            for(i = 0;i&lt;10;i++)
            {
            temp+=1;
            memcpy((*(p_map+i)).name,&amp;temp,1);
            (*(p_map+i)).age=20+i;
            }
            if(shmdt(p_map)==-1)
            perror(" detach error ");
            }
            /********** testread.c ************/
            #include &lt;sys/ipc.h&gt;
            #include &lt;sys/shm.h&gt;
            #include &lt;sys/types.h&gt;
            #include &lt;unistd.h&gt;
            typedef struct{
            char name[4];
            int age;
            } people;
            main(int argc, char** argv)
            {
            int shm_id,i;
            key_t key;
            people *p_map;
            char* name = "/dev/shm/myshm2";
            key = ftok(name,0);
            if(key == -1)
            perror("ftok error");
            shm_id = shmget(key,4096,IPC_CREAT);
            if(shm_id == -1)
            {
            perror("shmget error");
            return;
            }
            p_map = (people*)shmat(shm_id,NULL,0);
            for(i = 0;i&lt;10;i++)
            {
            printf( "name:%s\n",(*(p_map+i)).name );
            printf( "age %d\n",(*(p_map+i)).age );
            }
            if(shmdt(p_map) == -1)
            perror(" detach error ");
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>testwrite.c创建一个系统V共享内存区，并在其中写入格式化数据；testread.c访问同一个系统V共享内存区，读出其中的格式化数据。分别把两个程序编译为testwrite及testread，先后执行./testwrite及./testread 则./testread输出结果如下：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
            name: g	age 25;	name: h	age 26;	name: I	age 27;	name: j	age 28;	name: k	age 29;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>通过对试验结果分析，对比系统V与mmap()映射普通文件实现共享内存通信，可以得出如下结论：</p>
<p>1、 系统V共享内存中的数据，从来不写入到实际磁盘文件中去；而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注：前面讲到，系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的，文件系统shm的安装点在交换分区上，系统重新引导后，所有的内容都丢失。</p>
<p>2、 系统V共享内存是随内核持续的，即使所有访问共享内存的进程都已经正常终止，共享内存区仍然存在（除非显式删除共享内存），在内核重新引导之前，对该共享内存区域的任何改写操作都将一直保留。</p>
<p>3、 通过调用mmap()映射普通文件进行进程间通信时，一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注：这里没有给出shmctl的使用范例，原理与消息队列大同小异。</p>
<div class=ibm-alternate-rule>
<hr>
</div>
<p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html#ibm-pcon"><font color=#4c6e94><strong>回页首</strong></font></a></p>
<p><a name=5><span class=atitle><strong><font size=5>结论：</font></strong></span></a></p>
<p>共享内存允许两个或多个进程共享一给定的存储区，因为数据不需要来回复制，所以是最快的一种进程间通信机制。共享内存可以通过mmap()映射普通文件（特殊情况下还可以采用匿名映射）机制实现，也可以通过系统V共享内存机制实现。应用接口和原理很简单，内部机制复杂。为了实现更安全通信，往往还与信号灯等同步机制共同使用。</p>
<p>共享内存涉及到了存储管理以及文件系统等方面的知识，深入理解其内部机制有一定的难度，关键还要紧紧抓住内核使用的重要数据结构。系统V共享内存是以文件的形式组织在特殊文件系统shm中的。通过shmget可以创建或获得共享内存的标识符。取得共享内存标识符后，要通过shmat将这个内存区映射到本进程的虚拟地址空间。</p>
<!-- CMA ID: 21240 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file:  dw-article-6.0-beta.xsl --><br>
<p><a name=resources><span class=atitle><strong><font size=5>参考资料 </font></strong></span></a></p>
<p>[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 对各主题阐述得重点突出，脉络清晰。</p>
<p>[2] UNIX网络编程第二卷：进程间通信，作者：W.Richard Stevens，译者：杨继张，清华大学出版社。对mmap()有详细阐述。</p>
<p>[3] Linux内核源代码情景分析（上），毛德操、胡希明著，浙江大学出版社，给出了mmap()相关的源代码分析。</p>
<p>[4]shmget、shmat、shmctl、shmdt手册</p>
<br>
<img src ="http://www.cppblog.com/deane/aggbug/139057.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2011-01-21 20:59 <a href="http://www.cppblog.com/deane/articles/139057.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境进程间通信 共享内存（上）</title><link>http://www.cppblog.com/deane/articles/139056.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Fri, 21 Jan 2011 12:58:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/139056.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/139056.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/139056.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/139056.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/139056.html</trackback:ping><description><![CDATA[<p><br>采用共享内存通信的一个显而易见的好处是效率高，因为进程可以直接读写内存，而不需要任何数据的拷贝。对于像管道和消息队列等通信方式，则需要在内核和用户空间进行四次的数据拷贝，而共享内存则只拷贝两次数据[1]：一次从输入文件到共享内存区，另一次从共享内存区到输出文件。实际上，进程之间在共享内存时，并不总是读写少量数据后就解除映射，有新的通信时，再重新建立共享内存区域。而是保持共享区域，直到通信完毕为止，这样，数据内容一直保存在共享内存中，并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此，采用共享内存的通信方式效率是非常高的。</p>
<p>Linux的2.2.x内核支持多种共享内存方式，如mmap()系统调用，Posix共享内存，以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存，但还没实现Posix共享内存，本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。</p>
<p><a name=1><span class=atitle>一、内核怎样保证各个进程寻址到同一个共享内存区域的内存页面</span></a></p>
<p>1、page cache及swap cache中页面的区分：一个被访问文件的物理页面都驻留在page cache或swap cache中，一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ，它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。</p>
<p>2、文件与address_space结构的对应：一个具体的文件在打开后，内核会在内存中为之建立一个struct inode结构，其中的i_mapping域指向一个address_space结构。这样，一个文件就对应一个address_space结构，一个address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此，当要寻址某个数据时，很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。</p>
<p>3、进程调用mmap()时，只是在进程空间内新增了一块相应大小的缓冲区，并设置了相应的访问标识，但并没有建立进程空间到物理页面的映射。因此，第一次访问该空间时，会引发一个缺页异常。</p>
<p>4、对于共享内存映射情况，缺页异常处理程序首先在swap cache中寻找目标页（符合address_space以及偏移量的物理页），如果找到，则直接返回地址；如果没有找到，则判断该页是否在交换区(swap area)，如果在，则执行一个换入操作；如果上述两种情况都不满足，处理程序将分配新的物理页面，并把它插入到page cache中。进程最终将更新进程页表。 <br>注：对于映射普通文件情况（非共享映射），缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到，则说明文件数据还没有读入内存，处理程序会从磁盘读入相应的页面，并返回相应地址，同时，进程页表也会更新。 </p>
<p>5、所有进程在映射同一个共享内存区域时，情况都一样，在建立线性地址与物理地址之间的映射之后，不论进程各自的返回地址如何，实际访问的必然是同一个共享内存区域对应的物理页面。 <br>注：一个共享内存区域可以看作是特殊文件系统shm中的一个文件，shm的安装点在交换区上。 </p>
<p>上面涉及到了一些数据结构，围绕数据结构理解问题会容易一些。</p>
<div class=ibm-alternate-rule>
<hr>
</div>
<p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html#ibm-pcon"><u><font color=#0000ff>回页首</font></u></a></p>
<p><a name=2><span class=atitle>二、mmap()及其相关系统调用</span></a></p>
<p>mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后，进程可以向访问普通内存一样对文件进行访问，不必再调用read()，write（）等操作。</p>
<p>注：实际上，mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式，进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的，当然mmap()实现共享内存也是其主要应用之一。</p>
<p><a name=N1006A><span class=smalltitle>1、mmap()系统调用形式如下：</span></a></p>
<p>void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset ) <br>参数fd为即将映射到进程空间的文件描述字，一般由open()返回，同时，fd可以指定为-1，此时须指定flags参数中的MAP_ANON，表明进行的是匿名映射（不涉及具体的文件名，避免了文件的创建及打开，很显然只能用于具有亲缘关系的进程间通信）。len是映射到调用进程地址空间的字节数，它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或：PROT_READ（可读） , PROT_WRITE （可写）, PROT_EXEC （可执行）, PROT_NONE（不可访问）。flags由以下几个常值指定：MAP_SHARED , MAP_PRIVATE , MAP_FIXED，其中，MAP_SHARED , MAP_PRIVATE必选其一，而MAP_FIXED则不推荐使用。offset参数一般设为0，表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址，一般被指定一个空指针，此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址，进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数，读者可参考mmap()手册页获得进一步的信息。 </p>
<p><a name=N10074><span class=smalltitle>2、系统调用mmap()用于共享内存的两种方式：</span></a></p>
<p>（1）使用普通文件提供的内存映射：适用于任何进程之间；此时，需要打开或创建一个文件，然后再调用mmap()；典型调用代码如下： </p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>	fd=open(name, flag, mode);
            if(fd&lt;0)
            ...
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方，我们将在范例中进行具体说明。 </p>
<p>（2）使用特殊文件提供匿名内存映射：适用于具有亲缘关系的进程之间；由于父子进程特殊的亲缘关系，在父进程中先调用mmap()，然后调用fork()。那么在调用fork()之后，子进程继承父进程匿名映射后的地址空间，同样也继承mmap()返回的地址，这样，父子进程就可以通过映射区域进行通信了。注意，这里不是一般的继承关系。一般来说，子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址，却由父子进程共同维护。 <br>对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时，不必指定具体的文件，只要设置相应的标志即可，参见范例2。 </p>
<p><a name=N10089><span class=smalltitle>3、系统调用munmap()</span></a></p>
<p>int munmap( void * addr, size_t len ) <br>该调用在进程地址空间中解除一个映射关系，addr是调用mmap()时返回的地址，len是映射区的大小。当映射关系解除后，对原来映射地址的访问将导致段错误发生。 </p>
<p><a name=N10093><span class=smalltitle>4、系统调用msync()</span></a></p>
<p>int msync ( void * addr , size_t len, int flags) <br>一般说来，进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中，往往在调用munmap（）后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。 </p>
<div class=ibm-alternate-rule>
<hr>
</div>
<p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html#ibm-pcon"><u><font color=#0000ff>回页首</font></u></a></p>
<p><a name=3><span class=atitle>三、mmap()范例</span></a></p>
<p>下面将给出使用mmap()的两个范例：范例1给出两个进程通过映射普通文件实现共享内存通信；范例2给出父子进程通过匿名映射实现共享内存。系统调用mmap()有许多有趣的地方，下面是通过mmap（）映射普通文件实现进程间的通信的范例，我们通过该范例来说明mmap()实现共享内存的特点及注意事项。</p>
<p><a name=N100A5><span class=smalltitle>范例1：两个进程通过映射普通文件实现共享内存通信</span></a></p>
<p>范例1包含两个子程序：map_normalfile1.c及map_normalfile2.c。编译两个程序，可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件，把该文件映射到进程的地址空间，并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间，然后对映射后的地址空间执行读操作。这样，两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。</p>
<p>下面是两个程序代码：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>/*-------------map_normalfile1.c-----------*/
            #include &lt;sys/mman.h&gt;
            #include &lt;sys/types.h&gt;
            #include &lt;fcntl.h&gt;
            #include &lt;unistd.h&gt;
            typedef struct{
            char name[4];
            int  age;
            }people;
            main(int argc, char** argv) // map a normal file as shared mem:
            {
            int fd,i;
            people *p_map;
            char temp;
            fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
            lseek(fd,sizeof(people)*5-1,SEEK_SET);
            write(fd,"",1);
            p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
            MAP_SHARED,fd,0 );
            close( fd );
            temp = 'a';
            for(i=0; i&lt;10; i++)
            {
            temp += 1;
            memcpy( ( *(p_map+i) ).name, &amp;temp,2 );
            ( *(p_map+i) ).age = 20+i;
            }
            printf(" initialize over \n ")；
            sleep(10);
            munmap( p_map, sizeof(people)*10 );
            printf( "umap ok \n" );
            }
            /*-------------map_normalfile2.c-----------*/
            #include &lt;sys/mman.h&gt;
            #include &lt;sys/types.h&gt;
            #include &lt;fcntl.h&gt;
            #include &lt;unistd.h&gt;
            typedef struct{
            char name[4];
            int  age;
            }people;
            main(int argc, char** argv)  // map a normal file as shared mem:
            {
            int fd,i;
            people *p_map;
            fd=open( argv[1],O_CREAT|O_RDWR,00777 );
            p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
            MAP_SHARED,fd,0);
            for(i = 0;i&lt;10;i++)
            {
            printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );
            }
            munmap( p_map,sizeof(people)*10 );
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>map_normalfile1.c首先定义了一个people数据结构，（在这里采用数据结构的方式是因为，共享内存区的数据往往是有固定格式的，这由通信的各个进程决定，采用结构的方式有普遍代表性）。map_normfile1首先打开或创建一个文件，并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始，设置了10个people结构。然后，进程睡眠10秒钟，等待其他进程映射同一个文件，最后解除映射。</p>
<p>map_normfile2.c只是简单的映射一个文件，并以people数据结构的格式从mmap()返回的地址处读取10个people结构，并输出读取的值，然后解除映射。</p>
<p>分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后，在一个终端上先运行./map_normalfile2 /tmp/test_shm，程序输出结果如下：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>initialize over
            umap ok
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>在map_normalfile1输出initialize over 之后，输出umap ok之前，在另一个终端上运行map_normalfile2 /tmp/test_shm，将会产生如下输出(为了节省空间，输出结果为稍作整理后的结果)：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
            name: g	age 25;	name: h	age 26;	name: I	age 27;	name: j	age 28;	name: k	age 29;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>在map_normalfile1 输出umap ok后，运行map_normalfile2则输出如下结果：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
            name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0;
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p><strong>从程序的运行结果中可以得出的结论</strong> </p>
<p>1、 最终被映射文件的内容的长度不会超过文件本身的初始大小，即映射不能改变文件的大小；</p>
<p>2、 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小，但不完全受限于文件大小。打开文件被截短为5个people结构大小，而在map_normalfile1中初始化了10个people数据结构，在恰当时候（map_normalfile1输出initialize over 之后，输出umap ok之前）调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值，后面将给出详细讨论。 <br>注：在linux中，内存的保护是以页为基本单位的，即使被映射文件只有一个字节大小，内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时，进程可以对从mmap()返回地址开始的一个页面大小进行访问，而不会出错；但是，如果对一个页面以外的地址空间进行访问，则导致错误发生，后面将进一步描述。因此，可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。 </p>
<p>3、 文件一旦被映射后，调用mmap()的进程对返回地址的访问是对某一内存区域的访问，暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义，只有在调用了munmap()后或者msync()时，才把内存中的相应内容写回磁盘文件，所写内容仍然不能超过文件的大小。</p>
<p><a name=N100E0><span class=smalltitle>范例2：父子进程通过匿名映射实现共享内存</span></a></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include &lt;sys/mman.h&gt;
            #include &lt;sys/types.h&gt;
            #include &lt;fcntl.h&gt;
            #include &lt;unistd.h&gt;
            typedef struct{
            char name[4];
            int  age;
            }people;
            main(int argc, char** argv)
            {
            int i;
            people *p_map;
            char temp;
            p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
            MAP_SHARED|MAP_ANONYMOUS,-1,0);
            if(fork() == 0)
            {
            sleep(2);
            for(i = 0;i&lt;5;i++)
            printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);
            (*p_map).age = 100;
            munmap(p_map,sizeof(people)*10); //实际上，进程终止时，会自动解除映射。
            exit();
            }
            temp = 'a';
            for(i = 0;i&lt;5;i++)
            {
            temp += 1;
            memcpy((*(p_map+i)).name, &amp;temp,2);
            (*(p_map+i)).age=20+i;
            }
            sleep(5);
            printf( "parent read: the first people,s age is %d\n",(*p_map).age );
            printf("umap\n");
            munmap( p_map,sizeof(people)*10 );
            printf( "umap ok\n" );
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>考察程序的输出结果，体会父子进程匿名共享内存：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>child read: the 1 people's age is 20
            child read: the 2 people's age is 21
            child read: the 3 people's age is 22
            child read: the 4 people's age is 23
            child read: the 5 people's age is 24
            parent read: the first people,s age is 100
            umap
            umap ok
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<div class=ibm-alternate-rule>
<hr>
</div>
<p class="ibm-ind-link ibm-back-to-top"><a class=ibm-anchor-up-link href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html#ibm-pcon"><u><font color=#0000ff>回页首</font></u></a></p>
<p><a name=4><span class=atitle>四、对mmap()返回地址的访问</span></a></p>
<p>前面对范例运行结构的讨论中已经提到，linux采用的是页式管理机制。对于用mmap()映射普通文件来说，进程会在自己的地址空间新增一块空间，空间大小由mmap()的len参数指定，注意，进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说，能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始，能够有效访问的地址空间大小。超过这个空间大小，内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明：</p>
<br><img height=169 alt="图 1" src="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/image001.gif" width=580 border=0> <br>
<p>注意：文件被映射部分而不是整个文件决定了进程能够访问的空间大小，另外，如果指定文件的偏移部分，一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include &lt;sys/mman.h&gt;
            #include &lt;sys/types.h&gt;
            #include &lt;fcntl.h&gt;
            #include &lt;unistd.h&gt;
            typedef struct{
            char name[4];
            int  age;
            }people;
            main(int argc, char** argv)
            {
            int fd,i;
            int pagesize,offset;
            people *p_map;
            pagesize = sysconf(_SC_PAGESIZE);
            printf("pagesize is %d\n",pagesize);
            fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
            lseek(fd,pagesize*2-100,SEEK_SET);
            write(fd,"",1);
            offset = 0;	//此处offset = 0编译成版本1；offset = pagesize编译成版本2
            p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
            close(fd);
            for(i = 1; i&lt;10; i++)
            {
            (*(p_map+pagesize/sizeof(people)*i-2)).age = 100;
            printf("access page %d over\n",i);
            (*(p_map+pagesize/sizeof(people)*i-1)).age = 100;
            printf("access page %d edge over, now begin to access page %d\n",i, i+1);
            (*(p_map+pagesize/sizeof(people)*i)).age = 100;
            printf("access page %d over\n",i+1);
            }
            munmap(p_map,sizeof(people)*10);
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>如程序中所注释的那样，把程序编译成两个版本，两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间（大小为：pagesize*2-99），版本1的被映射部分是整个文件，版本2的文件被映射部分是文件大小减去一个页面后的剩余部分，不到一个页面大小(大小为：pagesize-99)。程序中试图访问每一个页面边界，两个版本都试图在进程空间中映射pagesize*3的字节数。</p>
<p>版本1的输出结果如下：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>pagesize is 4096
            access page 1 over
            access page 1 edge over, now begin to access page 2
            access page 2 over
            access page 2 over
            access page 2 edge over, now begin to access page 3
            Bus error		//被映射文件在进程空间中覆盖了两个页面，此时，进程试图访问第三个页面
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>版本2的输出结果如下：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>pagesize is 4096
            access page 1 over
            access page 1 edge over, now begin to access page 2
            Bus error		//被映射文件在进程空间中覆盖了一个页面，此时，进程试图访问第二个页面
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>结论：采用系统调用mmap()实现进程间通信是很方便的，在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容，可以参考一下相关重要数据结构来加深理解。在本专题的后面部分，将介绍系统v共享内存的实现。</p>
<!-- CMA ID: 22175 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file:  dw-article-6.0-beta.xsl --><br>
<p><a name=resources><span class=atitle>参考资料 </span></a></p>
<p>[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 对各主题阐述得重点突出，脉络清晰。</p>
<p>[2] UNIX网络编程第二卷：进程间通信，作者：W.Richard Stevens，译者：杨继张，清华大学出版社。对mmap()有详细阐述。</p>
<p>[3] Linux内核源代码情景分析（上），毛德操、胡希明著，浙江大学出版社，给出了mmap()相关的源代码分析。</p>
<p>[4]mmap()手册<br></p>
<img src ="http://www.cppblog.com/deane/aggbug/139056.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2011-01-21 20:58 <a href="http://www.cppblog.com/deane/articles/139056.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>shmat</title><link>http://www.cppblog.com/deane/articles/139048.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Fri, 21 Jan 2011 09:31:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/139048.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/139048.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/139048.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/139048.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/139048.html</trackback:ping><description><![CDATA[<br>
<h1 class=title>　　作用:共享内存区对象映射到调用进程的地址空间 </h1>
<div class=lemma-main-content id=lemmaContent-0>
<div class=spctrl></div>
　　核心处理函数： void *shmat( int shmid , char *shmaddr , int shmflag );shmat()是用来允许本进程访问一块共享内存的函数。
<div class=spctrl></div>
　　int shmid是那块共享内存的ID。
<div class=spctrl></div>
　　char *shmaddr是共享内存的起始地址
<div class=spctrl></div>
　　int shmflag是本进程对该内存的操作模式。如果是SHM_RDONLY的话，就是只读模式。其它的是读写模式
<div class=spctrl></div>
　　成功时，这个函数返回共享内存的起始地址。失败时返回-1
<div class=spctrl></div>
　　最近用到内存共享，收集整理了些资料，做了个简单的对比
<table>
    <tbody>
        <tr>
            <td width=101>&nbsp;</td>
            <td width=196>mmap系统调用</td>
            <td width=228>系统V共享内存</td>
        </tr>
        <tr>
            <td width=101>获取共享
            <div class=spctrl></div>
            　　内存ID</td>
            <td width=196>#include&nbsp;&lt;sys/mman.h&gt;
            <div class=spctrl></div>
            　　fd=open(name&nbsp;,flag,mode);
            <div class=spctrl></div>
            　　if(fd&lt;0)
            <div class=spctrl></div>
            　　&#8230;.</td>
            <td width=228>#include&nbsp;&lt;sys/ipc.h&gt;
            <div class=spctrl></div>
            　　#include&nbsp;&lt;sys/shm.h&gt;
            <div class=spctrl></div>
            　　int&nbsp;<a href="http://baike.baidu.com/view/3170880.htm" target=_blank><u><font color=#0000ff>shmget</font></u></a>(key_t&nbsp;key,&nbsp;size_t&nbsp;size,&nbsp;int&nbsp;shmflg); </td>
        </tr>
        <tr>
            <td width=101>映射内存</td>
            <td width=196>ptr=mmap(NULL,len,&nbsp;PROT_READ|PROT_WRITE,&nbsp;
            <div class=spctrl></div>
            　　MAP_SHARED&nbsp;,&nbsp;fd&nbsp;,&nbsp;0);&nbsp;</td>
            <td width=228>void&nbsp;*shmat(&nbsp;int&nbsp;shmid&nbsp;,&nbsp;char&nbsp;*shmaddr&nbsp;,&nbsp;int&nbsp;shmflag&nbsp;);</td>
        </tr>
        <tr>
            <td width=101>解除映射</td>
            <td width=196>int&nbsp;munmap(&nbsp;void&nbsp;*&nbsp;addr,&nbsp;size_t&nbsp;len&nbsp;)&nbsp;; </td>
            <td width=228>int&nbsp;shmdt(&nbsp;char&nbsp;*shmaddr&nbsp;);
            <div class=spctrl></div>
            　　使进程中的映射内存无效化，不可以使用。但是保留空间</td>
        </tr>
        <tr>
            <td width=101>其它</td>
            <td width=196>同步：
            <div class=spctrl></div>
            　　int&nbsp;msync&nbsp;(&nbsp;void&nbsp;*&nbsp;addr&nbsp;,&nbsp;size_t&nbsp;len,&nbsp;int&nbsp;flags);</td>
            <td width=228>控制：
            <div class=spctrl></div>
            　　shmctl(&nbsp;shmid&nbsp;,&nbsp;IPC_STAT&nbsp;,&nbsp;&amp;buf&nbsp;);&nbsp;
            <div class=spctrl></div>
            　　//&nbsp;取得共享内存的状态
            <div class=spctrl></div>
            　　shmctl(&nbsp;shmid&nbsp;,&nbsp;IPC_RMID&nbsp;,&nbsp;&amp;buf&nbsp;);&nbsp;
            <div class=spctrl></div>
            　　//&nbsp;删除共享内存&#8211;删除共享内存，彻底不可用，释放空间</td>
        </tr>
    </tbody>
</table>
</div>
<img src ="http://www.cppblog.com/deane/aggbug/139048.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2011-01-21 17:31 <a href="http://www.cppblog.com/deane/articles/139048.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux下获取时间的若干函数</title><link>http://www.cppblog.com/deane/articles/118718.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Fri, 25 Jun 2010 08:00:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/118718.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/118718.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/118718.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/118718.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/118718.html</trackback:ping><description><![CDATA[<p><br>asctime（将时间和日期以字符串格式表示） <br>&nbsp;<br>&nbsp;<br>相关函数<br>&nbsp;time，ctime，gmtime，localtime<br>&nbsp;<br>表头文件<br>&nbsp;#include&lt;time.h&gt;<br>&nbsp;<br>定义函数<br>&nbsp;char * asctime(const struct tm * timeptr);<br>&nbsp;<br>函数说明<br>&nbsp;asctime()将参数timeptr所指的tm结构中的信息转换成真实世界所使用的时间日期表示方法，然后将结果以字符串形态返回。此函数已经由时区转换成当地时间，字符串格式为:&#8220;Wed Jun 30 21:49:08 1993\n&#8221;<br>&nbsp;<br>返回值<br>&nbsp;若再调用相关的时间日期函数，此字符串可能会被破坏。此函数与ctime不同处在于传入的参数是不同的结构。<br>&nbsp;<br>附加说明<br>&nbsp;返回一字符串表示目前当地的时间日期。<br>&nbsp;<br>范例<br>&nbsp;#include &lt;time.h&gt;<br>main()<br>{<br>time_t timep;<br>time (&amp;timep);<br>printf(&#8220;%s&#8221;,asctime(gmtime(&amp;timep)));<br>}<br>&nbsp;<br>执行<br>&nbsp;Sat Oct 28 02:10:06 2000<br>&nbsp;<br>ctime（将时间和日期以字符串格式表示）<br>&nbsp;<br>相关函数<br>&nbsp;time，asctime，gmtime，localtime<br>&nbsp;<br>表头文件<br>&nbsp;#include&lt;time.h&gt;<br>&nbsp;<br>定义函数<br>&nbsp;char *ctime(const time_t *timep);<br>&nbsp;<br>函数说明<br>&nbsp;ctime()将参数timep所指的time_t结构中的信息转换成真实世界所使用的时间日期表示方法，然后将结果以字符串形态返回。此函数已经由时区转换成当地时间，字符串格式为&#8220;Wed Jun 30 21 :49 :08 1993\n&#8221;。若再调用相关的时间日期函数，此字符串可能会被破坏。<br>&nbsp;<br>返回值<br>&nbsp;返回一字符串表示目前当地的时间日期。<br>&nbsp;<br>范例<br>&nbsp;#include&lt;time.h&gt;<br>main()<br>{<br>time_t timep;<br>time (&amp;timep);<br>printf(&#8220;%s&#8221;,ctime(&amp;timep));<br>}<br>&nbsp;<br>执行<br>&nbsp;Sat Oct 28 10 : 12 : 05 2000<br>&nbsp;<br>gettimeofday（取得目前的时间）<br>&nbsp;<br>相关函数<br>&nbsp;time，ctime，ftime，settimeofday<br>&nbsp;<br>表头文件<br>&nbsp;#include &lt;sys/time.h&gt;<br>#include &lt;unistd.h&gt;<br>&nbsp;<br>定义函数<br>&nbsp;int gettimeofday ( struct timeval * tv , struct timezone * tz )<br>&nbsp;<br>函数说明<br>&nbsp;gettimeofday()会把目前的时间有tv所指的结构返回，当地时区的信息则放到tz所指的结构中。<br>timeval结构定义为:<br>struct timeval{<br>long tv_sec; /*秒*/<br>long tv_usec; /*微秒*/<br>};<br>timezone 结构定义为:<br>struct timezone{<br>int tz_minuteswest; /*和Greenwich 时间差了多少分钟*/<br>int tz_dsttime; /*日光节约时间的状态*/<br>};<br>上述两个结构都定义在/usr/include/sys/time.h。tz_dsttime 所代表的状态如下<br>DST_NONE /*不使用*/<br>DST_USA /*美国*/<br>DST_AUST /*澳洲*/<br>DST_WET /*西欧*/<br>DST_MET /*中欧*/<br>DST_EET /*东欧*/<br>DST_CAN /*加拿大*/<br>DST_GB /*大不列颠*/<br>DST_RUM /*罗马尼亚*/<br>DST_TUR /*土耳其*/<br>DST_AUSTALT /*澳洲（1986年以后）*/<br>&nbsp;<br>返回值<br>&nbsp;成功则返回0，失败返回－1，错误代码存于errno。附加说明EFAULT指针tv和tz所指的内存空间超出存取权限。<br>&nbsp;<br>范例<br>&nbsp;#include&lt;sys/time.h&gt;<br>#include&lt;unistd.h&gt;<br>main(){<br>struct timeval tv;<br>struct timezone tz;<br>gettimeofday (&amp;tv , &amp;tz);<br>printf(&#8220;tv_sec; %d\n&#8221;, tv,.tv_sec) ;<br>printf(&#8220;tv_usec; %d\n&#8221;,tv.tv_usec);<br>printf(&#8220;tz_minuteswest; %d\n&#8221;, tz.tz_minuteswest);<br>printf(&#8220;tz_dsttime, %d\n&#8221;,tz.tz_dsttime);<br>}<br>&nbsp;<br>执行<br>&nbsp;tv_sec: 974857339<br>tv_usec:136996<br>tz_minuteswest:-540<br>tz_dsttime:0<br>&nbsp;<br>gmtime（取得目前时间和日期）<br>&nbsp;<br>相关函数<br>&nbsp;time,asctime,ctime,localtime<br>&nbsp;<br>表头文件<br>&nbsp;#include&lt;time.h&gt;<br>&nbsp;<br>定义函数<br>&nbsp;struct tm*gmtime(const time_t*timep);<br>&nbsp;<br>函数说明<br>&nbsp;gmtime()将参数timep 所指的time_t 结构中的信息转换成真实世界所使用的时间日期表示方法，然后将结果由结构tm返回。<br>结构tm的定义为<br>struct tm<br>{<br>int tm_sec;<br>int tm_min;<br>int tm_hour;<br>int tm_mday;<br>int tm_mon;<br>int tm_year;<br>int tm_wday;<br>int tm_yday;<br>int tm_isdst;<br>};<br>int tm_sec 代表目前秒数，正常范围为0-59，但允许至61秒<br>int tm_min 代表目前分数，范围0-59<br>int tm_hour 从午夜算起的时数，范围为0-23<br>int tm_mday 目前月份的日数，范围01-31<br>int tm_mon 代表目前月份，从一月算起，范围从0-11<br>int tm_year 从1900 年算起至今的年数<br>int tm_wday 一星期的日数，从星期一算起，范围为0-6<br>int tm_yday 从今年1月1日算起至今的天数，范围为0-365<br>int tm_isdst 日光节约时间的旗标<br>此函数返回的时间日期未经时区转换，而是UTC时间。<br>&nbsp;<br>返回值<br>&nbsp;返回结构tm代表目前UTC 时间<br>&nbsp;<br>范例<br>&nbsp;#include &lt;time.h&gt;<br>main(){<br>char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};<br>time_t timep;<br>struct tm *p;<br>time(&amp;timep);<br>p=gmtime(&amp;timep);<br>printf(&#8220;%d%d%d&#8221;,(1900+p-&gt;tm_year), (1+p-&gt;tm_mon),p-&gt;tm_mday);<br>printf(&#8220;%s%d;%d;%d\n&#8221;, wday[p-&gt;tm_wday], p-&gt;tm_hour, p-&gt;tm_min, p-&gt;tm_sec);<br>}<br>&nbsp;<br>执行<br>&nbsp;2000/10/28 Sat 8:15:38<br>&nbsp;<br>localtime（取得当地目前时间和日期）<br>&nbsp;<br>相关函数<br>&nbsp;time, asctime, ctime, gmtime<br>&nbsp;<br>表头文件<br>&nbsp;#include&lt;time.h&gt;<br>&nbsp;<br>定义函数<br>&nbsp;struct tm *localtime(const time_t * timep);<br>&nbsp;<br>函数说明<br>&nbsp;localtime()将参数timep所指的time_t结构中的信息转换成真实世界所使用的时间日期表示方法，然后将结果由结构tm返回。结构tm的定义请参考gmtime()。此函数返回的时间日期已经转换成当地时区。<br>&nbsp;<br>返回值<br>&nbsp;返回结构tm代表目前的当地时间。<br>&nbsp;<br>范例<br>&nbsp;#include&lt;time.h&gt;<br>main(){<br>char *wday[]={&#8220;Sun&#8221;,&#8221;Mon&#8221;,&#8221;Tue&#8221;,&#8221;Wed&#8221;,&#8221;Thu&#8221;,&#8221;Fri&#8221;,&#8221;Sat&#8221;};<br>time_t timep;<br>struct tm *p;<br>time(&amp;timep);<br>p=localtime(&amp;timep); /*取得当地时间*/<br>printf (&#8220;%d%d%d &#8221;, (1900+p-&gt;tm_year),( l+p-&gt;tm_mon), p-&gt;tm_mday);<br>printf(&#8220;%s%d:%d:%d\n&#8221;, wday[p-&gt;tm_wday],p-&gt;tm_hour, p-&gt;tm_min, p-&gt;tm_sec);<br>}<br>&nbsp;<br>执行<br>&nbsp;2000/10/28 Sat 11:12:22<br>&nbsp;<br>mktime（将时间结构数据转换成经过的秒数）<br>&nbsp;<br>相关函数<br>&nbsp;time，asctime，gmtime，localtime<br>&nbsp;<br>表头文件<br>&nbsp;#include&lt;time.h&gt;<br>&nbsp;<br>定义函数<br>&nbsp;time_t mktime(strcut tm * timeptr);<br>&nbsp;<br>函数说明<br>&nbsp;mktime()用来将参数timeptr所指的tm结构数据转换成从公元1970年1月1日0时0分0 秒算起至今的UTC时间所经过的秒数。<br>&nbsp;<br>返回值<br>&nbsp;返回经过的秒数。<br>&nbsp;<br>范例<br>&nbsp;/* 用time()取得时间（秒数），利用localtime()<br>转换成struct tm 再利用mktine（）将struct tm转换成原来的秒数*/<br>#include&lt;time.h&gt;<br>main()<br>{<br>time_t timep;<br>strcut tm *p;<br>time(&amp;timep);<br>printf(&#8220;time() : %d \n&#8221;,timep);<br>p=localtime(&amp;timep);<br>timep = mktime(p);<br>printf(&#8220;time()-&gt;localtime()-&gt;mktime():%d\n&#8221;,timep);<br>}<br>&nbsp;<br>执行<br>&nbsp;time():974943297<br>time()-&gt;localtime()-&gt;mktime():974943297<br>&nbsp;<br>settimeofday（设置目前时间）<br>&nbsp;<br>相关函数<br>&nbsp;time，ctime，ftime，gettimeofday<br>&nbsp;<br>表头文件<br>&nbsp;#include&lt;sys/time.h&gt;<br>#include&lt;unistd.h&gt;<br>&nbsp;<br>定义函数<br>&nbsp;int settimeofday ( const struct timeval *tv,const struct timezone *tz);<br>&nbsp;<br>函数说明<br>&nbsp;settimeofday()会把目前时间设成由tv所指的结构信息，当地时区信息则设成tz所指的结构。详细的说明请参考gettimeofday()。注意，只有root权限才能使用此函数修改时间。<br>&nbsp;<br>返回值<br>&nbsp;成功则返回0，失败返回－1，错误代码存于errno。<br>&nbsp;<br>错误代码<br>&nbsp;EPERM 并非由root权限调用settimeofday（），权限不够。<br>EINVAL 时区或某个数据是不正确的，无法正确设置时间。<br>&nbsp;<br>time（取得目前的时间）<br>&nbsp;<br>相关函数<br>&nbsp;ctime，ftime，gettimeofday<br>&nbsp;<br>表头文件<br>&nbsp;#include&lt;time.h&gt;<br>&nbsp;<br>定义函数<br>&nbsp;time_t time(time_t *t);<br>&nbsp;<br>函数说明<br>&nbsp;此函数会返回从公元1970年1月1日的UTC时间从0时0分0秒算起到现在所经过的秒数。如果t 并非空指针的话，此函数也会将返回值存到t指针所指的内存。<br>&nbsp;<br>返回值<br>&nbsp;成功则返回秒数，失败则返回((time_t)-1)值，错误原因存于errno中。<br>&nbsp;<br>范例<br>&nbsp;#include&lt;time.h&gt;<br>mian()<br>{<br>int seconds= time((time_t*)NULL);<br>printf(&#8220;%d\n&#8221;,seconds);<br>}<br>&nbsp;<br></p>
<img src ="http://www.cppblog.com/deane/aggbug/118718.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2010-06-25 16:00 <a href="http://www.cppblog.com/deane/articles/118718.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>vim目录树插件NERD tree的安装方法 </title><link>http://www.cppblog.com/deane/articles/109379.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Wed, 10 Mar 2010 10:33:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/109379.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/109379.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/109379.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/109379.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/109379.html</trackback:ping><description><![CDATA[<br>系统 Debian 4.0<br>[1]下载NERD tree<br>wget http://www.vim.org/scripts/download_script.php?src_id=9870 -O NERDTree.zip<br><br>[2]添加.vim配置<br>查看~/.vim目录，如果不存在，创建此目录<br><br>[3]copy NERDTree.zip to ~/.vim<br><br>[4]unzip NERDTree.zip<br><br>ok .<br><br>使用方法和效果如图：<br><img height=584 alt=nerd_tree.png src="http://www.blogjava.net/images/blogjava_net/zhyiwww/linux/nerd_tree.png" width=727 border=0><br><br><br><br>
<img src ="http://www.cppblog.com/deane/aggbug/109379.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2010-03-10 18:33 <a href="http://www.cppblog.com/deane/articles/109379.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Vim之Nerd Tree杂草帮助</title><link>http://www.cppblog.com/deane/articles/109378.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Wed, 10 Mar 2010 10:32:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/109378.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/109378.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/109378.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/109378.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/109378.html</trackback:ping><description><![CDATA[<p>&nbsp;</p>
<table style="BORDER-COLLAPSE: collapse; WORD-WRAP: break-word" cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td align=middle>
            <table style="BORDER-COLLAPSE: collapse; WORD-WRAP: break-word" cellSpacing=0 cellPadding=0 width="100%" border=0>
                <tbody>
                    <tr>
                        <td width="100%">
                        <div id=art style="MARGIN: 15px" width="100%">
                        <div class=post-body id=post-7830504114453713128>
                        <div xmlns="http://www.w3.org/1999/xhtml">
                        <div style="LINE-HEIGHT: 1.5"><font face=simsun>　　一直苦于没有好的文件浏览器，别人所称道的WinManager我也不习惯不喜欢，虽然听说过NerdTree却也因为觉得不会怎么样而没有试用。这次实在忍不过了，一试之下才发现，原来还是很趁手滴——到底还是树状显示方式好啊&#8230;&#8230;比netrw方便&#8230;&#8230;<br>　　仔细看了一遍帮助，好多快捷键！没用熟了还真记不住。为了哀悼我那已成天外飞仙的英语，将nerdtree的帮助录一部分如下，以供参考使用：<br><br><font color=#804040>&nbsp;&nbsp;1 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#ff0000><strong>目录</strong></font></span><br><font color=#804040>&nbsp;&nbsp;2 </font>================================================================================<br><font color=#804040>&nbsp;&nbsp;3 </font>1.简介<br><font color=#804040>&nbsp;&nbsp;4 </font>2.功能<br><font color=#804040>&nbsp;&nbsp;5 </font>&nbsp;&nbsp;2.1.全局命令<br><font color=#804040>&nbsp;&nbsp;6 </font>&nbsp;&nbsp;2.2.书签<br><font color=#804040>&nbsp;&nbsp;7 </font>&nbsp;&nbsp;&nbsp;&nbsp;2.2.1.书签表<br><font color=#804040>&nbsp;&nbsp;8 </font>&nbsp;&nbsp;&nbsp;&nbsp;2.2.2.书签命令<br><font color=#804040>&nbsp;&nbsp;9 </font>&nbsp;&nbsp;&nbsp;&nbsp;2.2.3.无效书签<br><font color=#804040>&nbsp;10 </font>&nbsp;&nbsp;2.3.Nerdtree映射<br><font color=#804040>&nbsp;11 </font>&nbsp;&nbsp;2.4.文件系统菜单<br><font color=#804040>&nbsp;12 </font>3.选项<br><font color=#804040>&nbsp;13 </font>&nbsp;&nbsp;3.1.选项总结<br><font color=#804040>&nbsp;14 </font>&nbsp;&nbsp;3.2.选项明细<br><font color=#804040>&nbsp;15 </font><br><font color=#804040>&nbsp;16 </font><br><font color=#804040>&nbsp;17 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#00ff00><strong>1.简介</strong></font></span><br><font color=#804040>&nbsp;18 </font>--------------------------------------------------------------------------------<br><font color=#804040>&nbsp;19 </font>Nerd tree可以让你浏览文件系统并打开文件或目录。<br><font color=#804040>&nbsp;20 </font>你可以通过键盘或鼠标控制它以树状图显示文件系统，也可以在其中进行一些简单的文件系统操作。<br><font color=#804040>&nbsp;21 </font><br><font color=#804040>&nbsp;22 </font>nerdtree提供如下功能及特性：<br><font color=#804040>&nbsp;23 </font>*以继承树的形式显示文件和目录<br><font color=#804040>&nbsp;24 </font>*对如下类型的文件进行不同的高亮显示<br><font color=#804040>&nbsp;25 </font>　*文件<br><font color=#804040>&nbsp;26 </font>　*目录<br><font color=#804040>&nbsp;27 </font>　*sym-links<br><font color=#804040>&nbsp;28 </font>　*快捷方式<br><font color=#804040>&nbsp;29 </font>　*只读文件<br><font color=#804040>&nbsp;30 </font>　*可执行文件<br><font color=#804040>&nbsp;31 </font>*提供许多映射来控制树状结构<br><font color=#804040>&nbsp;32 </font>　*对展开/收拢/浏览目录结点的映射<br><font color=#804040>&nbsp;33 </font>　*对在新的或已存在的窗口或Tab页中打开文件的映射<br><font color=#804040>&nbsp;34 </font>　*对改变根结点的映射<br><font color=#804040>&nbsp;35 </font>　*Mappings to navigate around the tree<br><font color=#804040>&nbsp;36 </font>*可以将文件和目录添加到收藏夹<br><font color=#804040>&nbsp;37 </font>*可以用鼠标进行大部分的树状结构导航<br><font color=#804040>&nbsp;38 </font>*对树状结构内容的过滤（可在运行时切换）<br><font color=#804040>&nbsp;39 </font>　*自定义文件过滤器可以阻止某些文件（比如vim备份文件等）的显示<br><font color=#804040>&nbsp;40 </font>　*可选是否显示隐藏文件<br><font color=#804040>&nbsp;41 </font>　*可选不显示文件只显示目录<br><font color=#804040>&nbsp;42 </font>*提供文本文件系统菜单来创建/删除/移动/复制目录或文件<br><font color=#804040>&nbsp;43 </font>*可以自定义Nerd窗口的位置和大小<br><font color=#804040>&nbsp;44 </font>*可以自定义结点排序方式<br><font color=#804040>&nbsp;45 </font>*当你浏览文件系统的时候就会有一个文件系统的模型被创建或维护。这样做有几个优点：<br><font color=#804040>&nbsp;46 </font>　*所有文件系统信息都被缓存了，有需要的时候只要重新读入缓存<br><font color=#804040>&nbsp;47 </font>　*如果重新浏览之后访问过的tree的一部分，结点就会以上次保持的展开或合拢的样子显示<br><font color=#804040>&nbsp;48 </font>*该脚本能记住光标位置和窗口位置，所以可以用NERDTreeToggle来切换tree的显示与隐藏<br><font color=#804040>&nbsp;49 </font>*对于多Tab，可以共享一个Tree，也可以各自拥有各自的tree，还可以混合以上两种方式<br><font color=#804040>&nbsp;50 </font>*默认情况下，该脚本覆盖vim的默认文件浏览器(netrw)，所以如果直接输入:edit命令也会用nerd树打开<br><font color=#804040>&nbsp;51 </font><br><font color=#804040>&nbsp;52 </font><br><font color=#804040>&nbsp;53 </font><br><font color=#804040>&nbsp;54 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#00ff00><strong>2.功能</strong></font></span><br><font color=#804040>&nbsp;55 </font>--------------------------------------------------------------------------------<br><font color=#804040>&nbsp;56 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#a020f0><strong>&nbsp;&nbsp;2.1.全局命令</strong></font></span><br><font color=#804040>&nbsp;57 </font>&nbsp;&nbsp;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br><font color=#804040>&nbsp;58 </font>&nbsp;&nbsp;:NERDTree [&lt;start-directory&gt; | &lt;bookmark&gt;]<br><font color=#804040>&nbsp;59 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;打开一个Nerdtree，根结点由参数指定，不指定参数就是以当前目录为根结点<br><font color=#804040>&nbsp;60 </font>&nbsp;&nbsp;:NERDTreeFromBookmark &lt;bookmark&gt;<br><font color=#804040>&nbsp;61 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;打开一个Nerdtree，根结点由参数所指定的书签<br><font color=#804040>&nbsp;62 </font>&nbsp;&nbsp;:NERDTreeToggle [&lt;start-directory&gt; | &lt;bookmark&gt;]<br><font color=#804040>&nbsp;63 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在当前Tab中如果Nerdtree已经存在，就切换显示与隐藏；<br><font color=#804040>&nbsp;64 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果不存在，就相当于执行:NERDTree命令<br><font color=#804040>&nbsp;65 </font>&nbsp;&nbsp;:NERDTreeMirror<br><font color=#804040>&nbsp;66 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;从另一个Tab中共享一个NerdTree过来（在当前Tab的Tree所作的改变也反应到原Tab中）<br><font color=#804040>&nbsp;67 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果总共只有一个Tree，就直接共享；如果不止一个，就会询问共享哪个<br><font color=#804040>&nbsp;68 </font>&nbsp;&nbsp;:NERDTreeClose<br><font color=#804040>&nbsp;69 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在当前Tab中关闭Tree<br><font color=#804040>&nbsp;70 </font><br><font color=#804040>&nbsp;71 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#a020f0><strong>&nbsp;&nbsp;2.2.书签</strong></font></span><br><font color=#804040>&nbsp;72 </font>&nbsp;&nbsp;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br><font color=#804040>&nbsp;73 </font>&nbsp;&nbsp;在NerdTree中，书签用于标记某个感兴趣的文件或目录，比如可以用书签标记所有Project目录<br><font color=#804040>&nbsp;74 </font><br><font color=#804040>&nbsp;75 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#0000ff><strong>&nbsp;&nbsp;&nbsp;&nbsp;2.2.1.书签表</strong></font></span><br><font color=#804040>&nbsp;76 </font>&nbsp;&nbsp;&nbsp;&nbsp;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br><font color=#804040>&nbsp;77 </font>&nbsp;&nbsp;&nbsp;&nbsp;如果书签被激活，则显示于树状图的上方<br><font color=#804040>&nbsp;78 </font>&nbsp;&nbsp;&nbsp;&nbsp;可以双击或用NERDTree-o来激活选中文件<br><font color=#804040>&nbsp;79 </font>&nbsp;&nbsp;&nbsp;&nbsp;可以用NERDTree-t映射使选中文件用新Tab打开，并跳到新tab页<br><font color=#804040>&nbsp;80 </font>&nbsp;&nbsp;&nbsp;&nbsp;可以用NERDTree-T映射使选中文件用新Tab打开，但不跳到新Tab页<br><font color=#804040>&nbsp;81 </font><br><font color=#804040>&nbsp;82 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#0000ff><strong>&nbsp;&nbsp;&nbsp;&nbsp;2.2.2.书签命令</strong></font></span><br><font color=#804040>&nbsp;83 </font>&nbsp;&nbsp;&nbsp;&nbsp;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br><font color=#804040>&nbsp;84 </font>&nbsp;&nbsp;&nbsp;&nbsp;以下命令只在在Nerdtree的buffer中有效<br><font color=#804040>&nbsp;85 </font>&nbsp;&nbsp;&nbsp;&nbsp;:Bookmark &lt;name&gt;<br><font color=#804040>&nbsp;86 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;将选中结点添加到书签列表中，并命名为name（书签名不可包含空格）；<br><font color=#804040>&nbsp;87 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如与现有书签重名，则覆盖现有书签。<br><font color=#804040>&nbsp;88 </font>&nbsp;&nbsp;&nbsp;&nbsp;:BookmarkToRoot &lt;bookmark&gt;<br><font color=#804040>&nbsp;89 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;以指定目录书签或文件书签的父目录作为根结点显示NerdTree<br><font color=#804040>&nbsp;90 </font>&nbsp;&nbsp;&nbsp;&nbsp;:RevealBookmark &lt;bookmark&gt;<br><font color=#804040>&nbsp;91 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果指定书签已经存在于当前目录树下，打开它的上层结点并选中该书签 <br><font color=#804040>&nbsp;92 </font>&nbsp;&nbsp;&nbsp;&nbsp;:OpenBookmark &lt;bookmark&gt;<br><font color=#804040>&nbsp;93 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;打开指定的文件。（参数必须是文件书签）<br><font color=#804040>&nbsp;94 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果该文件在当前的目录树下，则打开它的上层结点并选中该书签<br><font color=#804040>&nbsp;95 </font>&nbsp;&nbsp;&nbsp;&nbsp;:ClearBookmarks [&lt;bookmarks&gt;]<br><font color=#804040>&nbsp;96 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;清除指定书签；如未指定参数，则清除所有书签<br><font color=#804040>&nbsp;97 </font>&nbsp;&nbsp;&nbsp;&nbsp;:ClearAllBookmarks<br><font color=#804040>&nbsp;98 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;清除所有书签<br><font color=#804040>&nbsp;99 </font>&nbsp;&nbsp;&nbsp;&nbsp;:ReadBookmarks<br><font color=#804040>100 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;重新读入'NERDTreeBookmarksFile'中的所有书签<br><font color=#804040>101 </font><br><font color=#804040>102 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#0000ff><strong>&nbsp;&nbsp;&nbsp;&nbsp;2.2.3.无效书签</strong></font></span><br><font color=#804040>103 </font>&nbsp;&nbsp;&nbsp;&nbsp;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br><font color=#804040>104 </font>&nbsp;&nbsp;&nbsp;&nbsp;如果监测到无效书签，脚本就会发布一个错误消息并将无效书签置为不可用；<br><font color=#804040>105 </font>&nbsp;&nbsp;&nbsp;&nbsp;无效书签将被移到书签文件的最后，在有效书签和无效书签之间有一个空行。<br><font color=#804040>106 </font>&nbsp;&nbsp;&nbsp;&nbsp;书签文件中的每一行代表一个书签，格式是&lt;bookmark name&gt;&lt;space&gt;&lt;full path to the bookmark location&gt;<br><font color=#804040>107 </font>&nbsp;&nbsp;&nbsp;&nbsp;如果修正了某个无效书签，则可以重启vim或使用:ReadBookmarks命令重新读入书签信息<br><font color=#804040>108 </font><br><font color=#804040>109 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#a020f0><strong>&nbsp;&nbsp;2.3.Nerdtree映射</strong></font></span><br><font color=#804040>110 </font>&nbsp;&nbsp;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br><font color=#804040>111 </font>&nbsp;&nbsp;o.......在已有窗口中打开文件、目录或书签，并跳到该窗口&nbsp;&nbsp; .....|NERDTree-o|&nbsp;&nbsp;<br><font color=#804040>112 </font>&nbsp;&nbsp;go......在已有窗口中打开文件、目录或书签，但不跳到该窗口 .....|NERDTree-go| <br><font color=#804040>113 </font>&nbsp;&nbsp;t.......在新Tab中打开选中文件/书签，并跳到新Tab&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.....|NERDTree-t|&nbsp;&nbsp;<br><font color=#804040>114 </font>&nbsp;&nbsp;T.......在新Tab中打开选中文件/书签，但不跳到新Tab&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.....|NERDTree-T|&nbsp;&nbsp;<br><font color=#804040>115 </font>&nbsp;&nbsp;i.......split一个新窗口打开选中文件，并跳到该窗口&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.....|NERDTree-i|&nbsp;&nbsp;<br><font color=#804040>116 </font>&nbsp;&nbsp;gi......split一个新窗口打开选中文件，但不跳到该窗口&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.....|NERDTree-gi| <br><font color=#804040>117 </font>&nbsp;&nbsp;s.......vsp一个新窗口打开选中文件，并跳到该窗口&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.....|NERDTree-s|&nbsp;&nbsp;<br><font color=#804040>118 </font>&nbsp;&nbsp;gs......vsp一个新窗口打开选中文件，但不跳到该窗口&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.....|NERDTree-gs| <br><font color=#804040>119 </font>&nbsp;&nbsp;!.......执行当前文件&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .....|NERDTree-!|&nbsp;&nbsp;<br><font color=#804040>120 </font>&nbsp;&nbsp;O.......递归打开选中结点下的所有目录&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .....|NERDTree-O|&nbsp;&nbsp;<br><font color=#804040>121 </font>&nbsp;&nbsp;x.......合拢选中结点的父目录&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; .....|NERDTree-x|&nbsp;&nbsp;<br><font color=#804040>122 </font>&nbsp;&nbsp;X.......递归合拢选中结点下的所有目录&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .....|NERDTree-X|&nbsp;&nbsp;<br><font color=#804040>123 </font>&nbsp;&nbsp;e.......Edit the current dif&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; .....|NERDTree-e|&nbsp;&nbsp;<br><font color=#804040>124 </font><br><font color=#804040>125 </font>&nbsp;&nbsp;双击......相当于NERDTree-o<br><font color=#804040>126 </font>&nbsp;&nbsp;中键......对文件相当于NERDTree-i，对目录相当于NERDTree-e<br><font color=#804040>127 </font><br><font color=#804040>128 </font>&nbsp;&nbsp;D.......删除当前书签<br><font color=#804040>129 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br><font color=#804040>130 </font>&nbsp;&nbsp;P.......跳到根结点<br><font color=#804040>131 </font>&nbsp;&nbsp;p.......跳到父结点<br><font color=#804040>132 </font>&nbsp;&nbsp;K.......跳到当前目录下同级的第一个结点<br><font color=#804040>133 </font>&nbsp;&nbsp;J.......跳到当前目录下同级的最后一个结点<br><font color=#804040>134 </font>&nbsp;&nbsp;&lt;C-j&gt;...跳到当前目录下同级的前一个结点<br><font color=#804040>135 </font>&nbsp;&nbsp;&lt;C-k&gt;...跳到当前目录下同级的后一个结点<br><font color=#804040>136 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br><font color=#804040>137 </font>&nbsp;&nbsp;C.......将选中目录或选中文件的父目录设为根结点<br><font color=#804040>138 </font>&nbsp;&nbsp;u.......将当前根结点的父目录设为根目录，并变成合拢原根结点<br><font color=#804040>139 </font>&nbsp;&nbsp;U.......将当前根结点的父目录设为根目录，但保持展开原根结点<br><font color=#804040>140 </font>&nbsp;&nbsp;r.......递归刷新选中目录<br><font color=#804040>141 </font>&nbsp;&nbsp;R.......递归刷新根结点<br><font color=#804040>142 </font>&nbsp;&nbsp;m.......显示文件系统菜单<br><font color=#804040>143 </font>&nbsp;&nbsp;cd......将CWD设为选中目录<br><font color=#804040>144 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br><font color=#804040>145 </font>&nbsp;&nbsp;I.......切换是否显示隐藏文件<br><font color=#804040>146 </font>&nbsp;&nbsp;f.......切换是否使用文件过滤器<br><font color=#804040>147 </font>&nbsp;&nbsp;F.......切换是否显示文件<br><font color=#804040>148 </font>&nbsp;&nbsp;B.......切换是否显示书签<br><font color=#804040>149 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br><font color=#804040>150 </font>&nbsp;&nbsp;q.......关闭NerdTree窗口<br><font color=#804040>151 </font>&nbsp;&nbsp;?.......切换是否显示Quick Help<br><font color=#804040>152 </font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br><font color=#804040>153 </font>&nbsp;&nbsp;<br><font color=#804040>154 </font><br><font color=#804040>155 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#a020f0><strong>&nbsp;&nbsp;2.4.文件系统菜单</strong></font></span><br><font color=#804040>156 </font>&nbsp;&nbsp;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br><font color=#804040>157 </font>&nbsp;&nbsp;帮助说中包含新建、复制、移动、删除四种命令，但copy只支持*nix系统<br><font color=#804040>158 </font><br><font color=#804040>159 </font><span style="BACKGROUND-COLOR: rgb(255,255,255)"><font color=#00ff00><strong>3.自定义选项</strong></font></span><br><font color=#804040>160 </font>--------------------------------------------------------------------------------<br><font color=#804040>161 </font>loaded_nerd_tree&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;不使用NerdTree脚本<br><font color=#804040>162 </font>NERDChristmasTree&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 让Tree把自己给装饰得多姿多彩漂亮点<br><font color=#804040>163 </font>NERDTreeAutoCenter&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;控制当光标移动超过一定距离时，是否自动将焦点调整到屏中心<br><font color=#804040>164 </font>NERDTreeAutoCenterThreshold 与NERDTreeAutoCenter配合使用<br><font color=#804040>165 </font>NERDTreeCaseSensitiveSort&nbsp;&nbsp; 排序时是否大小写敏感<br><font color=#804040>166 </font>NERDTreeChDirMode&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 确定是否改变Vim的CWD<br><font color=#804040>167 </font>NERDTreeHighlightCursorline 是否高亮显示光标所在行<br><font color=#804040>168 </font>NERDTreeHijackNetrw&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 是否使用:edit命令时打开第二NerdTree<br><font color=#804040>169 </font>NERDTreeIgnore&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;默认的&#8220;无视&#8221;文件<br><font color=#804040>170 </font>NERDTreeBookmarksFile&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指定书签文件<br><font color=#804040>171 </font>NERDTreeMouseMode&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指定鼠标模式（1.双击打开；2.单目录双文件；3.单击打开）<br><font color=#804040>172 </font>NERDTreeQuitOnOpen&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;打开文件后是否关闭NerdTree窗口<br><font color=#804040>173 </font>NERDTreeShowBookmarks&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 是否默认显示书签列表<br><font color=#804040>174 </font>NERDTreeShowFiles&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 是否默认显示文件<br><font color=#804040>175 </font>NERDTreeShowHidden&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;是否默认显示隐藏文件<br><font color=#804040>176 </font>NERDTreeShowLineNumbers&nbsp;&nbsp;&nbsp;&nbsp; 是否默认显示行号<br><font color=#804040>177 </font>NERDTreeSortOrder&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 排序规则<br><font color=#804040>178 </font>NERDTreeStatusline&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;窗口状态栏<br><font color=#804040>179 </font>NERDTreeWinPos&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;窗口位置（'left' or 'right'）<br><font color=#804040>180 </font>NERDTreeWinSize&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 窗口宽<br><br>我的配置：<br><font color=#804040>&nbsp;1 </font><font color=#0000ff>"NERD Tree</font><br><font color=#804040>&nbsp;2 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDChristmasTree<font color=#804040><strong>=</strong></font><font color=#ff00ff>1</font><br><font color=#804040>&nbsp;3 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDTreeAutoCenter<font color=#804040><strong>=</strong></font><font color=#ff00ff>1</font><br><font color=#804040>&nbsp;4 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDTreeBookmarksFile<font color=#804040><strong>=</strong></font><font color=#a020f0>$VIM</font><font color=#804040><strong>.</strong></font><font color=#ff00ff>'\Data\NerdBookmarks.txt'</font><br><font color=#804040>&nbsp;5 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDTreeMouseMode<font color=#804040><strong>=</strong></font><font color=#ff00ff>2</font><br><font color=#804040>&nbsp;6 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDTreeShowBookmarks<font color=#804040><strong>=</strong></font><font color=#ff00ff>1</font><br><font color=#804040>&nbsp;7 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDTreeShowFiles<font color=#804040><strong>=</strong></font><font color=#ff00ff>1</font><br><font color=#804040>&nbsp;8 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDTreeShowHidden<font color=#804040><strong>=</strong></font><font color=#ff00ff>1</font><br><font color=#804040>&nbsp;9 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDTreeShowLineNumbers<font color=#804040><strong>=</strong></font><font color=#ff00ff>1</font><br><font color=#804040>10 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDTreeWinPos<font color=#804040><strong>=</strong></font><font color=#ff00ff>'left'</font><br><font color=#804040>11 </font><font color=#804040><strong>let</strong></font>&nbsp;NERDTreeWinSize<font color=#804040><strong>=</strong></font><font color=#ff00ff>31</font><br><font color=#804040>12 </font><font color=#804040><strong>nnoremap</strong></font>&nbsp;<font color=#6a5acd>&lt;</font><font color=#6a5acd>silent</font><font color=#6a5acd>&gt;</font>&nbsp;<font color=#6a5acd>&lt;</font><font color=#6a5acd>leader</font><font color=#6a5acd>&gt;</font>f&nbsp;:NERDTreeToggle<font color=#6a5acd>&lt;</font><font color=#6a5acd>CR</font><font color=#6a5acd>&gt;</font></font></div>
                        </div>
                        </div>
                        </div>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p style="MARGIN: 5px; LINE-HEIGHT: 150%"></p>
            </td>
        </tr>
        <tr>
            <td height=25>&nbsp;<font color=#000099><strong>原文地址</strong></font> <a href="http://mt-zj.blogspot.com/2009/02/vimnerd-tree.html" target=_blank><u><font color=#0000ff>http://mt-zj.blogspot.com/2009/02/vimnerd-tree.html</font></u></a> </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/deane/aggbug/109378.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2010-03-10 18:32 <a href="http://www.cppblog.com/deane/articles/109378.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>vi/vim使用进阶: vimgdb调试时的常见问题及解决</title><link>http://www.cppblog.com/deane/articles/109366.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Wed, 10 Mar 2010 08:59:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/109366.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/109366.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/109366.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/109366.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/109366.html</trackback:ping><description><![CDATA[<br>转：<br><br>&nbsp;
<p align=left><strong><span>[ </span></strong><strong><span>问题一</span></strong><strong><span>: </span></strong><strong><span>运行</span></strong><strong><span>GDB</span></strong><strong><span>命令时提示</span></strong><strong><span>"unable to read from GDB pseudo tty"? ]</span></strong></p>
<p align=left><span>有读者在试图执行</span><span>GDB</span><span>命令时，出现上面的提示。</span><span> </span></p>
<p align=left><span>出现这个问题是由于没有正确设置</span><span>gdbprg</span><span>变量。使用下面的命令设置一下</span><span>GDB</span><span>程序的位置即可解决此问题：</span><span> </span></p>
<div>
<p align=left><span>:set gdbprg=/path/to/gdb </span></p>
</div>
<p align=left><span>请把上面的</span><em><span>/path/to/gdb</span></em><span>替换成你计算机中</span><span>GDB</span><span>程序所在的路径。你可以把这句话加到你的</span><span><a href="http://easwy.com/blog/archives/advanced-vim-skills-introduce-vimrc/" target=_top><span>vimrc</span></a></span><span>中，这样每次启动</span><span>vi</span><span>时会自动设置此变量。</span><span> </span></p>
<p align=left><strong><span>[ </span></strong><strong><span>问题二：</span></strong><strong><span> </span></strong><strong><span>提示</span></strong><strong><span>"GDB busy: command discarded, please try again"? ]</span></strong></p>
<p align=left><span>当你的程序需要用</span><em><span>scanf()</span></em><span>或者</span><em><span>getchar()</span></em><span>这类函数读取用户输入时，你可能会看到过这样的提示：</span><span> </span></p>
<p align=left><span>GDB busy: command discarded, please try again </span></p>
<p align=left><span>出现这个提示，说明你的</span><span>GDB</span><span>正在等待用户输入，所以无法响应你所输入的其它</span><span>GDB</span><span>命令。</span><span> </span></p>
<p align=left><span>在使用</span><span>vimgdb</span><span>时，如果你的程序需要读取用户输入，你必须通过</span><span>GDB</span><span>的</span><strong><span>tty</span></strong><span>命令或</span><strong><span>run</span></strong><span>来重定向程序的标准输入。</span><span> </span></p>
<p align=left><span>首先，你打开一个终端</span><span>(</span><span>可以是</span><span>xterm/rxvt/urxvt/putty/&#8230;)</span><span>，在此终端内输入命令</span><strong><span>tty</span></strong><span>：</span><span> </span></p>
<div>
<p align=left>&nbsp;</p>
<p align=left><span>$ tty</span></p>
<p align=left><span>/dev/pts/17 </span></p>
</div>
<p align=left><span>这条命令用来显示此终端所使用的设备文件名。获得了这个设备文件名后，你就可以重定向程序的标准输入输出到这个终端，在</span><span>GDB</span><span>中输入下面的命令：</span><span> </span></p>
<div>
<p align=left><span>tty /dev/pts/17 </span></p>
</div>
<p align=left><span>执行完这条命令后，程序的标准输入</span><span>/</span><span>输出就被重定向到</span><em><span>/dev/pts/17</span></em><span>了，这样，当执行到</span><em><span>scanf()</span></em><span>或</span><em><span>getchar()</span></em><span>函数时，切换到那个终端输入指定参数，然后回车，程序就会继续向下执行。</span><span> </span></p>
<p align=left><strong><span>[ </span></strong><strong><span>问题三：</span></strong><strong><span> vimgdb</span></strong><strong><span>可以在</span></strong><strong><span>windows</span></strong><strong><span>下使用吗</span></strong><strong><span>? ]</span></strong></p>
<p align=left><span>vimgdb</span><span>不能在</span><span>windows</span><span>下使用。不过你可以用作者提供的</span><span>Clewn</span><span>或</span><span>Pyclewn</span><span>。这两个程序可以在</span><span>Windows</span><span>的</span><span>Vim/Gvim</span><span>中使用。</span><span> </span></p>
<p align=left><span>这里有我写的一篇关于</span><span>pyclewn</span><span>的教程</span><span>:<a href="http://easwy.com/blog/archives/advanced-vim-skills-vim-gdb-pyclewn/" target=_top><span><span>在</span></span><span>VIM</span><span><span>中使用</span></span><span>GDB</span><span><span>调试</span></span><span> &#8211; pyclewn</span></a></span><span>，</span><span>clewn</span><span>的用法与之类似。</span><span> </span></p>
<p align=left><strong><span>[ </span></strong><strong><span>问题四：</span></strong><strong><span> </span></strong><strong><span>按空格后</span></strong><strong><span>vimgdb</span></strong><strong><span>的命令窗口没有弹出来</span></strong><strong><span>? ]</span></strong></p>
<p align=left><span>首先确保</span><span>vimgdb</span><span>的按键定义文件存在。执行下面的命令：</span><span> </span></p>
<div>
<p align=left><span>:set runtimepath?&nbsp;</span></p>
</div>
<p align=left><span>检查这条命令所列出的所有目录，如果任一目录中包含</span><em><span>macros/gdb_mappings.vim</span></em><span>文件，说明你的按键映射文件已经存在。如果你没有找到该文件，到</span><span>vimgdb</span><span>目录中找到这个命令，拷贝到上述任意一个目录中。</span><span> </span></p>
<p align=left><span>接下来用下面的命令加载</span><span>vimgdb</span><span>的键绑定：</span><span> </span></p>
<div>
<p align=left><span>:run macros/gdb_mappings.vim </span></p>
</div>
<p align=left><span>现在，你应该可以使用</span><span>vimgdb</span><span>所定义的快捷键了。</span><span> <br><br><br><br><br></span></p>
<img src ="http://www.cppblog.com/deane/aggbug/109366.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2010-03-10 16:59 <a href="http://www.cppblog.com/deane/articles/109366.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Vim使用图示</title><link>http://www.cppblog.com/deane/articles/108817.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Wed, 03 Mar 2010 10:16:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/108817.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/108817.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/108817.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/108817.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/108817.html</trackback:ping><description><![CDATA[<br><br>下面两张Vim使用图是从网上收集的，供大家参考：<br><br><br><img style="WIDTH: 767px; HEIGHT: 788px" height=788 alt="" src="http://www.cppblog.com/images/cppblog_com/deane/vim1.png" width=767 border=0><br><br><br><br><br><br><br><br><br><br><br><img height=768 alt="" src="http://www.cppblog.com/images/cppblog_com/deane/o_v1.jpg" width=1024 border=0><br><br><br><br><br>
<img src ="http://www.cppblog.com/deane/aggbug/108817.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2010-03-03 18:16 <a href="http://www.cppblog.com/deane/articles/108817.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>个人常用的VIM命令列表</title><link>http://www.cppblog.com/deane/articles/108815.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Wed, 03 Mar 2010 10:01:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/108815.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/108815.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/108815.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/108815.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/108815.html</trackback:ping><description><![CDATA[<br>&nbsp;
<p style="COLOR: #000000">vim是一个多模式的编辑器。就目前来看，主要有以下几个主要模式。<br><br>　　1.通常模式(n) 在其它任何一个模式下,用ESC或者 ctrl+c 键可以退到通常模式<br><br>　　2.插入模式(i) 在这个模式下,vim像一个常见的编辑器.在通常模式下,用i或者a可以进行本模式,当然,还有一些其它命令也可以.<br><br>　　3.可视模式(v) 在这个模式下,可以使用hjkl进行选择.然后进行copy,paste或者其它操作. 在通常模式下,用v命令进行可视模式.<br><br>　　4. 块操作模式(V) 这是块操作模式. 在通常模式下,用ctrl+v进入本模式.<br><br>　　5. 修改模式(R) 这是改写的模式.很多软件法用insert键来完成这个切换.在vim中,从通常模式用R即可进入改写模式.<br><br>　　6. 扩展命令模式(ex) 这是命令执行模式 在通常模式下用:切换到此模式.</p>
<br><br>个人常用的VIM命令列表:<br><br>
<p style="COLOR: #000000">移动光标<br>上:k nk:向上移动n行 9999k或gg可以移到第一行 G移到最后一行<br>下:j nj:向下移动n行<br>左:h nh:向左移动n列<br>右:l nl:向右移动n列 </p>
<p style="COLOR: #000000">w：光标以单词向前移动 nw：光标向前移动n个单词 光标到单词的第一个字母上<br>b：与w相反<br>e: 光标以单词向前移动 ne：光标向前移动n个单词 光标到单词的最后一个字母上<br>ge:与e相反</p>
<p style="COLOR: #000000">$:移动光标到行尾 n$:移动到第n行的行尾<br>0（Num）：移动光标到行首<br>^:移动光标到行首第一个非空字符上去</p>
<p style="COLOR: #000000">f&lt;a&gt;:移动光标到当前行的字符a上，nf&lt;a&gt;移动光标到当前行的第n个a字符上<br>F:相反</p>
<p style="COLOR: #000000">%:移动到与制匹配的括号上去（），{}，[]，&lt;&gt;等。</p>
<p style="COLOR: #000000">nG:移动到第n行上 G:到最后一行</p>
<p style="COLOR: #000000">CTRL＋G 得到当前光标在文件中的位置</p>
<p style="COLOR: #000000">向前翻页：CTRL+F<br>向下移动半屏：CTRL＋G<br>向后翻页：CTRL+B</p>
<p style="COLOR: #000000">存盘：<br>:q! :不存盘退出<br>:e! :放弃修改文件内容，重新载入该文件编辑<br>:wq ：存盘退出</p>
<p style="COLOR: #000000">dw：删除一个单词,需将光标移到单词的第一个字母上，按dw，如果光标在单词任意位置，用daw<br>dnw:删除n个单词<br>dne:也可，只是删除到单词尾<br>dnl:向右删除n个字母<br>dnh:向左删除n个字母<br>dnj:向下删除n行<br>dnk:向上删除n行<br>d$：删除当前光标到改行的行尾的字母<br>dd：删除一行<br>cnw[word]:将n个word改变为word<br>cc:改变整行<br>C$:改变到行尾</p>
<p style="COLOR: #000000">J: 删除换行符，将光标移到改行，按shift+j删除行尾的换行符，下一行接上来了.<br>u: 撤销前一次的操作<br>shif+u(U):撤销对该行的所有操作。<br><br>:set showmode :设置显示工作模式</p>
<p style="COLOR: #000000">o：在当前行的下面另起一行<br>O（shift+o)：在当前行的上面另起一行</p>
<p style="COLOR: #000000">nk或nj：光标向上或向下移n行，n为数字<br>an!【ESC】：在行后面加n个感叹号(!)<br>nx:执行n次x(删除)操作</p>
<p style="COLOR: #000000">ZZ：保存当前文档并退出VIM</p>
<p style="COLOR: #000000">:help ：查看帮助文档，在这之中，按CTRL+] 进入超连接，按CTRL＋O 返回。<br>:help subject :看某一主题的帮助，ZZ 退出帮助</p>
<p style="COLOR: #000000">:set number / set nonumber :显示/不显示行号<br>:set ruler /set noruler:显示/不显示标尺</p>
<p style="COLOR: #000000">/pattern 正方向搜索一个字符模式<br>?pattern 反方向搜索一个字符模式<br>然后按n 继续向下找</p>
<p style="COLOR: #000000">把光标放到某个单词上面，然后按&#215;号键，表示查找这个单词<br>查找整个单词：/\&lt;word\&gt;</p>
<p style="COLOR: #000000">:set hlsearch 高亮显示查找到的单词<br>:set nohlsearch 关闭改功能<br><br>m[a-z]:在文中做标记，标记号可为a-z的26个字母，用`a可以移动到标记a处<br><br>r:替换当前字符<br>nr字符：替换当前n个字符</p>
<p style="COLOR: #000000">查找替换：<br>way1:<br>/【word】 :查找某个word<br>cw【newword】:替换为新word<br>n: 继续查找<br>.: 执行替换</p>
<p style="COLOR: #000000">way2:<br>:s/string1/string2/g:在一行中将string1替换为string2,g表示执行 用c表示需要确认<br>:num1,num2 s/string1/string2/g:在行num1至num2中间将string1替换为string2<br>:1,$ s/string1/string2/g:在全文中将string1替换为string2</p>
<p style="COLOR: #000000">&nbsp;</p>
<p style="COLOR: #000000"><br>v:进入visual 模式<br>【ESC】退出<br>V:shift+v 进入行的visual 模式<br>CTRL+V:进如块操作模式用o和O改变选择的边的大小。</p>
<p style="COLOR: #000000">粘贴：p，这是粘贴用x或d删除的文本<br>复制：<br>ynw：复制n个单词<br>yy：复制一行<br>ynl:复制n个字符<br>y$:复制当前光标至行尾处<br>nyy:拷贝n行<br>完了用p粘贴</p>
<p style="COLOR: #000000">:split:分割一个窗口<br>:split file.c ：为另一个文件file.c分隔窗口<br>:nsplit file.c: 为另一个文件file.c分隔窗口，并指定其行数<br>CTRL＋W在窗口中切换<br>:close：关闭当前窗口</p>
<p style="COLOR: #000000">在所有行插入相同的内容如include&lt;，操作方法如下：<br>将光标移到开始插入的位置，按CTRL+V进入VISUAL模式，选择好模块后<br>按I（shift+i)，后插入要插入的文本，按[ESC]完成。</p>
<p style="COLOR: #000000">:read file.c 将文件file.c的内容插入到当前光标所在的下面<br>:0read file.c 将文件file.c的内容插入到当前文件的开始处(第0行）<br>:nread file.c 将文件file.c的内容插入到当前文件的第n行后面<br>:read !cmd :将外部命令cmd的输出插如到当前光标所在的下面</p>
<p style="COLOR: #000000">:n1,n2 write temp.c 将本文件中的n1,到n2行写入temp.c这个文件中去</p>
<p style="COLOR: #000000">CTRL＋L刷新屏幕<br>shift + &lt; 左移一行<br>shift + &gt; 右移一行</p>
<p style="COLOR: #000000">u: undo<br>CTRL+R: re-do<br>J: 合并一行<br>CTRL+p 自动完成功能<br>CTRL+g 查看当前文件全路径<br></p>
<p style="COLOR: #000000">q[a-z] 开始记录但前开始的操作为宏，名称可为【a-z】，然后用q终止录制宏。<br>用reg显示当前定义的所有的宏，用@[a-z]来在当前光标处执行宏[a-z].</p>
<br><br>
<img src ="http://www.cppblog.com/deane/aggbug/108815.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2010-03-03 18:01 <a href="http://www.cppblog.com/deane/articles/108815.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>vim命令列表</title><link>http://www.cppblog.com/deane/articles/108813.html</link><dc:creator>李阳</dc:creator><author>李阳</author><pubDate>Wed, 03 Mar 2010 09:57:00 GMT</pubDate><guid>http://www.cppblog.com/deane/articles/108813.html</guid><wfw:comment>http://www.cppblog.com/deane/comments/108813.html</wfw:comment><comments>http://www.cppblog.com/deane/articles/108813.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/deane/comments/commentRss/108813.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/deane/services/trackbacks/108813.html</trackback:ping><description><![CDATA[<br>
<p>linux下不熟悉vim命令实在是说不过去了，下面是常用的vim命令列表。vim是linux下命令行一款经典编辑器操作简单功能强大，这是很多unix上元老级程序员的挚爱。我本地机器上一直使用的是vim+ctags组合，很受用。</p>
<p><span id=more-365></span></p>
<p>进入vi的命令<br>vi filename :打开或新建文件，并将光标置于第一行首<br>vi +n filename ：打开文件，并将光标置于第n行首<br>vi + filename ：打开文件，并将光标置于最后一行首<br>vi +/pattern filename：打开文件，并将光标置于第一个与pattern匹配的串处<br>vi -r filename ：在上次正用vi编辑时发生系统崩溃，恢复filename<br>vi filename&#8230;.filename ：打开多个文件，依次编辑</p>
<p>移动光标类命令<br>h ：光标左移一个字符<br>l ：光标右移一个字符<br>space：光标右移一个字符<br>Backspace：光标左移一个字符<br>k或Ctrl+p：光标上移一行<br>j或Ctrl+n ：光标下移一行<br>Enter ：光标下移一行<br>w或W ：光标右移一个字至字首<br>b或B ：光标左移一个字至字首<br>e或E ：光标右移一个字j至字尾<br>) ：光标移至句尾<br>( ：光标移至句首<br>}：光标移至段落开头<br>{：光标移至段落结尾<br>nG：光标移至第n行首<br>n+：光标下移n行<br>n-：光标上移n行<br>n$：光标移至第n行尾<br>H ：光标移至屏幕顶行<br>M ：光标移至屏幕中间行<br>L ：光标移至屏幕最后行<br>0：（注意是数字零）光标移至当前行首<br>$：光标移至当前行尾</p>
<p>屏幕翻滚类命令<br>Ctrl+u：向文件首翻半屏<br>Ctrl+d：向文件尾翻半屏<br>Ctrl+f：向文件尾翻一屏<br>Ctrl＋b；向文件首翻一屏<br>nz：将第n行滚至屏幕顶部，不指定n时将当前行滚至屏幕顶部。</p>
<p>插入文本类命令<br>i ：在光标前<br>I ：在当前行首<br>a：光标后<br>A：在当前行尾<br>o：在当前行之下新开一行<br>O：在当前行之上新开一行<br>r：替换当前字符<br>R：替换当前字符及其后的字符，直至按ESC键<br>s：从当前光标位置处开始，以输入的文本替代指定数目的字符<br>S：删除指定数目的行，并以所输入文本代替之<br>ncw或nCW：修改指定数目的字<br>nCC：修改指定数目的行</p>
<p>删除命令<br>ndw或ndW：删除光标处开始及其后的n-1个字<br>do：删至行首<br>d$：删至行尾<br>ndd：删除当前行及其后n-1行<br>x或X：删除一个字符，x删除光标后的，而X删除光标前的<br>Ctrl+u：删除输入方式下所输入的文本</p>
<p>搜索及替换命令 :<br>/pattern：从光标开始处向文件尾搜索pattern<br>?pattern：从光标开始处向文件首搜索pattern<br>n：在同一方向重复上一次搜索命令<br>N：在反方向上重复上一次搜索命令<br>：s/p1/p2/g：将当前行中所有p1均用p2替代<br>：n1,n2s/p1/p2/g：将第n1至n2行中所有p1均用p2替代<br>：g/p1/s//p2/g：将文件中所有p1均用p2替换</p>
<p>选项设置<br>all：列出所有选项设置情况<br>term：设置终端类型<br>ignorance：在搜索中忽略大小写<br>list：显示制表位(Ctrl+I)和行尾标志（$)<br>number：显示行号<br>report：显示由面向行的命令修改过的数目<br>terse：显示简短的警告信息<br>warn：在转到别的文件时若没保存当前文件则显示NO write信息<br>nomagic：允许在搜索模式中，使用前面不带&#8220;\&#8221;的特殊字符<br>nowrapscan：禁止vi在搜索到达文件两端时，又从另一端开始<br>mesg：允许vi显示其他用户用write写到自己终端上的信息</p>
<p>最后行方式命令<br>：n1,n2 co n3：将n1行到n2行之间的内容拷贝到第n3行下<br>：n1,n2 m n3：将n1行到n2行之间的内容移至到第n3行下<br>：n1,n2 d ：将n1行到n2行之间的内容删除<br>：w ：保存当前文件<br>：e filename：打开文件filename进行编辑<br>：x：保存当前文件并退出<br>：q：退出vi<br>：q!：不保存文件并退出vi<br>：!command：执行shell命令command<br>：n1,n2 w!command：将文件中n1行至n2行的内容作为command的输入并执行之，若不指<br>定n1，n2，则表示将整个文件内容作为command的输入<br>：r!command：将命令command的输出结果放到当前行 。</p>
<br><br>
<img src ="http://www.cppblog.com/deane/aggbug/108813.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/deane/" target="_blank">李阳</a> 2010-03-03 17:57 <a href="http://www.cppblog.com/deane/articles/108813.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>