进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用了这种方法。但一般说来, 进程间通信(IPC:InterProcess Communication)不包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多,而且不幸的是,极少方法能在所有的Unix系 统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式)。而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信 方法:管道、消息队列、共享内存、信号量、套接口等等。
管道概念
管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”, 管道的本质是固定大小的内核缓冲区;它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。
管道限制
1)管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
2)匿名管道只能用于具有共同祖先的进程(如父进程与fork出的子进程)之间进行通信, 原因是pipe创建的是两个文件描述符, 不同进程直接无法直接获得;(通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程共享该管道)
匿名管道创建
#include <unistd.h> int pipe(int pipefd[2]);
参数
Pipefd:文件描述符数组,其中pipefd[0]表示读端,pipefd[1]表示写端,示意图如下:
(1)接下来,我们利用匿名管道来进行父子进程之间的通信,子进程向父进程发送信息。
int main() { int pipefd[2]; if(pipe(pipefd)==-1) ERR_EXIT("pipe error!"); pid_t pid; pid=fork(); if(pid==-1) ERR_EXIT("fork error"); if(pid==0) { close(pipefd[0]); write(pipefd[1],"hello",5); close(pipefd[1]); exit(EXIT_SUCCESS); } close(pipefd[1]); char buf[10]={0}; read(pipefd[0],buf,10); printf("buf=%s\n",buf); return 0; }结果:父进程接收到子进程发送的hello
(2)我们来模拟实现管道命令 ls | wc -w 关键点就是:
1.子进程运行ls,dup2(pipefd[1],STDOUT_FILENO)重定向标准输出,定位到管道写端,ls写入到管道写端而不是标准输出设备;
2.父进程运行wc -w ,wc获取数据的时候从管道读端获取,不再从标准输入设备。
3.通过管道, 将子进程的输出发送到wc的输入 。
int main() { int pipefd[2]; if(pipe(pipefd)==-1) ERR_EXIT("pipe error!"); pid_t pid; pid=fork(); if(pid==-1) ERR_EXIT("fork error"); if(pid==0) { dup2(pipefd[1],STDOUT_FILENO);//重定向输出 close(pipefd[0]); close(pipefd[1]); execlp("ls","ls",NULL);//若出错才执行下面的代码 fprintf(stderr,"error execute ls\n"); exit(EXIT_FAILURE); } dup2(pipefd[0],STDIN_FILENO); close(pipefd[0]); close(pipefd[1]); execlp("wc","wc","-w",NULL); fprintf(stderr,"error execute wc\n"); exit(EXIT_FAILURE); return 0; }
不带任何参数的cat命令是从标准输入读入命令,写到标准输出。0->Makefile ; 1->Makefile2;
int main() { close(0); open("Makefile",O_RDONLY); close(1); open("Makefile2",O_WRONLY | O_CREAT | O_TRUNC,0644); execlp("cat","cat",NULL); return 0; }管道的读写规则
我们对以上的规则一一进行验证。
(1)如果管道为空,那么read会阻塞(模式),如果使用非阻塞模式的话,也就是使用fcntl函数,对模式进行修改后
int flags=fcntl(pipefd[0],F_SETFL,flags | O_NONBLOCK); read(pipefd[0],buf,10);
此时,读操作会失败,显示资源暂且不可用的错误。
(2)管道的写端关闭,read打印输出0,但是并不报错误,显示读到了文件的末尾。
int main() { int pipefd[2]; if (pipe(pipefd) != 0) err_exit("pipe error"); pid_t pid = fork(); if (pid == -1) err_exit("fork error"); else if (pid == 0) { close(pipefd[1]); exit(EXIT_SUCCESS); } close(pipefd[1]); sleep(2); char buf[2]; if (read(pipefd[0], buf, sizeof(buf)) == 0) cout << "sure" << endl; }(3) 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进程终止。如果我们自定义SIGPIPE的处理函数的话会生效。
int main() { if (signal(SIGPIPE, handler) == SIG_ERR) err_exit("signal error"); int pipefd[2]; if (pipe(pipefd) != 0) err_exit("pipe error"); pid_t pid = fork(); if (pid == -1) err_exit("fork error"); else if (pid == 0) { close(pipefd[0]); exit(EXIT_SUCCESS); } close(pipefd[0]); sleep(2); char test; if (write(pipefd[1], &test, sizeof(test)) < 0) err_exit("write error"); }会打印出 singal error错误。
(4)关于PIPE_BUF和原子性操作之间的关系,
已知管道的PIPE_BUF为4K, 我们启动两个进程A, B向管道中各自写入68K的内容, 然后我们以4K为一组, 为了方便我们查看管道最后一个字节的内容, 多运行该程序几次, 就会发现这68K的数据会有交叉写入的情况 。
int main() { const int TEST_BUF = 68 * 1024; //设置写入的数据量为68K char bufA[TEST_BUF]; char bufB[TEST_BUF]; memset(bufA, 'A', sizeof(bufA)); memset(bufB, 'B', sizeof(bufB)); int pipefd[2]; if (pipe(pipefd) != 0) err_exit("pipe error"); pid_t pid; if ((pid = fork()) == -1) err_exit("first fork error"); else if (pid == 0) //第一个子进程A, 向管道写入bufA { close(pipefd[0]); int writeBytes = write(pipefd[1], bufA, sizeof(bufA)); cout << "A Process " << getpid() << ", write " << writeBytes << " bytes to pipe" << endl; exit(EXIT_SUCCESS); } if ((pid = fork()) == -1) err_exit("second fork error"); else if (pid == 0) //第二个子进程B, 向管道写入bufB { close(pipefd[0]); int writeBytes = write(pipefd[1], bufB, sizeof(bufB)); cout << "B Process " << getpid() << ", write " << writeBytes << " bytes to pipe" << endl; exit(EXIT_SUCCESS); } // 父进程 close(pipefd[1]); sleep(2); //等待两个子进程写完 char buf[4 * 1024]; //申请一个4K的buf int fd = open("save.txt", O_WRONLY|O_TRUNC|O_CREAT, 0666); if (fd == -1) err_exit("file open error"); while (true) { int readBytes = read(pipefd[0], buf, sizeof(buf)); if (readBytes == 0) break; if (write(fd, buf, readBytes) == -1) err_exit("write file error"); cout << "Parent Process " << getpid() << " read " << readBytes << " bytes from pipe, buf[4095] = " << buf[4095] << endl; } }