📄 15.htm
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 3.0">
<title>C:\WINDOWS\Desktop\UnixProg\7.htm</title>
</head>
<body>
<p> <font SIZE="2"></p>
<h1 align="center">第十五章 高级进程间通信 </h1>
<p>15.1 引言 </p>
<p>上一章说明了各种UNIX系统提供的IPC经典方法,包括:管道、FIFO、消息队列、信
</p>
<p>号量和共享存储。本章介绍某些高级的IPC以及它们的应用方法,包括:流管道和命
</p>
<p>名流管道。使用这些机制,我们可以在进程间传送打开文件描述符。在分别为每一
</p>
<p>个客户进程提供一个通道的系统中,这些通信机制使客户进程能与精灵服务进程会
</p>
<p>合。4。2BSD和SVR3。2最早提供这些高级形式的IPC,但是至今尚未广泛使用,也缺
</p>
<p>少参政文献。本章中很多思想来自Pressotto和Ritchie[1990]的论文。 </p>
<p>15.2 流管道 </p>
<p>流管道是一个双向(全双工)管道。单个流管道就能向父、子进程提供双向的数据流
</p>
<p>。图15。1显示了观察流管道的两种方式。它与图14.2的唯一区别是双向箭头连线
</p>
<p>,这是因为流管道是全双工的。 </p>
<p>实例 </p>
<p>我们用一个流管道再次实现了程序14.9的协作进程实例。程序15.1是新的main函数
</p>
<p>。add2协作进程与程序14.8中的相同。程序15.1调用了创建一个流管道的新函数s
</p>
<p>_pipe。(在下面将说明该函数的SVR4和4.3+BSD版本。) </p>
<p>图15.1 观察流管道的两种方式 </p>
<p>#include <signal.h> </p>
<p>#include "ourhdr.h" </p>
<p>static void sig_pipe(int); /* our signal handler */ </p>
<p>int </p>
<p>main(void) </p>
<p>{ </p>
<p>int n, fd[2]; </p>
<p>pid_t pid; </p>
<p>char line[MAXLINE]; </p>
<p>if (signal(SIGPIPE, sig_pipe) == SIG_ERR) </p>
<p>err_sys("signal error"); </p>
<p>if (s_pipe(fd) < 0) /* only need a single stream pipe */ </p>
<p>err_sys("pipe error"); </p>
<p>if ( (pid = fork()) < 0) </p>
<p>err_sys("fork error"); </p>
<p>else if (pid > 0) { /* parent */ </p>
<p>close(fd[1]); </p>
<p>while (fgets(line, MAXLINE, stdin) != NULL) { </p>
<p>n = strlen(line); </p>
<p>if (write(fd[0], line, n) != n) </p>
<p>err_sys("write error to pipe"); </p>
<p>if ( (n = read(fd[0], line, MAXLINE)) < 0) </p>
<p>err_sys("read error from pipe"); </p>
<p>if (n == 0) { </p>
<p>err_msg("child closed pipe"); </p>
<p>break; </p>
<p>} </p>
<p>line[n] = 0; /* null terminate */ </p>
<p>if (fputs(line, stdout) == EOF) </p>
<p>err_sys("fputs error"); </p>
<p>} </p>
<p>if (ferror(stdin)) </p>
<p>err_sys("fgets error on stdin"); </p>
<p>exit(0); </p>
<p>} else { /* child */ </p>
<p>close(fd[0]); </p>
<p>if (fd[1] != STDIN_FILENO) { </p>
<p>if (dup2(fd[1], STDIN_FILENO) != STDIN_FILENO) </p>
<p>err_sys("dup2 error to stdin"); </p>
<p>} </p>
<p>if (fd[1] != STDOUT_FILENO) { </p>
<p>if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) </p>
<p>err_sys("dup2 error to stdout"); </p>
<p>} </p>
<p>if (execl("./add2", "add2", NULL) < 0) </p>
<p>err_sys("execl error"); </p>
<p>} </p>
<p>} </p>
<p>static void </p>
<p>sig_pipe(int signo) </p>
<p>{ </p>
<p>printf("SIGPIPE caught\n"); </p>
<p>exit(1); </p>
<p>} </p>
<p>程序15.1 用流管道驱动add2过滤进程的程序 </p>
<p>父程序只使用fd[0],子程序只使用fd[1]
。因为流管道的每一端都是全双工的 </p>
<p>,所以父进程读、写fd[0],而子程序将fd[1]复制到标准输入和标准输出。图15.2显
</p>
<p>示了由此构成的各描述符。 </p>
<p>图15.2 为协作进程安排的各描述符 </p>
<p>s_pipe函数定义为与标准pipe函数类似。它的调用参数与pipe相同,但返回的描述
</p>
<p>符以读 写方式打开。 </p>
<p>实例一SVR4下的s_pipe函数 </p>
<p>程序15.2是s_pipe函数的SVR4版本。它只是调用创建全双工管道的标准pipe函数
</p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>s_pipe(int fd[2]) /* two file descriptors returned in fd[0] & fd[1] */ </p>
<p>{ </p>
<p>return( pipe(fd) ); </p>
<p>} </p>
<p>程序15.2 s_pipe函数的SVR4版本 </p>
<p>在系统V的早期版本中也可以创建流管道,但要进行的处理较多。有关SVR3.2下创建
</p>
<p>流管道的详细情况,请参阅Stevens[1990]。 </p>
<p>图15.3显示SVR4之下管道的基本结构。它主要是两个相互连接的流首。
</p>
<p>图15.3 在SVR4之下的管道 </p>
<p>因为管道是一种流设备,我们可将处理模块压入管道的任一端。在15.5.1节,我们将
</p>
<p>用此技术提供一个可以装配的命名管道。 </p>
<p>实例一4.3+BSD之下的s_pipe函数 </p>
<p>程序15.3是s_pipe函数的BSD版本。此函数在4.2BSD及以后的各版本中起作用。它
</p>
<p>创建一对互连的UNIX域流套接口。 </p>
<p>自4.2BSD开始,常规的管道已用此方式实现。但是,当调用pipe时,第一个描述
</p>
<p>符的写端和第二个描述符的读端都被关闭。为获得全双工管道,必须直接调用sock
</p>
<p>etpair。 </p>
<p>#include <sys/types.h> </p>
<p>#include <sys/socket.h> </p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>s_pipe(int fd[2]) /* two file descriptors returned in fd[0] & fd[1] */ </p>
<p>{ </p>
<p>return( socketpair(AF_UNIX, SOCK_STREAM, 0, fd) ); </p>
<p>} </p>
<p>程序15.3 s_pipe函数的BSD版本。 </p>
<p>15.3 传送文件描述符 </p>
<p>在进程间传送打开文件描述符的能力是非常有用的。用此可以对客户/服务器
</p>
<p>应用进行不同的设计。它允许一个进程(典型地是一个服务器)处理与打开一个文件
</p>
<p>有关的所有操作(涉及的细节可能是:将网络名翻译为网络地址、拨号调制解调器、
</p>
<p>协商文件锁等。)以及向调用进程返回一描述符,该描述符可被用于以后的所有I/O
</p>
<p>函数。打开文件或 设备的所有细节对客户而言都是透明的。 </p>
<p>4.2BSD支持传送打开描述符,但其实施中有些错误。4.3BSD排除了这些错
</p>
<p>误。3.2 </p>
<p>及以上版本都支持传送打开描述符。 </p>
<p>下面进一步说明"从一个进程向另一个进程传送一打开文件描述符"的含义。回忆图
</p>
<p>3.3,其中显示了两个进程,它们打开了同一文件。虽然它们共享同一v_node,但每个
</p>
<p>进程都有它自己的文件表项。当从一个进程向另一个进程传送一打开文件描述符时
</p>
<p>,我们想要发送进程和接收进程共享同一文件表项。图15.4示出了所希望的安排。
</p>
<p>在技术上,发送进程实际上向接受进程传送一个指向一打开文件表项的指针。该指
</p>
<p>针被分配存放在接收进程的第一个可用描述符项中。(注意,不要得到错觉以为发送
</p>
<p>进程和接收进程中的描述符编号是相同的,通常它们是不同的。)这种情况与在for
</p>
<p>k之后,父、子进程完全共享一个打开文件表项相同(回忆图8.1)。 </p>
<p>当发送进程将描述符传送给接收进程后,通常它关闭该描述符。发送进程关闭该描
</p>
<p>述符并不造成关闭该文件或设备,其原因是该描述符对应的文件仍需为接收进程打
</p>
<p>开(即使接收进程尚未接收到该描述符)。 </p>
<p>图15.4 从上一进程传送一个打开文件至下一进程 </p>
<p>下面我们定义本章使用的三个函数(在第十八章也使用)以发送和接收文件描述符。
</p>
<p>本节将会给出对于SVR4和4.3+BSD的这三个函数的不同实现。 </p>
<p>_______________________________________________________________________ </p>
<p>______ </p>
<p>#include "ourhdr." </p>
<p>int send_fd(int spipefd, int filedes); </p>
<p>int send_err(int spipefd,int status, const char *errmsg); </p>
<p>两个函数返回:若成功为0,出错为-1 </p>
<p>int recv_fd(int spipefd, ssize_t (*userfunc)(int , const void *, size-t </p>
<p>)); </p>
<p>返回:若成功为文件描述符,出错<0 </p>
<p>_______________________________________________________________________ </p>
<p>______ </p>
<p>当一个进程(通常是一个服务器)希望将一个描述符传送给另一个进程时,它调用se
</p>
<p>nd_fd或send_err。等待接收描述符的进程(客户)调用recv_fd。 </p>
<p>Send_fd经由流管道spipefd发送描述符filedes。send_err 经由流管道spipefd发
</p>
<p>送errmsg和status字节。status的值应在-1至-225之间 </p>
<p>客户调用secv_fd接收一描述符。如果一切正常(发送者调用了send_fd),则作为函
</p>
<p>数值返回非负描述符。否则,返回值是由send_err发送的status(在-1和-255之间的
</p>
<p>一个负值)。另外,如果服务器发送了一条出错消息,则客户调用它自己的userfunc
</p>
<p>处理该消息。userfunc的第一个参数是常数STDERR_FILENO,
然后是指向出错消息 </p>
<p>的指针及其长度。客户常将userfunc指定为UNIX的Write函数。 </p>
<p>我们实现了用于这三个函数的我们自己制定的协议。为发送一描述符,send_fd先发
</p>
<p>送两个0字节,然后是实际描述符。为了发送一条出错消息,send_err
发送errmsg, </p>
<p>然后是1个0字节,最后是status字节的绝对值(1-255)。recv_fd读流管道中所有字
</p>
<p>节直至null字符。在null字符之前的所有字符都送给调用者的userfunc。recv_fd
</p>
<p>读到的下一个字节是status字节.若status字节为0,那么一个描述符已传送,否
</p>
<p>则表示没有接收到描述符. </p>
<p>Send_err函数在将出错消息写到流管道后,即调用send_fg函数.这示于程序15
</p>
<p>.4中。 </p>
<p>#include "ourhdr.h" </p>
<p>/* Used when we had planned to send an fd using send_fd(), </p>
<p>* but encountered an error instead. We send the error back </p>
<p>* using the send_fd()/recv_fd() protocol. */ </p>
<p>int </p>
<p>send_err(int clifd, int errcode, const char *msg) </p>
<p>{ </p>
<p>int n; </p>
<p>if ( (n = strlen(msg)) > 0) </p>
<p>if (writen(clifd, msg, n) != n) /* send the error message */ </p>
<p>return(-1); </p>
<p>if (errcode >= 0) </p>
<p>errcode = -1; /* must be negative */ </p>
<p>if (send_fd(clifd, errcode) < 0) </p>
<p>return(-1); </p>
<p>return(0); </p>
<p>} </p>
<p>程序15.4 send_err函数 </p>
<p>15.3.1 SVR4 </p>
<p>在SVR4之下,文件描述符用两条ioctl命令在一流管道中交换,这两条命令是:I_
</p>
<p>SENDFD 和I_RECVFD。为了发送一描述符,我们将ioctl的第三个参数设置为实际描
</p>
<p>述符。这示于程序15.5中。 </p>
<p>#include <sys/types.h> </p>
<p>#include <stropts.h> </p>
<p>#include "ourhdr.h" </p>
<p>/* Pass a file descriptor to another process. </p>
<p>* If fd<0, then -fd is sent back instead as the error status. */ </p>
<p>int </p>
<p>send_fd(int clifd, int fd) </p>
<p>{ </p>
<p>char buf[2]; /* send_fd()/recv_fd() 2-byte protocol */ </p>
<p>buf[0] = 0; /* null byte flag to recv_fd() */ </p>
<p>if (fd < 0) { </p>
<p>buf[1] = -fd; /* nonzero status means error */ </p>
<p>if (buf[1] == 0) </p>
<p>buf[1] = 1; /* -256, etc. would screw up protocol */ </p>
<p>} else { </p>
<p>buf[1] = 0; /* zero status means OK */ </p>
<p>} </p>
<p>if (write(clifd, buf, 2) != 2) </p>
<p>return(-1); </p>
<p>if (fd >= 0) </p>
<p>if (ioctl(clifd, I_SENDFD, fd) < 0) </p>
<p>return(-1); </p>
<p>return(0); </p>
<p>} </p>
<p>程序15.5 SVR4下的send_fd函数 </p>
<p>当接收一描述符时,ioctl的第三个参数是一指向strrecvfd结构的指针。
</p>
<p>_______________________________________________________________________ </p>
<p>______ </p>
<p>struct strrecvfd { </p>
<p>int fd; 新描述符 </p>
<p>uid_t uid; 发送者的有效用户ID </p>
<p>gid_t gid; 发送者的有效组ID </p>
<p>char fill[8]; </p>
<p>} </p>
<p>_______________________________________________________________________ </p>
<p>______ </p>
<p>recv_fd读流管道直到接收到双字节协议的第一个字节(null字节).当发出带I_
</p>
<p>RECVFD命令的ioctl时,在流读首处的第一条消息应当是一个描述符,它是由I_SE
</p>
<p>NDFD发来的,或者得到一条出错消息。这示于程序15.6中。 </p>
<p>#include <sys/types.h> </p>
<p>#include <stropts.h> </p>
<p>#include "ourhdr.h" </p>
<p>/* Receive a file descriptor from another process (a server). </p>
<p>* In addition, any data received from the server is passed </p>
<p>* to (*userfunc)(STDERR_FILENO, buf, nbytes). We have a </p>
<p>* 2-byte protocol for receiving the fd from send_fd(). */ </p>
<p>int </p>
<p>recv_fd(int servfd, ssize_t (*userfunc)(int, const void *, size_t)) </p>
<p>{ </p>
<p>int newfd, nread, flag, status; </p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -