﻿<?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++博客-茶博-随笔分类-liunx编程技术</title><link>http://www.cppblog.com/yehongly/category/8440.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 30 Oct 2008 16:28:35 GMT</lastBuildDate><pubDate>Thu, 30 Oct 2008 16:28:35 GMT</pubDate><ttl>60</ttl><item><title>system V队列</title><link>http://www.cppblog.com/yehongly/archive/2008/10/22/64677.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Wed, 22 Oct 2008 01:31:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/10/22/64677.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/64677.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/10/22/64677.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/64677.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/64677.html</trackback:ping><description><![CDATA[<div class="maintitle" align="center">Linux环境进程间通信（三）：消息队列</div>
<div align="center">作者：<a  href="http://www.bccn.net/ShowAuthor.asp?ChannelID=1&amp;AuthorName=%E9%83%91%E5%BD%A6%E5%85%B4" title="郑彦兴">郑彦兴</a>&nbsp;&nbsp;&nbsp;&nbsp;阅读人次：<span id="hits">2704</span>&nbsp;&nbsp;&nbsp;&nbsp;文章来源：<a  href="http://www.bccn.net/ShowCopyFrom.asp?ChannelID=1&amp;SourceName=developerWorks">developerWorks</a>&nbsp;&nbsp;&nbsp;&nbsp;发布时间：2007-8-29&nbsp;&nbsp;&nbsp;&nbsp;<a  href="http://www.bccn.net/article/comment/?/=5851.html" target="_blank">网友评论(<font color="#cc0000"><span id="replys">0</span></font>)条</a></div>
<div style="font-size: 1px; width: 100%; line-height: 1px; background-color: #d0d0d0;">&nbsp;</div>
<blockquote>本系列文章中的前两部分，我们探讨<font color="#003399">管道</font>及<font color="#003399">信号</font>两种通信机制，本文将深入第三部分，介绍系统 V 消息队列及其相应 API。</blockquote>
<p>消息队列（也叫做报文队列）能够克服早期unix通信机制的一些缺点。作为早期unix通信机制之一的信号能够传送的信息量有限，后来虽然
POSIX
1003.1b在信号的实时性方面作了拓广，使得信号在传递信息量方面有了相当程度的改进，但是信号这种通信方式更像"即时"的通信方式，它要求接受信号
的进程在某个时间范围内对信号做出反应，因此该信号最多在接受信号进程的生命周期内才有意义，信号所传递的信息是接近于随进程持续的概念
（process-persistent），见<font color="#003399">附录 1</font>；管道及有名管道及有名管道则是典型的随进程持续IPC，并且，只能传送无格式的字节流无疑会给应用程序开发带来不便，另外，它的缓冲区大小也受到限制。</p>
<p>消息队列就是一个消息的链表。可以把消息看作一个记录，具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息；对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的（参见<font color="#003399">附录 1</font>）。</p>
<p>目前主要有两种类型的消息队列：POSIX消息队列以及系统V消息队列，系统V消息队列目前被大量使用。考虑到程序的可移植性，新开发的应用程序应尽量使用POSIX消息队列。</p>
<p>在本系列专题的序（深刻理解Linux进程间通信（IPC））中，提到对于消息队列、信号灯、以及共享内存区来说，有两个实现版本：POSIX的以
及系统V的。Linux内核（内核2.4.18）支持POSIX信号灯、POSIX共享内存区以及POSIX消息队列，但对于主流Linux发行版本之一
redhad8.0（内核2.4.18），还没有提供对POSIX进程间通信API的支持，不过应该只是时间上的事。</p>
<p>因此，本文将主要介绍系统V消息队列及其相应API。<strong>在没有声明的情况下，以下讨论中指的都是系统V消息队列。</strong></p>
<p><span class="atitle2"><font color="#003399">一、消息队列基本概念</font></span></p>
<ol class="n01">
    <li>系统V消息队列是随内核持续的，只有在内核重起或者显示删除一个消息队列时，该消息队列才会真正被删除。因此系统中记录消息队列的数据结构（struct ipc_ids msg_ids）位于内核中，系统中的所有消息队列都可以在结构msg_ids中找到访问入口。
    </li>
    <li>消息队列就是一个消息的链表。每个消息队列都有一个队列头，用结构struct msg_queue来描述（参见<font color="#003399">附录 2</font>）。队列头中包含了该消息队列的大量信息，包括消息队列键值、用户ID、组ID、消息队列中消息数目等等，甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息，也可以设置其中的某些信息。
    </li>
    <li>下图说明了内核与消息队列是怎样建立起联系的：<br>其中：struct ipc_ids msg_ids是内核中记录消息队列的全局数据结构；struct msg_queue是每个消息队列的队列头。
    <center><img  src="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/fig1.gif" alt="" width="529" border="0" height="219"></center></li>
</ol>
<p>从上图可以看出，全局数据结构 struct ipc_ids msg_ids 可以访问到每个消息队列头的第一个成员：struct
kern_ipc_perm；而每个struct
kern_ipc_perm能够与具体的消息队列对应起来是因为在该结构中，有一个key_t类型成员key，而key则唯一确定一个消息队列。
kern_ipc_perm结构如下：</p>
<table width="100%" bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
    <tbody>
        <tr>
            <td>
            <pre><code>struct kern_ipc_perm{   //内核中记录消息队列的全局数据结构msg_ids能够访问到该结构；<br>            key_t   key;    //该键值则唯一对应一个消息队列<br>            uid_t   uid;<br>            gid_t   gid;<br>uid_t   cuid;<br>gid_t   cgid;<br>mode_t  mode;<br>unsigned long seq;<br>}<br></code><br></pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p><span class="atitle2"><font color="#003399">二、操作消息队列</font></span></p>
<p><span class="atitle3">对消息队列的操作无非有下面三种类型：</span></p>
<p>1、 打开或创建消息队列<br>消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值，所以，要获得一个消息队列的描述字，只需提供该消息队列的键值即可；</p>
<p>注：消息队列描述字是由在系统范围内唯一的键值生成的，而键值可以看作对应系统内的一条路经。</p>
<p>2、 读写操作</p>
<p>消息读写操作非常简单，对开发人员来说，每个消息都类似如下的数据结构：</p>
<table width="100%" bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
    <tbody>
        <tr>
            <td>
            <pre><code>struct msgbuf{<br>long mtype;<br>char mtext[1];<br>};</code><br></pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>mtype成员代表消息类型，从消息队列中读取消息的一个重要依据就是消息的类型；mtext是消息内容，当然长度不一定为1。因此，对于发送消息
来说，首先预置一个msgbuf缓冲区并写入消息类型和内容，调用相应的发送函数即可；对读取消息来说，首先分配这样一个msgbuf缓冲区，然后把消息
读入该缓冲区即可。</p>
<p>3、 获得或设置消息队列属性：</p>
<p>消息队列的信息基本上都保存在消息队列头中，因此，可以分配一个类似于消息队列头的结构(struct msqid_ds，见<font color="#003399">附录 2</font>)，来返回消息队列的属性；同样可以设置该数据结构。</p>
<br>
<center><img  src="http://www-900.ibm.com/developerWorks/cn/linux/l-ipc/part3/fig2.gif" alt="" width="507" border="0" height="219"></center><br>
<p><span class="atitle3">消息队列API</span></p>
<p><strong>1、文件名到键值</strong></p>
<table width="100%" bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
    <tbody>
        <tr>
            <td>
            <pre><code>#include &lt;sys/types.h&gt;<br>#include &lt;sys/ipc.h&gt;<br>key_t ftok (char*pathname, char proj)；<br></code><br></pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>它返回与路径pathname相对应的一个键值。该函数不直接对消息队列操作，但在调用ipc(MSGGET,&#8230;)或msgget()来获得消息队列描述字前，往往要调用该函数。典型的调用代码是：</p>
<table width="100%" bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
    <tbody>
        <tr>
            <td>
            <pre><code>   key=ftok(path_ptr, 'a');<br>    ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0);<br>    &#8230;<br></code><br></pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p><strong>2、linux为操作系统V进程间通信的三种方式（消息队列、信号灯、共享内存区）提供了一个统一的用户界面：</strong><br><code><font face="新宋体"><strong>int ipc</strong>(unsigned int <strong>call</strong>, int <strong>first</strong>, int <strong>second</strong>, int <strong>third</strong>, void *<strong>ptr</strong>, long <strong>fifth</strong>);</font></code></p>
<p>第一个参数指明对IPC对象的操作方式，对消息队列而言共有四种操作：MSGSND、MSGRCV、MSGGET以及MSGCTL，分别代表向消息
队列发送消息、从消息队列读取消息、打开或创建消息队列、控制消息队列；first参数代表唯一的IPC对象；下面将介绍四种操作。</p>
<ul class="n01">
    <li><strong>int ipc</strong>(<strong>MSGGET, int</strong> first, <strong>int</strong> second, <strong>int</strong> third, <strong>void</strong> *ptr, <strong>long</strong> fifth);<br>与该操作对应的系统V调用为：int msgget( (key_t)first，second)。
    </li>
    <li><strong>int ipc</strong>(<strong>MSGCTL, int</strong> first, <strong>int</strong> second, <strong>int</strong> third, <strong>void</strong> *ptr, <strong>long</strong> fifth)<br>与该操作对应的系统V调用为：int msgctl( first，second, (struct msqid_ds*) ptr)。
    </li>
    <li><strong>int ipc</strong>(<strong>MSGSND, int</strong> first, <strong>int</strong> second, <strong>int</strong> third, <strong>void</strong> *ptr, <strong>long</strong> fifth);<br>与该操作对应的系统V调用为：int msgsnd( first, (struct msgbuf*)ptr, second, third)。
    </li>
    <li><strong>int ipc</strong>(<strong>MSGRCV, int</strong> first, <strong>int</strong> second, <strong>int</strong> third, <strong>void</strong> *ptr, <strong>long</strong> fifth);<br>与该操作对应的系统V调用为：int msgrcv( first，(struct msgbuf*)ptr, second, fifth,third)， </li>
</ul>
<br>
<p>注：本人不主张采用系统调用ipc()，而更倾向于采用系统V或者POSIX进程间通信API。原因如下：</p>
<ul class="n01">
    <li>虽然该系统调用提供了统一的用户界面，但正是由于这个特性，它的参数几乎不能给出特定的实际意义（如以first、second来命名参数），在一定程度上造成开发不便。
    </li>
    <li>正如ipc手册所说的：ipc()是linux所特有的，编写程序时应注意程序的移植性问题；
    </li>
    <li>该系统调用的实现不过是把系统V IPC函数进行了封装，没有任何效率上的优势；
    </li>
    <li>系统V在IPC方面的API数量不多，形式也较简洁。 </li>
</ul>
<br>
<p><strong>3.系统V消息队列API</strong><br>系统V消息队列API共有四个，使用时需要包括几个头文件：</p>
<table width="100%" bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
    <tbody>
        <tr>
            <td>
            <pre><code>#include &lt;sys/types.h&gt;<br>#include &lt;sys/ipc.h&gt;<br>#include &lt;sys/msg.h&gt;<br></code><br></pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p><strong>1）int msgget(key_t key, int msgflg)</strong></p>
<p>参数key是一个键值，由ftok获得；msgflg参数是一些标志位。该调用返回与健值key相对应的消息队列描述字。</p>
<p>在以下两种情况下，该调用将创建一个新的消息队列：</p>
<ul class="n01">
    <li>如果没有消息队列与健值key相对应，并且msgflg中包含了IPC_CREAT标志位；
    </li>
    <li>key参数为IPC_PRIVATE； </li>
</ul>
<br>
<p>参数msgflg可以为以下：IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。</p>
<p><strong>调用返回：</strong>成功返回消息队列描述字，否则返回-1。</p>
<p>注：参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列，只意味着即将创建新的消息队列。</p>
<p><strong>2）int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);</strong><br>该系统调用从msgid代表的消息队列中读取一个消息，并把消息存储在msgp指向的msgbuf结构中。</p>
<p>msqid为消息队列描述字；消息返回后存储在msgp指向的地址，msgsz指定msgbuf的mtext成员的长度（即消息内容的长度），msgtyp为请求读取的消息类型；读消息标志msgflg可以为以下几个常值的或：</p>
<ul class="n01">
    <li>IPC_NOWAIT 如果没有满足条件的消息，调用立即返回，此时，errno=ENOMSG
    </li>
    <li>IPC_EXCEPT 与msgtyp&gt;0配合使用，返回队列中第一个类型不为msgtyp的消息
    </li>
    <li>IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节，则把该消息截断，截断部分将丢失。 </li>
</ul>
<br>
<p>msgrcv手册中详细给出了消息类型取不同值时(&gt;0; &lt;0; =0)，调用将返回消息队列中的哪个消息。</p>
<p>msgrcv()解除阻塞的条件有三个：</p>
<ol class="n01">
    <li>消息队列中有了满足条件的消息；
    </li>
    <li>msqid代表的消息队列被删除；
    </li>
    <li>调用msgrcv（）的进程被信号中断； </li>
</ol>
<br>
<p><strong>调用返回：</strong>成功返回读出消息的实际字节数，否则返回-1。</p>
<p><strong>3）int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);</strong><br>向msgid代表的消息队列发送一个消息，即将发送的消息存储在msgp指向的msgbuf结构中，消息的大小由msgze指定。</p>
<p>对发送消息来说，有意义的msgflg标志为IPC_NOWAIT，指明在消息队列没有足够空间容纳要发送的消息时，msgsnd是否等待。造成msgsnd()等待的条件有两种：</p>
<ul class="n01">
    <li>当前消息的大小与当前消息队列中的字节数之和超过了消息队列的总容量；
    </li>
    <li>当前消息队列的消息数（单位"个"）不小于消息队列的总容量（单位"字节数"），此时，虽然消息队列中的消息数目很多，但基本上都只有一个字节。 </li>
</ul>
<br>msgsnd()解除阻塞的条件有三个：
<ol class="n01">
    <li>不满足上述两个条件，即消息队列中有容纳该消息的空间；
    </li>
    <li>msqid代表的消息队列被删除；
    </li>
    <li>调用msgsnd（）的进程被信号中断； </li>
</ol>
<br>
<p><strong>调用返回：</strong>成功返回0，否则返回-1。</p>
<p><strong>4）int msgctl(int msqid, int cmd, struct msqid_ds *buf);</strong><br>该系统调用对由msqid标识的消息队列执行cmd操作，共有三种cmd操作：IPC_STAT、IPC_SET 、IPC_RMID。</p>
<ol class="n01">
    <li>IPC_STAT：该命令用来获取消息队列信息，返回的信息存贮在buf指向的msqid结构中；
    </li>
    <li>IPC_SET：该命令用来设置消息队列的属性，要设置的属性存储在buf指向的msqid结构中；可设置属性包括：msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes，同时，也影响msg_ctime成员。
    </li>
    <li>IPC_RMID：删除msqid标识的消息队列； </li>
</ol>
<br>
<p><strong>调用返回：</strong>成功返回0，否则返回-1。</p>
<p><span class="atitle2"><font color="#003399">三、消息队列的限制</font></span><br>每个消息队列的容量（所能容纳的字节数）都有限制，该值因系统不同而不同。在后面的应用实例中，输出了redhat 8.0的限制，结果参见<font color="#003399">附录 3</font>。</p>
<p>另一个限制是每个消息队列所能容纳的最大消息数：在redhad 8.0中，该限制是受消息队列容量制约的：消息个数要小于消息队列的容量（字节数）。</p>
<p>注：上述两个限制是针对每个消息队列而言的，系统对消息队列的限制还有系统范围内的最大消息队列个数，以及整个系统范围内的最大消息数。一般来说，实际开发过程中不会超过这个限制。</p>
<p><span class="atitle2"><font color="#003399">四、消息队列应用实例</font></span><br>消息队列应用相对较简单，下面实例基本上覆盖了对消息队列的所有操作，同时，程序输出结果有助于加深对前面所讲的某些规则及消息队列限制的理解。</p>
<table width="100%" bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
    <tbody>
        <tr>
            <td>
            <pre><code>#include &lt;sys/types.h&gt;<br>#include &lt;sys/msg.h&gt;<br>#include &lt;unistd.h&gt;<br>void msg_stat(int,struct msqid_ds );<br>main()<br>{<br>int gflags,sflags,rflags;<br>key_t key;<br>int msgid;<br>int reval;<br>struct msgsbuf{<br>        int mtype;<br>        char mtext[1];<br>    }msg_sbuf;<br>struct msgmbuf<br>    {<br>    int mtype;<br>    char mtext[10];<br>    }msg_rbuf;<br>struct msqid_ds msg_ginfo,msg_sinfo;<br>char* msgpath="/unix/msgqueue";<br>key=ftok(msgpath,'a');<br>gflags=IPC_CREAT|IPC_EXCL;<br>msgid=msgget(key,gflags|00666);<br>if(msgid==-1)<br>{<br>    printf("msg create error\n");<br>    return;<br>}<br>//创建一个消息队列后，输出消息队列缺省属性<br>msg_stat(msgid,msg_ginfo);<br>sflags=IPC_NOWAIT;<br>msg_sbuf.mtype=10;<br>msg_sbuf.mtext[0]='a';<br>reval=msgsnd(msgid,&amp;msg_sbuf,sizeof(msg_sbuf.mtext),sflags);<br>if(reval==-1)<br>{<br>    printf("message send error\n");<br>}<br>//发送一个消息后，输出消息队列属性<br>msg_stat(msgid,msg_ginfo);<br>rflags=IPC_NOWAIT|MSG_NOERROR;<br>reval=msgrcv(msgid,&amp;msg_rbuf,4,10,rflags);<br>if(reval==-1)<br>    printf("read msg error\n");<br>else<br>    printf("read from msg queue %d bytes\n",reval);<br>//从消息队列中读出消息后，输出消息队列属性<br>msg_stat(msgid,msg_ginfo);<br>msg_sinfo.msg_perm.uid=8;//just a try<br>msg_sinfo.msg_perm.gid=8;//<br>msg_sinfo.msg_qbytes=16388;<br>//此处验证超级用户可以更改消息队列的缺省msg_qbytes<br>//注意这里设置的值大于缺省值<br>reval=msgctl(msgid,IPC_SET,&amp;msg_sinfo);<br>if(reval==-1)<br>{<br>    printf("msg set info error\n");<br>    return;<br>}<br>msg_stat(msgid,msg_ginfo);<br>//验证设置消息队列属性<br>reval=msgctl(msgid,IPC_RMID,NULL);//删除消息队列<br>if(reval==-1)<br>{<br>    printf("unlink msg queue error\n");<br>    return;<br>}<br>}<br>void msg_stat(int msgid,struct msqid_ds msg_info)<br>{<br>int reval;<br>sleep(1);//只是为了后面输出时间的方便<br>reval=msgctl(msgid,IPC_STAT,&amp;msg_info);<br>if(reval==-1)<br>{<br>    printf("get msg info error\n");<br>    return;<br>}<br>printf("\n");<br>printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes);<br>printf("number of messages in queue is %d\n",msg_info.msg_qnum);<br>printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);<br>//每个消息队列的容量（字节数）都有限制MSGMNB，值的大小因系统而异。在创建新的消息队列时，//msg_qbytes的缺省值就是MSGMNB<br>printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);<br>printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);<br>printf("last msgsnd time is %s", ctime(&amp;(msg_info.msg_stime)));<br>printf("last msgrcv time is %s", ctime(&amp;(msg_info.msg_rtime)));<br>printf("last change time is %s", ctime(&amp;(msg_info.msg_ctime)));<br>printf("msg uid is %d\n",msg_info.msg_perm.uid);<br>printf("msg gid is %d\n",msg_info.msg_perm.gid);<br>}<br></code><br></pre>
            </td>
        </tr>
    </tbody>
</table>
程序输出结果见<font color="#003399">附录 3</font>。<br>
<p><span class="atitle2"><font color="#003399">小结：</font></span><br>消息队列
与管道以及有名管道相比，具有更大的灵活性，首先，它提供有格式字节流，有利于减少开发人员的工作量；其次，消息具有类型，在实际应用中，可作为优先级使
用。这两点是管道以及有名管道所不能比的。同样，消息队列可以在几个进程间复用，而不管这几个进程是否具有亲缘关系，这一点与有名管道很相似；但消息队列
是随内核持续的，与有名管道（随进程持续）相比，生命力更强，应用空间更大。</p>
<p><strong><font color="#003399">附录 1</font>：</strong>在参考文献[1]中，给出了IPC随进程持续、随内核持续以及随文件系统持续的定义：</p>
<ol>
    <li>随进程持续：IPC一直存在到打开IPC对象的最后一个进程关闭该对象为止。如管道和有名管道；
    </li>
    <li>随内核持续：IPC一直持续到内核重新自举或者显示删除该对象为止。如消息队列、信号灯以及共享内存等；
    </li>
    <li>随文件系统持续：IPC一直持续到显示删除该对象为止。 </li>
</ol>
<br>
<p><strong><font color="#003399">附录 2</font>：</strong><br>结构msg_queue用来描述消息队列头，存在于系统空间：</p>
<table width="100%" bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
    <tbody>
        <tr>
            <td>
            <pre><code>struct msg_queue {<br>    struct kern_ipc_perm q_perm;<br>    time_t q_stime;         /* last msgsnd time */<br>    time_t q_rtime;         /* last msgrcv time */<br>    time_t q_ctime;         /* last change time */<br>    unsigned long q_cbytes;     /* current number of bytes on queue */<br>    unsigned long q_qnum;       /* number of messages in queue */<br>    unsigned long q_qbytes;     /* max number of bytes on queue */<br>    pid_t q_lspid;          /* pid of last msgsnd */<br>    pid_t q_lrpid;          /* last receive pid */<br>    struct list_head q_messages;<br>    struct list_head q_receivers;<br>    struct list_head q_senders;<br>};<br></code><br></pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>结构msqid_ds用来设置或返回消息队列的信息，存在于用户空间；</p>
<table width="100%" bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
    <tbody>
        <tr>
            <td>
            <pre><code>struct msqid_ds {<br>    struct ipc_perm msg_perm;<br>    struct msg *msg_first;      /* first message on queue,unused  */<br>    struct msg *msg_last;       /* last message in queue,unused */<br>    __kernel_time_t msg_stime;  /* last msgsnd time */<br>    __kernel_time_t msg_rtime;  /* last msgrcv time */<br>    __kernel_time_t msg_ctime;  /* last change time */<br>    unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */<br>    unsigned long  msg_lqbytes; /* ditto */<br>    unsigned short msg_cbytes;  /* current number of bytes on queue */<br>    unsigned short msg_qnum;    /* number of messages in queue */<br>    unsigned short msg_qbytes;  /* max number of bytes on queue */<br>    __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */<br>    __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */<br>};<br></code><br></pre>
            </td>
        </tr>
    </tbody>
</table>
//可以看出上述两个结构很相似。<br>
<p><strong><font color="#003399">附录 3</font>：</strong>消息队列实例输出结果：</p>
<table width="100%" bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0">
    <tbody>
        <tr>
            <td>
            <pre><code>current number of bytes on queue is 0<br>number of messages in queue is 0<br>max number of bytes on queue is 16384<br>pid of last msgsnd is 0<br>pid of last msgrcv is 0<br>last msgsnd time is Thu Jan  1 08:00:00 1970<br>last msgrcv time is Thu Jan  1 08:00:00 1970<br>last change time is Sun Dec 29 18:28:20 2002<br>msg uid is 0<br>msg gid is 0<br>//上面刚刚创建一个新消息队列时的输出<br>current number of bytes on queue is 1<br>number of messages in queue is 1<br>max number of bytes on queue is 16384<br>pid of last msgsnd is 2510<br>pid of last msgrcv is 0<br>last msgsnd time is Sun Dec 29 18:28:21 2002<br>last msgrcv time is Thu Jan  1 08:00:00 1970<br>last change time is Sun Dec 29 18:28:20 2002<br>msg uid is 0<br>msg gid is 0<br>read from msg queue 1 bytes<br>//实际读出的字节数<br>current number of bytes on queue is 0<br>number of messages in queue is 0<br>max number of bytes on queue is 16384   //每个消息队列最大容量（字节数）<br>pid of last msgsnd is 2510<br>pid of last msgrcv is 2510<br>last msgsnd time is Sun Dec 29 18:28:21 2002<br>last msgrcv time is Sun Dec 29 18:28:22 2002<br>last change time is Sun Dec 29 18:28:20 2002<br>msg uid is 0<br>msg gid is 0<br>current number of bytes on queue is 0<br>number of messages in queue is 0<br>max number of bytes on queue is 16388   //可看出超级用户可修改消息队列最大容量<br>pid of last msgsnd is 2510<br>pid of last msgrcv is 2510  //对操作消息队列进程的跟踪<br>last msgsnd time is Sun Dec 29 18:28:21 2002<br>last msgrcv time is Sun Dec 29 18:28:22 2002<br>last change time is Sun Dec 29 18:28:23 2002    //msgctl()调用对msg_ctime有影响<br>msg uid is 8<br>msg gid is 8<br></code><br></pre>
            </td>
        </tr>
    </tbody>
</table>
<br><!-- RESOURCES-->
<p><span class="atitle2"><font color="#003399">参考文献：</font></span></p>
<ul>
    <li>UNIX网络编程第二卷：进程间通信，作者：W.Richard Stevens，译者：杨继张，清华大学出版社。对POSIX以及系统V消息队列都有阐述，对Linux环境下的程序开发有极大的启发意义。
    </li>
    <li>linux内核源代码情景分析（上），毛德操、胡希明著，浙江大学出版社，给出了系统V消息队列相关的源代码分析。
    </li>
    <li><font color="#003399">http://www.fanqiang.com/a4/b2/20010508/113315.html</font>，主要阐述linux下对文件的操作，详细介绍了对文件的存取权限位，对IPC对象的存取权限同样具有很好的借鉴意义。
    </li>
    <li>msgget、msgsnd、msgrcv、msgctl手册</li>
</ul><img src ="http://www.cppblog.com/yehongly/aggbug/64677.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-10-22 09:31 <a href="http://www.cppblog.com/yehongly/archive/2008/10/22/64677.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下多线程编程详解</title><link>http://www.cppblog.com/yehongly/archive/2008/10/13/63898.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Mon, 13 Oct 2008 07:35:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/10/13/63898.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/63898.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/10/13/63898.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/63898.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/63898.html</trackback:ping><description><![CDATA[http://www.yuanma.org/data/2007/0921/article_2859.htm<br><br>线程（thread）技术早在60年代就被提出，但真正应用多线程到操作系统中去，是在80年代中期，solaris是这方面的佼佼者。传统的Unix也支持线程的概念，但是在一个进程（process）中只允许有一个线程，这样多线程就意味着多进程。<br>现在，多线程技术已经被许多操作系统所支持，包括Windows/NT，当然，也包括Linux。<br><br>　　为什么有了进程的概念后，还要再引入线程呢？使用多线程到底有哪些好处？什么的系统应该选用多线程？我们首先必须回答这些问题。<br><br>
使用多线程的理由之一是和进程相比，它是一种非常"节俭"的多任务操作方式。我们知道，在Linux系统下，启动一个新的进程必须分配给它独立的地址空
间，建立众多的数据表来维护它的代码段、堆栈段和数据段，这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程，它们彼此之间使用相同的地址
空间，共享大部分数据，启动一个线程所花费的空间远远小于启动一个进程所花费的空间，而且，线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
据统计，总的说来，一个进程的开销大约是一个线程开销的30倍左右，当然，在具体的系统上，这个数据可能会有较大的区别。<br><br>　　使用多线程
的理由之二是线程间方便的通信机制。对不同进程来说，它们具有独立的数据空间，要进行数据的传递只能通过通信的方式进行，这种方式不仅费时，而且很不方
便。线程则不然，由于同一进程下的线程之间共享数据空间，所以一个线程的数据可以直接为其它线程所用，这不仅快捷，而且方便。当然，数据的共享也带来其他
一些问题，有的变量不能同时被两个线程所修改，有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击，这些正是编写多线程程序时最
需要注意的地方。<br><br>　　除了以上所说的优点外，不和进程比较，多线程程序作为一种多任务、并发的工作方式，当然有以下的优点：<br><br>　　1) 提高应用程序响应。这对图形界面的程序尤其有意义，当一个操作耗时很长时，整个系统都会等待这个操作，此时程序不会响应键盘、鼠标、菜单的操作，而使用多线程技术，将耗时长的操作（time consuming）置于一个新的线程，可以避免这种尴尬的情况。<br><br>　　2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时，不同的线程运行于不同的CPU上。<br><br>　　3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程，成为几个独立或半独立的运行部分，这样的程序会利于理解和修改。<br><br>　　下面我们先来尝试编写一个简单的多线程程序。<br><br>　　简单的多线程编程<br><br>
Linux系统下的多线程遵循POSIX线程接口，称为pthread。编写Linux下的多线程程序，需要使用头文件pthread.h，连接时需要
使用库libpthread.a。顺便说一下，Linux下pthread的实现是通过系统调用clone（）来实现的。clone（）是Linux所特
有的系统调用，它的使用方式类似fork，关于clone（）的详细情况，有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序
pthread_create.c。<br><br><br>一个重要的线程创建函数原型：<br>#include &lt;pthread.h&gt;<br>int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr, void *(*start_rtn)(void),void *restrict arg);<br><br>&nbsp;&nbsp;&nbsp; 返回值：若是成功建立线程返回0,否则返回错误的编号<br>&nbsp;&nbsp;&nbsp; 形式参数：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthread_t *restrict tidp 要创建的线程的线程id指针<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const pthread_attr_t *restrict attr 创建线程时的线程属性<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void* (start_rtn)(void) 返回值是void类型的指针函数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void *restrict arg&nbsp;&nbsp; start_rtn的行参<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>例程1：&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; <br>&nbsp;&nbsp;&nbsp; 功能：创建一个简单的线程<br>&nbsp;&nbsp;&nbsp; 程序名称：pthread_create.c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　<br>/********************************************************************************************<br>**&nbsp;&nbsp;&nbsp; Name:pthread_create.c<br>**&nbsp;&nbsp;&nbsp; Used to study the multithread programming in Linux OS<br>**&nbsp;&nbsp;&nbsp; Author:zeickey<br>**&nbsp;&nbsp;&nbsp; Date:2006/9/16&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br>**&nbsp;&nbsp;&nbsp; Copyright (c) 2006,All Rights Reserved!<br>*********************************************************************************************/<br><br>#include &lt;stdio.h&gt;<br>#include &lt;pthread.h&gt;<br><br>void *myThread1(void)<br>{<br>&nbsp;&nbsp;&nbsp; int i;<br>&nbsp;&nbsp;&nbsp; for (i=0; i&lt;100; i++)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("This is the 1st pthread,created by zieckey.\n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; sleep(1);//Let this thread to sleep 1 second,and then continue to run<br>&nbsp;&nbsp;&nbsp; }<br>}<br><br>void *myThread2(void)<br>{<br>&nbsp;&nbsp;&nbsp; int i;<br>&nbsp;&nbsp;&nbsp; for (i=0; i&lt;100; i++)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("This is the 2st pthread,created by zieckey.\n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; sleep(1);<br>&nbsp;&nbsp;&nbsp; }<br>}<br><br>int main()<br>{<br>&nbsp;&nbsp;&nbsp; int i=0, ret=0;<br>&nbsp;&nbsp;&nbsp; pthread_t id1,id2;<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; ret = pthread_create(&amp;id2, NULL, (void*)myThread1, NULL);<br>&nbsp;&nbsp;&nbsp; if (ret)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("Create pthread error!\n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return 1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; ret = pthread_create(&amp;id2, NULL, (void*)myThread2, NULL);<br>&nbsp;&nbsp;&nbsp; if (ret)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("Create pthread error!\n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return 1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; pthread_join(id1, NULL);<br>&nbsp;&nbsp;&nbsp; pthread_join(id2, NULL);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br><br><br>　　我们编译此程序：<br># gcc pthread_create.c -lpthread<br><br>因为pthread的库不是linux系统的库，所以在进行编译的时候要加上-lpthread，否则编译不过，会出现下面错误<br>thread_test.c: 在函数 &#8216;create&#8217; 中：<br>thread_test.c:7: 警告： 在有返回值的函数中，程序流程到达函数尾<br>/tmp/ccOBJmuD.o: In function `main':thread_test.c:(.text+0x4f)：对&#8216;pthread_create&#8217;未定义的引用<br>collect2: ld 返回 1<br><br>　　运行，我们得到如下结果：<br># ./a.out <br>This is the 1st pthread,created by zieckey.<br>This is the 2st pthread,created by zieckey.<br>This is the 1st pthread,created by zieckey.<br>This is the 2st pthread,created by zieckey.<br>This is the 2st pthread,created by zieckey.<br>This is the 1st pthread,created by zieckey.<br>....<br><br>两个线程交替执行。<br>此例子介绍了创建线程的方法。<br>下面例子介绍向线程传递参数。<br><br><br>例程2：<br>&nbsp;&nbsp;&nbsp; 功能：向新的线程传递整形值<br>&nbsp;&nbsp;&nbsp; 程序名称：pthread_int.c<br>/********************************************************************************************<br>**&nbsp;&nbsp;&nbsp; Name:pthread_int.c<br>**&nbsp;&nbsp;&nbsp; Used to study the multithread programming in Linux OS<br>**&nbsp;&nbsp;&nbsp; Pass a parameter to the thread.<br>**&nbsp;&nbsp;&nbsp; Author:zeickey<br>**&nbsp;&nbsp;&nbsp; Date:2006/9/16&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br>**&nbsp;&nbsp;&nbsp; Copyright (c) 2006,All Rights Reserved!<br>*********************************************************************************************/<br><br>#include &lt;stdio.h&gt;<br>#include &lt;pthread.h&gt;<br>#include &lt;unistd.h&gt;<br><br>void *create(void *arg)<br>{<br>&nbsp;&nbsp;&nbsp; int *num;<br>&nbsp;&nbsp;&nbsp; num=(int *)arg;<br>&nbsp;&nbsp;&nbsp; printf("create parameter is %d \n",*num);<br>&nbsp;&nbsp;&nbsp; return (void *)0;<br>}<br>int main(int argc ,char *argv[])<br>{<br>&nbsp;&nbsp;&nbsp; pthread_t tidp;<br>&nbsp;&nbsp;&nbsp; int error;<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; int test=4;<br>&nbsp;&nbsp;&nbsp; int *attr=&amp;test;<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; error=pthread_create(&amp;tidp,NULL,create,(void *)attr);<br><br>&nbsp;&nbsp;&nbsp; if(error)<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("pthread_create is created is not created ... \n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp;&nbsp; sleep(1);<br>&nbsp;&nbsp;&nbsp; printf("pthread_create is created ...\n");<br>&nbsp;&nbsp;&nbsp; return 0;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br>}<br><br><br>&nbsp;&nbsp;&nbsp; 编译方法：<br><br>gcc -lpthread pthread_int.c -Wall<br><br><br>&nbsp;&nbsp;&nbsp; 执行结果：<br><br>create parameter is 4<br>pthread_create is created is&nbsp; created ...<br><br><br>&nbsp;&nbsp;&nbsp; 例程总结：<br>&nbsp;&nbsp;&nbsp; 可以看出来，我们在main函数中传递的整行指针，传递到我们新建的线程函数中。<br>&nbsp;&nbsp;&nbsp; 在上面的例子可以看出来我们向新的线程传入了另一个线程的int数据，线程之间还可以传递字符串或是更复杂的数据结构。<br>例程3：<br>&nbsp;&nbsp;&nbsp; 程序功能：向新建的线程传递字符串<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 程序名称：pthread_string.c<br>/********************************************************************************************<br>**&nbsp;&nbsp;&nbsp; Name:pthread_string.c<br>**&nbsp;&nbsp;&nbsp; Used to study the multithread programming in Linux OS<br>**&nbsp;&nbsp;&nbsp; Pass a &#8216;char*&#8216; parameter to the thread.<br>**&nbsp;&nbsp;&nbsp; Author:zeickey<br>**&nbsp;&nbsp;&nbsp; Date:2006/9/16&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br>**&nbsp;&nbsp;&nbsp; Copyright (c) 2006,All Rights Reserved!<br>*********************************************************************************************/<br>#include &lt;pthread.h&gt;<br>#include &lt;stdio.h&gt;<br>#include &lt;unistd.h&gt;<br><br>void *create(void *arg)<br>{<br>&nbsp;&nbsp;&nbsp; char *name;<br>&nbsp;&nbsp;&nbsp; name=(char *)arg;<br>&nbsp;&nbsp;&nbsp; printf("The parameter passed from main function is %s&nbsp; \n",name);<br>&nbsp;&nbsp;&nbsp; return (void *)0;<br>}<br><br>int main(int argc, char *argv[])<br>{<br>&nbsp;&nbsp;&nbsp; char *a="zieckey";<br>&nbsp;&nbsp;&nbsp; int error;<br>&nbsp;&nbsp;&nbsp; pthread_t tidp;<br><br>&nbsp;&nbsp;&nbsp; error=pthread_create(&amp;tidp, NULL, create, (void *)a);<br><br>&nbsp;&nbsp;&nbsp; if(error!=0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("pthread is not created.\n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; sleep(1);<br>&nbsp;&nbsp;&nbsp; printf("pthread is created... \n");<br>&nbsp;&nbsp;&nbsp; return 0;<br>}&nbsp;&nbsp;&nbsp; <br><br>&nbsp; 编译方法：<br><br>gcc -Wall pthread_string.c -lpthread<br><br><br>&nbsp;&nbsp;&nbsp; 执行结果：<br>The parameter passed from main function is zieckey&nbsp; <br>pthread is created... <br><br><br>&nbsp;&nbsp;&nbsp; 例程总结：<br>&nbsp;&nbsp;&nbsp; 可以看出来main函数中的字符串传入了新建的线程中。<br><br>例程4：<br>&nbsp;&nbsp;&nbsp; 程序功能：向新建的线程传递字符串<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 程序名称：pthread_struct.c<br>/********************************************************************************************<br>**&nbsp;&nbsp;&nbsp; Name:pthread_struct.c<br>**&nbsp;&nbsp;&nbsp; Used to study the multithread programming in Linux OS<br>**&nbsp;&nbsp;&nbsp; Pass a &#8216;char*&#8216; parameter to the thread.<br>**&nbsp;&nbsp;&nbsp; Author:zeickey<br>**&nbsp;&nbsp;&nbsp; Date:2006/9/16&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br>**&nbsp;&nbsp;&nbsp; Copyright (c) 2006,All Rights Reserved!<br>*********************************************************************************************/<br>#include &lt;stdio.h&gt;<br>#include &lt;pthread.h&gt;<br>#include &lt;unistd.h&gt;<br>#include &lt;stdlib.h&gt;<br><br>struct menber<br>{<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; char *s;<br>};<br><br>void *create(void *arg)<br>{<br>&nbsp;&nbsp;&nbsp; struct menber *temp;<br>&nbsp;&nbsp;&nbsp; temp=(struct menber *)arg;<br>&nbsp;&nbsp;&nbsp; printf("menber-&gt;a = %d&nbsp; \n",temp-&gt;a);<br>&nbsp;&nbsp;&nbsp; printf("menber-&gt;s = %s&nbsp; \n",temp-&gt;s);<br>&nbsp;&nbsp;&nbsp; return (void *)0;<br>}<br><br>int main(int argc,char *argv[])<br>{<br>&nbsp;&nbsp;&nbsp; pthread_t tidp;<br>&nbsp;&nbsp;&nbsp; int error;<br>&nbsp;&nbsp;&nbsp; struct menber *b;<br>&nbsp;&nbsp;&nbsp; b=(struct menber *)malloc( sizeof(struct menber) );<br>&nbsp;&nbsp;&nbsp; b-&gt;a = 4;<br>&nbsp;&nbsp;&nbsp; b-&gt;s = "zieckey";<br><br>&nbsp;&nbsp;&nbsp; error = pthread_create(&amp;tidp, NULL, create, (void *)b);<br><br>&nbsp;&nbsp;&nbsp; if( error )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("phread is not created...\n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; sleep(1);<br>&nbsp;&nbsp;&nbsp; printf("pthread is created...\n");<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br><br>&nbsp; 编译方法：<br><br>gcc -Wall pthread_struct.c -lpthread<br><br><br>&nbsp;&nbsp;&nbsp; 执行结果：<br>menber-&gt;a = 4&nbsp; <br>menber-&gt;s = zieckey&nbsp; <br>pthread is created...<br><br>&nbsp;&nbsp;&nbsp; 例程总结：<br>&nbsp;&nbsp;&nbsp; 可以看出来main函数中的一个结构体传入了新建的线程中。<br>&nbsp;&nbsp;&nbsp; 线程包含了标识进程内执行环境必须的信息。他集成了进程中的所有信息都是对线程进行共享的，包括文本程序、程序的全局内存和堆内存、栈以及文件描述符。<br>&nbsp;&nbsp;&nbsp; <br><br>例程5：<br>&nbsp;&nbsp;&nbsp; 程序目的：验证新建立的线程可以共享进程中的数据<br>&nbsp;&nbsp;&nbsp; 程序名称：pthread_share.c&nbsp; <br><br>/********************************************************************************************<br>**&nbsp;&nbsp;&nbsp; Name:pthread_share_data.c<br>**&nbsp;&nbsp;&nbsp; Used to study the multithread programming in Linux OS<br>**&nbsp;&nbsp;&nbsp; Pass a &#8216;char*&#8216; parameter to the thread.<br>**&nbsp;&nbsp;&nbsp; Author:zeickey<br>**&nbsp;&nbsp;&nbsp; Date:2006/9/16&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br>**&nbsp;&nbsp;&nbsp; Copyright (c) 2006,All Rights Reserved!<br>*********************************************************************************************/<br>#include &lt;stdio.h&gt;<br>#include &lt;pthread.h&gt;<br>#include &lt;unistd.h&gt;<br><br>static int a=4;<br>void *create(void *arg)<br>{<br>&nbsp;&nbsp;&nbsp; printf("new pthread ... \n");<br>&nbsp;&nbsp;&nbsp; printf("a=%d&nbsp; \n",a);<br>&nbsp;&nbsp;&nbsp; return (void *)0;<br>}<br><br>int main(int argc,char *argv[])<br>{<br>&nbsp;&nbsp;&nbsp; pthread_t tidp;<br>&nbsp;&nbsp;&nbsp; int error;<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; a=5;<br><br>&nbsp;&nbsp;&nbsp; error=pthread_create(&amp;tidp, NULL, create, NULL);<br><br>&nbsp;&nbsp;&nbsp; if(error!=0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("new thread is not create ... \n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; sleep(1);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; printf("new thread is created ... \n");<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br>&nbsp;&nbsp;&nbsp; <br>&nbsp; 编译方法：<br><br>gcc -Wall pthread_share_data.c -lpthread<br><br><br>&nbsp;&nbsp;&nbsp; 执行结果：<br>new pthread ... <br>a=5&nbsp; <br>new thread is created ... <br><br><br>&nbsp;&nbsp;&nbsp; 例程总结：<br>可以看出来，我们在主线程更改了我们的全局变量a的值的时候，我们新建立的线程则打印出来了改变的值，可以看出可以访问线程所在进程中的数据信息。<br>2、线程的终止<br><br>&nbsp;&nbsp;&nbsp; 如果进程中任何一个线程中调用exit，_Exit,或者是_exit，那么整个进程就会终止，<br>&nbsp;&nbsp;&nbsp; 与此类似，如果信号的默认的动作是终止进程，那么，把该信号发送到线程会终止进程。<br>&nbsp;&nbsp;&nbsp; 线程的正常退出的方式：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 线程只是从启动例程中返回，返回值是线程中的退出码<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 线程可以被另一个进程进行终止<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 线程自己调用pthread_exit函数<br>&nbsp;&nbsp;&nbsp; 两个重要的函数原型：<br><br>#include &lt;pthread.h&gt;<br>void pthread_exit(void *rval_ptr);<br>/*rval_ptr 线程退出返回的指针*/<br><br>int pthread_join(pthread_t thread,void **rval_ptr);<br>&nbsp;&nbsp; /*成功结束进程为0,否则为错误编码*/<br><br><br>&nbsp;&nbsp;&nbsp; 例程6<br>&nbsp;&nbsp;&nbsp; 程序目的：线程正常退出，接受线程退出的返回码<br>&nbsp;&nbsp;&nbsp; 程序名称：pthread_exit.c<br><br>/********************************************************************************************<br>**&nbsp;&nbsp; &nbsp;Name:pthread_exit.c<br>**&nbsp;&nbsp; &nbsp;Used to study the multithread programming in Linux OS<br>**&nbsp;&nbsp; &nbsp;A example showing a thread to exit and with a return code.<br>**&nbsp;&nbsp; &nbsp;Author:zeickey<br>**&nbsp;&nbsp; &nbsp;Date:2006/9/16&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;<br>**&nbsp;&nbsp; &nbsp;Copyright (c) 2006,All Rights Reserved!<br>*********************************************************************************************/<br><br>#include &lt;stdio.h&gt;<br>#include &lt;pthread.h&gt;<br>#include &lt;unistd.h&gt;<br><br>void *create(void *arg)<br>{<br>&nbsp;&nbsp; &nbsp;printf("new thread is created ... \n");<br>&nbsp;&nbsp; &nbsp;return (void *)8;<br>}<br><br>int main(int argc,char *argv[])<br>{<br>&nbsp;&nbsp; &nbsp;pthread_t tid;<br>&nbsp;&nbsp; &nbsp;int error;<br>&nbsp;&nbsp; &nbsp;void *temp;<br><br>&nbsp;&nbsp; &nbsp;error = pthread_create(&amp;tid, NULL, create, NULL);<br><br>&nbsp;&nbsp; &nbsp;if( error )<br>&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;printf("thread is not created ... \n");<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return -1;<br>&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;error = pthread_join(tid, &amp;temp);<br><br>&nbsp;&nbsp; &nbsp;if( error )<br>&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;printf("thread is not exit ... \n");<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return -2;<br>&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp; printf("thread is exit code %d \n", (int )temp);<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br><br>&nbsp; 编译方法：<br><br>gcc -Wall pthread_exit.c -lpthread<br><br><br>&nbsp;&nbsp;&nbsp; 执行结果：<br>new thread is created ...<br>thread is exit code 8<br><br>&nbsp;&nbsp;&nbsp; 例程总结：<br>可以看出来，线程退出可以返回线程的int数值。线程退出不仅仅可以返回线程的int数值，还可以返回一个复杂的数据结构。<br><br>&nbsp;&nbsp;&nbsp; 例程7<br>&nbsp;&nbsp;&nbsp; 程序目的：线程结束返回一个复杂的数据结构<br>&nbsp;&nbsp;&nbsp; 程序名称：pthread_return_struct.c<br>#include &lt;stdio.h&gt;<br>#include &lt;pthread.h&gt;<br>#include &lt;unistd.h&gt;<br><br>struct menber<br>{<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; char *b;<br>}temp={8,"zieckey"};<br>void *create(void *arg)<br>{<br>&nbsp;&nbsp;&nbsp; printf("new thread ... \n");<br>&nbsp;&nbsp;&nbsp; return (void *)&amp;temp;<br>}<br><br>int main(int argc,char *argv[])<br>{<br>&nbsp;&nbsp;&nbsp; int error;<br>&nbsp;&nbsp;&nbsp; pthread_t tid;<br>&nbsp;&nbsp;&nbsp; struct menber *c;<br><br>&nbsp;&nbsp;&nbsp; error = pthread_create(&amp;tid, NULL, create, NULL);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; if( error )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("new thread is not created ... \n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; printf("main ... \n");<br><br>&nbsp;&nbsp;&nbsp; error = pthread_join(tid,(void *)&amp;c);<br><br>&nbsp;&nbsp;&nbsp; if( error )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("new thread is not exit ... \n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return -2;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; printf("c-&gt;a = %d&nbsp; \n",c-&gt;a);<br>&nbsp;&nbsp;&nbsp; printf("c-&gt;b = %s&nbsp; \n",c-&gt;b);<br>&nbsp;&nbsp;&nbsp; sleep(1);<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br><br><br>&nbsp; 编译方法：<br><br>gcc -Wall pthread_return_struct.c -lpthread<br><br><br>&nbsp;&nbsp;&nbsp; 执行结果：<br><br>main ...<br>new thread ...<br>c-&gt;a = 8<br>c-&gt;b = zieckey<br><br><br>例程总结：<br>一定要记得返回的数据结构要是在这个数据要返回的结构没有释放的时候应用，<br>如果数据结构已经发生变化，那返回的就不会是我们所需要的，而是脏数据<br>3、线程标识<br><br>&nbsp;&nbsp;&nbsp; 函数原型：<br>&nbsp; &nbsp;<br>#include &lt;pthread.h&gt;<br>pthread_t pthread_self(void);<br><br>pid_t getpid(void);<br>&nbsp;&nbsp;&nbsp; getpid()用来取得目前进程的进程识别码,函数说明<br><br>&nbsp;&nbsp;&nbsp; 例程8<br>&nbsp;&nbsp;&nbsp; 程序目的：实现在新建立的线程中打印该线程的id和进程id<br>&nbsp;&nbsp;&nbsp; 程序名称：pthread_id.c<br>&nbsp;&nbsp; <br>/********************************************************************************************<br>**&nbsp;&nbsp;&nbsp; Name:pthread_id.c<br>**&nbsp;&nbsp;&nbsp; Used to study the multithread programming in Linux OS.<br>**&nbsp;&nbsp;&nbsp; Showing how to get the thread's tid and the process's pid.<br>**&nbsp;&nbsp;&nbsp; Author:zeickey<br>**&nbsp;&nbsp;&nbsp; Date:2006/9/16&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br>**&nbsp;&nbsp;&nbsp; Copyright (c) 2006,All Rights Reserved!<br>*********************************************************************************************/<br>#include &lt;stdio.h&gt;<br>#include &lt;pthread.h&gt;<br>#include &lt;unistd.h&gt; /*getpid()*/<br><br>void *create(void *arg)<br>{<br>&nbsp;&nbsp;&nbsp; printf("New thread .... \n");<br>&nbsp;&nbsp;&nbsp; printf("This thread's id is %u&nbsp; \n", (unsigned int)pthread_self());<br>&nbsp;&nbsp;&nbsp; printf("The process pid is %d&nbsp; \n",getpid());<br>&nbsp;&nbsp;&nbsp; return (void *)0;<br>} <br><br>int main(int argc,char *argv[])<br>{<br>&nbsp;&nbsp;&nbsp; pthread_t tid;<br>&nbsp;&nbsp;&nbsp; int error;<br><br>&nbsp;&nbsp;&nbsp; printf("Main thread is starting ... \n");<br><br>&nbsp;&nbsp;&nbsp; error = pthread_create(&amp;tid, NULL, create, NULL);<br><br>&nbsp;&nbsp;&nbsp; if(error)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; printf("thread is not created ... \n");<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; printf("The main process's pid is %d&nbsp; \n",getpid());<br>&nbsp;&nbsp;&nbsp; sleep(1);<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br><br><br>&nbsp;&nbsp;&nbsp; 编译方法：<br><br>&nbsp;&nbsp; <br>gcc -Wall -lpthread pthread_id.c<br><br>&nbsp;&nbsp;&nbsp; 执行结果：<br><br>Main thread is starting ... <br>The main process's pid is 3307&nbsp; <br>New thread .... <br>This thread's id is 3086347152&nbsp; <br>The process pid is 3307&nbsp; <br><img src ="http://www.cppblog.com/yehongly/aggbug/63898.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-10-13 15:35 <a href="http://www.cppblog.com/yehongly/archive/2008/10/13/63898.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>字节对齐详解</title><link>http://www.cppblog.com/yehongly/archive/2008/10/13/63857.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Mon, 13 Oct 2008 01:30:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/10/13/63857.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/63857.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/10/13/63857.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/63857.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/63857.html</trackback:ping><description><![CDATA[http://www.yuanma.org/data/2006/0723/article_1213.htm<br><br>其实字节对齐的细节和具体编译器实现相关，但一般而言，满足三个准则：<br>&nbsp;<br>&nbsp;&nbsp; 1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除；
<p>&nbsp;&nbsp; 2) 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍，如有需要编译器会在成员之间加上填充字节；例如上面第二个结构体变量的地址空间。</p>
&nbsp;&nbsp;&nbsp; 3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍，如有需要编译器会在最末一个成员之后加上填充字节。例如上面第一个结构体变量。<br>
<h3>一.什么是字节对齐,为什么要对齐?</h3>
<p>&nbsp;&nbsp;&nbsp;
现代计算机中内存空间都是按照byte划分的，从理论上讲似乎对任何类型的变量的访问可以从任何地址开始，但实际情况是在访问特定类型变量的时候经常在特
定的内存地址访问，这就需要各种类型数据按照一定的规则在空间上排列，而不是顺序的一个接一个的排放，这就是对齐。<br>&nbsp;&nbsp;&nbsp;
对齐的作用和原因：各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问
一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况，但是最常见的是如果不按照适合其平台要求对
数据存放进行对齐，会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始，如果一个int型（假设为32位系统）如果存放在偶地址开始的地方，那
么一个读周期就可以读出这32bit，而如果存放在奇地址开始的地方，就需要2个读周期，并对两次读出的结果的高低字节进行拼凑才能得到该32bit数
据。显然在读取效率上下降很多。</p>
<h3>二.字节对齐对程序的影响:</h3>
<p>&nbsp;&nbsp;&nbsp; 先让我们看几个例子吧(32bit,x86环境,gcc编译器):<br>设结构体如下定义：<br>struct A<br>{<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>struct B<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>现在已知32位机器上各种数据类型的长度如下:<br>char:1(有符号无符号同)&nbsp;&nbsp;&nbsp;&nbsp;<br>short:2(有符号无符号同)&nbsp;&nbsp;&nbsp;&nbsp;<br>int:4(有符号无符号同)&nbsp;&nbsp;&nbsp;&nbsp;<br>long:4(有符号无符号同)&nbsp;&nbsp;&nbsp;&nbsp;<br>float:4&nbsp;&nbsp;&nbsp;&nbsp;double:8<br>那么上面两个结构大小如何呢?<br>结果是:<br>sizeof(strcut A)值为8<br>sizeof(struct B)的值却是12</p>
<p>结构体A中包含了4字节长度的int一个，1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。<br>之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:<br>#pragma pack (2) /*指定按2字节对齐*/<br>struct C<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>#pragma pack () /*取消指定对齐，恢复缺省对齐*/<br>sizeof(struct C)值是8。<br>修改对齐值为1：<br>#pragma pack (1) /*指定按1字节对齐*/<br>struct D<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>#pragma pack () /*取消指定对齐，恢复缺省对齐*/<br>sizeof(struct D)值为7。<br>后面我们再讲解#pragma pack()的作用.</p>
<h3>三.编译器是按照什么样的原则进行对齐的?</h3>
<p>&nbsp;&nbsp;&nbsp; 先让我们看四个重要的基本概念：<br><font color="#0000ff">1.数据类型自身的对齐值：<br></font>&nbsp; 对于char型数据，其自身对齐值为1，对于short型为2，对于int,float,double类型，其自身对齐值为4，单位字节。<br><font color="#3300ff">2.结构体或者类的自身对齐值：</font>其成员中自身对齐值最大的那个值。<br><font color="#0000ff">3.指定对齐值</font>：#pragma pack (value)时的指定对齐值value。<br><font color="#0000ff">4.数据成员、结构体和类的有效对齐值：</font>自身对齐值和指定对齐值中小的那个值。<br>有
了这些值，我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值，最重要。有效对齐N，就是
表示&#8220;对齐在N上&#8221;，也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数
据结构的起始地址。结构体的成员变量要对齐排放，结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数
倍，结合下面例子理解)。这样就不能理解上面的几个例子的值了。<br>例子分析：<br>分析例子B；<br>struct B<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>假
设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值，在笔者环境下，该值默认为4。第一个成员变量b的自身对齐值是1，比指定或者默认指定
对齐值4小，所以其有效对齐值为1，所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a，其自身对齐值为4，所以有效对齐值也为4，
所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中，复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为
2，所以有效对齐值也是2，可以存放在0x0008到0x0009这两个字节空间中，符合0x0008%2=0。所以从0x0000到0x0009存放的
都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b）所以就是4，所以结构体的有效对齐值也是4。根据结构体圆整的要求，
0x0009到0x0000=10字节，（10＋2）％4＝0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B
共有12个字节,sizeof(struct B)=12;<font color="#3300ff">其实如果就这一个就来说它已将满足字节对齐了,
因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那
么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一
个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据，其
自身对齐值为1，对于short型为2，对于int,float,double类型，其自身对齐值为4，这些已有类型的自身对齐值也是基于数组考虑的,只
是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.<br></font>同理,分析上面例子C：<br>#pragma pack (2) /*指定按2字节对齐*/<br>struct C<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>#pragma pack () /*取消指定对齐，恢复缺省对齐*/<br>第
一个变量b的自身对齐值为1，指定对齐值为2，所以，其有效对齐值为1，假设C从0x0000开始，那么b存放在0x0000，符合0x0000%1=
0;第二个变量，自身对齐值为4，指定对齐值为2，所以有效对齐值为2，所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续
字节中，符合0x0002%2=0。第三个变量c的自身对齐值为2，所以有效对齐值为2，顺序存放<br>在0x0006、0x0007中，符合
0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4，所以C的有效对齐值为2。又8%2=0,C
只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.</p>
<h3>四.如何修改编译器的默认对齐值?</h3>
<p>1.在VC IDE中，可以这样修改：[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改，默认是8字节。<br>2.在编码时，可以这样动态修改：#pragma pack .<font color="#ff0000">注意:是pragma而不是progma.</font></p>
<h3>五.针对字节对齐,我们在编程中如何考虑?</h3>
<p><br>&nbsp;&nbsp;&nbsp;
如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照
类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做
法是显式的插入reserved成员：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct A{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char a;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char reserved[3];//使用空间换时间<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int b;<br>}<br><br>reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.</p>
<h3>六.字节对齐可能带来的隐患:</h3>
<p>&nbsp;&nbsp;&nbsp; 代码中关于对齐的隐患，很多是隐式的。比如在强制类型转换的时候。例如：<br>unsigned int i = 0x12345678;<br>unsigned char *p=NULL;<br>unsigned short *p1=NULL;</p>
<p>p=&amp;i;<br>*p=0x00;<br>p1=(unsigned short *)(p+1);<br>*p1=0x0000;<br>最后两句代码，从奇数边界去访问unsignedshort型变量，显然不符合对齐的规定。<br>在x86上，类似的操作只会影响效率，但是在MIPS或者sparc上，可能就是一个error,因为它们要求必须字节对齐.</p>
<h3>七.如何查找与字节对齐方面的问题:</h3>
<p>如果出现对齐或者赋值问题首先查看<br>1. 编译器的big little端设置<br>2. 看这种体系本身是否支持非对齐访问<br>3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。</p>
<p>八.相关文章:转自<a href="http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx"><u>http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx</u></a></p>
<quote></quote>
<p>&nbsp;ARM下的对齐处理 <br>from DUI0067D_ADS1_2_CompLib </p>
<p>3.13 type&nbsp; qulifiers </p>
<p>有部分摘自ARM编译器文档对齐部分</p>
<p>对齐的使用:<br>1.__align(num)<br>&nbsp;&nbsp; 这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时<br>&nbsp;&nbsp; 就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。<br>&nbsp;&nbsp; 这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节<br>&nbsp;&nbsp; 对齐,但是不能让4字节的对象2字节对齐。<br>&nbsp;&nbsp; __align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。<br>&nbsp;&nbsp; <br>2.__packed <br>&nbsp; __packed是进行一字节对齐<br>&nbsp; 1.不能对packed的对象进行对齐<br>&nbsp; 2.所有对象的读写访问都进行非对齐访问<br>&nbsp; 3.float及包含float的结构联合及未用__packed的对象将不能字节对齐<br>&nbsp; 4.__packed对局部整形变量无影响<br>&nbsp; 5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定<br>&nbsp; 义为packed。<br>&nbsp;&nbsp;&nbsp;&nbsp; __packed int* p;&nbsp; //__packed int 则没有意义<br>&nbsp; 6.对齐或非对齐读写访问带来问题<br>&nbsp; __packed struct STRUCT_TEST<br>&nbsp;{<br>&nbsp; char a;<br>&nbsp; int b;<br>&nbsp; char c;<br>&nbsp;}&nbsp; ;&nbsp;&nbsp;&nbsp; //定义如下结构此时b的起始地址一定是不对齐的<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]<br>//将下面变量定义成全局静态不在栈上 <br>static char* p;<br>static struct STRUCT_TEST a;<br>void Main()<br>{<br>&nbsp;__packed int* q;&nbsp; //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以</p>
<p>&nbsp;p = (char*)&amp;a;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;q = (int*)(p+1);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;<br>&nbsp;*q = 0x87654321; <br>/*&nbsp;&nbsp; <br>得到赋值的汇编指令很清楚<br>ldr&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r5,0x20001590 ; = #0x12345678<br>[0xe1a00005]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r0,r5<br>[0xeb0000b0]&nbsp;&nbsp; bl&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __rt_uwrite4&nbsp; //在此处调用一个写4byte的操作函数 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>[0xe5c10000]&nbsp;&nbsp; strb&nbsp;&nbsp;&nbsp;&nbsp; r0,[r1,#0]&nbsp;&nbsp; //函数进行4次strb操作然后返回保证了数据正确的访问<br>[0xe1a02420]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,r0,lsr #8<br>[0xe5c12001]&nbsp;&nbsp; strb&nbsp;&nbsp;&nbsp;&nbsp; r2,[r1,#1]<br>[0xe1a02820]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,r0,lsr #16<br>[0xe5c12002]&nbsp;&nbsp; strb&nbsp;&nbsp;&nbsp;&nbsp; r2,[r1,#2]<br>[0xe1a02c20]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,r0,lsr #24<br>[0xe5c12003]&nbsp;&nbsp; strb&nbsp;&nbsp;&nbsp;&nbsp; r2,[r1,#3]<br>[0xe1a0f00e]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pc,r14<br>*/</p>
<p>/*<br>如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败<br>[0xe59f2018]&nbsp;&nbsp; ldr&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,0x20001594 ; = #0x87654321<br>[0xe5812000]&nbsp;&nbsp; str&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,[r1,#0]<br>*/</p>
//这样可以很清楚的看到非对齐访问是如何产生错误的<br>//以及如何消除非对齐访问带来问题<br>//也可以看到非对齐访问和对齐访问的指令差异导致效率问题<br> <img src ="http://www.cppblog.com/yehongly/aggbug/63857.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-10-13 09:30 <a href="http://www.cppblog.com/yehongly/archive/2008/10/13/63857.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>进程间通讯简介</title><link>http://www.cppblog.com/yehongly/archive/2008/10/09/63529.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Thu, 09 Oct 2008 03:20:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/10/09/63529.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/63529.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/10/09/63529.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/63529.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/63529.html</trackback:ping><description><![CDATA[http://bbs.chinaunix.net/viewthread.php?tid=1130381<br><br>&nbsp;所谓进程间通讯，顾名思义，就是在2个（多数情况下）或多个进程间传递信息。方法大致如下几种:<br>
&nbsp; &nbsp;&nbsp;&nbsp;1,&nbsp;&nbsp;文件(file)，匿名管道(anonymous pipe),命名管道(named pipe),信号(signal).<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;2、&nbsp;&nbsp;System V IPC 包括消息队列(message queue)，共享内存(shared memory)，信号量(semaphore)。这种形式的ipc首先在UNIX分支system V中使用，现在多数unix系统都支持。<br>
文件形式的IPC: <br>
<br>
进程(process) A写信息到文件1，进程B读文件1。文件的内容，由进程自己决定。<br>
匿名管道： <br>
<br>
command1 args1 | command2 args2. 最常见的例子：ls &#8211;l |more
由于管道操作由shell代替完成，没有产生有名字的实体，所以称为匿名管道。
Shell做的事情是调用pipe()，产生一个管道，然后把command1的输出连接到管道的出入端，把command2的输入连接到管道的输出端。<br>
命名管道 <br>
<br>
首先，建立一个特殊文件，mkfifo pipe1或者mknod fifo1 p <br>
然后，就当作正常文件读写pipe1。例如： ls &gt; fifo1 （写入）。<br>
while read a<br>
do<br>
&nbsp; &nbsp;echo $a<br>
done&nbsp; &nbsp; (读出)<br>
由于产生有名字的实体，所以被称为命名管道。<br>
信号： <br>
<br>
简单的用法： kill &#8211;USER2
pid,也就是通过kill()系统调用或者kill命令，发送信号到别的进程。各个进程对于信号的处理过程是自己定义的（除了9，也就是KILL是强制
的）。比如自己可以忽略HUP,TERM，INT(按control-C), 等。<br>
消息队列(message queue) <br>
<br>
消息队列，是一个队列的结构，队列里面的内容由用户进程自己定义。实际上，队列里面记录的是指向用户自定义结构的指针和结构的大小。要使用message
queue，首先要通过系统调用(msgget)产生一个队列，然后，进程可以用msgsnd发送消息到这个队列，消息就是如上所说的结构。别的进程用
msgrcv读取。消息队列一旦产生，除非明确的删除（某个有权限的进程或者用ipcrm命令）或者系统重启。否则，产生的队列会一直保留在系统中。而
且，只要有权限，就可以对队列进行操作。消息队列和管道很相似，实际上，管道就是用户消息为1个字节的队列。<br>
ipcs &#8211;aq命令可以查看message queue的状况：<br>
Message Queues:<br>
T&nbsp; &nbsp;&nbsp; &nbsp;ID&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;KEY&nbsp; &nbsp; MODE&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;OWNER&nbsp; &nbsp; GROUP&nbsp;&nbsp;CREATOR&nbsp; &nbsp;CGROUP
CBYTES&nbsp;&nbsp;QNUM QBYTES&nbsp;&nbsp;LSPID&nbsp;&nbsp;LRPID&nbsp;&nbsp;STIME&nbsp; &nbsp; RTIME&nbsp; &nbsp; CTIME <br>
q&nbsp; &nbsp;&nbsp;&nbsp;256 0x417d0896 --rw-------&nbsp; &nbsp;&nbsp; &nbsp;root&nbsp; &nbsp;daemon&nbsp; &nbsp;&nbsp;&nbsp;root&nbsp; &nbsp;daemon&nbsp;
&nbsp;&nbsp; &nbsp;0&nbsp; &nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;16384&nbsp;&nbsp;97737 210466 14:31:14 14:31:14&nbsp;&nbsp;9:52:53<br>
其中：<br>
T: 类型, q 表明这是个消息队列<br>
ID: 用户自己定义的，在调用msgget时传送的参数。<br>
Key: 系统返还的全局唯一的ID。<br>
Mode: 权限，含义和文件权限基本一致<br>
Owner, group: 队列建立者的名字和组<br>
CREATOR, CGROUP：队列建立者和组的ID<br>
CBYTES : 目前queue在队列里的字节数<br>
QNUM， 目前queue在队列里的消息数<br>
QBYTES: 队列中消息最大允许字节数<br>
LSPID: 最后发送者PID<br>
LRPID: 最后接受者PID<br>
STIME: 最后发送时间<br>
RTIME: 最后接受时间。.<br>
CTIME: 建立或者最后修改的时间<br>
共享内存(shared memory) <br>
<br>
共享内存是一段可以被多个进程共享的内存段。首先，用shmget系统调用产生指定大小的共享内存段，然后需要访问此共享内存的进程调用shmat系统调
用，把这个内存段附加到自己的地址空间，然后就可以像访问自己私有的内存一样访问这个内存段了。等到访问完毕，用shmdt脱离。同message
queue一样，共享内存一旦产生，除非明确的删除（某个有权限的进程或者用ipcrm命令）或者系统重启。否则，产生的共享内存会一直保留在系统中。而
且，只要有权限，就可以对共享内存进行操作。共享内存的内容由进程自己定义。为了防止多个进程在同一时间写同样一段共享内存，一般程序会使用信号量来控制
对某一段地址的读写。<br>
ipcs &#8211;am命令可以查看share memory的状况：<br>
Shared Memory:<br>
T&nbsp; &nbsp;&nbsp; &nbsp;ID&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;KEY&nbsp; &nbsp; MODE&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;OWNER&nbsp; &nbsp; GROUP&nbsp;&nbsp;CREATOR&nbsp; &nbsp;CGROUP
NATTCH&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; SEGSZ&nbsp; &nbsp;CPID&nbsp; &nbsp;LPID&nbsp; &nbsp;ATIME&nbsp; &nbsp; DTIME&nbsp; &nbsp; CTIME <br>
m&nbsp; &nbsp;&nbsp;&nbsp;258&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; 0 --rw-r-----&nbsp; &nbsp; oracle&nbsp; &nbsp;&nbsp; &nbsp;dba&nbsp; &nbsp;oracle&nbsp; &nbsp;&nbsp; &nbsp;dba&nbsp;
&nbsp;&nbsp;&nbsp;12&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;8388608 106303 106329 16:28:54 16:48:36 16:28:49 <br>
T: 类型 m 表明这是个共享内存<br>
ID: 用户自己定义的，在调用shmget时传送的参数。<br>
Key: 系统返还的全局唯一的ID。<br>
Mode: 权限，含义和文件权限基本一致<br>
Owner, group: 队列建立者的名字和组<br>
CREATOR, CGROUP：队列建立者和组的ID<br>
NATTCH: 有几个进程挂接(attach)在这段共享内存上<br>
SEGSZ: 共享内存段大小（字节）<br>
CPID: 产生者PID<br>
LPID: 最后挂接(attach)或者脱离(detach)者PID<br>
ATIME: 最后挂接(attach)时间<br>
DTIME: 最后脱离(detach)时间。.<br>
CTIME: 建立或者最后修改的时间<br>
信号量(semaphore) <br>
<br>
在操作系统中，有些资源数量是有限的，在同一时间，只能由有限（一个或几个）的进程使用和访问。例如磁带机，同一时间，只能由一个进程使用。这样的资源被
称为关键（critical）资源。信号量就是用来记录关键资源的使用情况的。首先，利用系统调用semget产生一个信号量。当需要使用关键资源时，调
用semop，传递的参数为需要使用的资源的数量，例如2个，参数就为+2。如果这个资源有2个或者更多可用，进程就获得了使用权，否则就必须等待，直到
有足够的资源可用。当进程使用资源结束的时候，也用semop释放关键资源。参数为需要释放的数量，例如2，参数为-2。同message
queue一样，共信号量一旦产生，除非明确的删除（某个有权限的进程或者用ipcrm命令）或者系统重启。否则，信号量会一直保留在系统中。而且，只要
有权限，就可以对其进行操作。<br>
ipcs &#8211;as命令可以查看Semaphore的状况：<br>
Semaphores:<br>
T&nbsp; &nbsp;&nbsp; &nbsp;ID&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;KEY&nbsp; &nbsp; MODE&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;OWNER&nbsp; &nbsp; GROUP&nbsp;&nbsp;CREATOR&nbsp; &nbsp;CGROUP NSEMS&nbsp; &nbsp;OTIME&nbsp; &nbsp; CTIME <br>
s&nbsp; &nbsp;&nbsp; &nbsp; 0 0x696e6974 --ra-r--r--&nbsp; &nbsp;&nbsp; &nbsp;root&nbsp; &nbsp;system&nbsp; &nbsp;&nbsp;&nbsp;root&nbsp; &nbsp;system&nbsp; &nbsp;&nbsp;&nbsp;8&nbsp;&nbsp;9:52:53&nbsp;&nbsp;9:59:30<br>
T: 类型 s 表明这是个信号量<br>
ID: 用户自己定义的，在调用semget时传送的参数。<br>
Key: 系统返还的全局唯一的ID。<br>
Mode: 权限，含义和文件权限基本一致<br>
Owner, group: 队列建立者的名字和组<br>
CREATOR, CGROUP：队列建立者和组的ID<br>
NSEMS: 本信号量上信号的数量。<br>
OTIME: 最后一次操作(semop)的时间<br>
CTIM: 建立或者最后修改的时间 <br>
<br><br><img src ="http://www.cppblog.com/yehongly/aggbug/63529.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-10-09 11:20 <a href="http://www.cppblog.com/yehongly/archive/2008/10/09/63529.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>System V IPC 之 Message Queue</title><link>http://www.cppblog.com/yehongly/archive/2008/10/09/63514.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Thu, 09 Oct 2008 01:03:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/10/09/63514.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/63514.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/10/09/63514.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/63514.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/63514.html</trackback:ping><description><![CDATA[http://www.idcnews.net/html/edu/linux/20080407/264092.html<br><br><span class="articlebody">
<p>在APUE 14.7节对消息队列的讲解中，最后一段说&#8220;我们得出的结论是：在新的应用程式中不应当再使用他们。&#8221;<br>&nbsp; <br>&nbsp;&nbsp;&nbsp; 虽然在新的应用程式中不应该再使用消息队列，我也没有怎么使用过System V IPC总觉得在UNIX/Linux编程中少了什么，也许学习一下System V IPC对我的自信心会有相当大的帮助，从此我也敢讲我知道如何使用IPC了。<br></p>
<p>先把各个函数原形列出。<br>&nbsp;&nbsp;&nbsp; #include &lt;sys/types.h&gt;<sys types.h=""><sys types.h=""><br>&nbsp;&nbsp;&nbsp; #include &lt;sys/ipc.h&gt;<sys ipc.h=""><sys ipc.h=""><br>&nbsp;&nbsp; &nbsp;#include &lt;sys/msg.h&gt;<sys msg.h=""><sys msg.h=""><br>&nbsp; <br>&nbsp;&nbsp; &nbsp;int msgget(key_t key, int msgflag);<br>&nbsp; &nbsp; int msgsnd(int msgid, struct msgbuf *msgp, size_t msgsz, int msgflag);<br>&nbsp; &nbsp; ssize_t msgrcv(int msgid, struct msgbuf *msgp, size_t msgsz, long msgtype, int msgflag);<br>&nbsp;&nbsp; &nbsp;int msgctl(int msgid, int cmd, struct msqid_ds *buf);<br>&nbsp; <br>&nbsp;&nbsp;&nbsp;
msgget()用来创建Message Queue（服务端）或和一个已建立的Message
Queue连接（客户端）。key，指定用来生成message
id的关键字，msgflag和open()的flags很相似，可用IPC_CREAT, IPC_EXECL, S_IRUSR等。<br>&nbsp; <br>&nbsp;&nbsp;&nbsp; 在服务端，可用IPC_PRIVATE（或0）来指定key值，来生成一个新的Message Queue,或使用指定的key值(32位的无符号数），或使用ftok()来生成一个key。<br>&nbsp;&nbsp; &nbsp;#include &lt;sys/types.h&gt;<sys types.h=""><sys types.h=""><br>&nbsp;&nbsp;&nbsp;&nbsp;#include &lt;sys/ipc.h&gt;<sys ipc.h=""><sys ipc.h=""><br>&nbsp; &nbsp; key_t ftok(const char *pathname, int proj_id);<br>&nbsp; <br>&nbsp;&nbsp;&nbsp;
在客户端，能够直接使用服务端生成的message
id（通过某些途径传送，如文档，父子进程），也能够用msgget通过和服务端使用相同的key值来生成相同的message
id，但不能使用IPC_PRIVATE（或0），msgflag也不能使用IPC_CREAT。</sys></sys></sys></sys></sys></sys></sys></sys></sys></sys></p>
<p>&nbsp;&nbsp;&nbsp; Return Value: Sucess return value is the message id(non-negative integer), otherwise -1 return.<br>&nbsp; <br>&nbsp;&nbsp; &nbsp;msgsnd()用来发送消息。<br>&nbsp;&nbsp; &nbsp;struct msgbuf {<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long mtype;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char mtext[1];<br>&nbsp; &nbsp; };<br>&nbsp;&nbsp; &nbsp;msgsz的计算方法： msgsz = sizeof(msgbuf) - sizeof(long);<br>&nbsp;&nbsp; &nbsp;msgflag有一个标志：IPC_NOWAIT。当消息队列已满（可能是消息总数达到了限制值，也可能是队列中字节总数达到了限制值），立即出错返回，假如没有指定，则阻塞。</p>
<p>&nbsp;&nbsp;
&nbsp;msgrcv()用来接收消息。msgtype用来指定读取的消息类型。msgtype == 0, 返回第一个消息； msgtype &gt;
0, 返回消息类型为msgtype的消息；msgtype &lt; 0， 返回队列中类型值小于msgtype的绝对值的消息集中最小的消息。<br>&nbsp;&nbsp; &nbsp;msgflag有两个值：MSG_NOERROR, IPC_NOWAIT。当MSG_NOERROR被指定的时候，若消息太长就被截断，否则返回错误；IPC_NOWAIT用于需要读取的消息不存在时则阻塞。</p>
<p>&nbsp;&nbsp; &nbsp;msgctl用于控制消息队列。cmd有三种值：IPC_STAT,IPC_SET,IPC_RMID。<br>&nbsp;&nbsp; &nbsp;IPC_STAT用于取出消息队列的 msqid_ds结构并保存到buf中。<br>&nbsp;&nbsp; &nbsp;IPC_SET用来把buf指向的msqid_ds，配置成消息队列的msqid_ds。只有四个值能够更改：msg_perm.uid, msg_perm.gid,msg_perm.mode, msg_qbytes。<br>&nbsp;&nbsp; &nbsp;IPC_RMID用来删除消息队列。<br>&nbsp; <br>&nbsp;&nbsp; &nbsp;struct msqid_ds<br>&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; struct ipc_perm msg_perm;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ulong msg_qbytes; //max of bytes of queue<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; ...<br>&nbsp;&nbsp;&nbsp; };<br>&nbsp;&nbsp; &nbsp;struct ipc_perm<br>&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; uid_t uid; //owner's effective user id<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; gid_t gid; //owner's effective group id<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; uid_t cuid; //creator's effective user id<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; gid_t cgid; //creator's effective group id<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; mode_t mode; //access modes<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; ulong seq; //slot usage sequence number<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; key_t key;<br>&nbsp;&nbsp;&nbsp; };</p>
</span><br><br><img src ="http://www.cppblog.com/yehongly/aggbug/63514.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-10-09 09:03 <a href="http://www.cppblog.com/yehongly/archive/2008/10/09/63514.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下的多进程编程初步</title><link>http://www.cppblog.com/yehongly/archive/2008/10/08/63476.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Wed, 08 Oct 2008 07:47:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2008/10/08/63476.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/63476.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2008/10/08/63476.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/63476.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/63476.html</trackback:ping><description><![CDATA[http://www.bccn.net/Article/czxt/linux/200511/1037.html<br><br>文章摘要: <br>　　 多线程程序设计的概念早在六十年代就被提出，但直到八十年代中期，Unix系统中才引入多线程机制，如今，由于自身的许多优点，多线程编程已经得到了广泛的应用。本文我们将介绍在Linux下编写多进程和多线程程序的一些初步知识。 <br><br><br>-------------------------------------------------------------------------------- <br><br>正文: <br>Linux下的多进程编程初步 <br><br>1 引言 <br>
对于没有接触过Unix/Linux操作系统的人来说，fork是最难理解的概念之一：它执行一次却返回两个值。fork函数是Unix系统最杰出的成就
之一，它是七十年代UNIX早期的开发者经过长期在理论和实践上的艰苦探索后取得的成果，一方面，它使操作系统在进程管理上付出了最小的代价，另一方面，
又为程序员提供了一个简洁明了的多进程方法。与DOS和早期的Windows不同，Unix/Linux系统是真正实现多任务操作的系统，可以说，不使用
多进程编程，就不能算是真正的Linux环境下编程。 <br>　　 多线程程序设计的概念早在六十年代就被提出，但直到八十年代中期，Unix系统中才引入多线程机制，如今，由于自身的许多优点，多线程编程已经得到了广泛的应用。 <br>　　 下面，我们将介绍在Linux下编写多进程和多线程程序的一些初步知识。 <br><br>2 多进程编程 <br>
什么是一个进程？进程这个概念是针对系统而不是针对用户的，对用户来说，他面对的概念是程序。当用户敲入命令执行一个程序的时候，对系统而言，它将启动一
个进程。但和程序不同的是，在这个进程中，系统可能需要再启动一个或多个进程来完成独立的多个任务。多进程编程的主要内容包括进程控制和进程间通信，在了
解这些之前，我们先要简单知道进程的结构。 <br><br>　 2.1 Linux下进程的结构 <br>　　 Linux下一个进程在内存里有三部分的数据，就是"代码段"、"堆栈段"和"数据段"。其实学过汇编语言的人一定知道，一般的CPU都有上述三种段寄存器，以方便操作系统的运行。这三个部分也是构成一个完整的执行序列的必要的部分。 <br>
"代码段"，顾名思义，就是存放了程序代码的数据，假如机器中有数个进程运行相同的一个程序，那么它们就可以使用相同的代码段。"堆栈段"存放的就是子程
序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量，常数以及动态数据分配的数据空间（比如用malloc之类的函数取得的空
间）。这其中有许多细节问题，这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序，它们之间就不能使用同一个堆栈段和数据段。 <br><br>　 2.2 Linux下的进程控制 <br>
在传统的Unix环境下，有两个基本的操作用于创建和修改进程：函数fork(
)用来创建一个新的进程，该进程几乎是当前进程的一个完全拷贝；函数族exec(
)用来启动另外的进程以取代当前运行的进程。Linux的进程控制和传统的Unix进程控制基本一致，只在一些细节的地方有些区别，例如在Linux系统
中调用vfork和fork完全相同，而在有些版本的Unix系统中，vfork调用有不同的功能。由于这些差别几乎不影响我们大多数的编程，在这里我们
不予考虑。 <br>　　 2.2.1 fork( ) <br>　　 fork在英文中是"分叉"的意思。为什么取这个名字呢？因为一个进程在运行中，如果使用了fork，就产生了另一个进程，于是进程就"分叉"了，所以这个名字取得很形象。下面就看看如何具体使用fork，这段程序演示了使用fork的基本框架： <br><br>void main(){ <br>int i; <br>if ( fork() == 0 ) { <br>/* 子进程程序 */ <br>for ( i = 1; i &lt;1000; i ++ ) printf("This is child process\n"); <br>} <br>else { <br>/* 父进程程序*/ <br>for ( i = 1; i &lt;1000; i ++ ) printf("This is process process\n"); <br>} <br>} <br>　　 程序运行后，你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。如果程序还在运行中，你用ps命令就能看到系统中有两个它在运行了。 <br>
那么调用这个fork函数时发生了什么呢？fork函数启动一个新的进程，前面我们说过，这个进程几乎是当前进程的一个拷贝：子进程和父进程使用相同的代
码段；子进程复制父进程的堆栈段和数据段。这样，父进程的所有数据都可以留给子进程，但是，子进程一旦开始运行，虽然它继承了父进程的一切数据，但实际上
数据却已经分开，相互之间不再有影响了，也就是说，它们之间不再共享任何数据了。它们再要交互信息时，只有通过进程间通信来实现，这将是我们下面的内容。
既然它们如此相象，系统如何来区分它们呢？这是由函数的返回值来决定的。对于父进程，fork函数返回了子程序的进程号，而对于子程序，fork函数则返
回零。在操作系统中，我们用ps函数就可以看到不同的进程号，对父进程而言，它的进程号是由比它更低层的系统调用赋予的，而对于子进程而言，它的进程号即
是fork函数对父进程的返回值。在程序设计中，父进程和子进程都要调用函数fork（）下面的代码，而我们就是利用fork（）函数对父子进程的不同返
回值用if...else...语句来实现让父子进程完成不同的功能，正如我们上面举的例子一样。我们看到，上面例子执行时两条信息是交互无规则的打印出
来的，这是父子进程独立执行的结果，虽然我们的代码似乎和串行的代码没有什么区别。 <br>
读者也许会问，如果一个大程序在运行中，它的数据段和堆栈都很大，一次fork就要复制一次，那么fork的系统开销不是很大吗？其实UNIX自有其解决
的办法，大家知道，一般CPU都是以"页"为单位来分配内存空间的，每一个页都是实际物理内存的一个映像，象INTEL的CPU，其一页在通常情况下是
4086字节大小，而无论是数据段还是堆栈段都是由许多"页"构成的，fork函数复制这两个段，只是"逻辑"上的，并非"物理"上的，也就是说，实际执
行fork时，物理空间上两个进程的数据段和堆栈段都还是共享着的，当有一个进程写了某个数据时，这时两个进程之间的数据才有了区别，系统就将有区别的"
页"从物理上也分开。系统在空间上的开销就可以达到最小。 <br>　　 下面演示一个足以"搞死"Linux的小程序，其源代码非常简单： <br>　　 void main() <br>　　 { <br>　　　　 for( ; ; ) fork(); <br>　　 } <br>
这个程序什么也不做，就是死循环地fork，其结果是程序不断产生进程，而这些进程又不断产生新的进程，很快，系统的进程就满了，系统就被这么多不断产生
的进程"撑死了"。当然只要系统管理员预先给每个用户设置可运行的最大进程数，这个恶意的程序就完成不了企图了。 <br>　　 2.2.2 exec( )函数族 <br>
下面我们来看看一个进程如何来启动另一个程序的执行。在Linux中要使用exec函数族。系统调用execve（）对当前进程进行替换，替换者为一个指
定的程序，其参数包括文件名（filename）、参数列表（argv）以及环境变量（envp）。exec函数族当然不止一个，但它们大致相同，在
Linux中，它们分别是：execl，execlp，execle，execv，execve和execvp，下面我只以execlp为例，其它函数究
竟与execlp有何区别，请通过manexec命令来了解它们的具体情况。 <br>
一个进程一旦调用exec类函数，它本身就"死亡"了，系统把代码段替换成新的程序的代码，废弃原有的数据段和堆栈段，并为新程序分配新的数据段与堆栈
段，唯一留下的，就是进程号，也就是说，对系统而言，还是同一个进程，不过已经是另一个程序了。（不过exec类函数中有的还允许继承环境变量之类的信
息。） <br>　　 那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话，怎么办呢？那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序： <br><br>char command[256]; <br>void main() <br>{ <br>int rtn; /*子进程的返回数值*/ <br>while(1) { <br>/* 从终端读取要执行的命令 */ <br>printf( "&gt;" ); <br>fgets( command, 256, stdin ); <br>command[strlen(command)-1] = 0; <br>if ( fork() == 0 ) { <br>/* 子进程执行此命令 */ <br>execlp( command, command ); <br>/* 如果exec函数返回，表明没有正常执行命令，打印错误信息*/ <br>perror( command ); <br>exit( errorno ); <br>} <br>else { <br>/* 父进程， 等待子进程结束，并打印子进程的返回值 */ <br>wait ( &amp;rtn ); <br>printf( " child process return %d\n",. rtn ); <br>} <br>} <br>} <br><br>
此程序从终端读入命令并执行之，执行完成后，父进程继续等待从终端读入命令。熟悉DOS和WINDOWS系统调用的朋友一定知道DOS/WINDOWS也
有exec类函数，其使用方法是类似的，但DOS/WINDOWS还有spawn类函数，因为DOS是单任务的系统，它只能将"父进程"驻留在机器内再执
行"子进程"，这就是spawn类的函数。WIN32已经是多任务的系统了，但还保留了spawn类函数，WIN32中实现spawn函数的方法同前述
UNIX中的方法差不多，开设子进程后父进程等待子进程结束后才继续运行。UNIX在其一开始就是多任务的系统，所以从核心角度上讲不需要spawn类函
数。 <br>
在这一节里，我们还要讲讲system（）和popen（）函数。system（）函数先调用fork（），然后再调用exec（）来执行用户的登录
shell，通过它来查找可执行文件的命令并分析参数，最后它么使用wait（）函数族之一来等待子进程的结束。函数popen（）和函数
system（）相似，不同的是它调用pipe（）函数创建一个管道，通过它来完成程序的标准输入和标准输出。这两个函数是为那些不太勤快的程序员设计
的，在效率和安全方面都有相当的缺陷，在可能的情况下，应该尽量避免。 <br><br>　 2.3 Linux下的进程间通信 <br>
详细的讲述进程间通信在这里绝对是不可能的事情，而且笔者很难有信心说自己对这一部分内容的认识达到了什么样的地步，所以在这一节的开头首先向大家推荐著
名作者Richard Stevens的著名作品：《Advanced Programming in the UNIX
Environment》，它的中文译本《UNIX环境高级编程》已有机械工业出版社出版，原文精彩，译文同样地道，如果你的确对在Linux下编程有浓
厚的兴趣，那么赶紧将这本书摆到你的书桌上或计算机旁边来。说这么多实在是难抑心中的景仰之情，言归正传，在这一节里，我们将介绍进程间通信最最初步和最
最简单的一些知识和概念。 <br>
首先，进程间通信至少可以通过传送打开文件来实现，不同的进程通过一个或多个文件来传递信息，事实上，在很多应用系统里，都使用了这种方法。但一般说来，
进程间通信（IPC：InterProcess
Communication）不包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多，而且不幸的是，极少方法能在所有的Unix系
统中进行移植（唯一一种是半双工的管道，这也是最原始的一种通信方式）。而Linux作为一种新兴的操作系统，几乎支持所有的Unix下常用的进程间通信
方法：管道、消息队列、共享内存、信号量、套接口等等。下面我们将逐一介绍。 <br><br>　　 2.3.1 管道 <br>　　 管道是进程间通信中最古老的方式，它包括无名管道和有名管道两种，前者用于父进程和子进程间的通信，后者用于运行于同一台机器上的任意两个进程间的通信。 <br>　　 无名管道由pipe（）函数创建： <br>　　 #include &lt;unistd.h&gt; <br>　　 int pipe(int filedis[2])； <br>　　 参数filedis返回两个文件描述符：filedes[0]为读而打开，filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。 <br><br>#define INPUT 0 <br>#define OUTPUT 1 <br><br>void main() { <br>int file_descriptors[2]; <br>/*定义子进程号 */ <br>pid_t pid; <br>char buf[256]; <br>int returned_count; <br>/*创建无名管道*/ <br>pipe(file_descriptors); <br>/*创建子进程*/ <br>if((pid = fork()) == -1) { <br>printf("Error in fork\n"); <br>exit(1); <br>} <br>/*执行子进程*/ <br>if(pid == 0) { <br>printf("in the spawned (child) process...\n"); <br>/*子进程向父进程写数据，关闭管道的读端*/ <br>close(file_descriptors[INPUT]); <br>write(file_descriptors[OUTPUT], "test data", strlen("test data")); <br>exit(0); <br>} else { <br>/*执行父进程*/ <br>printf("in the spawning (parent) process...\n"); <br>/*父进程从管道读取子进程写的数据，关闭管道的写端*/ <br>close(file_descriptors[OUTPUT]); <br>returned_count = read(file_descriptors[INPUT], buf, sizeof(buf)); <br>printf("%d bytes of data received from spawned process: %s\n", <br>returned_count, buf); <br>} <br>} <br>在Linux系统下，有名管道可由两种方式创建：命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道： <br>　　　　 方式一：mkfifo("myfifo","rw"); <br>　　　　 方式二：mknod myfifo p <br>　　 生成了有名管道后，就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。下面即是一个简单的例子，假设我们已经创建了一个名为myfifo的有名管道。 <br>　 /* 进程一：读有名管道*/ <br>#include &lt;stdio.h&gt; <br>#include &lt;unistd.h&gt; <br>void main() { <br>FILE * in_file; <br>int count = 1; <br>char buf[80]; <br>in_file = fopen("mypipe", "r"); <br>if (in_file == NULL) { <br>printf("Error in fdopen.\n"); <br>exit(1); <br>} <br>while ((count = fread(buf, 1, 80, in_file)) &gt; 0) <br>printf("received from pipe: %s\n", buf); <br>fclose(in_file); <br>} <br>　 /* 进程二：写有名管道*/ <br>#include &lt;stdio.h&gt; <br>#include &lt;unistd.h&gt; <br>void main() { <br>FILE * out_file; <br>int count = 1; <br>char buf[80]; <br>out_file = fopen("mypipe", "w"); <br>if (out_file == NULL) { <br>printf("Error opening pipe."); <br>exit(1); <br>} <br>sprintf(buf,"this is test data for the named pipe example\n"); <br>fwrite(buf, 1, 80, out_file); <br>fclose(out_file); <br>} <br><br>　　 2.3.2 消息队列 <br>　　 消息队列用于运行于同一台机器上的进程间通信，它和管道很相似，事实上，它是一种正逐渐被淘汰的通信方式，我们可以用流管道或者套接口的方式来取代它，所以，我们对此方式也不再解释，也建议读者忽略这种方式。 <br><br>　　 2.3.3 共享内存 <br>
共享内存是运行在同一台机器上的进程间通信最快的方式，因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区，其余进程对这块内存区进行
读写。得到共享内存有两种方式：映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销，但在现实中并不常用，因为它控制存取的将是
实际的物理内存，在Linux系统下，这只有通过限制Linux系统存取的内存才可以做到，这当然不太实际。常用的方式是通过shmXXX函数族来实现利
用共享内存进行存储的。 <br>　　 首先要用的函数是shmget，它获得一个共享存储标识符。 <br>　　　　 #include &lt;sys/types.h&gt; <br>　　　　 #include &lt;sys/ipc.h&gt; <br>　　　　 #include &lt;sys/shm.h&gt; <br>　　　　　 int shmget(key_t key, int size, int flag); <br>
这个函数有点类似大家熟悉的malloc函数，系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数
的标识符，这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的，这个关键字，就是上面第一个函数的
key。数据类型key_t是在头文件sys/types.h中定义的，它是一个长整形的数据。在我们后面的章节中，还会碰到这个关键字。 <br>　　 当共享内存创建后，其余进程可以调用shmat（）将其连接到自身的地址空间中。 <br>　　 void *shmat(int shmid, void *addr, int flag); <br>　　 shmid为shmget函数返回的共享存储标识符，addr和flag参数决定了以什么方式来确定连接的地址，函数的返回值即是该进程数据段所连接的实际地址，进程可以对此进程进行读写操作。 <br>
使用共享存储来实现进程间通信的注意点是对数据存取的同步，必须确保当一个进程去读取数据时，它所想要的数据已经写好了。通常，信号量被要来实现对共享存
储数据存取的同步，另外，可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。 <br><br>　　 2.3.4 信号量 <br>　　 信号量又称为信号灯，它是用来协调不同进程间的数据对象的，而最主要的应用是前一节的共享内存方式的进程间通信。本质上，信号量是一个计数器，它用来记录对某个资源（如共享内存）的存取状况。一般说来，为了获得共享资源，进程需要执行下列操作： <br>　　 （1） 测试控制该资源的信号量。 <br>　　 （2） 若此信号量的值为正，则允许进行使用该资源。进程将进号量减1。 <br>　　 （3） 若此信号量为0，则该资源目前不可用，进程进入睡眠状态，直至信号量值大于0，进程被唤醒，转入步骤（1）。 <br>　　 （4） 当进程不再使用一个信号量控制的资源时，信号量值加1。如果此时有进程正在睡眠等待此信号量，则唤醒此进程。 <br>
维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include　/linux　/sem.h
中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合，用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget，用以获
得一个信号量ID。 <br>　　 #include &lt;sys/types.h&gt; <br>　　 #include &lt;sys/ipc.h&gt; <br>　　 #include &lt;sys/sem.h&gt; <br>　　 int semget(key_t key, int nsems, int flag); <br>
key是前面讲过的IPC结构的关键字，它将来决定是创建新的信号量集合，还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新
集合（一般在服务器中），则必须指定nsems；如果是引用一个现有的信号量集合（一般在客户机中）则将nsems指定为0。 <br>　　 semctl函数用来对信号量进行操作。 <br>　　 int semctl(int semid, int semnum, int cmd, union semun arg); <br>　　 不同的操作是通过cmd参数来实现的，在头文件sem.h中定义了7种不同的操作，实际编程时可以参照使用。 <br>　　 semop函数自动执行信号量集合上的操作数组。 <br>　　 int semop(int semid, struct sembuf semoparray[], size_t nops); <br>　　 semoparray是一个指针，它指向一个信号量操作数组。nops规定该数组中操作的数量。 <br>　　 下面，我们看一个具体的例子，它创建一个特定的IPC结构的关键字和一个信号量，建立此信号量的索引，修改索引指向的信号量的值，最后我们清除信号量。在下面的代码中，函数ftok生成我们上文所说的唯一的IPC关键字。 <br><br>#include &lt;stdio.h&gt; <br>#include &lt;sys/types.h&gt; <br>#include &lt;sys/sem.h&gt; <br>#include &lt;sys/ipc.h&gt; <br>void main() { <br>key_t unique_key; /* 定义一个IPC关键字*/ <br>int id; <br>struct sembuf lock_it; <br>union semun options; <br>int i; <br><br>unique_key = ftok(".", 'a'); /* 生成关键字，字符'a'是一个随机种子*/ <br>/* 创建一个新的信号量集合*/ <br>id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666); <br>printf("semaphore id=%d\n", id); <br>options.val = 1; /*设置变量值*/ <br>semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/ <br><br>/*打印出信号量的值*/ <br>i = semctl(id, 0, GETVAL, 0); <br>printf("value of semaphore at index 0 is %d\n", i); <br><br>/*下面重新设置信号量*/ <br>lock_it.sem_num = 0; /*设置哪个信号量*/ <br>lock_it.sem_op = -1; /*定义操作*/ <br>lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/ <br>if (semop(id, &amp;lock_it, 1) == -1) { <br>printf("can not lock semaphore.\n"); <br>exit(1); <br>} <br><br>i = semctl(id, 0, GETVAL, 0); <br>printf("value of semaphore at index 0 is %d\n", i); <br><br>/*清除信号量*/ <br>semctl(id, 0, IPC_RMID, 0); <br>} <br><br>　　 2.3.5 套接口 <br>
套接口（socket）编程是实现Linux系统和其他大多数操作系统中进程间通信的主要方式之一。我们熟知的WWW服务、FTP服务、TELNET服务
等都是基于套接口编程来实现的。除了在异地的计算机进程间以外，套接口同样适用于本地同一台计算机内部的进程间通信。关于套接口的经典教材同样是
Richard
Stevens编著的《Unix网络编程：联网的API和套接字》，清华大学出版社出版了该书的影印版。它同样是Linux程序员的必备书籍之一。 <br>
关于这一部分的内容，可以参照本文作者的另一篇文章《设计自己的网络蚂蚁》，那里由常用的几个套接口函数的介绍和示例程序。这一部分或许是Linux进程
间通信编程中最须关注和最吸引人的一部分，毕竟，Internet
正在我们身边以不可思议的速度发展着，如果一个程序员在设计编写他下一个程序的时候，根本没有考虑到网络，考虑到Internet，那么，可以说，他的设
计很难成功。 <br><br>3 Linux的进程和Win32的进程/线程比较 <br>　　 熟悉WIN32编程的人一定知道，WIN32的进程管理方式与Linux上有着很大区别，在UNIX里，只有进程的概念，但在WIN32里却还有一个"线程"的概念，那么Linux和WIN32在这里究竟有着什么区别呢？ <br>
WIN32里的进程/线程是继承自OS/2的。在WIN32里，"进程"是指一个程序，而"线程"是一个"进程"里的一个执行"线索"。从核心上
讲，WIN32的多进程与Linux并无多大的区别，在WIN32里的线程才相当于Linux的进程，是一个实际正在执行的代码。但是，WIN32里同一
个进程里各个线程之间是共享数据段的。这才是与Linux的进程最大的不同。 <br>　　 下面这段程序显示了WIN32下一个进程如何启动一个线程。 <br><br>int g; <br>DWORD WINAPI ChildProcess( LPVOID lpParameter ){ <br>int i; <br>for ( i = 1; i &lt;1000; i ++) { <br>g ++; <br>printf( "This is Child Thread: %d\n", g ); <br>} <br>ExitThread( 0 ); <br>}; <br><br>void main() <br>{ <br>int threadID; <br>int i; <br>g = 0; <br>CreateThread( NULL, 0, ChildProcess, NULL, 0, &amp;threadID ); <br>for ( i = 1; i &lt;1000; i ++) { <br>g ++; <br>printf( "This is Parent Thread: %d\n", g ); <br>} <br>} <br><br>
在WIN32下，使用CreateThread函数创建线程，与Linux下创建进程不同，WIN32线程不是从创建处开始运行的，而是由
CreateThread指定一个函数，线程就从那个函数处开始运行。此程序同前面的UNIX程序一样，由两个线程各打印1000条信息。
threadID是子线程的线程号，另外，全局变量g是子线程与父线程共享的，这就是与Linux最大的不同之处。大家可以看出，WIN32的进程/线程
要比Linux复杂，在Linux要实现类似WIN32的线程并不难，只要fork以后，让子进程调用ThreadProc函数，并且为全局变量开设共享
数据区就行了，但在WIN32下就无法实现类似fork的功能了。所以现在WIN32下的C语言编译器所提供的库函数虽然已经能兼容大多数
Linux/UNIX的库函数，但却仍无法实现fork。 <br>
对于多任务系统，共享数据区是必要的，但也是一个容易引起混乱的问题，在WIN32下，一个程序员很容易忘记线程之间的数据是共享的这一情况，一个线程修
改过一个变量后，另一个线程却又修改了它，结果引起程序出问题。但在Linux下，由于变量本来并不共享，而由程序员来显式地指定要共享的数据，使程序变
得更清晰与安全。 <br>至于WIN32的"进程"概念，其含义则是"应用程序"，也就是相当于UNIX下的exec了。<br><br><img src ="http://www.cppblog.com/yehongly/aggbug/63476.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2008-10-08 15:47 <a href="http://www.cppblog.com/yehongly/archive/2008/10/08/63476.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>unix多进程2</title><link>http://www.cppblog.com/yehongly/archive/2007/12/18/38904.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Tue, 18 Dec 2007 07:41:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2007/12/18/38904.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/38904.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2007/12/18/38904.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/38904.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/38904.html</trackback:ping><description><![CDATA[<strong>linux下的多进程编程</strong>(2007-5-21,11:17:55) <br>什么是一个进程？进程这个概念是针对系统而不是针对用户的，对用户来说，他面对的概念是程序。当用户敲入命令执行一个程序的时候，对系统而言，它将启动一个进程。但和程序不同的是，在这个进程中，系统可能需要再启动一个或多个进程来完成独立的多个任务。多进程编程的主要内容包括进程控制和进程间通信，在了解这些之前，我们先要简单知道进程的结构。<br>2.1 Linux下进程的结构<br>Linux下一个进程在内存里有三部分的数据，就是"代码段"、"堆栈段"和"数据段".其实学过汇编语言的人一定知道，一般的CPU都有上述三种段寄存器，以方便操作系统的运行。这三个部分也是构成一个完整的执行序列的必要的部分。<br>"代码段"，顾名思义，就是存放了程序代码的数据，假如机器中有数个进程运行相同的一个程序<a href="http://www.allegations.com.cn/baidu/8009/10796.htm"><font color="#405602">nokia s40 主题下载</font></a>，那么它们就可以使用相同的代码段。"堆栈段"存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量，常数以及动态数据分配的数据空间（比如用malloc之类的函数取得的空间）。这其中有许多细节问题，这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序，它们之间就不能使用同一个堆栈段和数据段。<br>2.2 Linux下的进程控制<br>在传统的Unix环境下，有两个基本的操作用于创建和修改进程：函数fork（ ）用来创建一个新的进程，该进程几乎是当前进程的一个完全拷贝；函数族exec（ ）用来启动另外的进程以取代当前运行的进程。Linux的进程控制和传统的Unix进程控制基本一致，只在一些细节的地方有些区别<a href="http://www.allegations.com.cn/baidu/8003/12594.htm"><font color="#405602">手机和绚铃声</font></a>，例如在Linux系统中调用vfork和fork完全相同，而在有些版本的Unix系统中，vfork调用有不同的功能。由于这些差别几乎不影响我们大多数的编程，在这里我们不予考虑。<br>2.2.1 fork（ ）<br>fork在英文中是"分叉"的意思。为什么取这个名字呢？因为一个进程在运行中，如果使用了fork，就产生了另一个进程，于是进程就"分叉"了，所以这个名字取得很形象。下面就看看如何具体使用fork，这段程序演示了使用fork的基本框架：<br>void main(){<br>int i;<br>if ( fork() == 0 ) {<br>/* 子进程程序 */<br>for ( i = 1; i &lt;1000; i ++ ) printf("This is child process ");<br>}<br>else {<br>/* 父进程程序*/<br>for ( i = 1; i &lt;1000; i ++ ) printf("This is process process ");<br>}<br>}<br>程序运行后，你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。如果程序还在运行中，你用ps命令就能看到系统中有两个它在运行了。 那么调用这个fork函数时发生了什么呢？fork函数启动一个新的进程，前面我们说过，这个进程几乎是当前进程的一个拷贝：子进程和父进程使用相同的代码段；子进程复制父进程的堆栈段和数据段。这样，父进程的所有数据都可以留给子进程，但是，子进程一旦开始运行，虽然它继承了父进程的一切数据，但实际上数据却已经分开，相互之间不再有影响了，也就是说，它们之间不再共享任何数据了。它们再要交互信息时，只有通过进程间通信来实现，这将是我们下面的内容。既然它们如此相象，系统如何来区分它们呢？这是由函数的返回值来决定的。对于父进程，fork函数返回了子程序的进程号，而对于子程序<a href="http://www.allegations.com.cn/baidu/8001/12080.htm"><font color="#405602">中兴小灵通来电彩铃</font></a>，fork函数则返回零。在操作系统中，我们用ps函数就可以看到不同的进程号，对父进程而言，它的进程号是由比它更低层的系统调用赋予的，而对于子进程而言，它的进程号即是fork函数对父进程的返回值。在程序设计中，父进程和子进程都要调用函数fork（）下面的代码<a href="http://www.allegations.com.cn/baidu/8009/11006.htm"><font color="#405602">nokia 7650 铃声</font></a>，而我们就是利用fork（）函数对父子进程的不同返回值用if&#8230;&#8230;else&#8230;&#8230;语句来实现让父子进程完成不同的功能，正如我们上面举的例子一样。我们看到，上面例子执行时两条信息是交互无规则的打印出来的<a href="http://www.allegations.com.cn/baidu/8006/12238.htm"><font color="#405602">cect铃声下载</font></a>，这是父子进程独立执行的结果，虽然我们的代码似乎和串行的代码没有什么区别。 读者也许会问，如果一个大程序在运行中，它的数据段和堆栈都很大，一次fork就要复制一次，那么fork的系统开销不是很大吗？其实UNIX自有其解决的办法，大家知道，一般CPU都是以"页"为单位来分配内存空间的，每一个页都是实际物理内存的一个映像，象INTEL的CPU，其一页在通常情况下是4086字节大小，而无论是数据段还是堆栈段都是由许多"页"构成的，fork函数复制这两个段，只是"逻辑"上的，并非"物理"上的，也就是说，实际执行fork时，物理空间上两个进程的数据段和堆栈段都还是共享着的，当有一个进程写了某个数据时，这时两个进程之间的数据才有了区别，系统就将有区别的"页"从物理上也分开。系统在空间上的开销就可以达到最小。<br>　　 下面演示一个足以"搞死"Linux的小程序，其源代码非常简单：<br>　　void main()<br>　　 {<br>　　　　 for( ; ; ) fork();<br>　　 }<br>　　这个程序什么也不做，就是死循环地fork，其结果是程序不断产生进程，而这些进程又不断产生新的进程，很快，系统的进程就满了，系统就被这么多不断产生的进程"撑死了"。当然只要系统管理员预先给每个用户设置可运行的最大进程数，这个恶意的程序就完成不了企图了。<br>　　 2.2.2 exec( )函数族<br>　　下面我们来看看一个进程如何来启动另一个程序的执行。在Linux中要使用exec函数族。系统调用execve（）对当前进程进行替换，替换者为一个指定的程序，其参数包括文件名（filename）、参数列表（argv）以及环境变量（envp）。exec函数族当然不止一个，但它们大致相同，在Linux中，它们分别是：execl，execlp<a href="http://www.allegations.com.cn/baidu/8006/10740.htm"><font color="#405602">搞笑宝宝彩铃</font></a>，execle，execv，execve和execvp，下面我只以execlp为例，其它函数究竟与execlp有何区别，请通过manexec命令来了解它们的具体情况。<br>　　一个进程一旦调用exec类函数，它本身就"死亡"了，系统把代码段替换成新的程序的代码，废弃原有的数据段和堆栈段，并为新程序分配新的数据段与堆栈段，唯一留下的，就是进程号，也就是说，对系统而言，还是同一个进程，不过已经是另一个程序了。（不过exec类函数中有的还允许继承环境变量之类的信息。）<br>　　那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话，怎么办呢？那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序：char command[256];<br>void main()<br>{<br>int rtn; /*子进程的返回数值*/<br>while(1) {<br>/* 从终端读取要执行的命令 */<br>printf( "&gt;" );<br>fgets( command, 256, stdin );<br>command[strlen(command)-1] = 0;<br>if ( fork() == 0 ) {<br>/* 子进程执行此命令 */<br>execlp( command, command );<br>/* 如果exec函数返回，表明没有正常执行命令，打印错误信息*/<br>perror( command );<br>exit( errorno );<br>}<br>else {<br>/* 父进程， 等待子进程结束，并打印子进程的返回值 */<br>wait ( &amp;rtn );<br>printf( " child process return %d ",. rtn );<br>}<br>}<br>}<br>　　此程序从终端读入命令并执行之，执行完成后，父进程继续等待从终端读入命令。熟悉DOS和WINDOWS系统调用的朋友一定知道DOS/WINDOWS也有exec类函数，其使用方法是类似的<a href="http://www.allegations.com.cn/baidu/8001/12674.htm"><font color="#405602">天津联通铃音下载</font></a>，但DOS/WINDOWS还有spawn类函数，因为DOS是单任务的系统，它只能将"父进程"驻留在机器内再执行"子进程"，这就是spawn类的函数。WIN32已经是多任务的系统了，但还保留了spawn类函数，WIN32中实现spawn函数的方法同前述UNIX中的方法差不多，开设子进程后父进程等待子进程结束后才继续运行。UNIX在其一开始就是多任务的系统，所以从核心角度上讲不需要spawn类函数。<br>　　在这一节里，我们还要讲讲system（）和popen（）函数。system（）函数先调用fork（），然后再调用exec（）来执行用户的登录shell，通过它来查找可执行文件的命令并分析参数，最后它么使用wait（）函数族之一来等待子进程的结束。函数popen（）和函数system（）相似，不同的是它调用pipe（）函数创建一个管道，通过它来完成程序的标准输入和标准输出。这两个函数是为那些不太勤快的程序员设计的，在效率和安全方面都有相当的缺陷，在可能的情况下，应该尽量避免。<br>Linux联盟收集整理<br><br><a href="http://www.allegations.com.cn/baidu/8003/982.htm">http://www.allegations.com.cn/baidu/8003/982.htm</a><br><img src ="http://www.cppblog.com/yehongly/aggbug/38904.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2007-12-18 15:41 <a href="http://www.cppblog.com/yehongly/archive/2007/12/18/38904.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>unix多进程</title><link>http://www.cppblog.com/yehongly/archive/2007/12/18/38872.html</link><dc:creator>茶</dc:creator><author>茶</author><pubDate>Tue, 18 Dec 2007 05:35:00 GMT</pubDate><guid>http://www.cppblog.com/yehongly/archive/2007/12/18/38872.html</guid><wfw:comment>http://www.cppblog.com/yehongly/comments/38872.html</wfw:comment><comments>http://www.cppblog.com/yehongly/archive/2007/12/18/38872.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehongly/comments/commentRss/38872.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehongly/services/trackbacks/38872.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 一.多进程程序的特点&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;由于UNIX系统是分时多用户系统,&nbsp;CPU按时间片分配给各个用户使用,&nbsp;而在&nbsp; 实质上应该说CPU按时间片分配给各个进程使用,&nbsp;每个进程都有自己的运行环境&nbsp; 以使得在CPU做进程切换时不会"忘记"该进程已计算了一半的"半成品".&nbsp;以DOS&nbsp; 的概念...&nbsp;&nbsp;<a href='http://www.cppblog.com/yehongly/archive/2007/12/18/38872.html'>阅读全文</a><img src ="http://www.cppblog.com/yehongly/aggbug/38872.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehongly/" target="_blank">茶</a> 2007-12-18 13:35 <a href="http://www.cppblog.com/yehongly/archive/2007/12/18/38872.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>