(1)什么是协同进程?
当一个过滤程序既产生某个过滤程序的输入,又读取该过滤程序的输出时,它就变成了协同进程。
(2)
协同进程通常在shell的后台运行,其标准输入和标准输出通过管道连接到另一个程序。
(3)popen所创建的进程与协同进程的区别。
popen只提供连接到另一个进程的标准输入或标准输出的一个单向管道。
协同进程有连接到另一个进程的两个单向管道。
(1)一个简单的过滤程序(add2为编译后的可执行程序)
#include "apue.h" int main(void) { int n, int1, int2; char line[MAXLINE]; while ((n = read(STDIN_FILENO, line, MAXLINE)) > 0) { line[n] = 0; /* null terminate */ 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"); } } exit(0); }
(2)
程序通过底层I/O(UNIX系统调用):read,write函数实现对标准输入输出的读写。(它们是不带缓冲的I/O)<7>
若使用标准I/O函数fgets和printf代替,则此程序不工作。因为标准I/O库对管道默认使用全缓冲。(标准I/O函数为那些不带缓冲I/O函数提供了一个带缓冲的接口)
当add2从其标准输入读取而发生阻塞时,驱动程序从管道读时也发生阻塞,于是产生了死锁。
可通过setvbuf函数将标准输入输出改为行缓冲使程序正常工作。
若在其他情况下,无法修改过滤程序,则只能通过其他技术来时过滤程序正常工作。比如伪终端技术。
(3)
int sscanf(const char *str, const char *format, ...);
int sprintf(char *str, const char *format, ...);
这两个函数对字符串进行读写。
(1)驱动过滤程序的程序。
#include "apue.h" static void sig_pipe(int); /* our signal handler */ 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("fork error"); } else if (pid > 0) { /* parent */ 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; /* null terminate */ if (fputs(line, stdout) == EOF) err_sys("fputs error"); } if (ferror(stdin)) err_sys("fgets error on stdin"); exit(0); } else { /* child */ 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"); } exit(0); } static void sig_pipe(int signo) { printf("SIGPIPE caught\n"); exit(1); }
(2)
1)注册SIGPIPE的信号处理程序。
SIGPIPE信号在向没有读进程的管道写时产生。
这个例子中,在等待输入的时候杀死了协同进程,然后又输入两个数,则产生这个信号。
2)创建两个管道。
3)创建子进程。
4)父子进程分别关闭不需要的管道。
5)父进程使用fgets和fputs读写标准输入,用write和read读写管道。