📄 14.htm
字号:
<p>考虑一个应用程序,它向标准输出写一个提示,然后从标准输入读1行。使用pope
</p>
<p>n,我们可以在应用程序和输入之间插入一道程序以对输入进行变换处理。图14.8显
</p>
<p>示了进程的安排。 </p>
<p>图14.8 用popen变换输入 </p>
<p>#include <ctype.h> </p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>main(void) </p>
<p>{ </p>
<p>int c; </p>
<p>while ( (c = getchar()) != EOF) { </p>
<p>if (isupper(c)) </p>
<p>c = tolower(c); </p>
<p>if (putchar(c) == EOF) </p>
<p>err_sys("output error"); </p>
<p>if (c == '\n') </p>
<p>fflush(stdout); </p>
<p>} </p>
<p>exit(0); </p>
<p>} </p>
<p>程序14.6 过滤程序,将大写字符变换成小写字符 </p>
<p>#include <sys/wait.h> </p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>main(void) </p>
<p>{ </p>
<p>char line[MAXLINE]; </p>
<p>FILE *fpin; </p>
<p>if ( (fpin = popen("myuclc", "r")) == NULL) </p>
<p>err_sys("popen error"); </p>
<p>for ( ; ; ) { </p>
<p>fputs("prompt> ", stdout); </p>
<p>fflush(stdout); </p>
<p>if (fgets(line, MAXLINE, fpin) == NULL) /* read from pipe */ </p>
<p>break; </p>
<p>if (fputs(line, stdout) == EOF) </p>
<p>err_sys("fputs error to pipe"); </p>
<p>} </p>
<p>if (pclose(fpin) == -1) </p>
<p>err_sys("pclose error"); </p>
<p>putchar('\n'); </p>
<p>exit(0); </p>
<p>} </p>
<p>程序14.7 调用大写/小写过滤程序以读取命令 </p>
<p>对输入进行的变换可能是路径名的扩充,或者是提供一种历史机制(记住以前输入
</p>
<p>的命令)。 </p>
<p>程序14.6是一个简单的过滤程序,它只是将输入复制到输出,在复制时将任一大
</p>
<p>写字符变换为小写字符。在写了一行之后,我们对标准输出进行了刷清(用fflus
</p>
<p>h),其理由将在下一节介绍协同进程时讨论。 </p>
<p>我们对该过滤程序进行编译,其可执行目标代码存放在文件myuclc中,然后在程序
</p>
<p>14.7中用popen调用它们。 </p>
<p>因为标准输出通常是按行进行缓冲的,而提示并不包含新行符,所以在写了提示之
</p>
<p>后,需要调用fflush。 </p>
<p>14.4 协同进程 </p>
<p>Unix过滤程序从标准输入读取数据,对其进行适当处理后写到标准输出。几个过滤
</p>
<p>进程通常在shell管道命令中线性地连接。当同一道程序产生某个过滤程序的输入
</p>
<p>,同时又读取该过滤程序的输出时,则该过滤程序就成为协同进程。
</p>
<p>KornShell提供了协同进程。Bourne shell和C shell并没有提供将进程连接起来按
</p>
<p>协同进程方式工作的方法。协同进程通常在shell的后台运行,其标准输入和标准
</p>
<p>输出通过管道连接到另一道程序。虽然要求初始化一个协同进程,并将其输入和输
</p>
<p>出连接到另一个进程的shell语法是十分奇特的(详细情况见Bolsky和Korn[1989]
</p>
<p>中的pp.66-66),但是协同进程的工作方式在C程序中也是非常有用的。
</p>
<p>popen提供连到另一个进程的标准输入或标准输出的一个单行管道,而对于协同进
</p>
<p>程,则它有连到另一个进程的两个单行管道--一个接到其标准输入,另一个则来从
</p>
<p>标准输出开始。我们先要将数据写到其标准输入,经其处理后,再从其标准输出读
</p>
<p>取数据。 </p>
<p>实例 </p>
<p>让我们通过一个实例来观察协同进程。进程先创建两个管道:一个是协同进程的标
</p>
<p>准输入,另一个是协同进程的标准输出。图14.9显示了这种安排。 </p>
<p>图14.9 驱动一个协同进程--写其标准输入,读其标准输出 </p>
<p>程序14.8是一个简单的协同进程,它从其标准输入读两个数,计算它们的和,然后
</p>
<p>将结果写至标准输出。 </p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>main(void) </p>
<p>{ </p>
<p>int n, int1, int2; </p>
<p>char line[MAXLINE]; </p>
<p>while ( (n = read(STDIN_FILENO, line, MAXLINE)) > 0) { </p>
<p>line[n] = 0; /* null terminate */ </p>
<p>if (sscanf(line, "%d%d", &int1, &int2) == 2) { </p>
<p>sprintf(line, "%d\n", int1 + int2); </p>
<p>n = strlen(line); </p>
<p>if (write(STDOUT_FILENO, line, n) != n) </p>
<p>err_sys("write error"); </p>
<p>} else { </p>
<p>if (write(STDOUT_FILENO, "invalid args\n", 13) != 13) </p>
<p>err_sys("write error"); </p>
<p>} </p>
<p>} </p>
<p>exit(0); </p>
<p>} </p>
<p>程序14.8 加两个数的简单滤波器 </p>
<p>对此程序进行编译,将其可执行目标代码存入名为add2的文件。 </p>
<p>程序14.9在从其标准输入读入两个数之后调用add2协同进程。从协同进程送来的值
</p>
<p>则写到其标准输出。 </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, fd1[2], fd2[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 (pipe(fd1) < 0 || pipe(fd2) < 0) </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(fd1[0]); </p>
<p>close(fd2[1]); </p>
<p>while (fgets(line, MAXLINE, stdin) != NULL) { </p>
<p>n = strlen(line); </p>
<p>if (write(fd1[1], line, n) != n) </p>
<p>err_sys("write error to pipe"); </p>
<p>if ( (n = read(fd2[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(fd1[1]); </p>
<p>close(fd2[0]); </p>
<p>if (fd1[0] != STDIN_FILENO) { </p>
<p>if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO) </p>
<p>err_sys("dup2 error to stdin"); </p>
<p>close(fd1[0]); </p>
<p>} </p>
<p>if (fd2[1] != STDOUT_FILENO) { </p>
<p>if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO) </p>
<p>err_sys("dup2 error to stdout"); </p>
<p>close(fd2[1]); </p>
<p>} </p>
<p>if (execl("./add2", "add2", (char *) 0) < 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>程序14.9 驱动add2过滤程序的程序 </p>
<p>在程序中创建了两个管道,父、子进程各自关闭它们不需使用的端口。创建两个管
</p>
<p>道的理由是;一个用作为协同进程的标准输入,另一个则用作为它的标准输出。然
</p>
<p>后在调用execl之前,子进程调用dup2使管道描述符移至其标准输入和输出。
</p>
<p>若编译和运行程序14.9,它如所希望的那样进行工作。进一步考虑,在程序14.9正
</p>
<p>等待我们的输入时,若kill add2协同进程;然后输入两个数;当程序14.9对管道
</p>
<p>进行写操作时,由于该管道无读进程,于是调用信号处理程序(见练习14.4)。
</p>
<p>在程序15.1中,我们将提供这一实例的另一个版本,它使用一个全双工管道而不是
</p>
<p>两个半双工管道。 </p>
<p>实例 </p>
<p>在协同进程add2(程序14.8)中,我们使用了Unix I/O:read和write。如果我们使
</p>
<p>用标准I/O改写该协同进程,其后果是什么呢?程序14.10就是这个新版本。
</p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>main(void) </p>
<p>{ </p>
<p>int int1, int2; </p>
<p>char line[MAXLINE]; </p>
<p>while (fgets(line, MAXLINE, stdin) != NULL) { </p>
<p>if (sscanf(line, "%d%d", &int1, &int2) == 2) { </p>
<p>if (printf("%d\n", int1 + int2) == EOF) </p>
<p>err_sys("printf error"); </p>
<p>} else { </p>
<p>if (printf("invalid args\n") == EOF) </p>
<p>err_sys("printf error"); </p>
<p>} </p>
<p>} </p>
<p>exit(0); </p>
<p>} </p>
<p>程序14.10 加两个数的滤波器,使用标准I/O </p>
<p>若程序14.9调用此新的协同进程,则它就不再工作。问题出在系统默认的标准I/O
</p>
<p>缓存机制上。当程序14.10被调用时,对标准输入的第一个fgets引起标准I/O库分
</p>
<p>配一个缓存,并选择缓存的类型。因为标准输入是个管道,所以isatty为假,于是
</p>
<p>标准I/O库由系统默认是全缓冲的。对标准输出也有同样的处理。当add2从其标准
</p>
<p>输入读取而发生堵塞时,程序14.9在从管道读时也发生堵塞,于是产生了死锁。
</p>
<p>我们对将要执行(exec)的这样一个协同进程可以加以控制。在程序14.10中的wh
</p>
<p>ile循环之前加上下面4行。 </p>
<p>if ( setvbuf(stdin ,NULL, _IOLBF, O) !=0 ) </p>
<p>err_sys("setvbuf error"); </p>
<p>if ( setvbuf(stdout,NULL,_IOLBF,O)!=0 ) </p>
<p>err_sys("setvbuf error"); </p>
<p>这使得当有一行可用时,fgets即返回,使得当输出一新行符时,printf即执行ff
</p>
<p>lush操作。对setvbuf进行了这些显式调用,使得程序14.10能正常工作。
</p>
<p>如果我们不能修改这样的协同进程,则需使用其它技术。例如,如果在我们的程序
</p>
<p>中使用awk(1)代替add2作为协同进程,则下列命令行不能工作; </p>
<p>#!/bin/awk/ -f </p>
<p>{print $1+$2} </p>
<p>不能工作的原因还是标准I/O的缓冲机制问题。但是,在这种情况下我们不能改变
</p>
<p>awk的工作方式(除非我们有awk的源代码)。 </p>
<p>对这种问题的一般解决方法是使被调用(在本例中是awk)的协同进程认为它的标
</p>
<p>准输入和输出是连到一个终端。这使得在协同进程中的标准I/O例程对这两个I/O流
</p>
<p>进行行缓存,这类似于我们在前面所做的显式setvbuf调用。在第十九章中,我们
</p>
<p>将用伪终端实现这一点。 </p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -