进程间通信IPC(InterProcess Communication)是指能在两个进程间进行数据交换的机制。
1、管道
管道是UNIX系统IPC的最古老形式,并且所有UNIX系统都提供此通信机制。管道有下面两种局限性:
(1)历史上,它们是半双工的,现在某些系统提供全双工管道
(2)它们只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可以应用该管道。
管道是由调用pipe函数创建
#include <unistd.h>
int pipe(int filedes[2])
经由参数filedes返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开,filedes[1]的输出是filedes[0]的输入
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int fd[2];
pid_t pid;
char line[32];
pipe(fd);
pid = fork();
if(pid > 0) {
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
close(fd[1]);
write(1,"hello linux\n",12);//由于父子进程执行顺序不定,下面最好加上waitpid(pid,NULL,0)
}else{
close(fd[1]);
dup2(fd[0],STDIN_FILENO);
close(fd[0]);
int n = read(0,line,32);
write(1,line,n);
}
exit(0);
}
将文件复制到分页程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#define PAGER "/bin/more"
int main(int argc,char*argv[]){
int fd[2];
pid_t pid;
char line[64];
FILE *fp;
fp = fopen(argv[1],"r");
pipe(fd);
pid = fork();
if(pid > 0){
close(fd[0]);
while(fgets(line,63,fp) != NULL){//父进程先将文件内容通过fd[1]写端,写到缓冲区中
int n = strlen(line);
write(fd[1],line,n);
}
close(fd[1]);
waitpid(pid,NULL,0);//等待子进程读取
}else{
close(fd[1]);
if(fd[0] != 0){
dup2(fd[0],0);
close(fd[0]);
}
char* argv0;
if((argv0 = strrchr(PAGER,'/')) != NULL)
argv0 ++;
else argv0 = PAGER;
execl(PAGER,argv0,(char*)0);
}
exit(0);
}
2、popen和pclose函数
常见的操作是创建一个管道连接到另一个进程,然后读取输出或向其输入端发送数据,为此,标准IO库提供两个函数popen和pclose。
这两个函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止。
#include <stdio.h>
FILE *popen(const char* cmdstring,const char* type)
int pclose(FILE* fp)
函数popen先执行fork,然后调用exec以执行cmdstring,并且返回一个标准IO文件指针。
如果type是"r",则文件指针连接到cmdstring的标准输出。
如果type是"w",则文件指针连接到cmdstring的标准输入。
将文件复制到分页程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char* argv[]){
FILE *fpin,*fpout;
fpin = fopen(argv[1],"r");//以读方式打开文件
fpout = popen("more","w");
char line[64];
while(fgets(line,63,fpin) != NULL){
fputs(line,fpout);
}
pclose(fpin);
pclose(fpout);
exit(0);
}
小写换大写程序
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
int c;
char line[164],out[164];
FILE* fp;
fp = fopen("upper.c","r");
while(fgets(line,163,fp) != NULL){
memset(out,0,sizeof(out));
for(c = 0 ; c < strlen(line) ; c ++)
if(line[c] >= 'a' && line[c] <= 'z') out[c] = toupper(line[c]);
else out[c] = line[c];
fputs(out,stdout);
fflush(stdout);
}
exit(0);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char* argv[]){
FILE *fop;
fop = popen("./upper","r");//连接到cmdstring的标准输出
char line[64];
while(1){
fflush(stdout);
if(fgets(line,63,fop) == NULL) break;
fputs(line,stdout);
}
pclose(fop);
}
3、协同进程
UNIX系统过滤程序从标准输入读取数据(popen函数),对其进行适当处理后写到标准输出。几个过滤程序通常在shell管道命令行中线性地连接。当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出时,则该过滤程序就成为协同进程(coprocess)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(){
int n,a,b;
char line[64];
while((n = read(0,line,63)) > 0){
line[n] = 0;
sscanf(line,"%d %d",&a,&b);
sprintf(line,"%d + %d = %d\n",a,b,a+b);
write(1,line,strlen(line));
}
exit(0);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(){//代码没有任何容错处理啊
int n,fd1[2],fd2[2];
char line[64];
pipe(fd1),pipe(fd2);
pid_t pid;
pid = fork();
if(pid > 0){
close(fd1[0]);
close(fd2[1]);
while(fgets(line,63,stdin) != NULL){
write(fd1[1],line,strlen(line));
n = read(fd2[0],line,63);
line[n] = 0;
fputs(line,stdout);
}
}else{
close(fd1[1]);
close(fd2[0]);
dup2(fd1[0],0);
dup2(fd2[1],1);
execl("./add","add",(char*)0);
}
exit(0);
}
4、FIFO
FIFO也被成为命名管道,通过FIFO,不相关的进程也能交换数据。创建FIFO类似于创建文件。
#include <sys/stat.h>
int mkfifo(const char* pathname,mode_t mode);
一旦用mkfido创建一个FIFO,就可以open打开,一般的文件IO函数(close,read,write,unlink等)都可以用于FIFO
命名管道的打开:命名管道比管道多了一个打开操作:open ,在open时,用O_NONBLOCK 标志表示非阻塞模式,如fd=open(“/tmp/fifo”,O_RDONLY|O_NONBLOCK,0)。
命名管道的读入:read 读取管道数据,读取分为阻塞和非阻塞模式,阻塞模式下,如果没有数据被入,进程会在read处停下来.直到有新数据被写入,或管道被关闭,才会继续。
命名管道的写入:write 写入管道数据,PIPE_BUF表示一次触发管道读操作最大长度.如果每次写入数据长于PIPE_BUF ,write将会多次触发read 操作。
命名管道的关闭:管道文件也是一种文件,因此用close关闭即可。
FIFO的两种用途:
(1)FIFO有shell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。
(2)FIFO用于客户进程—服务器进程应用程序中,以在客户进程和服务器进程之间传递数据。
5、XSI IPC
有三种IPC称为XSI IPC:消息队列,信号量,共享存储器。
下面单独分章节分析XSI IPC 和 FIFO