进程间通信- - -管道
进程间通信
由于进程具有独立性(都各自操作自己虚拟地址空间的虚拟地址,无法访问别人的地址),因此进程之间无法直接通信,操作系统根据不同场景,提供了几种不同的进程间通信方式:
SYSTEM V: 管道, 共享内存, 信号量, 消息队列
管道
管道本质上还是是内核中的一块缓冲区,可以实现半双工通信(可选方向的单向传输)
管道实现进程间通信的原理:
让多个进程通过访问到相同的缓冲区来实现通信,管道实现通信使用的是系统调用接口
管道的分类
匿名管道:用于具有亲缘关系的进程间通信
一个进程创建匿名管道,操作系统在内核中创建一块缓冲区,并返回两个文件描述符作为管道的额操作句柄(一个用于读,一个用于写,具体是读还是写由用户选择);但这个缓冲区在内核中没有标识,其他进程找不到,只能用于具有亲缘关系的进程间通信- - -(子进程通过复制父进程的文件描述符获取管道的操作句柄)
操作接口
int pipe(int pipefd[2]);
//pipefd[]:至少具有两个int型元素的数组
//功能:创建一个管道,通过pipefd获取系统返回的管道操作句柄,其中:
pipefd[0]:用于从管道读取数据
pipefd[1]:用于向管道写入数据
//返回值: 0 失败:-1
管道的读写特性
1.若管道中没有数据,则read阻塞;直到数据被写入(缓冲区中有数据可读)
2.若管道中数据满了,则write会阻塞;直到数据被读取(缓冲区有空余空间),缓冲区大小为4096字节
3.若管道的所有读端被关闭,则write会触发异常,进程退出
4.若管道的所有写端被关闭,则read会返回0(所以管道的read返回0,不一定都是读取到0个数据,也可能是所有写端被关闭)
匿名管道简单使用实例
//父进程写,子进程读
int main()
{
//匿名管道必须创建于创建子进程之前(子进程这样才能复制到管道的操作句柄)
int pipefd[2];
int ret = pipe(pipefd);
if (ret < 0) {
perror("pipe error");
return -1;
}
int pid = fork();
if (pid == 0) {
//child
char buf[1024] = {0};
int ret = read(pipefd[0], buf, 1023);
printf("child read buf:[%d-%s]\n", ret, buf);
}else if (pid > 0) {
//parent
write(pipefd[1], "hello world", 11);
}
return 0;
}
如果要在程序中将所有读端或写端都关闭close(pipefd[0]);
,需要在父进程和子进程中分别关闭,因为管道是复制来的
命名管道:用于同一主机上任意进程间的通信
命名管道是在内核中的一块有标识的缓冲区,这意味着所有的进程都可以通过这个标识进而访问到这块缓冲区实现进程间的通信
命名管道的标识看上去是一个文件,可见于文件系统,如test.fifo
,但是实际上还是一块缓冲区,这意味着所有进程都可以通过打开文件进而访问到内核中的缓冲区
操作接口:
int mkfifo(const char *pathname,mode_t mode);
参数:
pathname:管道文件路径及名称
mode:管道文件权限
返回值:0 失败-1
生成管道文件test.fifo
//创建fifo.c,运行后即可生成test.fifo
//注意头文件为#include<sys/stat.h>
char *fifo = "./test.fifo";
umask(0);//受限于权限掩码,所以要umask(0)
int ret = mkfifo(fifo, 0664);
if (ret < 0) {
if (errno != EEXIST) {
perror("mkfifo error");
return -1;
}
}
命名管道的读写特性类似于匿名管道
命名管道文件的打开特性
若文件当前没有被其他进程以只读的方式打开,直接以O_WRONLY打开时会阻塞
若文件当前没有被其他进程以只写的方式打开,直接以O_RDONLY打开时会阻塞
写端代码
int main()
{
char *fifo = "./test.fifo";
umask(0);//受限于权限掩码,所以要umask(0)
int ret = mkfifo(fifo, 0664);
if (ret < 0) {
if (errno != EEXIST) {//判断如果管道文件存在则继续往下走
perror("mkfifo error");
return -1;
}
}
int fd = open(fifo, O_WRONLY);
while(1) {
char buf[1024] = {0};
printf("i say: ");
fflush(stdout);//刷新缓冲区
scanf("%s", buf);
write(fd, buf, strlen(buf));
}
close(fd);//用完记得关闭
return 0;
}
读端代码
int main()
{
char *fifo = "./test.fifo";
umask(0);//受限于权限掩码,所以要umask(0)
int ret = mkfifo(fifo, 0664);
if (ret < 0) {
if (errno != EEXIST) {//判断如果管道文件存在则继续往下走
perror("mkfifo error");
return -1;
}
}
int fd = open(fifo, O_RDONLY);
while(1) {
sleep(5);
char buf[1024] = {0};
read(fd, buf, 1023);
printf("readthings: %s\n", buf);
}
close(fd);
return 0;
}
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
- 匿名管道只能用于具有亲缘关系的进程间通信;命名管道用于同一主机上的任意进程间的通信
管道的特性:
- 管道的本质是内核中的一块缓冲区,是半双工通信
- 管道的读写特性+管道的打开特性
- 管道的生命周期跟随进程(所有管道的的操作句柄被关闭)
- 管道自带同步与互斥(管道的读写数据大小在不超过PIPE_BUF(4096字节)时可以保证操作的原子性
- 管道提供字节流服务