進程間通信之管道、函數popen和pclose、協同進程以及FIFO

本文來自個人博客:https://dunkwan.cn

管道

管道是UNIX系統IPC的最古老形式,所有UNIX系統都支持該通信機制。

管道有以下兩種侷限性。

  1. 歷史上,它們是半雙工的(即數據只能在一個方向上流動)。
  2. 管道只能在具有公共祖先的兩個進程間使用。

管道需要通過pipe函數來進行創建。

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

下圖顯示了半雙工管道的兩種描繪方法。

左圖是管道兩端在一個進程中相互連接,右圖則強調數據需要通過內核在管道中流動。

由於單個進程中管道作用不大,通常,進程會先調用pipe,接着fork,從而創建從父進程到子進程的IPC通道,從子進程到父進程亦然。如下圖所示。

對於從父進程到子進程的管道,父進程需要關閉管道的讀端(fd[0]),子進程則要關閉管道寫端(fd[1])。

對於從子進程到父進程的管道,父進程需要關閉管道寫端(fd[1]),子進程則要關閉管道讀端(fd[0])。

當管道的一端被關閉後,下列兩條規則起作用。

  1. 當讀(read)一個寫端已關閉的管道時,在所有數據都被讀取後,read返回0,表示文件結束。
  2. 如果寫(write)一個讀端已被關閉的管道,則產生信號SIGPIPE。如果忽略該信號或者捕捉該信號並從其處理程序返回,則write返回-1,errno設置爲EPIPE

測試示例1:

父進程到子進程的管道與子進程到父進程的管道的例子。

/* parent to child */
#include "apue.h"
int
main(void)
{
	int		n;
	int		fd[2];
	pid_t	pid;
	char	line[MAXLINE];

	if (pipe(fd) < 0)
		err_sys("pipe error");
	if ((pid = fork()) < 0) {
		err_sys("fork error");
	} else if (pid > 0) {		/* parent */
		close(fd[0]);
		write(fd[1], "hello world\n", 12);
	} else {					/* child */
		close(fd[1]);
		n = read(fd[0], line, MAXLINE);
		write(STDOUT_FILENO, line, n);
	}
	exit(0);
}

/* child to parent */
#include "apue.h"

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

	if (pipe(fd) < 0)
		err_sys("pipe error");
	if ((pid = fork()) < 0) {
		err_sys("fork error");
	} else if (pid > 0) {		/* parent */
		close(fd[1]);
		n = read(fd[0], line, MAXLINE);
		write(STDOUT_FILENO, line, n);
	} else {					/* child */
		close(fd[0]);
		write(fd[1], "pipe for child to parent", 
              strlen("pipe for child to parent"));
	}
	exit(0);
}

結果如下:


測試示例2:

每次一頁的顯示已產生的輸出。

#include "apue.h"
#include <sys/wait.h>

#define	DEF_PAGER	"/bin/more"		/* default pager program */

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

	if (argc != 2)
		err_quit("usage: a.out <pathname>");

	if ((fp = fopen(argv[1], "r")) == NULL)
		err_sys("can't open %s", argv[1]);
	if (pipe(fd) < 0)
		err_sys("pipe error");

	if ((pid = fork()) < 0) {
		err_sys("fork error");
	} else if (pid > 0) {								/* parent */
		close(fd[0]);		/* close read end */

		/* parent copies argv[1] to pipe */
		while (fgets(line, MAXLINE, fp) != NULL) {
			n = strlen(line);
			if (write(fd[1], line, n) != n)
				err_sys("write error to pipe");
		}
		if (ferror(fp))
			err_sys("fgets error");

		close(fd[1]);	/* close write end of pipe for reader */

		if (waitpid(pid, NULL, 0) < 0)
			err_sys("waitpid error");
		exit(0);
	} else {										/* child */
		close(fd[1]);	/* close write end */
		if (fd[0] != STDIN_FILENO) {
			if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)
				err_sys("dup2 error to stdin");
			close(fd[0]);	/* don't need this after dup2 */
		}

		/* get arguments for execl() */
		if ((pager = getenv("PAGER")) == NULL)
			pager = DEF_PAGER;
		if ((argv0 = strrchr(pager, '/')) != NULL)
			argv0++;		/* step past rightmost slash */
		else
			argv0 = pager;	/* no slash in pager */

		if (execl(pager, argv0, (char *)0) < 0)
			err_sys("execl error for %s", pager);
	}
	exit(0);
}

結果如下:

函數popenpclose

popenpclose函數實現的操作是:創建一個管道,fork一個子進程,關閉未使用的管道端,執行一個shell運行命令,然後等待命令終止。

#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
返回值:若成功,返回文件指針;若出錯,返回NULLint pclose(FILE *fp);
返回值:若成功,返回cmdstring的終止狀態;若出錯,返回-1

測試示例1:

用popen重寫分頁輸出程序。

#include "apue.h"
#include <sys/wait.h>

#define	PAGER	"${PAGER:-more}" /* environment variable, or default */

int
main(int argc, char *argv[])
{
	char	line[MAXLINE];
	FILE	*fpin, *fpout;

	if (argc != 2)
		err_quit("usage: a.out <pathname>");
	if ((fpin = fopen(argv[1], "r")) == NULL)
		err_sys("can't open %s", argv[1]);

	if ((fpout = popen(PAGER, "w")) == NULL)
		err_sys("popen error");

	/* copy argv[1] to pager */
	while (fgets(line, MAXLINE, fpin) != NULL) {
		if (fputs(line, fpout) == EOF)
			err_sys("fputs error to pipe");
	}
	if (ferror(fpin))
		err_sys("fgets error");
	if (pclose(fpout) == -1)
		err_sys("pclose error");

	exit(0);
}

測試示例2:
通過管道執行過濾程序進行大寫字符串轉小寫。

/*   myuclc.c    */
#include "apue.h"
#include <ctype.h>

int
main(void)
{
	int		c;

	while ((c = getchar()) != EOF) {
		if (isupper(c))
			c = tolower(c);
		if (putchar(c) == EOF)
			err_sys("output error");
		if (c == '\n')
			fflush(stdout);
	}
	exit(0);
}

/*  popen1.c  */
#include "apue.h"
#include <sys/wait.h>

int
main(void)
{
	char	line[MAXLINE];
	FILE	*fpin;

	if ((fpin = popen("./myuclc", "r")) == NULL)
		err_sys("popen error");
	for ( ; ; ) {
		fputs("prompt> ", stdout);
		fflush(stdout);
		if (fgets(line, MAXLINE, fpin) == NULL)	/* read from pipe */
			break;
		if (fputs(line, stdout) == EOF)
			err_sys("fputs error to pipe");
	}
	if (pclose(fpin) == -1)
		err_sys("pclose error");
	putchar('\n');
	exit(0);
}

結果如下:

協同進程

當一個過濾程序既產生某個過濾程序的輸入,又讀取該過濾程序的輸出時,它就變成了協同進程

測試示例1:

利用協同進程來進行兩數和運算。

底層I/O版本

/* add.c */
#include "apue.h"

int main(void)
{
    int n, int1, int2;
    char line[MAXLINE];
    
    while((n = read(STDIN_FILENO, line, MAXLINE)) > 0){
        line[n] = 0;
        if(sscanf(line, "%d%d", &int1, &int2) == 2){
            sprintf(line, "%d\n", int1 + int2);
            n = strlen(line);
            if(write(STDOUT_FILENO, line, n) != n)
                err_sys("write error");
        }else{
            if(write(STDOUT_FILENO, "invalid args\n", 13) != 13)
                err_sys("write error");
        }
    }
    return 0;
}

/* main.c  */
#include "apue.h"
static void sig_pipe(int);

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

    if(signal(SIGPIPE, sig_pipe) == SIG_ERR)
        err_sys("signal error");

    if(pipe(fd1) < 0 || pipe(fd2) < 0)
        err_sys("pipe error");

    if((pid = fork()) < 0)
        err_sys("pipe error");

    if((pid = fork()) < 0){
        err_sys("pipe error");
    }else if(pid > 0)
    {
        close(fd1[0]);
        close(fd2[1]);

        while(fgets(line, MAXLINE, stdin) != NULL){
            n = strlen(line);

            if(write(fd1[1], line, n) != n)
                err_sys("write error to pipe");
            if((n = read(fd2[0], line, MAXLINE)) < 0)
                err_sys("read error from pipe");

            if(n == 0){
                err_msg("child closed pipe");
                break;    
            }
            line[n] = 0;
            if(fputs(line, stdout) == EOF)
                err_sys("fputs error");
        }
        
        if(ferror(stdin))
            err_sys("fgets error on stdin");
        exit(0);
    }else {
        close(fd1[1]);
        close(fd2[0]);

        if(fd1[0] != STDIN_FILENO){
            if(dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
                err_sys("dup2 error to stdin");
            close(fd1[0]);
        }

        if(fd2[1] != STDOUT_FILENO){
            if(dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
                err_sys("dup2 error to stdout");
            close(fd2[1]);
        }
        if(execl("./add2", "add2", (char *)0) < 0)
            err_sys("execl error");
    }
    return 0;
}

static void sig_pipe(int signo)
{
    printf("SIGPIPE caught\n");
    exit(1);
}

結果如下:

標準I/O版本

/* add2stdio.c */
#include "apue.h"

int main(void)
{
    int int1, int2;
    char line[MAXLINE];

    if(setvbuf(stdin, NULL, _IOLBF, 0) != 0)
        err_sys("setvbuf error");
    if(setvbuf(stdout, NULL, _IOLBF, 0) != 0)
        err_sys("setvbuf error");

    while(fgets(line, MAXLINE, stdin) != NULL){
        if(sscanf(line, "%d%d", &int1, &int2) == 2){
            if(printf("%d\n", int1 + int2) == EOF)
                err_sys("printf error");
        }else{
            if(printf("invalid args\n") == EOF)
                err_sys("printf error");
        }
    }
    return 0;
}
/* main.c */
/* same as above */

FIFO(命名管道)

創建FIFO文件類似於創建文件。

#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
兩個函數的返回值:若成功,返回0;若失敗,返回-1

參數說明:

參數 說明
path 指定絕對路徑時,fd參數忽略,此時mkfifomkfifoat行爲類似。指定相對路徑名時,fd參數爲一個打開目錄的有效文件描述符,路徑名和目錄有關;若此時fd參數爲AT_FDCWD時,則路徑名以當前目錄開始,mkfifomkfifoat類似。
fd 打開文件的有效描述符。
mode open函數mode參數相同。

源代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章