⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 14.htm

📁 UNIX环境下C编程的详细详细介绍
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<p>如我们在14.6.1中所述,至少服务者应将分配给队列的标识符写到一个文件中以便 
</p>

<p>客户读取。 </p>

<p>这些作者列举的消息队列的其它优点是:(a)它们是可靠的(b)流是受到控制的 
</p>

<p>,(c)面向记录,(d)可以用非先进先出方式处理。正如我们在12.4节中所见, 
</p>

<p>流也具有所有这些优点,虽然在向一个流发送数据之前,需要一个open,在结束时 
</p>

<p>需要一个close。图14.15对这些不同形式的IPC的某些特征进行了比较。 
</p>

<p>图14.15 不同形式IPC之间特征的比较 </p>

<p>(我们将在第十五章对Unix流和数据报套接口进行简要说明。)表中的&quot;无连接&quot;, 
</p>

<p>我们指的是无需先调用某种形式的open,就能发送消息的能力。正如前述,因为需 
</p>

<p>要有某种技术以获得队列标识符,所以我们并不认为消息队列具有无连接特性。因 
</p>

<p>为所有这些形式的IPC都限制用在单主机上,所以它们都是可靠的。当消息通过网 
</p>

<p>络传送时,丢失消息的可能性就要加以考虑。流控制的意思是:如果系统资源短缺 
</p>

<p>(缓存)或者如果接收进程不能再接收更多消息,则发送进程就要睡眠。当流控制 
</p>

<p>条件消失时,发送进程应自动地被唤醒。 </p>

<p>在图14.15中我们没有表示的一个特征是:IPC设施能否自动地为每个客户自动地创 
</p>

<p>建一个到服务者的唯一连接。我们将在第十五章中说明,流以及Unix流套接口提供 
</p>

<p>这种能力。 </p>

<p>下面三节顺次对三种形式的系统V IPC进行详细说明。 </p>

<p>14.7消息队列(Message Queues) </p>

<p>消息队列是消息的链接表,存放在核内并由消息队列标识符标识。我们将称消 
</p>

<p>息队列为&quot;队列&quot;,其标识符为&quot;队列ID&quot;。msgget创建一个新队列或打开一个现存的 
</p>

<p>队列。msgsnd将新消息添加到队列尾端。每个消息包含有一个正长整型类型字段, 
</p>

<p>一个非负长度以及实际数据字节(对应于长度),所有这些都在将消息添加到队列 
</p>

<p>时,传送给msgsnd。用msgrcv从队列中取消息。我们并不一定要以先进先出次序取 
</p>

<p>消息。我们可以按消息的类型字段取消息。 </p>

<p>每个队列都有一个msqid_ds结构与其相关。此结构规定了队列的当前状态。 
</p>

<p>struct msqid_ds { </p>

<p>struct ipc_perm msg_perm ; 参见14.6.2 节 </p>

<p>struct msg *msg_first; 指向队列中第一条消息的指针 </p>

<p>struct msg *msg_last; 指向队列中最后一条消息的指针 </p>

<p>ulong msg_cbytest; 队列中的当前字节号 </p>

<p>ulong msg_qnum; 队列中的消息数 </p>

<p>ulong msg_qbytes; 队列中的最大字节数 </p>

<p>pid_t msg_lspid; 最后msgsnd()的pid </p>

<p>pid_t msg_lrpid; 最后msgrcv()的pid </p>

<p>time_t msg_stime 最后msgsnd()时间 </p>

<p>time_t msg_rtime 最后msgrcv()时间 </p>

<p>time_t msg_ctime; 最后更改时间 </p>

<p>}; </p>

<p>两个指针msgfirst和msglast分别指向相应消息在核内的存放位置,所以它们对用 
</p>

<p>户进程而言是无价值的。结构的其他成员是自定义的。 </p>

<p>图14.16列出了影响消息队列的系统限制(14。6。3节)。 </p>

<p>图14.6影响消息队列的系统限制 </p>

<p>调用的第一个函数通常是msgget,其功能是打开一个现存队列或创建一个新队列 
</p>

<p>。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/ipc.h&gt; </p>

<p>#include &lt;sys/msg.h&gt; </p>

<p>int msgget(key_t key, int flag); </p>

<p>返回:若成功为消息队列ID,出错为-1 </p>

<p>在14.6节中,我们说明了将key变换成一个标识符的规则,并且讨论是否创建一个 
</p>

<p>新队列或访一个现存队列。当创建一个新队列时,初始化msqid-ds结构的下列成员 
</p>

<p>: </p>

<p>&middot;ipc-perm结构按14.6.2节中所述进行初始化。该结构中mode按flag;中的相 
</p>

<p>应许可权位设置。这些许可权用图14.14中的常数指定 </p>

<p>&middot; msg_qnum,msg_lspid、msg_lrpid、msg_stime和msg_rtime都设置为0。 </p>

<p>&middot; msg_ctime设置为当前时间。 </p>

<p>&middot; msg_gbytes设置为系统限制值。 </p>

<p>若执行成功,则返回非负队列ID。此后,此值就可被用于其它三个消息队列函数。 
</p>

<p>msgctl函数对队列执行多种操作。它以及另外两个与信号量和共享存储有关的函数 
</p>

<p>(semctl和shmctl)是系统V IPC的类似于ioctl的函数(亦即垃圾桶函数)。 
</p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/ipc.h&gt; </p>

<p>#include &lt;sys/msg.h&gt; </p>

<p>int msgctl(int msqid, int cmd, struct msqid_ds *buf); </p>

<p>返回:若成功为0,出错为-1 </p>

<p>cmd参数指定对于由msqid规定的队列要执行的命令: </p>

<p>IPC_STAT 取此队列的msqid_ds结构,并将其存放在buf指向的结构中。 </p>

<p>IPC_SET 按由buf指向的结构中的值,设置与此队列相关的结构中的下列四 
</p>

<p>个字段msg_perm.uid、msg_perm.gid、msg_perm;mode和msg_qbytes。此命令只能 </p>

<p>由下列两种进程执行:一种进程是其有效用户ID等于msg_perm. cuid或msg_per 
</p>

<p>m.uid;另一种进程是具有超级用户特权的进程。只有超级用户才能增加msg_qbyte 
</p>

<p>s的值 </p>

<p>IPC-RMID 
从系统中删除该消息队列以及仍在该队列上的所有数据。这种删除 </p>

<p>是立即生效的。仍在使用这一消息队列的其它进程在它们下一次试图对此队列进行 
</p>

<p>操作时,将出错返回,错误号为EIDRM。此命令只能由下列两种进程执行:一种进 
</p>

<p>程是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种进程是具有超级用户 
</p>

<p>特权的进程。 </p>

<p>我们将会看到这三条命令(IPC_STAT、IPC_SET和IPC_RMID)也用于信号量和共享 
</p>

<p>存储。 </p>

<p>调用msgsnd将数据放到消息队列上。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/ipc.h&gt; </p>

<p>#include &lt;sys/msg.h&gt; </p>

<p>int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag); </p>

<p>返回:若成功为0,出错为-1 </p>

<p>正如我们在前面提及的,每个消息都由三部分组成,它们是:正长整型类型字段、 
</p>

<p>非负长度(nbytes)以及实际数据字节(对应于长度)。消息总是放在队列尾端。 
</p>

<p>Ptr指向一个长整型数,它包含了正整型消息类型,在其后立即跟随了消息数据。 
</p>

<p>(若nbytes是0,则无消息数据。)若我们发送的最长消息是512字节,则可定义下 
</p>

<p>列结构: </p>

<p>struct mymesg { </p>

<p>long mtypes; 正的消息类型 </p>

<p>char mtext[512]; 消息数据,长度为nbytes </p>

<p>}; </p>

<p>于是,ptr就是一个指向mymesg结构的指针。接收者可以使用消息类型以非先进先 
</p>

<p>出的次序取消息。 </p>

<p>flag的值可以指定为IPC_NOWAIT。这类似于文件I/O的非阻塞I/O标志(见12. 
</p>

<p>2)。若消息队列已满(或者是队列中的消息总数等于系统限制值,或队列中的字 
</p>

<p>节总数等于系统限制值),则指定IPC_NOWAIT </p>

<p>使得msgsnd立即出错返回,出错号是EAGAIN。如若没有指定IPC_NOWAIT,则进程阻 
</p>

<p>塞直到(a)有空间可以容纳要发送的消息,(b)从系统中删除了此队列,或(c 
</p>

<p>)捕捉到一个信号,并从信号处理程序返回。在第二种情况,返回出错号EIDRM( 
</p>

<p>&quot;标志符被删除&quot;),最后一种情况则返回出错号EINTR。 </p>

<p>注意,对消息队列删除的处理是不很得体的。因为对每个消息队列并没有设置一个 
</p>

<p>引用计数器(对打开文件则有这种计数器),所以删除一个队列使得仍在使用这一 
</p>

<p>队列的进程在下次对队列进行操作时产生出错返回。信号量机构也以同样方式处理 
</p>

<p>其删除。删除一个文件则要等到使用该文件的最后一个进程关闭了它,才删除文件 
</p>

<p>的内容。 </p>

<p>Msgrcv从队列中取用消息 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/ipc.h&gt; </p>

<p>#include &lt;sys/msg.h&gt; </p>

<p>int msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag); </p>

<p>返回:若成功为消息数据部分的长度,出错为-1 </p>

<p>如同msgsnd中一样,ptr参数指向一个长整型数(返回的消息类型存放在其中 
</p>

<p>),跟随其后的是存放实际消息数据的缓存。nbytes说明数据缓存的长度。若返回 
</p>

<p>的消息大于nbytes,而且在flag中设置MSG_ </p>

<p>NOERROR,则该消息就被截短。(在这种情况下,不通知我们消息截短了。)如果 
</p>

<p>没有设置这一标志,而消息又太长,则出错返回,出错号是EIBIG(消息仍留在队 
</p>

<p>列中)。 </p>

<p>参数type使我们可以指定想要哪一种消息: </p>

<p>type== 返回队列中的第一个消息。 </p>

<p>type&gt;0 返回队列中消息类型为type的第一个消息。 </p>

<p>type&lt;0 返回队列中消息类型值小于或等于type绝对值,而且在这种消息中,其 
</p>

<p>类型值又最小的消息。 </p>

<p>非0type用于以非先进先出次序读消息。例如,若应用程序对消息赋优先权,那么 
</p>

<p>type就可以是优先权值。如果一个消息队列由多个客户和一个服务者使用,那么t 
</p>

<p>ype字段可以用来包含客户进程ID。 </p>

<p>我们可以指定flag值为IPC_NOWAIT,使操作不阻塞。这造成如果没有所指定类 
</p>

<p>型的消息,则ms </p>

<p>-grcv出错返回,出错号为ENOMSG。如果没有指定IPC_NOWAIT,则进程阻塞直至( 
</p>

<p>a)有了指定类型的消息,(b)从系统中删除了此队列(返回出错号EIDRM),或 
</p>

<p>(c)捕捉到一个信号并从信号处理程序返回(返回出错号EINTR)。 
</p>

<p>实例一消息队列对流管道的时间比较 </p>

<p>如若需要在客户和服务者之间的双向数据流,我们可以使用消息队列或流管道 
</p>

<p>。(我们在15.2介绍流管道,它与管道类似,但是是全双工的。) </p>

<p>图14.17显示了在两个不同系统上这两种技术在时间方面的比较。测试程序先 
</p>

<p>创建IPC通道,调用fork,然后从父进程向子进程发送20mbytes数据。数据发送的 
</p>

<p>方式是:对于消息队列,调用10,000次msgsnd,每个消息长度为20,000bytes; 
</p>

<p>对于流管道,调用10,000次write,每次写20,000bytes。时间都以秒为单位。 
</p>

<p>图14.17 消息队列和流管道的时间比较 </p>

<p>在SPARC上,流管道是用Unix域套接口实现的。在SVR4之下,pipe函数提供流管道 
</p>

<p>(使用我们在12.4中所述的流机制)。 </p>

<p>从这些数字中可见,消息队列原来的实施目的是提供比一般IPC更高速度的进 
</p>

<p>程通信方法,但现在与其它形式的IPC相比,在速度方面已经没有什么差别了。( 
</p>

<p>在原来实施消息队列时,唯一的其它形式的IPC是半双工管道。)当我们考虑到使 
</p>

<p>用消息队列具有的问题时(14.6.4),我们得出的结论是,在新的应用程序中不应 
</p>

<p>当再使用它们。 </p>

<p>14.8 信号量(Semaphores) </p>

<p>信号量与我们已经介绍过的IPC机构(管道、FIFO以及消息列队)不同。它是 
</p>

<p>一个计数器,用于多进程对共享数据对象的存取。为了获得共享资源,进程需要执 
</p>

<p>行下列操作: </p>

<p>1. 测试控制该资源的信号量。 </p>

<p>2. 
若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使 
</p>

<p>用了一个资源单位。 </p>

<p>3. 若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。若进程被唤 
</p>

<p>醒后,它返回至步骤1。 </p>

<p>当进程不再使用由一个信息量控制的共享资源时,该信号量值增1。如果有进程正 
</p>

<p>在睡眠等待此信号量,则唤醒它们。 </p>

<p>  为了正确地实现信息量,信号量值的测试及减1操作应当是原子操作。为此, 
</p>

<p>信号量通常是在核内实现的。 </p>

<p>常用的信号量形式被称之为双态信号量。它控制单个资源,其初试值为1。但 
</p>

<p>是,一般而言,信号量的初值可以是任一正值,该值说明有多少个共享资源单位可 
</p>

<p>供共享应用。 </p>

<p>不幸的是,系统V的信号量与此相比要复杂得多。三种特性造成了这种并非必要的 
</p>

<p>复杂性。 </p>

<p>1. 
一个信号量并非是一个非负值。代之以我们必需将一个信号量定义为含有一个 
</p>

<p>或多个信号量值的集合。当创建一个信号量时,我们要指定该集合中的各个值 
</p>

<p>2. 创建信息量(semget)与对其赋初值(semctl)分开。这是一个致命的弱点, 
</p>

<p>因为我们不能原子地创建一个信号量集合,并且对该集合中的所有值赋初值。 
</p>

<p>3. 即使没有进程正在使用各种形式的系统V IPC它们仍然是存在的,所以不得不为 
</p>

<p>这种程序担心,它在终止时并没有释放已经分配给它的信号量。将在下面说明的&quot; 
</p>

<p>undo&quot;功能就是假定要处理这种情况的。 </p>

<p>核为每个信号量设置了一个semid_ds结构。 </p>

<p>struct semid_ds { </p>

<p>struct ipc_perm sem_perm; 见14.6.2节 </p>

<p>struct sem *sem_base; 指向集中的第一个信号量 </p>

<p>ushort sem_nsems; 集中的信号量数 </p>

<p>time_t sem_otime; 最后semop()时间 </p>

<p>time_t sem_ctime; 最后更改时间 </p>

<p>}; </p>

<p>  对用户而言,sem_base指针是没有价值的,它指向核内的sem结构数组,该数 
</p>

<p>组中包含了sem_nsems个元素每个元素,各对应于集合中的一

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -