通信介绍
进程之间可能会存在特定的协同工作的场景,为此一个进程要把自己的数据交付给另一个进程,让其进行处理,而进程是具有独立性的,一个进程看不到另一个进程的资源,因此交互数据成本一定很高。为了完成进程间的通信,首先得先有一份各进程都能看到公共资源,而这里的资源指的就是内存,且它属于操作系统(因为当它属于操作系统时才能让各个进程都能看到),换言之进程间通信的前提本质是由OS参与,提供有一份所有进程能看到的公共资源(可能以文件方式提供,也可能以队列的方式,也可能提供的就是原始的内存块,这也是通信方式有很多种的原因)
目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享相同的资源
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug)进程,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够即使知道它的状态改变
进程间通信发展
- 管道
- Ststem V进程间通信
- POSIX进程间通信
进程间通信分类
管道
- 匿名管道pipe
- 命名管道
System V IPC - System V 消息队列
- System V 共享内存
- System V信号量
POSIX IPX - 消息队列
- 共享内存
- 信号量
- 条件变量
- 读写锁
管道
管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流成为一个管道
匿名管道
我们首先来思考一个问题,父子进程是两个独立进程吗?答案是肯定的,也就是说父子进程具有独立性,因此它们之间不能直接交换资源。当我们希望父子进程进行通信时,我们就可以使用匿名管道,匿名管道用于进程之间通信,且仅限于本地父子进程之间通信。
我们先来看看pipe的函数接口,它可以创建一个匿名管道
接下来我们写一个程序来体验一下匿名管道的使用,我们会创建一个子进程,并要求在子进程向管道写入数据,父进程从管道读取数据
#include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <string.h>
5
6 int main()
7 {
8 int pipefd[2] = {0};
9 if (pipe(pipefd) != 0)
10 {
11 perror("pipefd");
12 return 1;
13 }
14 // pipe[0] 读取端
15 // pipe[1] 写入端
16 // 目标father write child read
17 if (fork() == 0)
18 {
19 // 子进程
20 close(pipefd[0]);
21 while(1)
22 {
23 char str[] = "hello ssj\n";
24 write(pipefd[1], str, strlen(str));
25 sleep(1);
26 }
27 exit(0);
28 }
29 close(pipefd[1]);
30 while (1)
31 {
32 char buf[64];
ssize_t s = read(pipefd[0], buf, sizeof(buf));
34 if (s == 0)
35 {
36 break;
37 }
38 else if (s > 0)
39 {
40 buf[s] = 0;
41 printf("child say : %s", buf);
42 }
43 else
44 {
45 break;
46 }
47 }
48 return 0;
49 }
程序运行后结果如下
匿名管道的特点
1.管道是一个只能单向通信的通信信道
2.管道是面向字节流的
3.仅限于父子通信
4.管道自带同步机制,原子性输入
5.管道的声明周期是随进程的(管道是一个文件,只能被当前进程打开,相关进程退出了,会被OS自动关闭)
匿名管道读写的4种情况
1.读端不读或者读的慢,写端要等读端
2.读端关闭,写端收到SIGPIPE信号直接终止
3.写端不写或者写得慢,读端要等写端
4.写端关闭,读端读完pipe内部的数据然后再读,直到0,表明文件结尾