进程通信
1, 管道(pipe)
在理解管道时需要注意的地方:
.管道是半双工的,不能假设它是全双工的。
.它只能用在有公共祖先的进程之间。比如shell几个命令之间用管道连接,但父进程都是终端进程。
1.1 管道的创建
#include
int pipe(int filedes[2]);
DESCRIPTION
pipe() creates a pair of file descriptors, pointing to a pipe inode, and places them in the array pointed to by filedes. filedes[0] is for reading, filedes[1] is for writing.
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
可以这样理解:filedes[0]:可以看着是标准读描述符0;而fileds[1]:可以看做是标准写描述符1。
*在使用pipe时要注意:
.fstat可以得到该管道的文件描述符,用S_ISFIFO来测试管道。
.单个进程创建管没有任何意义,一般是父子进程协同进行,关闭多余的描述符,然后,父进程用来写管道,子进程用来读管道。
.当读一个写端已经被关闭的管道时,在所有数据被读完后,read返回0。
.当写一个读端关别的管道时,产生信号SIGPIPE,write返回-1,errno设置为EPIPE。
.在写管道时PIPE_BUF规定了内核中管道缓冲区的大小,如果多个进程同时写一个管道(或FIFO)其中有一个进程要求写入的字节数超过了该常量,那么写操作的数据可能相互穿插。
.还要注意,对于管道来说I/O函数是全缓存的。比如对于函数:fgets,fputs等。
1.2 管道的应用
管道一般用在有共同祖先的进程间进行协同工作。典型的应用是shell的各命令间用管道相连接,形成一个流水线作业,还可以利用管道实现一个令牌环状的结构,后面会有例子给出。
例子1:
/* apue 中的第一个管道的例子 */
#include "apue.h"
int main(void) { int n; int fd[2]; pid_t pid; char line[MAXLINE]; if(pipe(fd) < 0){ /* 先建立管道得到一对文件描述符 */ exit(0); } if((pid = fork()) < 0) /* 父进程把文件描述符复制给子进程 */ exit(1); else if(pid > 0){ /* 父进程写 */ close(fd[0]); /* 关闭读描述符 */ write(fd[1], "\nhello world\n", 14); } else{ /* 子进程读 */ close(fd[1]); /* 关闭写端 */ n = read(fd[0], line, MAXLINE); write(STDOUT_FILENO, line, n); } exit(0); }
|
例子2
这个例子更常用一点,利用管道的时候一般的做法是把标准输出和输入端重定向到管道,这样就可以调用一些标准的I/O函数了。例如:
#include "all.h"
static int do_pipe(char *str); //static int do_pipe2(char *str);
int main(int argc, char *argv[]) { char *str = "this is a test!\n";
return (do_pipe(str)); }
static int do_pipe(char *str) { int pfd[2]; pid_t pid; char buf;
assert(str); if (pipe(pfd) == -1) { perror("pipe"); exit(FAIL); } if ((pid = fork()) < 0) { perror("fork"); exit(FAIL); }
if (pid == 0) { /* child: read */ close(pfd[1]); /* * Here, we must check. * If pfd[0] == STDIN_FILENO, we will close both of them, * since they are the same fd. */ if (pfd[0] != STDIN_FILENO) { /* * If fd1 is equal to fd2, dup2(fd1,fd2) do nothing. */ if (dup2(pfd[0], STDIN_FILENO) < 0) { perror("dup2"); exit(FAIL); } close(pfd[0]); } /* * 这里作了重定向后,就可以使用标准的I/O函数了,比如:fgets等 */ /* 法1 : while (read(STDIN_FILENO, &buf, 1) > 0) */ while ((buf = getchar()) > 0) write(STDOUT_FILENO, &buf, 1); _exit(0); } else { /* parent: write */ close(pfd[0]); write(pfd[1], str, strlen(str)); close(pfd[1]); wait(NULL); } return OK; }
|
1.3 流水线管道
流水线管道向这样 stdin->proc1-->(==管道=)->proc2->stdout
就是由进程1从标准输入中读入数据,数据通过管道,发送到进程2,进程2接受到数据后把数据输出到标准输出。这样就形成了一个流水线, 就向命令 ls -l | sort 一样。下面是两个进程组成的流水线,当然可以是多个进程的。
例子:
/*
* do_pipe2.c
*
* Function : ls -l | sort
*/
#include "all.h"
int
main(void)
{
pid_t cpid;
int fd[2];
int master;
char name[1024];
if (pipe(fd) == -1)
exit(0);
if ((cpid = fork()) < 0) {
perror("fork()");
exit(0);
}
if (cpid > 0) { //parent
close(fd[0]);
if (fd[1] != STDOUT_FILENO) {
if (dup2(fd[1], STDOUT_FILENO) == -1) {
perror("dup2()");
exit(0);
}
close(fd[1]);
}
if (execl("/bin/ls", "ls", "-l", NULL) < 0) {
perror("execl()");
}
//wait(NULL);
exit(0);
} else if (cpid == 0) { /* child read */
close(fd[1]);
if (fd[0] != STDIN_FILENO) {
if (dup2(fd[0], STDIN_FILENO) == -1) {
perror("dup2()");
exit(0);
}
close(fd[0]);
}
/*
* Sort default default FILE is stdin.
* So child will block until parent closed pipe write end.
*/
if (execl("/bin/sort", "sort", NULL) < 0)
perror("execl()");
_exit(0);
}
return OK;
}
|
注意:这里如何把child的命令改成less将会出现问题。
1.4 协同进程
所谓协同进程就是:一个进程和一个外部程序协作完成一项任务。比如说让外部程序把从终端输出的字符串全部变成大写,等等。
参考书籍
<
>
<>
阅读(1689) | 评论(0) | 转发(1) |