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

📄 14.htm

📁 UNIX环境下C编程的详细详细介绍
💻 HTM
📖 第 1 页 / 共 5 页
字号:

<p>14.5 FIFO </p>

<p>FIFO有时被称为命名管道。管道只能由相关进程使用,它们共同的祖先进程创建了 
</p>

<p>管道。但是,通过FIFO,不相关的进程也能交换数据。 </p>

<p>在十四章中,已经提及FIFO是一种文件类型。Stat结构(4.2节)成员st_mode的编 
</p>

<p>码指明文件是否是FIFO类型。我们可以用S_ISFIFO宏对此进行测试。 </p>

<p>创建一个FIFO类似于创建一个文件。确实,一个FIFO的路径名存在于文件系统中。 
</p>

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

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

<p>int mkfifo(const char *pathname, mode_t mode); </p>

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

<p>mkfifo函数中mode参数的规格说明与open函数中的mode相同(见3.3节)。新FIFO 
</p>

<p>的用户和组的所有权与我们在4.6节所述的相同。 </p>

<p>一旦已经用mkfifo创建了一个FIFO,我们就可用open打开它。 
确实,一般的文件 </p>

<p>I/O函数(close,read,write,unlink等)都可用于FIFO。 </p>

<p>mkfifo是POSIX.1首先提出的。SVR3用mknod(2)系统调用创建FIFO。而在 </p>

<p>SVR4中,mkfifo调用mknod创建FIFO。 </p>

<p>POSIX.2已经建议了一个mkfifo(1)命令。SVR4和4.3+BSD现在支持此命令 </p>

<p>。于 </p>

<p>是,用一条shell命令就可以创建一个FIFO,然后用一般的shell I/O重新 
</p>

<p>定向对 </p>

<p>其进行存取。 </p>

<p>当打开一个FIFO时,非阻塞标志(O_NONBLOCK)产生下列影响: </p>

<p>1. 在一般情况中(没有说明O_NONBLOCK),只读打开要阻塞到某个其它进程为写 
</p>

<p>打开此FIFO。类似,为写而打开一个FIFO要阻塞到某个其它进程为读而打开它。 
</p>

<p>2. 如果指定了O_NONBLOCK,则只读打开立即返回。但是,如果没有进程已经为读 
</p>

<p>而打开一个FIFO,那么只写打开将出错返回,其errno是ENXIO。 </p>

<p>类似于管道,若写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。若某个 
</p>

<p>FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束标 
</p>

<p>志。 </p>

<p>一个给定的FIFO有多个写进程是常见的。这就意味这如果我们不希望多个进程所写 
</p>

<p>的数据互相穿插,则 
需考虑原子写操作。正如对于管道一样,常数PIPE_BUF说明 </p>

<p>了可被原子写到FIFO的最大数据量。 </p>

<p>FIFO有两种用途。 </p>

<p>1. FIFO由shell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中 
</p>

<p>间临时文件。 </p>

<p>2. FIFO用于客户-服务者应用程序中,以在客户和服务者之间传递数据。 
</p>

<p>我们各用一个例子来说明这两种用途。 </p>

<p>实例-用FIFO复制输出流 </p>

<p>FIFO可被用于复制串行管道命令之间的输出流,于是也就不需要写数据到中间磁盘 
</p>

<p>文件中(类似于使用管道以避免中间磁盘文件)。管道只能用于进程间的线性连接 
</p>

<p>,然而,因为FIFO具有名字,所以它可用于非线性连接。 </p>

<p>考虑这样一个操作过程,它需要处理一个经过过滤的输入流两次。图14.10表示了 
</p>

<p>这种安排。 </p>

<p>使用FIFO以及Unix程序tee(1),我们就可以实现这样的过程而无需使用临时文件。 
</p>

<p>(tee程序将其标准输入同时复制到其标准输出以及其命令行中包含的命名文件中 
</p>

<p>。) </p>

<p>mkfifo fifo1 </p>

<p>prog3&lt;fifo1&amp; </p>

<p>prog1&lt;infile 1 tee fifo1 | prog2 </p>

<p>图14.10 处理一个经过过滤的输入流两次 </p>

<p>我们创建FIFO,然后在后台启动prog3,它从FIFO读数据。然后,我们启动progl,用 
</p>

<p>tee将其输出发送到FIFO和prog2。图14.11 显示了有关安排。 </p>

<p>图14.11 使用FIFO和tee将一个流发送到两个不同的进程 </p>

<p>实例-客户-服务者使用FIFO进行通信 </p>

<p>FIFO的另一个应用是在客户和服务者之间传送数据。如果有一个服务者,它与很多 
</p>

<p>客户有关,每个客户都可将其请求写到一个该服务者创建的众所周知的FIFO中。( 
</p>

<p>&quot;众所周知&quot;的意思是;所有需与服务者联系的客户都知道该FIFO的路径名。)图1 
</p>

<p>4.12显示了这种安排。因为对于该FIFO有多个写进程,客户发送给服务者的请求其 
</p>

<p>长度要小于PIPE_BUF字节。这样就能避免客户各次写之间的穿插。 </p>

<p>图14.12 客户用-FIFO向服务者发送请求 </p>

<p>在这种类型的客户-服务者通信中使用FIFO的问题是:服务者如何将回答送回各个 
</p>

<p>客户。不能使用单个FIFO的问题是:服务者如何将回答送回各个客户。因为服务者 
</p>

<p>会发出对各个客户请求的响应,而请求者却不可能知道什么时候去读才能恰恰得到 
</p>

<p>对它的响应。一种解决方法是每个客户都在其请求中发送其进程ID。然后服务者为 
</p>

<p>每个客户创建一个FIFO,所使用的路径名是以客户的进程ID为基础的。例如,服务 
</p>

<p>者可以用名字/tmp/serv1.xxxxx创建FIFO,其中xxxxx被代换成客户的进程ID。图 
</p>

<p>14.13显示了这种安排。 </p>

<p>图14.13 客户-服务者用FIFO进行通信 </p>

<p>这种安排可以工作,但也有一些不足之处。其中之一是服务者不能判断一个客户是 
</p>

<p>否崩溃终止,这就使得客户专用的FIFO会遗留在文件系统中。另一个是服务者必须 
</p>

<p>捕捉SIGPIPE信号,因为客户在发送一个请求后没有读取响应就可能终止,于是留 
</p>

<p>下一个有写进程(服务者)而无读进程的客户专用FIFO。 </p>

<p>按照图14.13中的安排,如果服务者以只读方式打开众所周知的FIFO(因为它只需 
</p>

<p>读该FIFO),则每次客户数从1变成0,服务者就将在FIFO中读到一个文件结束标记 
</p>

<p>。为使服务者免于处理这种情况,一种常见的技巧是使服务者以读-写方式打开该 
</p>

<p>FIFO(见练习14.10)。 </p>

<p>14.6 系统V IPC </p>

<p>三种系统V IPC-消息队列、信号量以及共享存储器之间有很多相似之处。在以下各 
</p>

<p>节说明这些IPC的各自特殊功能之前,我们先在本节介绍它们的类似特征。 
</p>

<p>这三种IPC源自于1970年的一种称为&quot;Columbus Unix&quot;的Unix内部版本。后 
</p>

<p>来 </p>

<p>它们被加到SV上。 </p>

<p>14.6.1 标识符和关键字 </p>

<p>每个在核中的IPC结构(消息队列、信号量或共享存储段)都用一个非负整数的标 
</p>

<p>识符加以引用。例如,为了对一个消息队列发送或取消息,我们只需知道其队列标 
</p>

<p>识符。与文件描述符不同,IPC标识符不是小的整数。当一个IPC结构被创建,以后 
</p>

<p>又被删除时,与这种结构相关的标识符连续加1,直至达到一个整型数的最大正值 
</p>

<p>,然后又回转到0。(即使在IPC结构被删除后也记住该值,每次使用此结构时则增 
</p>

<p>1,该值被称为&quot;槽使用顺序号&quot;。它在ipc_perm结构中,我们将在下一节说明此结 
</p>

<p>构。) </p>

<p>无论何时在创建IPC结构时(调用msgget、semget或shmget),都应指定一个key( 
</p>

<p>关键字),关键字的数据类型由系统规定为key_t,通常在头文件&lt;sys/types.h&gt;中 
</p>

<p>被规定为长整型。关键字由核变换成标识符。 </p>

<p>有多种方法使客户和服务者在同一IPC结构上会合。 </p>

<p>1. 服务者可以指定关键字IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放 
</p>

<p>在某处(例如一个文件)以便客户取用。关键字IPC_PRIVATE保证服务者创建一个 
</p>

<p>新IPC结构。这种技术的缺点是;服务者要将整型标识符写到文件中,然后客户在 
</p>

<p>此后又要读文件取得此标识符。 </p>

<p>IPC_PRIVATE关键字也可用于父、子关系进程。父进程指定IPC_PRIVATE创建一个新 
</p>

<p>IPC结构,所返回的标识符在fork后可由子进程使用。子进程可将此标识符作为ex 
</p>

<p>ec函数的一个参数传给一个新程序。 </p>

<p>2. 
在一个公用头文件中定义一个客户和服务者都认可的关键字。然后服务者指定 
</p>

<p>此关键字创建一个新的IPC结构。这种方法的问题是该关键字可能已与一个IPC结构 
</p>

<p>相结合,在此情况下,get函数(msgget,semget或shmget)出错返回。服务者必须 
</p>

<p>处理这一错误,删除已存在的IPC结构,然后试着再创建它。 </p>

<p>3. 客户和服务者认同一个路径名和课题ID(课题ID是在0~255之间的字符值),然 
</p>

<p>后调用函数ftok将这两个值变换为一个关键字。(函数ftok在手册页stdipc(3) 
</p>

<p>中说明。)然后在方法2中使用此关键字。ftok提供的唯一服务就是由一个路径名 
</p>

<p>和课题ID产生一个关键字。因为客户和服务者典型地至少共享一个头文件,所以一 
</p>

<p>个比较简写的方法是避免使用ftok,而只是在该头文件中存放一个大家都知道的关 
</p>

<p>键字。这样做还避免了使用另一个函数。 </p>

<p>三个get函数(msgget,semget和shmget)都有两个类似的参数key和一个整型的fl 
</p>

<p>ag。如若满足下列条件,则创建一个新的IPC结构(通常由服务者创建); 
</p>

<p>1. key是IPC_PRIVATE,或 </p>

<p>2. key当前末与特定类型的IPC结构相结合,flag中指定了IPC_CREAT位。为访问现 
</p>

<p>存的队列(通常由客户进行),key必须等于创建该队列时所指定的关键字,并且 
</p>

<p>不应指定IPC_CREAT。 </p>

<p>注意,为了访问一个现存队列,决不能指定IPC_PRIVATE作为关键字因为这是 
</p>

<p>一个特殊的键值,它总是用于创建一个新队列。为了访问一个用IPC_PRIVATE关键 
</p>

<p>字创建的现存队列,一定要知道与该队列相结合的标识符,然后在其它IPC调用中 
</p>

<p>(例如msgsnd、msgrcv)使用该标识符。 </p>

<p>如果我们希望创建一个新IPC结构,保证不是引用具有同一标识符的一个现行IPC结 
</p>

<p>构,那么我们必须在flag中同时指定IPC_CREAT和IPC_EXCL位。这样做了以后,如 
</p>

<p>果IPC结构已经存在就会造成出错,返回EEXIST(这与指定了O_CREAT和O_EXCL标志 
</p>

<p>的open相类似)。 </p>

<p>14.6.2 许可权结构 </p>

<p>系统V IPC为每一个IPC结构设置了一个ipc_perm结构。该结构规定了许可权和属主 
</p>

<p>。 </p>

<p>struct ipc_perm { </p>

<p>uid_t uid; 所有者的有效用户id </p>

<p>gid_t gid; 所有者的有效组id </p>

<p>uid_t cuid; 创建者的有效用户id </p>

<p>gid_t cgid; 创建者的有效组id </p>

<p>mode_t mode; 访问方式 </p>

<p>ulong seq; 槽使用序列号 </p>

<p>key_t key; 关键字 </p>

<p>} </p>

<p>在创建IPC结构时,除seq以外的所有字段都赋初值。以后,可以调用msgctl、sem 
</p>

<p>ctl或shmctl修改uid、gid和mode字段。为了改变这些值,调用进程必须或者是IP 
</p>

<p>C结构的创建者,或者是超级用户。更改这些字段类似于对文件调用chown和chmod 
</p>

<p>。 </p>

<p>mode字段的值类似于在图4.4中所示的值,但是对于任何IPC结构都不存在执行许可 
</p>

<p>权。另外,消息队列和共享存储使用术语&quot;读&quot;和&quot;写&quot;,而信号量则用术语&quot;读&quot;和&quot; 
</p>

<p>更改&quot;。图14.14中对每种IPC说明了六种许可权。 </p>

<p>图14.14 系统V IPC许可权 </p>

<p>14.6.3 结构限制 </p>

<p>三种形式的系统V IPC都有我们可能会遇到的内在限制。这些限制的大多数是可以 
</p>

<p>通过重新配置而加以更改的。当叙说每种IPC时,我们都会指出它的限制 
</p>

<p>在SVR4中,这些值,以及它们的最小、最大值都在文件/etc/conf/cf.d/mtun 
</p>

<p>e中。 </p>

<p>14.6.4 优、缺点 </p>

<p>系统V IPC的主要问题是;IPC结构是在系统范围内起作用的,没有访问计数。例如 
</p>

<p>,如若我们创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该 
</p>

<p>消息队列及其内容并不被删除。它们余留在系统中直至;由某个进程调用msgrcv或 
</p>

<p>msgctl读消息或删除消息队列;某个进程执行ipcrm(1)命令删除消息队列;或由正 
</p>

<p>在再启动的系统删除消息队列。将此与管道pipe相比,那么当最后一个访问管道的 
</p>

<p>进程终止时,管道就被完全地删除了。对于FIFO而言虽然当最后一个引用FIFO的进 
</p>

<p>程终止时其名字仍保留在系统中,直至显式地删除它,但是留在FIFO中的数据却在 
</p>

<p>此时全部删除。 </p>

<p>系统V IPC的另一个问题是;这些IPC结构并不按名字为文件系统所知。我们不能用 
</p>

<p>第3、4章中所述的函数来存取它们或修改它们的特性。为了支持它们不得不增加了 
</p>

<p>十个以上全新的系统调用(msgget、semop、shmat等)。我们不能用ls命令见到它 
</p>

<p>们,不能用rm命令删除它们,不能用chmod命令更改它们的存取权。于是,也不得 
</p>

<p>不增加了全新的命令ipcs和 ipcrm。 </p>

<p>因为这些IPC不使用文件描述符,所以我们不能对它们使用多路转接I/O函数:sel 
</p>

<p>ect和poll。这就使得难于在一次使用多个IPC结构,难于用文件或设备I/O来使用 
</p>

<p>任何一个IPC结构。例如,没有某种形式的忙一等待循环,我们就不能使一个服务 
</p>

<p>者等待一个消息放在两个消息队列中的任一个上。 </p>

<p>Andrade、Carges以及Kovach[1989]对使用系统V IPC的一个实际事务处理系统进行 
</p>

<p>了综述。他们认为系统V IPC使用的名字空间(标识符)是一个优点而不是我们在 
</p>

<p>前面所说的问题,理由是使用标识符使一个进程只要使用单个函数调用(msgsnd) 
</p>

<p>就能将一个消息发送到一个队列,而其它形式的IPC则通常要求open,write和clos 
</p>

<p>e。这种论据是不真实的。为了避免使用一个关键字和调用msgget,客户总要以某 
</p>

<p>种方式获得服务者队列的标识符。分派给特定队列的标识符取决于在创建该队列时 
</p>

<p>,有多少消息队列已经存在,取决于自核自举以来,在核中将分配给新队列的表项 
</p>

<p>已经使用了多少次。这是一个动态值,不能被猜测或事先存放在一个头文件中。正 
</p>

⌨️ 快捷键说明

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