管道是Unix中最古老的进程间通信的形式。
既然是通信,那么也逃不脱通信的共有的特点那就是中间的进程之间的公共部分。在管道通信中,管道就充当这个公共的部分。
管道分两种,一种是匿名管道,一种是命名管道。
匿名管道
在命令行下输入的|
都是匿名管道。
那么除去指令,如何在程序中创建一个匿名管道呢?
#include<unistd.h>
int pipe(int fd[2]);
//fd:文件描述符数组
//fd[0]表示读端
//fd[1]表示写端
//成功返回0,失败返回错误代码
前面已经聊过文件描述符的概念:用系统调用open打开文件的返回值就是文件描述符。如果想看文件描述符概念的朋友请点这里
而看待pipe也如同看待一个文件,所以也是通过文件描述符来进行操作的,正迎合了Linux一切皆文件的思想。
下面来写一个小案例,父子进程间的通信:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
//父子进程间通信
int main(int argc, char *argv[])
{
int pipefd[2];
if(pipe(pipefd) == -1)
ERR_EXIT("pipe");
pid_t pid = fork();
char buf[128];
memset(buf, 0x00, sizeof(buf));
if( pid == -1)
ERR_EXIT("fork");
if( pid == 0){//写端
close(pipefd[0]);
strcpy(buf, "Hey, Coder");
write(pipefd[1], buf, strlen(buf));
close(pipefd[1]);
exit(EXIT_SUCCESS);
}
close(pipefd[1]);
read(pipefd[0], buf, 128);//读端
printf("%s\n", buf);
return 0;
}
这个代码完成的主要功能是子进程在写,父进程在读,通过匿名管道完成了父子进程间的通信。
匿名管道其具有一下特点:只能用于具有共同祖先的进程(具有血缘关系的进程)之间进行通信。
管道提供流式服务。
一般而言,进程退出,管道释放,所以管道生命周期随进程。
内核会对管道操作进行同步与互斥。
管道是半双工的,数据只能单向流动;所以需要双方通信的时候,需要建立两个管道。
命名管道
由于匿名管道只能在具有亲缘关系的进程间通信。如果想在不相关进程之间交换数据,可以使用FIFO文件来做这项工作,它就是命名管道。
命名管道是一种特殊类型的文件。
创建命名管道:
mkfifo("mypipe",0644);
//相关函数:int mkfifo(const *filename, mode_t mode)
//第一个参数很好理解,就是文件名,第二个是管道文件的权限
命名管道与匿名管道之间只有创建与打开的区别,其他意义相同。
这里我放上一个不同进程之间通信的案例:有兴趣的朋友可以点这里
管道之间的读写还有一些规则:
- 无数据可读
read调用阻塞,直到有数据来为止
read调用返回-1,errno职位EAGAIN - 管道满的时候
write调用阻塞,直到有进程读走数据
调用返回-1,errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,read返回0
如果所有管道读端对应的文件描述符被关闭,write操作会产生信号SIGPIPE(管道破裂),进而导致write进程退出
当写入数据量不大于PIPE_BUF时,linux保证写入原子性
当写入数据量大于PIPE_BUF时,linux不保证写入原子性