第十五章:进程间通信

15.1:引言

 第8章说明了进程控制原语并且观察了如何调用多个进程。但是这些进程之间交换信息的方法只能经由fork或exec传送打开文件,或者通过文件系统。本章将说明进程之间相互通信的其他技术--IPC(InterProcess Communication)。

过去,Unix系统IPC是各种进程间通信方式的统称,但是其中极少能在所有Unix系统实现中进行移植。随着Posix和open group(以前是X/Open)标准化的推进和影响的扩大,情况虽然已得到改善,但差别仍然存在。下图列出了本书讨论的四种实现所支持的不同形式的IPC。

我们将IPC的讨论分为3章。本章讨论经典的IPC:管道、FIFO、消息队列、信号量以及共享存储器。下一章将观察使用套接字的网络IPC。第17章将考查IPC的某些高级特征。

15.2:管道

管道是由调用pipe函数而创建的:

#include <unistd.h>
int pipe(int filedes[2]);
// 返回值:若成功则返回0,若出错则返回-1

经由参数filedes返回的两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。

调用fork之后做什么取决于我们想要有的数据流的方向。对于从父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程则关闭写端(fd[1])。下图显示了在此之后描述符的安排。

为了构造从子进程到父进程的管道,父进程关闭fd[1],子进程关闭fd[0]。

当管道的一端被关闭后,下列两条规则起作用:

(1)当读一个写端已被关闭的管道时,在所有数据都被读取之后,read返回0,以指示达到了文件结束处。

(2)如果写一个读端已被关闭的管道时,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1,errno设置为EPIPE。

示例:经由管道父进程向子进程传送数据

#include <unistd.h>
#include <stdio.h>
#include <errno.h>

#define MAXLINE 256

int main(void)
{
    int n;
    int fd[2];
    pid_t pid;
    char line[MAXLINE];

    if (pipe(fd) < 0)
    {
        perror("pipe error");
    }

    if ((pid = fork()) > 0)
    { // parent 
        close(fd[0]);
        write(fd[1], "Hello world!", 12);
    }
    else if (pid == 0)
    { // child
        close(fd[1]);
        n = read(fd[0], line, MAXLINE);
        write(1, line, n);
    }
    else
    { // error
       perror("fork error"); 
    }

    return 0;
}

在上面的例子中,直接对管道描述符调用read和write。更好的方法是将管道描述符复制为标准输入和标准输出。在此之后通常子进程执行另一个程序,改程序或者从标准输入读数据,或者将数据写至其标准输出。

试编写一个程序,其功能时每次一页显示已产生的输出。

示例:将文件复制到分页程序

#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DEF_PAGER "/bin/more" // default pager program
#define MAXLINE 256

int main(int argc, char **argv)
{
    int n;
    int fd[2];
    pid_t pid;
    char *pager, *argv0;
    char line[MAXLINE + 1];
    memset(line, 0, MAXLINE + 1);
    FILE *fp;

    if (argc != 2)
    {
        printf("Usage: a.out <pathname>\n");
        return -1;
    }

    if ((fp = fopen(argv[1], "r")) == NULL)
    {
        perror("can't open file");
        return -1;
    }

    if (pipe(fd) < 0)
    {
        perror("pipe error");
        return -1;
    }

    if ((pid = fork()) > 0)
    { // parent
        close(fd[0]);
        while (fgets(line, MAXLINE, fp) != NULL)
        {
            n = strlen(line);
            if (write(fd[1], line, n) != n)
            {
                perror("write error to pipe");
            }
        }

        if (ferror(fp))
        {
            perror("fgets error");
        }
        close(fd[1]);
        if (waitpid(pid, NULL, 0) < 0)
        {
            perror("waitpid error");
        }

        return 0;
    }
    else if (pid == 0)
    { // child
        close(fd[1]);
        if (fd[0] != STDIN_FILENO)
        {
            if (dup2(fd[0], STDIN_FILENO) != 0)
            {
                perror("dup2 error to stdin");
            }
            close(fd[0]);
        }
        pager = getenv("PAGER");

        if ((pager = getenv("PAGER")) == NULL)
        {
            pager = DEF_PAGER;
        }

        if ((argv0 = strrchr(pager, '/')) != NULL)
        {
            argv0++;
        }
        else
        {
            argv0 = pager;
        }

        if (execl(pager, argv0, (char*)0) < 0)
        {
            perror("execl error for pager");
        }
    }
    else
    { // error
        perror("fork error");
        return -1;
    }

    return 0;
}

15.3:popen和pclose函数

常见的操作是创建一个管道连接到另一个进程,然后读取其输出或者向其输入端发送数据,为此,标准I/O库提供了两个函数popen和pclose。这两个函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止。

 

posted @ 2015-11-02 10:53  冷冰若水  阅读(237)  评论(0编辑  收藏  举报