Linux进程间通信二之管道
默认情况下,一个进程会打开三个默认的设备文件:标准输入设备即键盘,标准输出设备即显示器,标准错误输出设备即显示器。默认从标准输入读取信息,将正确的信息写入到标准输出,将错误的信息写入到标准输出。在shell命令应用中“|”可以将两个命令连接起来,将前一个命令的输出作为另一个命令的输入,连接输入输出的中间设备即为一个管道文件。这种管道是临时的,命令执行完后会自动消失,这类管道称为无名管道。
无名管道是一种特殊的文件,它不属于文件系统,而是对于内核中的一段内存区域,由操作系统管理和维护,应用程序只能通过系统调用访问它。
无名管道的内核资源会在进程退出后自动释放。不能跟普通文件那样存放大量常规数据。在编程应用方面具有普通文件一样的特点,可以用read/write函数读写,但不能用lseek函数修改读写的位置,管道需要满足先进先出的原则。
无名管道的操作:
1.创建无名管道
int fd[2];
pipe(fd[2]);
如果执行成功它会存储两个整型的文件描述符于fd[2],它分别代表管道的两端。系统调用失败会返回-1。无名管道是单向的,即只能从一个进程向另一个进程发送信息。管道中的数据从fd[0]读文件描述符读出,输入到管道中的数据从fd[1]写文件描述符写入。如果需要全双工的需要两个管道。
2.读写无名管道
任何的进程读/写无名管道时必须确认还存在一个进程(可以是自己),该进程以写/读的方式访问管道(可以操作相应的文件描述符)。读写管道用read和write,两者以堵塞方式读写管道,可以用fcntl函数修改。
(1)以堵塞的方式读无名管道,如果当前没有一个进程(包括当前进程)可以访问写端,读操作将立即返回。并按如写操作:
如果管道现有数据无数据,立即返回0;
如果管道现有数据大于要读出的数据,立即读出期望大小的数据;
如果管道现有数据小于要读出的数据,立即读出现有所有的数据;
#include<stdio.h>
#include<unistd.h>
int main(void)
{
int p[2];
pipe(p);
close(p[1]);
char buf[128];
memset(buf,'\0',128);
int ret=-1;
ret=read(p[0],buf,128);
printf("buf=%s\n",buf);
printf("ret=%d\n",ret);
}
情况一验证如上代码;
#include<stdio.h>
#include<unistd.h>
int main(void)
{
int p[2];
pipe(p);
write(p[1],"hello world",10);
close(p[1]);
char buf[128];
memset(buf,'\0',128);
int ret=-1;
ret=read(p[0],buf,3);
printf("first ret=%d,buf=%s\n",ret,buf);
ret=read(p[0],buf,15);
printf("second ret=%d,buf=%s\n",ret,buf);
}
情况二三验证如上;
(2) 如果以堵塞的方式读无名管道,有某个进程可以访问写端,按如下操作;
如果管道现有数据无数据,读操作堵塞;
如果管道现有数据大于要读出的数据,读出期望大小的数据;
如果管道现有数据小于要读出的数据,读出现有所有的数据;
#include<stdio.h>
#include<unistd.h>
int main(void)
{
int p[2];
pipe(p);
char buf[128];
memset(buf,'\0',128);
read(p[0],buf,128);
printf("buf=%s\n",buf);
}
情况一验证如上,二三不再验证;
(3)如果以堵塞的方式写无名管道,但是没有某个进程可以访问读端,写操作将收到SIGPIPE信号,write()返回-1,如果某个进程可以可以访问读端,则写入成功。
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
/*信号处理函数*/
void handler(int sig)
{
if(SIGPIPE==sig)
printf("recv SIGPIPE\n");
}
int main()
{
int p[2];
signal(SIGPIPE,handler);
pipe(p);
close(p[0]);
int ret=0;
ret=write(p[1],"hello world",10);
printf("ret=%d\n",ret);
}
验证如上;
补充signal()用法:
第一个参数指明所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。
第二个参数描述了与信号关联的动作,它可以取以下三种值:
1)一个无返回值的函数地址.此函数必须在signal()被调用前申明,handler为这个函数的名字。当接收到一个类型为sig的信号时,就执行handler 所指定的函数。sig是传递给它的唯一参数。执行了signal()调用后,进程只要接收到类型为sig的信号,不管其正在执行程序的哪一部分,就立即执行handler()函数。当handler()函数执行结束后,控制权返回进程被中断的那一点继续执行。
2)SIG_IGN.这个符号表示忽略该信号,执行了相应的signal()调用后,进程会忽略类型为sig的信号。
3)SIG_DFL.这个符号表示恢复系统对信号的默认处理。
signal()会依第一个参数指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。
返回值:
返回先前的信号处理函数指针
,如果有错误则返回SIG_ERR(-1)。
(4)如果以堵塞的方式写无名管道,但是当前管道已满,写操作就会发生堵塞。若是有好多进程试图写管道,有进程读操作会唤醒写管道,唤醒那一个不一定。写操作写入数据大小建议小于PIPE_BUF大小(默认4096)。
(5)如果以O_NDELAY和O_NONBLOCK设置了管道读端,如果写管道无数据,将立即返回-1.且置errno为EAGAIN错误。
(6)如果以O_NDELAY和O_NONBLOCK设置了管道写端,如果管道无空间,将立即返回-1.且置errno为EAGAIN错误。
无名管道是临时的,完成通信后就会消失。有名管道依赖文件系统,是一个存在的特殊文件,具有磁盘存放路径,文件权限和其他属性;有名管道并没有在磁盘上存放真正的信息,它存储的通信信息在内存中,两个进程结束后会自动丢失,拥有的磁盘路径仅仅是一个接口。通信的两个进程结束后,有名管道的文件路径本身依然存在。
命令行应用:
mknod 管道名 p;
例如:mknod pipe p;
echo test>pipe&;//写管道
cat<pipe;//读管道
1.创建有名管道
mkfifo()函数,第一个参数为建立有名管道文件名,第二个为生成文件的模式。mkfifo()建立有名管道时,该有名管道文件名必须不存在,mkfifo()建立的FIFI文件其他进程可以用读写一般文件的方式存取。执行成功返回0,否则返回-1,失败原因存储在errno中。
2.读写有名管道
读写操作之前必须先open()函数打开该文件。
1)如果进程以某种方式打开管道操作,系统将堵塞该进程,直到另一个进程以另一种方式打开该管道后才会继续执行。当然一个进程可以以读写的方式打开管道。
2)两进程已经完成打开管道操作,堵塞读操作按以下方式进行。
如果管道现有数据无数据,读操作堵塞;
如果管道现有数据大于要读出的数据,读出期望大小的数据;
如果管道现有数据小于要读出的数据,读出现有所有的数据;
3)
两进程已经完成打开管道操作,堵塞写操作按以下方式进行。
如果管道中没有空间,写操作堵塞;
如果管道中有空间,但小于欲写入的数据,写满空间后堵塞;
如果管道中有空间,且大于欲写入的数据,写入数据后返回;
4)两进程已经完成打开管道操作,中途其中一个进程退出
未退出一端如果是写操作,将返回SIGPIPE信号;
未退出一端如果是堵塞读操作,读操作将不再堵塞,直接返回0;
非亲缘关系进程使用有名管道通信实例:
向有名管道中写入数据源代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<limits.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_NAME "/tmp/my_fifo"
int main(int argc,char* argv[])
{
int pipe_fd;
int res;
char buf[]="hello world!";
if(access(FIFO_NAME,F_OK)==-1)//文件是否存在
{
res=mkfifo(FIFO_NAME,0766);
if(res!=0)
{
fprintf(stderr,"could not create fifo %s\n",FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n",getpid());//打印提示信息
pipe_fd=open(FIFO_NAME,O_WRONLY);
printf("file's descriptor is %d\n",pipe_fd);
if(pipe_fd!=-1)
{
res=write(pipe_fd,buf,sizeof(buf));
if(res==-1)
{
fprintf(stderr,"Write error on pipe\n");
exit(EXIT_FAILURE);
}
printf("write data is %s,%d bytes is write\n",buf,res);//打印写入的数据
close(pipe_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished,\n",getpid());
exit(EXIT_SUCCESS);
}
向有名管道中读取数据源代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<limits.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_NAME "/tmp/my_fifo"
int main(int argc,char* argv[])
{
int pipe_fd;
int res;
char buf[4096];
int bytes_read=0;
memset(buf,'\0',sizeof(buf));
printf("Process %d opening FIFO O_RDONLY\n",getpid());//打印提示信息
pipe_fd=open(FIFO_NAME,O_RDONLY);
printf("file's descriptor is %d\n",pipe_fd);
if(pipe_fd!=-1)
{
bytes_read=read(pipe_fd,buf,sizeof(buf));//读数据输出
printf("the read data is %s\n",buf);
close(pipe_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished,%d bytes read\n",getpid(),bytes_read);
exit(EXIT_SUCCESS);
}