本文來自個人博客:https://dunkwan.cn
管道
管道是UNIX系統IPC的最古老形式,所有UNIX系統都支持該通信機制。
管道有以下兩種侷限性。
- 歷史上,它們是半雙工的(即數據只能在一個方向上流動)。
- 管道只能在具有公共祖先的兩個進程間使用。
管道需要通過pipe
函數來進行創建。
#include <unistd.h>
int pipe(int fd[2]);
返回值:若成功,返回0;若出錯,返回-1。
下圖顯示了半雙工管道的兩種描繪方法。
左圖是管道兩端在一個進程中相互連接,右圖則強調數據需要通過內核在管道中流動。
由於單個進程中管道作用不大,通常,進程會先調用pipe
,接着fork
,從而創建從父進程到子進程的IPC通道,從子進程到父進程亦然。如下圖所示。
對於從父進程到子進程的管道,父進程需要關閉管道的讀端(fd[0]),子進程則要關閉管道寫端(fd[1])。
對於從子進程到父進程的管道,父進程需要關閉管道寫端(fd[1]),子進程則要關閉管道讀端(fd[0])。
當管道的一端被關閉後,下列兩條規則起作用。
- 當讀(read)一個寫端已關閉的管道時,在所有數據都被讀取後,read返回0,表示文件結束。
- 如果寫(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);
}
結果如下:
函數popen
和pclose
popen
和pclose
函數實現的操作是:創建一個管道,fork
一個子進程,關閉未使用的管道端,執行一個shell
運行命令,然後等待命令終止。
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
返回值:若成功,返回文件指針;若出錯,返回NULL。
int 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 參數忽略,此時mkfifo 和mkfifoat 行爲類似。指定相對路徑名時,fd 參數爲一個打開目錄的有效文件描述符,路徑名和目錄有關;若此時fd 參數爲AT_FDCWD 時,則路徑名以當前目錄開始,mkfifo 和mkfifoat 類似。 |
fd |
打開文件的有效描述符。 |
mode |
與open 函數mode 參數相同。 |