进程间通信 - 管道
进程间通信的方式:
- 管道:匿名管道和有名管道
- 信号 - 系统开销小
- 共享映射区 - 有无血缘关系的进程间通信都可以
- 本地套接字 - 稳定
IPC(InterProcess Communication):进程间通信
1. 管道
1.1管道的概念:管道是内核缓冲区,可以叫做伪文件(不占用磁盘文件)
(1)管道的特点:
①管道有读端和写端。数据从写端流入,读端流出。读端和写端是两个文件描述符。
②含有管道的进程结束后,管道被释放
③管道默认是阻塞的。数据必须读端流入,写端流出
2.管道的原理:
(1)内部实现方式:队列(环形队列),先进先出。
(2)缓冲区大小:默认为4k,大小会根据实际情况做适当的调整
3.管道的局限性
(1)数据只能读取一次,不能重复使用,数据传输方向是单向的(半双工)
单工:遥控器
半双工:数据传输的方向是单向的,对讲机
双工:数据时双向流动的,电话
(2)匿名管道:只适用于由血缘关系的进程间通信
4.创建匿名管道
函数:int pipe(int pipefd[2]);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret == -1){
perror("pipe");
exit(1);
}
printf("pipe[0] = %d\n", fd[0]);
printf("pipe[1] = %d\n", fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
5.父子进程使用匿名管道通信
单个进程也能使用管道完成通信,平时没有这个必要
例:父进程将数据写入管道,子进程从管道中读取数据
/* 实现 ps aux | gerp "bash" */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret == -1){
perror("pipe");
exit(1);
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
exit(1);
}
//父进程 ps aux
//子进程 grep "bush"
if(pid > 0){
//写管道操作,关闭读端
close(fd[0]);
//重定向,STDOUT -> 管道的写端
dup2(fd[1], STDOUT_FILENO);
//执行ps aux
execlp("ps", "ps", "aux", NULL);
perror("execlp");
exit(1);
}
else if(pid == 0){
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("grep", "grep", "bash", "--color=auto", NULL);
perror("execlp");
exit(1);
}
close(fd[0]);
close(fd[1]);
return 0;
}
注意事项:
父进程写数据 – 关闭写端
子进程读数据 – 关闭读端
因为管道是阻塞的
程序中的重定向:
父进程中 – dup2(fd[1], STDOUT_FILENO); //STDOUT_FILENO跟随 fd[1]
子进程中 – dup2(fd[0], STDIN_FILENO); //STDIN_FILENO跟随 fd[0]
5.1 管道的读写行为
1.读操作
(1)管道中有数据:
- read(fd) - 正常读数据,返回读出的字符数
(2)管道中无数据:
- 写端全部关闭:read解除阻塞,返回0,相当于读文件时读到了尾部
- 没有全部关闭:read阻塞
2.写操作
(1)读端全部关闭
- 管道破裂,进程被终止:内核给当前进程发信号SIGPIPE(13号信号)
(2)读端没有被全部关闭
- 缓冲区写满:write阻塞
- 缓冲区没满:write继续写
(3)设置管道为非阻塞
- 获取原来的flags:int flag = fcntl(fd[0].F_GETFL);
- 设置新的flags:flag |= O_NONBLOCK;
//flags = flags | O_NONBLOCK; - fcntl(fd[0], F_SETFL, flags);
5.2 查看管道缓冲区大小
1.命令查看:ulimit -a
2.函数:
#include <unistd.h>
long fpathconf(int fd, int name);
long pathconf(const char *path, int name);
6. 有名管道(fifo)
6.1 fifo的特点
- 在磁盘上有文件
- 磁盘上的文件为伪文件,占用的大小为0
- 内核中由对应的缓冲区
6.2 fifo的使用
1.使用场景:没有血缘关系的进程间通信
2.创建方式:
- 命令:mkfifo 管道名
- 函数:mkfifo
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
①pathname:fifo文件名
②mode:fifo文件的打开方式
例:该进程以只读的方式打开 myfifo
int fd = open("myfifo", O_RDONLY);
6.3 fifo文件的IO函数操作
- open() / close()
- read() / write()
- 不能使用lseek()
6.4 进程间通信:使用 myfifo 进程通信
现有两个进程a,b属于不同的进程组。
a进程进行读操作,b进程进行写操作
a.c —> read
int fd = open("myfifo", O_RDONLY);
read(fd, buf, sizeof(buf);
close(fd);
b.c —>read
int fd1 = open("myfifo", O_WRONLY);
write(fd1, "hello,world", 11);
close(fd1);