Linux下的进程间通信(一)
一、 Linux进程间通信概述
主要分为以下几种:管道(无名管道pipe和命名管道FIFO)、信号(signal)、消息队列、共享内存、信号量、套接字(socket)等。
主要分为以下4个领域
(1)消息传递(管道,FIFO,消息队列)
(2)同步(互斥锁,条件变量,读写锁,信号量)
(3)共享内存区(匿名共享内存区,有名共享内存区)
(4)过程调用(Solaris门,Sun RPC)
二、 无名管道PIPE
普通Linux都允许重定向,而重定向就是使用的管道。管道是单向的,先进先出,固定大小,一个进程向管道里进行输入,另一个进程从管道里获取输出。一旦数据被读出,则从管道里面删除,其它进程无法再读到该数据。
管道采用了简单的流控制模式,进程在读空管道时,会一直阻塞到有进程对管道进行写时;同样,进行在写一个已满的管道时,会阻塞到有进程将管道数据取出时。
1.主要函数
l int pipe(int fd[2])
返回值:如果成功,返回0;否则返回-1
errno=EMFILE(没有空亲的文件描述符)
EMFILE(系统文件表已满)
EFAULT(fd数组无效)
注意:fd[0]用于读取管道,fd[1]用于写入管道。
l int read(int fd, char *buf, int len)
int write(int fd, char *buf, int len)
返回值:如果成功,则返回读出或写入的字节数;否则,返回-1
l FILE *popen(const char *command, const char *type)
与linux中文件操作有文件流的标准I/O一样,管道的操作也支持基于文件流的模式。
返回值:如果成功,返回一个新的文件流;否则返回NULL
参数:command为输入的命令,type为r或w,表示读或写,不能同时为读写。如果输入rw,则只读取第一个字符作为type,即为读。
l int pclose(FILE *stream)
返回值:返回系统调用wait4()的状态。如果stream无效或调用wait4()失败,则返回1。
2. 实例
(1)管道实例
#include <iostream.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; int main(int argc, char *argv[]) { int pipe_fd1[2], pipe_fd2[2]; pid_t pid; char buf_r[100], buf_pr[100]; char* p_wbuf; int r_num, pr_num; if(pipe(pipe_fd1) < 0 || pipe(pipe_fd2) < 0) { cout<<"Create pipe failed!"<<endl; return -1; } if((pid = fork()) < 0) { cout<<"Create process failed!"<<endl; return -1; } else if(pid == 0) //child { close(pipe_fd1[1]); if((r_num=read(pipe_fd1[0],buf_r,100))>0){ cout<<"Child receive : "<<buf_r<<endl; close(pipe_fd2[0]); if(write(pipe_fd2[1], "I receive the data", strlen("I receive the data")) < 0) cout<<"child write failed"<<endl; close(pipe_fd2[1]); } else { close(pipe_fd2[0]); if(write(pipe_fd2[1], "I cannot receive the data", strlen("I cannot receive the data")) < 0) cout<<"child write failed"<<endl; close(pipe_fd2[1]); } close(pipe_fd1[0]); exit(0); } else //parent { close(pipe_fd1[0]); if(write(pipe_fd1[1], "Hello child, I am parent!", strlen("Hello child, I am parent!")) < 0) cout<<"parent write failed"<<endl; close(pipe_fd1[1]); close(pipe_fd2[1]); if((pr_num=read(pipe_fd2[0],buf_pr,100))>0){ cout<<"Parent receive : "<<buf_pr<<endl; } close(pipe_fd2[0]); waitpid(pid, NULL, 0); exit(0); } return 0; }
(2)popen实例
#include <iostream.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <fcntl.h> #define BUFSIZE 1024 int main(int argc, char *argv[]) { FILE *fp = NULL; char *cmd = "ps -ef"; char buf[BUFSIZE]; buf[BUFSIZE]='\0'; if((fp = popen(cmd, "r")) < 0) { cout<<"popen failed!"<<endl; return -1; } while(fgets(buf, BUFSIZE, fp) != NULL) { cout<<buf<<endl; } pclose(fp); exit(0); }
3.协同进程
定义:当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出,该过滤程序就是协同进程。
图:协同进程
实例的第一个例子中的子进程就是一个协同进程。
三、 有名管道FIFO
FIFO有时被称为命名管道。管道(无名管道)只能由相关进程使用,这些相关进程的共同祖先创建了管道。但是,FIFO可以使不相干的进程也能交换数据。
FIFO其实也是一种文件类型,存在于文件系统中,所以创建FIFO类似于创建一个文件。
当共享管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中以便以后使用。
1.函数
Int mkfifo(const char *pathname, mode_t mode);
头文件:#include <sys/stat.h>
参数:mode与open函数中的mode相同,有各种权限。
返回值:如果创建成功,则返回0;否则返回-1
一旦已经用mkfifo创建了一个FIFO,就可以用open打开它。一般的文件操作函数都能够用于FIFO(close,read,write和unlink等)。
当打开一个FIFO时,非阻塞标志(O_NONBLOCK)产生下列影响:
ü 没有指定O_NONBLOCK(一般情况下),只读open要阻塞到某个进程以写的方式打开此FIFO时,同样,只写open要阻塞到某个进程以读的方式打开此FIFO。
ü 如果指定O_NONBLOCK,则只读open立即返回;而如果没有进程已经为读打开一个FIFO,那么只写open将会出错,返回-1,errno是ENXIO。
类似管道PIPE,如果用write写一个尚无进程为读而打开的FIFO,则产生一个SIGPIPE信号。若某个FIFO的最后一个写进程关闭该FIFO,则将为该FIFO的读进程产生一个文件结束标识。
一个FIFO可能有多个写进程,这可能会产生数据的穿插,可以用原子写操作来避免这个问题。
2.实例
(1) 用FIFO复制输出流
FIFO可被用于复制串行管道命令之间的输出流,这样就不需要写数据到临时磁盘文件中。
大家都知道,linux管道命令只能有一个进程来接收上一个进程的输出作为输入,如果有两个以上进程需要上一个进程的输出作为输入呢?这时就可以用FIFO了。
图:a为目标,b为通过FIFO实现目标
# mkfifo fifo1 # wc –l < fifo1 & # ls -al | tee fifo1 | grep fifo1
注意:wc -l < fifo1(FIFO的读操作)一定要写向fifo1写操作的前面,否则会阻塞住。
(2) 客户进程-服务器进程使用FIFO进行通信
FIFO的另一个应用是在客户端进程和服务器进程之间传送数据。多个客户进程同时向一个FIFO中进行写操作,即向服务器进程发送请求。如果写入长度小于PIPE_BUF字节,则无需担心数据的交错;否则则需要采用原子写操作。
图:客户进程-服务器进程使用FIFO进行通信
这种设计是有缺陷的,一旦客户进程退出,很有可能留下一个只有写进程而没有读进程的客户专用FIFO。另外,如果服务进程只对众所周知的FIFO只读,那么,当客户进程减到0时,服务进程将读到文件结束标识,从而结束对FIFO的监听,解决办法是服务进程对众所周知的FIFO有读-写方式。
(1)代码-一个进程向FIFO写数据,另一个进程从FIFO读数据
write_fifo.cpp
#include<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include <iostream.h> #define FIFO "/tmp/myfifo" using namespace std; int main(int argc, char *argv[]) { char *buf = "Hello, I am a writer for FIFO!"; int fd; int nwrite = 0; if((mkfifo(FIFO,O_CREAT|O_EXCL) < 0) && (errno!=EEXIST)) { cout<<"Create FIFO failed and the FIFO is not existed!"<<endl; return -1; } if((fd = open(FIFO, O_WRONLY,0777)) == -1) { cout<<"Open failed!"<<endl; cout<<"Errno is "<<errno<<endl; return -1; } if((nwrite = write(fd, buf, strlen(buf))) < 0) { cout<<"Write Failed!"<<endl; return -1; } cout<<"Write:"<<buf<<endl; return 0; }
read_fifo.cpp
#include<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include <iostream.h> #define FIFO "/tmp/myfifo" using namespace std; int main(int argc, char *argv[]) { char buf_r[100]; int fd; int nread; if((mkfifo(FIFO,O_CREAT|O_EXCL) < 0) && (errno!=EEXIST)) { cout<<"Create FIFO failed and the FIFO is not existed!"<<endl; return -1; } if((fd = open(FIFO, O_RDONLY, 0777)) == -1) { cout<<"Open failed!"<<endl; cout<<"Errno is "<<errno<<endl; return -1; } while(1){ memset(buf_r, 0, sizeof(buf_r)); if((nread = read(fd, buf_r, 100))== -1) { if(errno == EAGAIN) cout<<"no data yet\n"<<endl; } else if(nread > 0) break; } cout<<"I receive the data:"<<buf_r<<endl; return 0; }