进程间通信的目的:
(1)数据传输:一个进程需要将它的数据发送给另一个进程;
(2)资源共享:多个进程之间需要共享同样的资源;
(3)通知事件:一个进程需要向另一个进程发送消息,通知它(它们)发生了某些事(比如子进程终止时要通知父进程);
(4)进程控制:有些进程希望完全控制另一个进程的执行,能够及时知道它的状态改变。
进程间通信(IPC)分类:
(1)管道
1)匿名管道
2)命名管道
(2)System V IPC
1)System V消息队列
2)System V共享内存
3)System V信号量
(3)POSIX IPC
1)消息队列
2)共享内存
3)信号量
4)互斥量
5)条件变量
6)读写锁
本文主要介绍进程间通信的前两类,即管道和System V标准的IPC。
一、管道
我们把从一个进程连接到另一个进程的一个数据流称为一个管道。
1、相关知识介绍
(1)管道的几个特性
管道只能实现单向通信,若要实现双方通信,可以建立两个管道;
只要带有血缘关系的进程都可通信(命名管道可以实现任意两个进程间的通信,不用管是否有血缘关系);
进程退出,管道释放,即管道的生命周期随进程;
管道自带互斥与同步机制(不用担心若两个进程同时向管道写数据而产生的数据的不一致等问题);
管道提供面向字节流的服务(不用将管道内的数据全部读完,想读多少字节完全由上层应用决定)。
(2)相关名词解释
数据不一致:读写双方共同访问资源,写端还未写完,但读端已读,此时读到的数据可能就是错误的;
临界资源:两个毫不相干的进程看到的公共资源;
临界区:访问临界资源的代码;
互斥:任一时刻,在临界区访问临界资源的,有且仅有它一个;
同步:在互斥的访问临界资源的基础上,具有的顺序性;
原子性:要么不做,要么做完,无中间状态。
(3)管道本质
在Linux下,一切皆文件。所以在内核看来,管道也是一个文件。所以向管道读写数据时,可以调用write、read函数(具体用法在之前的基础IO中有写过)
2、匿名管道
只要有血缘关系(常见父子进程)的进程间都可通信。
(1)使用方式
1)函数原型
2)功能:创建一个匿名管道
3)参数:pipefd[2]是一个文件描述符数组,其中pipefd[1]表示读端,pipefd[2]表示写端
4)返回值:成功返回0,失败返回错误代码
(2)匿名管道的读写规则:
管道内无数据可读时,读端一直阻塞式等待,直至有数据为止;
管道满时,写端阻塞,直至读端读走数据;
写端写入一定数据后不再写入且关闭,读端读完管道内数据后(读完管道内数据时,会返回一个0值,表示读到结尾)通信才算结束,管道才关闭;
若写端一直在写,但读端不读且关闭,写端会被OS发送13号SIGPIPE信号而中止。
(3)匿名管道实现
以下代码实现父子进程间的通信,子进程每隔1秒向管道写数据,父进程则一直在管道里读数据。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2];
int ret = pipe(fd);//创建管道
if(ret < 0)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id == 0)//child->w
{
close(fd[0]);//关闭读段
const char* msg = "hello ,i am child\n";
while(1)
{
sleep(1);
write(fd[1],msg,strlen(msg));//向管道里写数据,即向文件里写数据,\0不属于字符串内容
}
}
else//parent->r
{
close(fd[1]);//关闭写段
char buf[64];
while(1)
{
size_t s = read(fd[0],buf,sizeof(buf)-1);//s为实际读的个数
if(s > 0)//读成功了
{
buf[s] = '\0';
printf("f say: %s\n",buf);
}
}