unix高级编程14.pdf

上传人:asd****56 文档编号:70322425 上传时间:2023-01-19 格式:PDF 页数:36 大小:1.12MB
返回 下载 相关 举报
unix高级编程14.pdf_第1页
第1页 / 共36页
unix高级编程14.pdf_第2页
第2页 / 共36页
点击查看更多>>
资源描述

《unix高级编程14.pdf》由会员分享,可在线阅读,更多相关《unix高级编程14.pdf(36页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、下载下载第1 4章进程间通信14.1 引言第8章说明了进程控制原语并且观察了如何调用多个进程。但是这些进程之间交换信息的唯一方法是经由f o r k或e x e c传送打开文件,或通过文件系统。本章将说明进程之间相互通信的其他技术I P C(InterProcess Communication)。UNIX IPC已经是而且继续是各种进程通信方式的统称,其中极少能在所有 U N I X的实现中进行移植。表1 4-1列出了不同实现所支持的不同形式的I P C。表14-1 UNIX IPC正如上表所示,不管哪一种U N I X实现,都可依靠的唯一一种I P C是半双工的管道。表中前7种I P C通常

2、限于同一台主机的各个进程间的 I P C。最后两种;套接口和流,则支持不同主机上各个进程间I P C(关于网络I P C的详细情况,请参见S t e v e n s1 9 9 0)。虽然中间三种形式的I P C(消息队列、信号量以及共享存储器)在表中说明为只受到系统 V的支持,但是在大多数制造商所支持的,从伯克利U N I X导出的U N I X系统中(例如,S u n O S以及U l t r i x),已经添加了这三种形式的I P C。几个P O S I X小组正在对I P C进行工作,但是最后结果还不很清楚,可能要到1 9 9 4年甚至更迟一点与I P C有关的P O S I X才能制定

3、出来。我们将与I P C有关的讨论分成两章。本章将讨论经典的 I P C;管道、F I F O、消息队列、信号量以及共享存储器。下一章将观察S V R 4和4.3+B S D共同支持的I P C的某些高级特征,包括;流管道和命名流管道,以及用这些更高级形式的I P C可以做的一些事情。14.2 管道管道是UNIX IPC的最老形式,并且所有U N I X系统都提供此种通信机制,管道有两种限制;(1)它们是半双工的。数据只能在一个方向上流动。(2)它们只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该I P C类型管道(半双工)F I F O s(命令管道)流管道(全双工)命

4、令流管道消息队列信号量共享存储套接口流进程调用f o r k,此后父、子进程之间就可应用该管道。我们将会看到流管道(见1 5.2节)没有第一种限制,F I F O(见1 4.5节)和命名流管道(见1 5.5节)则没有第二种限制。尽管有这两种限制,半双工管道仍是最常用的 I P C形式。管道是由调用p i p e函数而创建的。#include int pipe(int f i l e d e s 2);返回:若成功则为0,若出错则为-1经由参数f i l e d e s返回两个文件描述符:f i l e d e s 0 为读而打开,f i l e d e s 1 为写而打开。f i l e d

5、e s 1 的输出是f i l e d e s 0 的输入。有两种方法来描绘一个管道,见图1 4-1。左半图显示了管道的两端在一个进程中相互连接,右半图则说明数据通过内核在管道中流动。在S V R 4下,管道是双全工的。两个描述符都可用于读、写。于是,图 1 4-1中的箭头在两端都有。我们称这种全双工管道为“流管道”,下一章将详细讨论这种管道。因为P O S I X.1只提供半双工管道,为了可移植性,我们假定p i p e函数创建一个单方向的管道。图14-1 观察U N I X管道的两种方法f s t a t函数(见 4.2节)对管道的每一端都返回一个F I F O类型的文件描述符,可以用S

6、_ I S F I F O宏来测试管道。P O S I X.1规定s t a t结构的s t _ s i z e成员对于管道是未定义的。但是当 f s t a t函数应用于管道读端的文件描述符时,很多系统在 s t _ s i z e中存放管道中可用于读的字节数。但是,这是不可移植的。单个进程中的管道几乎没有任何用处。通常,调用 p i p e的进程接着调用f o r k,这样就创建了从父进程到子进程或反之的I P C通道。图1 4-2显示了这种情况。f o r k之后做什么取决于我们想要有的数据流的方向。对于从父进程到子进程的管道,父进程关闭管道的读端(f d 0),子进程则关闭写端(f d

7、 1)。图1 4-3显示了描述符的最后安排。第1 4章进程间通信3 2 1下载内核管道用户进程用户进程或对于从子进程到父进程的管道,父进程关闭 f d 1,子进程关闭f d 0。当管道的一端被关闭后,下列规则起作用:(1)当读一个写端已被关闭的管道时,在所有数据都被读取后,r e a d返回0,以指示达到了文件结束处(从技术方面考虑,管道的写端还有进程时,就不会产生文件的结束。可以复制一个管道的描述符,使得有多个进程具有写打开文件描述符。但是,通常一个管道只有一个读进程,一个写进程。下一节介绍F I F O时,我们会看到对于一个单一的F I F O常常有多个写进程)。(2)如果写一个读端已被关

8、闭的管道,则产生信号S I G P I P E。如果忽略该信号或者捕捉该信号并从其处理程序返回,则w r i t e出错返回,e r r n o设置为E P I P E。在写管道时,常数P I P E _ B U F规定了内核中管道缓存器的大小。如果对管道进行w r i t e调用,而且要求写的字节数小于等于P I P E _ B U F,则此操作不会与其他进程对同一管道(或 F I F O)的w r i t e操作穿插进行。但是,若有多个进程同时写一个管道(或 F I F O),而且某个或某些进程要求写的字节数超过P I P E _ B U F字节数,则数据可能会与其他写操作的数据相穿插。实

9、例程序1 4-1创建了一个从父进程到子进程的管道,并且父进程经由该管道向子进程传送数据。程序14-1 经由管道父进程向子进程传送数据3 2 2U N I X环境高级编程下载图14-2 fork之后的半双工管道图14-3 从父进程到子进程的管道父进程子进程父进程子进程管道内核管道内核在上面的例子中,直接对管道描述符调用 r e a d和w r i t e。更为有益的是将管道描述符复制为标准输入和标准输出。在此之后通常子进程调用 e x e c,执行另一个程序,该程序从标准输入(已创建的管道)或将数据写至其标准输出(管道)。实例试编写一个程序,其功能是每次一页显示已产生的输出。已经有很多 U N

10、I X公用程序具有分页功能,因此无需再构造一个新的分页程序,而是调用用户最喜爱的分页程序。为了避免先将所有数据写到一个临时文件中,然后再调用系统中的有关程序显示该文件,我们希望将输出通过管道直接送到分页程序。为此,先创建一个管道,一个子进程,使子进程的标准输入成为管道的读端,然后e x e c用户喜爱的分页程序。程序 1 4-2显示了如何实现这些操作。(本例要求在命令行中有一个参数说明要显示的文件的名称。通常,这种类型的程序要求在终端上显示的数据已经在存储器中。)程序14-2 将文件复制到分页程序第1 4章进程间通信3 2 3下载在调用f o r k之前先创建一个管道。f o r k之后父进程

11、关闭其读端,子进程关闭其写端。子进程然后调用d u p 2,使其标准输入成为管道的读端。当执行分页程序时,其标准输入将是管道的读端。当我们将一个描述符复制到另一个时(在子进程中,f d 0 复制到标准输入),应当注意该描述符的值并不已经是所希望的值。如果该描述符已经具有所希望的值,并且我们先调用d u p 2,然后调用c l o s e则将关闭此进程中只有该单个描述符所代表的打开文件。(回忆3.1 2节中所述,当d u p 2中的两个参数值相等时的操作。)在本程序中,如果s h e l l没有打开标准输入,那么程序开始处的f o p e n应已使用描述符0,也就是最小未使用的描述符,所以f d

12、 0 决不会等于标准输入。尽管如此,只要先调用d u p 2,然后调用c l o s e以复制一个描述符到另一个,作为一种保护性的编程措施,我们总是先将两个描述符进行比较。请注意,我们是如何使用环境变量 PA G E R获得用户分页程序名称的。如果这种操作没有成功,则使用系统默认值。这是环境变量的常见用法。实例回忆 8.8节中的五个函数:T E L L _ WA I T、T E L L _ PA R E N T、T E L L _ C H I L D、WA I T _ PA R E N T以及WA I T _ C H I L D。程序1 0-1 7提供了一个使用信号的实现。程序 1 4-3则是

13、一个使用管道的实现。见图1 4-4,在f o r k之前创建了两个管道。父进程在调用 T E L L _ C H I L D时经由上一个管道写一个字符“P”,子进程在调用T E L L _ PA R E N T时,经由下一个管道写一个字符“C”。相应的WA I T _ X X X函数调用r e a d读一个字符,没有读到字符时阻塞(睡眠等待)。请注意,每一个管道都有一个额外的读取进程,这没有关系。也就是说除了子进程从 p f d 1 0 读取,父进程也有上一个管道的读端。因为父进程并没有执行对该管道的读操作,所以这不会产生任何影响。3 2 4U N I X环境高级编程下载图14-4 用两个管道

14、实现父-子进程的同步父进程子进程程序14-3 使父、子进程同步的例程14.3 popen和p c l o s e函数因为常见的操作是创建一个连接到另一个进程的管道,然后读其输出或向其发送输入,所以标准I/O库为实现这些操作提供了两个函数 p o p e n和p c l o s e。这两个函数实现的操作是:创建一个管道,f o r k一个子进程,关闭管道的不使用端,e x e c一个s h e l l以执行命令,等待命令终止。#include FILE*popen(const char*c m d s t r i n g,const char*t y p e);返回:若成功则为文件指针,若出错则

15、为 N U L L第1 4章进程间通信3 2 5下载int pclose(FILE *f p);返回:c m d s t r i n g的终止状态,若出错则为-1函数popen 先执行f o r k,然后调用e x e c以执行c m d s t r i n g,并且返回一个标准I/O文件指针。如果t y p e是r,则文件指针连接到c m d s t r i n g的标准输出(见图1 4-5)。如果t y p e 是w,则文件指针连接到c m d s t r i n g 的标准输入(见图1 4-6)。有一种方法可以帮助我们记住 p o p e n最后一个参数及其作用,这种方法就是与 f o

16、p e n进行类比。如果t y p e是r,则返回的文件指针是可读的,如果t y p e是w,则是可写的。p c l o s e函数关闭标准I/O流,等待命令执行结束,然后返回 s h e l l的终止状态。(我们曾在8.6节对终止状态进行过说明,s y s t e m函数(见8.1 2节)也返回终止状态。)如果s h e l l不能被执行,则p c l o s e返回的终止状态与s h e l l执行e x i t(1 2 7)一样。cmdstring 由Bourne shell以下列方式执行;sh-c c m d s t r i n g这表示s h e l l将扩展c m d s t r

17、i n g中的任何特殊字符。例如,可以使用;fp=popen(ls*.c,r);或者fp=popen(cmd 2&1,r);P O S I X.1没有说明p o p e n、p c l o s e,因为它们与s h e l l有交互作用,而 shell 是由P O S I X.2说明的。我们对这两个函数的说明与 P O S I X.2的11.2草案相一致。该草案对这两个函数的说明与以前的实现有些区别。实例用p o p e n重写程序1 4-2,其结果是程序1 4-4。使用p o p e n减少了需要编写的代码量。s h e l l命令$PA G E R:-m o r e 的意思是:如果s h

18、e l l变量PA G E R已经定义,且其值非空,则使用其值,否则使用字符串m o r e。程序14-4 用p o p e n向分页程序传送文件3 2 6U N I X环境高级编程下载图14-5 fp=popen(c o m m a n d,r)的结果图14-6 fp=popen(c o m m a n d,w )的结果父进程子进程父进程子进程实例p o p e n函数程序1 4-5是我们编写的p o p e n和p c l o s e版本。虽然p o p e n的核心部分与本章中以前用过的代码类似,但是增加了很多需要考虑的细节。首先每次调用 p o p e n时,应当记住所创建的子进程的进

19、程I D,以及其文件描述符或F I L E指针。我们选择在数组c h i l d p i d中保存子进程I D,并用文件描述符作为其下标。于是,当以 F I L E指针作为参数调用 p c l o s e时,我们调用标准 I/O函数f i l e n o以得到文件描述符,然后取得子进程 I D,并用于调用w a i t p i d。因为一个进程可能调用p o p e n多次,所以在动态分配c h i l d p i d数组时(第一次调用p o p e n时),其长度可以容纳与文件描述符数相同的进程数。调用p i p e、f o r k以及为每个进程复制相应的文件描述符,这些操作与本章前面所述的

20、类似。P O S I X.2要求子进程关闭在以前调用 p o p e n时形成,当前仍旧打开的所有 I/O流。为此,在子进程中从头逐个检查c h i l d p i d数组的各元素,关闭仍旧打开的任一描述符。若p c l o s e的调用者已经为信号 S I G C H L D设置了一个信号处理程序,则 w a i t p i d将返回一个E I N T R。因为允许调用者捕捉此信号(或者任何其他可能中断 w a i t p i d调用的信号),所以当w a i t p i d被一个捕捉到的信号中断时,我们只是再次调用w a i t p i d。如果一个信号中断了w a i t,p c l o

21、 s e的早期版本返回E I N T R。p c l o s e的早期版本在 w a i t期间,阻塞或忽略信号 S I G I N T、S I G Q U I T以及S I G H U P。P O S I X.2则不允许这一点。程序14-5 popen和p c l o s e函数第1 4章进程间通信3 2 7下载3 2 8U N I X环境高级编程下载实例考虑一个应用程序,它向标准输出写一个提示,然后从标准输入读 1行。使用p o p e n,可以在应用程序和输入之间插入一个程序以对输入进行变换处理。图1 4-7显示了进程的安排。对输入进行的变换可能是路径名的扩充,或者是提供一种历史机制(记

22、住以前输入的命令)。(本实例取自P O S I X.2草案。)程序1 4-6是一个简单的过滤程序,它只是将输入复制到输出,在复制时将任一大写字符变换为小写字符。在写了一行之后,对标准输出进行了刷清(用ff l u s h),其理由将在下一节介绍协同进程时讨论。程序14-6 过滤程序,将大写字符变换成小写字符对该过滤程序进行编译,其可执行目标代码存放在文件 m y u c l c中,然后在程序 1 4-7中用p o p e n调用它们。因为标准输出通常是按行进行缓存的,而提示并不包含新行符,所以在写了提示之后,需要调用ff l u s h。第1 4章进程间通信3 2 9下载图14-7 用p o

23、p e n变换输入父进程过滤程序p o p e n管道使用终端的用户程序14-7 调用大写/小写过滤程序以读取命令14.4 协同进程U N I X过滤程序从标准输入读取数据,对其进行适当处理后写到标准输出。几个过滤进程通常在s h e l l管道命令中线性地连接。当同一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出时,则该过滤程序就成为协同进程(c o p r o c e s s)。K o r n S h e l l提供了协同进程。Bourne shell和C shell并没有提供将进程连接起来按协同进程方式工作的方法。协同进程通常在s h e l l的后台运行,其标准输入和标准输出

24、通过管道连接到另一个程序。虽然要求初始化一个协同进程,并将其输入和输出连接到另一个进程的 s h e l l语法是十分奇特的(详细情况见B o l s k y和K o r n1 9 8 9中的p p.6 66 6),但是协同进程的工作方式在 C程序中也是非常有用的。p o p e n提供连接到另一个进程的标准输入或标准输出的一个单行管道,而对于协同进程,则它有连接到另一个进程的两个单行管道一个接到其标准输入,另一个则来自标准输出。我们先要将数据写到其标准输入,经其处理后,再从其标准输出读取数据。实例让我们通过一个实例来观察协同进程。进程先创建两个管道:一个是协同进程的标准输入,另一个是协同进程

25、的标准输出。图1 4-8显示了这种安排。程序1 4-8是一个简单的协同进程,它从其标准输入读两个数,计算它们的和,然后将结果写至标准输出。程序14-8 对两个数求和的简单过滤程序3 3 0U N I X环境高级编程下载图14-8 驱动一个协同进程写其标准输入,读其标准输出父进程子进程(协同进程)管道1管道2对此程序进行编译,将其可执行目标代码存入名为a d d 2的文件。程序1 4-9在从其标准输入读入两个数之后调用 a d d 2协同进程。从协同进程送来的值则写到其标准输出。程序14-9 驱动a d d 2过滤程序的程序第1 4章进程间通信3 3 1下载在程序中创建了两个管道,父、子进程各自

26、关闭它们不需使用的端口。创建两个管道的理由是:一个用做协同进程的标准输入,另一个则用做它的标准输出。然后在调用e x e c l之前,子进程调用d u p 2使管道描述符移至其标准输入和输出。若编译和运行程序1 4-9,它如所希望的那样进行工作。并且,当程序 1 4-9正等待输入时,若杀死 a d d 2协同进程,然后输入两个数,当程序1 4-9对管道进行写操作时,由于该管道无读进程,于是调用信号处理程序(见习题1 4.4)。程序1 5-1将提供这一实例的另一个版本,它使用一个全双工管道而不是两个半双工管道。实例在协同进程a d d 2(见程序1 4-8)中,使用了UNIX I/O:read和

27、w r i t e。如果使用标准I/O改写该协同进程,其后果是什么呢?程序1 4-1 0即改写后的版本。程序14-10 对两个数求和的滤波程序,使用标准 I/O3 3 2U N I X环境高级编程下载若程序1 4-9调用此新的协同进程,则它不再工作。问题出在系统默认的标准 I/O缓存机制上。当程序1 4-1 0被调用时,对标准输入的第一个f g e t s引起标准I/O库分配一个缓存,并选择缓存的类型。因为标准输入是个管道,所以 i s a t t y为假,于是标准I/O库由系统默认是全缓存的。对标准输出也有同样的处理。当a d d 2从其标准输入读取而发生堵塞时,程序 1 4-9从管道读时也

28、发生堵塞,于是产生了死锁。对将要执行的这样一个协同进程可以加以控制。在程序 1 4-1 0中的w h i l e循环之前加上下面4行:if(setvbuf(stdin,NULL,_IOLBF,0)!=0)err_sys(setvbuf error);if(setvbuf(stdout,NULL,_IOLBF,0)!=0)err_sys(setvbuf error);这使得当有一行可用时,f g e t s即返回,并使得当输出一新行符时,p r i n t f即执行ff l u s h操作。对s e t v b u f进行了这些显式调用,使得程序1 4-1 0能正常工作。如果不能修改程序,则需使

29、用其他技术。例如,如果在程序中使用 a w k(1)代替a d d 2作为协同进程,则下列命令行不能工作;#!/bin/awk/-f print$1+$2 不能工作的原因还是标准 I/O的缓存机制问题。但是,在这种情况下不能改变 a w k的工作方式(除非有a w k的源代码)。对这种问题的一般解决方法是使被调用(在本例中是 a w k)的协同进程认为它的标准输入和输出被连接到一个终端。这使得协同进程中的标准 I/O例程对这两个I/O流进行行缓存,这类似于前面所做的显式s e t v b u f调用。第1 9章将用伪终端实现这一点。14.5 FIFOF I F O有时被称为命名管道。管道只能由

30、相关进程使用,它们共同的祖先进程创建了管道。但是,通过F I F O,不相关的进程也能交换数据。第1 4章已经提及F I F O是一种文件类型。s t a t结构(见4.2节)成员s t _ m o d e的编码指明文件是否是F I F O类型。可以用S _ I S F I F O宏对此进行测试。创建F I F O类似于创建文件。确实,F I F O的路径名存在于文件系统中。#include#include int mkfifo(const char*p a t h n a m e,mode_t m o d e);返回:若成功则为0,若出错则为-1m k f i f o函数中m o de参数的

31、规格说明与o p e n函数中的m o d e相同(见3.3节)。新F I F O的用户和组的所有权与4.6节所述的相同。一旦已经用 m k f i f o创建了一个 F I F O,就可用 o p e n打开它。确实,一般的文件 I/O函数(c l o s e、r e a d、w r i t e、u n l i n k等)都可用于F I F O。第1 4章进程间通信3 3 3下载m k f i f o是P O S I X.1首先提出的。S V R 3用m k n o d(2)系统调用创建F I F O。而在S V R 4中,m k f i f o调用m k n o d创建F I F O。P

32、O S I X.2已经建议了一个m k f i f o(1)命令。S V R 4和4.3+B S D现在支持此命令。于是,用一条s h e l l命令就可以创建一个F I F O,然后用一般的shell I/O重新定向对其进行存取。当打开一个F I F O时,非阻塞标志(O _ N O N B L O C K)产生下列影响:(1)在一般情况中(没有说明O _ N O N B L O C K),只读打开要阻塞到某个其他进程为写打开此F I F O。类似,为写而打开一个F I F O要阻塞到某个其他进程为读而打开它。(2)如果指定了O _ N O N B L O C K,则只读打开立即返回。但是,

33、如果没有进程已经为读而打开一个F I F O,那么只写打开将出错返回,其e r r n o是E N X I O。类似于管道,若写一个尚无进程为读而打开的F I F O,则产生信号S I G P I P E。若某个F I F O的最后一个写进程关闭了该F I F O,则将为该F I F O的读进程产生一个文件结束标志。一个给定的F I F O有多个写进程是常见的。这就意味着如果不希望多个进程所写的数据互相穿插,则需考虑原子写操作。正如对于管道一样,常数 P I P E _ B U F说明了可被原子写到F I F O的最大数据量。F I F O有两种用途:(1)FIFO由s h e l l命令使用

34、以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。(2)FIFO用于客户机-服务器应用程序中,以在客户机和服务器之间传递数据。我们各用一个例子来说明这两种用途。实例用F I F O复制输出流F I F O可被用于复制串行管道命令之间的输出流,于是也就不需要写数据到中间磁盘文件中(类似于使用管道以避免中间磁盘文件)。管道只能用于进程间的线性连接,然而,因为F I F O具有名字,所以它可用于非线性连接。考虑这样一个操作过程,它需要对一个经过过滤的输入流进行两次处理。图1 4-9表示了这种安排。使用F I F O以及U N I X程序t e e(1),就可以实现这样的过程而无需使用临时

35、文件。(t e e程序将其标准输入同时复制到其标准输3 3 4U N I X环境高级编程下载图14-9 对一个经过过滤的输入流进行两次处理输入文件输入文件F I F O图14-10 使用F I F O和t e e将一个流发送到两个不同的进程出以及其命令行中包含的命名文件中。)mkfifo fifo1prog3 fifo1&prog1 infile|tee fifo1|prog2创建F I F O,然后在后台起动prog3,它从F I F O读数据。然后起动progl,用t e e将其输出发送到F I F O和p r o g 2。图1 4-1 0显示了有关安排。实例客户-服务器使用F I F O

36、进行通信F I F O的另一个应用是在客户机和服务器之间传送数据。如果有一个服务器,它与很多客户机有关,每个客户机都可将其请求写到一个该服务器创建的众所周知的 F I F O中(“众所周知”的意思是;所有需与服务器联系的客户机都知道该 F I F O的路径名)。图1 4-11显示了这种安排。因为对于该F I F O有多个写进程,客户机发送给服务器的请求其长度要小于 P I P E _ B U F字节。这样就能避免客户机各次写之间的穿插。在这种类型的客户机-服务器通信中使用F I F O的问题是:服务器如何将回答送回各个客户机。不能使用单个 F I F O,因为服务器会发出对各个客户机请求的响应

37、,而请求者却不可能知道什么时候去读才能恰恰得到对它的响应。一种解决方法是每个客户机都在其请求中发送其进程I D。然后服务器为每个客户机创建一个 F I F O,所使用的路径名是以客户机的进程 I D为基础的。例如,服务器可以用名字/t m p/s e r v 1.X X X X X创建F I F O,其中X X X X X被替换成客户机的进程I D。图1 4-1 2显示了这种安排。这种安排可以工作,但也有一些不足之处。其中之一是服务器不能判断一个客户机是否崩溃终止,这就使得客户机专用的F I F O会遗留在文件系统中。另一个是服务器必须捕捉 S I G P I P E信号,因为客户机在发送一个

38、请求后没有读取响应就可能终止,于是留下一个有写进程(服务器)而无读进程的客户机专用F I F O。按照图1 4-1 2中的安排,如果服务器以只读方式打开众所周知的 F I F O(因为它只需读该F I F O),则每次客户机数从1变成0,服务器就将在F I F O中读到一个文件结束标记。为使服务器免于处理这种情况,一种常见的技巧是使服务器以读-写方式打开该F I F O(见习题1 4.1 0)。14.6 系统V IPC三种系统V IPC:消息队列、信号量以及共享存储器之间有很多相似之处。以下各节将说明这些I P C的各自特殊功能,本节先介绍它们类似的特征。第1 4章进程间通信3 3 5下载图1

39、 4-11 客户机用F I F O向服务器发送请求图14-12 客户机-服务器用F I F O进行通信管道客户机客户机服务器读请求众所周知的F I F O服务器读请求客户机专用F I F O客户机客户机众所周知的F I F O客户机专用F I F O这三种I P C源自于1 9 7 0年的一种称为Columbus UNIX的U N I X内部版本。后来它们被加到S V上。14.6.1 标识符和关键字每个内核中的 I P C结构(消息队列、信号量或共享存储段)都用一个非负整数的标识符(i d e n t i f i e r)加以引用。例如,为了对一个消息队列发送或取消息,只需知道其队列标识符。与

40、文件描述符不同,I P C标识符不是小的整数。当一个 I P C结构被创建,以后又被删除时,与这种结构相关的标识符连续加1,直至达到一个整型数的最大正值,然后又回转到0。(即使在I P C结构被删除后也记住该值,每次使用此结构时则增1,该值被称为“槽使用顺序号”。它在i p c _ p e r m结构中,下一节将说明此结构。)无论何时创建I P C结构(调用m s g g e t、s e m g e t或s h m g e t),都应指定一个关键字(k e y),关键字的数据类型由系统规定为k e y _ t,通常在头文件中被规定为长整型。关键字由内核变换成标识符。有多种方法使客户机和服务器在

41、同一I P C结构上会合:(1)服务器可以指定关键字I P C _ P R I VAT E创建一个新I P C结构,将返回的标识符存放在某处(例如一个文件)以便客户机取用。关键字 I P C _ P R I VAT E保证服务器创建一个新 I P C结构。这种技术的缺点是:服务器要将整型标识符写到文件中,然后客户机在此后又要读文件取得此标识符。I P C _ P R I VAT E关键字也可用于父、子关系进程。父进程指定 I P C _ P R I VAT E创建一个新I P C结构,所返回的标识符在f o r k后可由子进程使用。子进程可将此标识符作为 e x e c函数的一个参数传给一个新

42、程序。(2)在一个公用头文件中定义一个客户机和服务器都认可的关键字。然后服务器指定此关键字创建一个新的 I P C结构。这种方法的问题是该关键字可能已与一个 I P C结构相结合,在此情况下,g e t函数(m s g g e t、s e m g e t或s h m g e t)出错返回。服务器必须处理这一错误,删除已存在的I P C结构,然后试着再创建它。(3)客户机和服务器认同一个路径名和课题 I D(课题I D是0 2 5 5之间的字符值),然后调用函数f t o k将这两个值变换为一个关键字(函数f t o k在手册页s t d i p c(3)中说明)。然后在方法(2)中使用此关键字

43、。f t o k提供的唯一服务就是由一个路径名和课题 I D产生一个关键字。因为一般来说,客户机和服务器至少共享一个头文件,所以一个比较简单的方法是避免使用 f t o k,而只是在该头文件中存放一个大家都知道的关键字。这样做还避免了使用另一个函数。三个g e t函数(m s g g e t、s e m g e t和s h m g e t)都有两个类似的参数k e y和一个整型的f l a g。如若满足下列条件,则创建一个新的I P C结构(通常由服务器创建):(1)k e y是I P C _ P R I VAT E,或(2)k e y当前未与特定类型的I P C结构相结合,f l a g中指

44、定了I P C _ C R E AT位。为访问现存的队列(通常由客户机进行),k e y必须等于创建该队列时所指定的关键字,并且不应指定I P C _ C R E AT。注意,为了访问一个现存队列,决不能指定I P C _ P R I VAT E作为关键字。因为这是一个特殊的键值,它总是用于创建一个新队列。为了访问一个用 I P C _ P R I VAT E关键字创建的现存队列,一定要知道与该队列相结合的标识符,然后在其他 I P C调用中(例如m s g s n d、m s g r c v)使用该标识符。3 3 6U N I X环境高级编程下载如果希望创建一个新的I P C结构,保证不是引

45、用具有同一标识符的一个现行I P C结构,那么必须在f l a g中同时指定I P C _ C R E AT和I P C _ E X C L位。这样做了以后,如果 I P C结构已经存在就会造成出错,返回E E X I S T(这与指定了O _ C R E AT和O _ E X C L标志的o p e n相类似)。14.6.2 许可权结构系统V IPC为每一个I P C结构设置了一个i p c _ p e r m结构。该结构规定了许可权和所有者。struct ipc_perm u i d _ tu i d;/*owners effective user id*/g i d _ tg i d;/

46、*owners effective group id*/u i d _ tcuid;/*creators effective user id*/g i d _ tc g i d;/*creators effective group id*/m o d e _ tmode;/*access modes*/u l o n gs e q;/*slot usage sequence number*/k e y _ tkey;/*key*/在创建I P C结构时,除 s e q以外的所有字段都赋初值。以后,可以调用 m s g c t l、s e m c t l或s h m c t l修改u i d、g

47、 i d和m o d e字段。为了改变这些值,调用进程必须是I P C结构的创建者或超级用户。更改这些字段类似于对文件调用c h o w n和c h m o d。m o d e字段的值类似于表4-4中所示的值,但是对于任何I P C结构都不存在执行许可权。另外,消息队列和共享存储使用术语“读”和“写”,而信号量则用术语“读”和“更改”。表1 4-2中对每种I P C说明了6种许可权。表14-2 系统V IPC许可权14.6.3 结构限制三种形式的系统V IPC都有我们可能会遇到的内在限制。这些限制的大多数可以通过重新配置而加以更改。当叙说每种I P C时,都会指出它的限制在SVR4中,这些值以

48、及它们的最小、最大值都在文件/etc/conf/cf.d/mtune中。14.6.4 优点和缺点系统V IPC的主要问题是;I P C结构是在系统范围内起作用的,没有访问计数。例如,如果创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容并不被删除。它们余留在系统中直至:由某个进程调用 m s g r c v或m s g c t l读消息或删除消息队列,或某个进程执行i p c r m(1)命令删除消息队列;或由正在再起动的系统删除消息队列。将此与管道p i p e相比,那么当最后一个访问管道的进程终止时,管道就被完全地删除了。对于 F I F O而言虽第1 4章进程

49、间通信3 3 7下载许可权消息队列信号量共享存储用户读用户写(更改)组读组写(更改)其他读其他写(更改)然当最后一个引用 F I F O的进程终止时其名字仍保留在系统中,直至显式地删除它,但是留在F I F O中的数据却在此时全部删除。系统V IPC的另一个问题是;这些I P C结构并不按名字为文件系统所知。我们不能用第3、4章中所述的函数来存取它们或修改它们的特性。为了支持它们不得不增加了十多个全新的系统调用(m s g g e t、s e m o p、s h m a t等)。我们不能用l s命令见到它们,不能用 r m命令删除它们,不能用c h m o d命令更改它们的存取权。于是,也不得

50、不增加了全新的命令 i p c s和 i p c r m。因为这些I P C不使用文件描述符,所以不能对它们使用多路转接 I/O函数:s e l e c t和p o l l。这就使得一次使用多个I P C结构,以及用文件或设备I/O来使用I P C结构很难做到。例如,没有某种形式的忙-等待循环,就不能使一个服务器等待一个消息放在两个消息队列的任一一个中。A n d r a d e、C a rg e s以及K o v a c h1 9 8 9对使用系统V IPC的一个实际事务处理系统进行了综述。他们认为系统V IPC使用的名字空间(标识符)是一个优点而不是前面所说的问题,理由是使用标识符使一个进

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 技术资料 > 其他杂项

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁