进程间通信
目的:
- 数据传输:一个进程将数据发给另一个进程
- 资源共享:多进程共享同样的资源
- 通知事件:一个进程向另一个进程通知某事件发生,如进程终止要通知父进程
- 进程控制:有些进程希望完全控制另一个进程的执行,如Debug进程,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道他的状态改变
分类:
- 管道:匿名管道pipe、命名管道
- System V IPC:消息队列、共享内存、信号量
- POSIX IPC:消息队列、共享内存、信号量、互斥量、条件变量、读写锁
管道:把一个进程连接到另一个进程的一个数据流称为管道
匿名管道
#include <unistd.h>
//创建一个无名管道
int pipe(int pipefd[2]);
//pipefd:文件描述附数组;pipefd[0]表示读端,pipefd[1]表示写端
//成功返回0;失败返回错误代码
调用pipe函数的进程(用户):
pipefd[1] —w---> 管道(内核) —r---> pipefd[0]
#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<cstring>
#include<cstdlib>
using namespace std;
int main()
{
char buffer[128];
int pipefds[2];
int flag = pipe(pipefds);
if(flag == -1)
{
perror("make pipe");
_exit(1);
}
//读取键盘输入写入buffer
while(fgets(buffer, sizeof(buffer), stdin))
{
int len = strlen(buffer);
//将buffer内容写入管道
if(write(pipefds[1], buffer, len) != len)
{
perror("write to pipe");
break;
}
//把buffer内容置空
memset(buffer, 0x00, sizeof(buffer));
//从管道读的内容写入buffer
if((len = read(pipefds[0], buffer, sizeof(buffer))) == -1)
{
perror("read from pipe");
break;
}
//将buffer内容写入显示器
if(write(1, buffer, len) != len)
{
perror("write to stdout");
break;
}
}
return 0;
}
用fork来共享管道原理
fork之后各自关掉不用的描述附,即关掉父进程fd[1]和子进程fd[0]
文件描述附角度理解管道
- 图为一个父进程创建管道。
- 若父进程fork出子进程,则子进程fd也连接到管道两端。
- 父进程关闭fd[0],子进程关闭fd[1]时,父进程只写,子进程只读。
内核角度理解管道
#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<cstdlib>
#include<error.h>
#include<cstring>
#define ERR_EXIT(m) do{perror(m);exit(EXIT_FAILURE);}while(0)
using namespace std;
int main()
{
int pipefd[2];
if(pipe(pipefd) == -1)
{
ERR_EXIT("pipe error");
}
pid_t pid = fork();
if(pid == -1)
{
ERR_EXIT("fork error");
}
if(pid == 0)
{
close(pipefd[0]);
write(pipefd[1], "hello", 5);
close(pipefd[1]);
exit(EXIT_SUCCESS);
}
close(pipefd[1]);
char buf[10] = {};
read(pipefd[0], buf, 10);
cout<<buf<<endl;
close(pipefd[0]);
return 0;
}
管道读写规则
- 没有数据可读时
O_NONBLOCK disable:read调用阻塞,进程暂停执行,一直等到有数据来
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN - 当管道满的时候
O_NONBLOCK disable:write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:write调用返回-1,errno值为EAGAIN - 如果所有管道写端对应文件描述附被关闭,则read返回0
- 如果所有管道读端对应文件描述附被关闭,则write操作会产生信号SIGPIPE,可能导致write进程退出
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性
- 当要写入的数据量大于PIPE_BUF时,linux将不保证写入的原子性
管道的特点
- 只能用于具有共同祖先的进程间通信。通常一个管道由一个进程创建,然后改进程调用fork,此后父子进程就可用该管道
- 管道提供流式服务
- 一般的,进程退出,管道释放
- 一般的,内核会对管道操作进行同步与互斥
- 管道是半双工的,数据只能向一个方向流动;需要双方通信只能建立两个管道