📄 00000014.htm
字号:
管道,一个子进程,使子进程的标准输入成为管道的读端,然后exec用户喜爱的分 <BR>页程序。程序14.2显示了如何实现这些操作。(本例要求在命令行中有一个参 <BR>数说明要显示的文件的名称。通常,这种类型的程序要求在终端上显示的数据已经 <BR>在存储器中。) <BR>#include <sys/wait.h> <BR>#include "ourhdr.h" <BR>#define DEF_PAGER "/usr/bin/more" /* default pager program */ <BR>int <BR>main(int argc, char *argv[]) <BR>{ <BR> int n, fd[2]; <BR> pid_t pid; <BR> char line[MAXLINE], *pager, *argv0; <BR> FILE *fp; <BR> if (argc != 2) <BR> err_quit("usage: a.out <pathname>"); <BR> if ( (fp = fopen(argv[1], "r")) == NULL) <BR> err_sys("can't open %s", argv[1]); <BR> if (pipe(fd) < 0) <BR> err_sys("pipe error"); <BR> if ( (pid = fork()) < 0) <BR> err_sys("fork error"); <BR> else if (pid > 0) { <BR> /* parent */ <BR> close(fd[0]); /* close read end */ <BR> /* parent copies argv[1] to pipe */ <BR> while (fgets(line, MAXLINE, fp) != NULL) { <BR> n = strlen(line); <BR> if (write(fd[1], line, n) != n) <BR> err_sys("write error to pipe"); <BR> } <BR> if (ferror(fp)) <BR> err_sys("fgets error"); <BR> close(fd[1]); /* close write end of pipe for reader */ <BR> if (waitpid(pid, NULL, 0) < 0) <BR> err_sys("waitpid error"); <BR> exit(0); <BR> } else { <BR> /* child */ <BR> close(fd[1]); /* close write end */ <BR> if (fd[0] != STDIN_FILENO) { <BR> if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) <BR> err_sys("dup2 error to stdin"); <BR> close(fd[0]); /* don't need this after dup2 */ <BR> } <BR> /* get arguments for execl() */ <BR> if ( (pager = getenv("PAGER")) == NULL) <BR> pager = DEF_PAGER; <BR> if ( (argv0 = strrchr(pager, '/')) != NULL) <BR> argv0++; /* step past rightmost slash */ <BR> else <BR> argv0 = pager; /* no slash in pager */ <BR> if (execl(pager, argv0, (char *) 0) < 0) <BR> err_sys("execl error for %s", pager); <BR> } <BR>} <BR>程序14.2 将文件复制到分页程序 <BR>在调用fork之前先创建一个pipe。fork之后父进程关闭其读端,子进程关闭其写端 <BR>。子进程然后调用dup2,使其标准输入成为管道的读端。当执行分页程序时,其标 <BR>准输入将是管道的读端。 <BR>当我们将一个描述符复制到另一个时(在子进程中,fd[0]复制到标准输入),应 <BR>当注意该描述符的值并不已经是所希望的值。如果该描述符已经具有所希望的值, <BR>并且我们先调用dup2,然后close则将关闭在此进程中只有该单个描述符所代表的 <BR>打开文件。(请回忆3.12节中所述,当dup2中的两个参数值相等时的操作。)在本 <BR>程序中,如果shell没有打开标准输入,那么程序开始处的fopen应已使用描述符0 <BR>,也就是最小末使用的描述符,所以fd[0]决不会等于标准输入。尽管如此,只要 <BR>先调用dup2,然后close以复制一个描述符到另一个,作为一种保护性的编程措施 <BR>,我们总是先将两个描述符进行比较。 <BR>请注意,我们是如何使用环境变量PAGER而获得用户分页程序名称的。如果这种操 <BR>作没有成功,则使用系统默认值。这是环境变量的常见用法。 <BR>实例 <BR>请回忆8.8节中的五个函数TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT以 <BR>及WAIT_CHILD。在程序10.17中,我们提供了一个使用信号的实现。程序14.3则是 <BR>一个使用管道的实现。 <BR>如图14.5所示,我们在fork之前创建了两个管道。 <BR>图14.5 用两个管道实现父-子进程的同步 <BR>父进程在调用TELL_CHILD时经由上一个管道写一个字符"P",子进程在调用TELL_P <BR>ARENT时,经由下一个管道写一个字符"C"。相应的WAIT_XXX函数调用read读一个字 <BR>符,没有读到字符时阻塞(睡眠等待)。 <BR>请注意,每一个管道都有一个额外的读取进程,这没有关系。也就是说除了子进程 <BR>从pfd1[0]读取,父进程也有上一个管道的读端。因为父进程并没有执行对该管道 <BR>的读操作,所以这不会产生任何影响。 <BR>#include "ourhdr.h" <BR>static int pfd1[2], pfd2[2]; <BR>void <BR>TELL_WAIT() <BR>{ <BR> if (pipe(pfd1) < 0 || pipe(pfd2) < 0) <BR> err_sys("pipe error"); <BR>} <BR>void <BR>TELL_PARENT(pid_t pid) <BR>{ <BR> if (write(pfd2[1], "c", 1) != 1) <BR> err_sys("write error"); <BR>} <BR>void <BR>WAIT_PARENT(void) <BR>{ <BR> char c; <BR> if (read(pfd1[0], &c, 1) != 1) <BR> err_sys("read error"); <BR> if (c != 'p') <BR> err_quit("WAIT_PARENT: incorrect data"); <BR>} <BR>void <BR>TELL_CHILD(pid_t pid) <BR>{ <BR> if (write(pfd1[1], "p", 1) != 1) <BR> err_sys("write error"); <BR>} <BR>void <BR>WAIT_CHILD(void) <BR>{ <BR> char c; <BR> if (read(pfd2[0], &c, 1) != 1) <BR> err_sys("read error"); <BR> if (c != 'c') <BR> err_quit("WAIT_CHILD: incorrect data"); <BR>} <BR>程序14.3 使父、子进程同步的例程 <BR>14.3 popen和pclose函数 <BR>因为常见的操作是创建一个连到另一个进程的管道,然后读其输出或向其发送输入 <BR>,所以标准I/O库为实现这些操作提供了两个函数popen和pclose。这两个函数实现 <BR>的操作是;创建一个管道,fork一个子进程,关闭管道的不使用端,exec一个she <BR>ll以执行命令,等待命令终止。 <BR> #include <stdio.h> <BR> FILE *popen(const char *cmdstring, const char *type); <BR>返回:若成功为文件指针,出错为NULL <BR> int pclose(FILE *fp); <BR> 返回:cmdstring
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -