进程间通信概述与管道机制
Linux进程间通信类型:
1.信号机制 发送信号
2.管道机制 读者-写者
3.消息队列 消息缓冲队列通信机制,传送小量数据
4 . 共享内存 传送大量数据,锁机制
5. 信号量 P、V操作,进程间同步互斥,生产者-消费者,哲学家进餐等。
现在linux使用的进程间通信方式:
(1) 管道(pipe)和有名管道(FIFO) 读者-写者
(2) 信号(signal) 发送信号
(3) 消息队列 传送小量数据,消息缓冲队列通信机制
(4) 共享内存 传送大量数据,锁机制
(5) 信号量 最重要
(6) 套接字(socket)
管道通信
普通的Linux shell都允许重定向,而重定向使用的就是管道。例如:ps | grep vsftpd
管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
管道主要用于不同进程间通信。
1. 管道创建与关闭
创建一个简单的管道,可以使用系统调用pipe( )。它接受一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。
系统调用:pipe( );
原型:int pipe( int fd[2] );
返回值:如果系统调用成功,返回0。如果系统调用失败返回- 1:
errno = EMFILE (没有空闲的文件描述符)
EMFILE (系统文件表已满)
EFAULT (fd数组无效)
注意:fd[0] 用于读取管道,fd[1] 用于写入管道。
图1 linux中管道与文件描述符的关系
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pipe_fd[2];
if(pipe(pipe_fd)<0)
{
printf("pipe create error/n");
return -1;
}
else
printf("pipe create success/n");
close(pipe_fd[0]); /* */
close(pipe_fd[1]); /* */
}
注:管道创建在内核中,命名管道创建在文件系统中
关闭后没有了;关闭后内容没了,剩下空壳
2. 管道读写
管道主要用于不同进程间通信。实际上,通常先创建一个管道,再通过fork函数创建一个子进程。
图2 父子进程管道的文件描述符对应关系
子进程写入和父进程读的命名管道:
图 3 关闭父进程fd[1] 和 子进程[0]
3. 管道读写注意事项
可以通过打开两个管道来创建一个双向的管道。但需要在子进程中正确地设置文件描述符。
必须在系统调用fork( )中调用pipe( ),否则子进程将不会继承文件描述符。
当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。而在命名管道中却不是这样。注:管道只用于父子进程间。
管道实例见:pipe_rw.c
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
char* p_wbuf;
int r_num;
memset(buf_r,0,sizeof(buf_r));
if(pipe(pipe_fd)<0)
{
printf("pipe create error/n");
return -1;
}
if((pid=fork())==0) 在子进程里,子进程读
{
printf("/n");
close(pipe_fd[1]);
sleep(2);
if((r_num=read(pipe_fd[0],buf_r,100))>0){
printf( "%d numbers read from the pipe is %s/n",r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}
else if(pid>0) 在父进程里,父进程写
{
close(pipe_fd[0]);
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent write1 success!/n");
if(write(pipe_fd[1]," Pipe",5)!=-1)
printf("parent write2 success!/n");
close(pipe_fd[1]);
sleep(3);
waitpid(pid,NULL,0); 在父进程中等待某个进程结束,pid是等待结束子进程的ID号
exit(0);
}
}
程序功能:子进程读,父进程写
4. 标准流管道
与linux中文件操作有文件流的标准I/O一样,管道的操作也支持基于文件流的模式。接口函数如下
库函数:popen();
原型: FILE *popen ( char *command, char *type);
返回值:如果成功,返回一个新的文件流。如果无法创建进程或者管道,返回NULL。
管道中数据流的方向是由第二个参数type控制的。此参数可以是r或者w,分别代表读或写。但不能同时为读和写。在Linux系统下,管道将会以参数type中第一个字符代表的方式打开。所以,如果你在参数type中写入rw,管道将会以读的方式打开。
使用popen()创建的管道必须使用pclose( )关闭。其实,popen/pclose和标准文件输入/输出流中的fopen() / fclose()十分相似。
库函数: pclose();
原型: int pclose( FILE *stream );
返回值: 返回系统调用wait4( )的状态。
如果stream无效,或者系统调用wait4( )失败,则返回 -1。
注意此库函数等待管道进程运行结束,然后关闭文件流。
库函数pclose( )在使用popen( )创建的进程上执行wait4( )函数。当它返回时,它将破坏管道和文件系统。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFSIZE 1024
int main()
{
FILE *fp;
char *cmd = "ps -ef";
char buf[BUFSIZE];
buf[BUFSIZE] = '/0';
if((fp=popen(cmd,"r"))==NULL)
perror("popen");
while((fgets(buf,BUFSIZE,fp))!=NULL)
printf("%s",buf);
pclose(fp);
exit(0);
}
5. 命名管道(FIFO)
5.1 基本概念
命名管道和一般的管道基本相同,但也有一些显著的不同:
n 命名管道是在文件系统中作为一个特殊的设备文件而存在的。
n 不同祖先的进程之间可以通过管道共享数据。
n 当共享管道的进程执行完所有的I / O操作以后,命名管道将继续保存在文件系统中以便以后使用。
管道只能由相关进程使用,它们共同的祖先进程创建了管道。但是,通过FIFO,不相关的进程也能交换数据。
5.2 命名管道创建与操作
名管道创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname, mode_t mode) ;
返回:若成功则为0,若出错则为- 1
一旦已经用mkfifo创建了一个FIFO,就可用open打开它。确实,一般的文件I / O函数(close、read、write、unlink等)都可用于FIFO。
当打开一个FIFO时,非阻塞标志(O_NONBLOCK)产生下列影响:
(1) 在一般情况中(没有说明O_NONBLOCK),只读打开要阻塞到某个其他进程为写打开此FIFO。类似,为写而打开一个FIFO要阻塞到某个其他进程为读而打开它。
(2) 如果指定了O_NONBLOCK,则只读打开立即返回。但是,如果没有进程已经为读而打开一个FIFO,那么只写打开将出错返回,其errno是ENXIO。
类似于管道,若写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。若某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束标志。
FIFO相关出错信息:
n EACCES (无存取权限)
n EEXIST (指定文件不存在)
n ENAMETOOLONG (路径名太长)
n ENOENT (包含的目录不存在)
n ENOSPC (文件系统剩余空间不足)
n ENOTDIR (文件路径无效)
n EROFS (指定的文件存在于只读文件系统中)
实例见:fifo_write.c 、 fifo_read.c
fifo_write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO "/tmp/myfifo"
main(int argc,char** argv)
{
char buf_r[100];
int fd;
int nread;
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf("cannot create fifoserver/n");
printf("Preparing for reading bytes.../n");
memset(buf_r,0,sizeof(buf_r));
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
exit(1);
}
while(1)
{
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1){
if(errno==EAGAIN)
printf("no data yet/n");
}
printf("read %s from FIFO/n",buf_r);
sleep(1);
}
pause();
unlink(FIFO);
}
fifo_read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO_SERVER "/tmp/myfifo"
main(int argc,char** argv)
{
int fd;
char w_buf[100];
int nwrite;
if(fd==-1)
if(errno==ENXIO)
printf("open error; no reading process/n");
fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
if(argc==1)
printf("Please send something/n");
strcpy(w_buf,argv[1]);
if((nwrite=write(fd,w_buf,100))==-1)
{
if(errno==EAGAIN)
printf("The FIFO has not been read yet.Please try later/n");
}
else
printf("write %s to the FIFO/n",w_buf);
}