进程间通信_1_管道
首先对进程间通信做一个基本介绍,进程有独立性,独享各种资源,运行期间互不干扰,每个进程的数据都保存在自己的PCB中,但是问题就来了,进程需要相互之间共享传递数据时,例如父子进程,父进程需要子进程的一个处理数据,这个时候就需要进程间的通信。进程通信指的就是在进程间传输数据,实质上就是进行通信的进程(可以是两个进程互相通信,也可以是多个)可以打开同一份资源。
提到进程间通信还有几个基本的概念要说:
1. 临界资源:进程间进行通信时,会共同访问一份资源,这份称为临界资源
2. 临界区:访问临界区的代码
3. 数据不一致:两个(或多个)进程访问临界资源时可能发生读写内容不一致
4. 原子性:在访问临界资源时,将正在做的事情做完不受打扰(避免数据不一致)
5. 互斥:临界区访问临界资源时,当前临界资源仅只有当前访问者
6. 同步:访问临界资源时,维持合理的顺序
进程间通信的目的有:
- 数据传输:进程需要将数据发送给其他的进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程向其他进程(一个或多个)发送消息,通知某件事情的发生。
- 进程控制:一个进程控制另一个进程的执行过程,知道被控制进程的运行状态,大多希望能阻拦被控制进程异常
进程间通信的基本分类:
- 管道 :匿名管道pipe、命名管道
- System V 进程间通信:System V 消息队列 、System V 共享内存、 System V 信号量
- POSIX 进程间通信:消息队列、共享内存、信号量、互斥量、条件变量、读写锁
这里先说一下管道的基本概念
把一个进程连接到另一个进程的一个数据流称为“管道“(数据传输过程在内核中完成)。管道是由内核管理的一个缓冲区,它的一端连接一个进程的输出,另一端连接一个进程的输入,一个管道只能在两个进程之间单向传输数据,若需要双向传输,还需要另一个人管道。大致如下图:
一、匿名管道(pipe)
1. 管道的创建
// 头文件
#include<unstid.h>
// 原型
int pipe(int fd[2]);
// 参数:fd 文件描述符数组,fd[0]表示读端,fd[1]表示写端
// 返回值: 成功返回 0 ,失败返回错误码
(1)例1:从标准输入(键盘)读取数据,写入管道,从管道中读取数据输出到标准输出(显示器)
// pipe.c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4 #include<stdlib.h>
5
6 int main()
7 {
8 int fd[2];
9 char buf[64];
10 int len;
11
12 if( pipe(fd) == -1){ // 创建管道
13 perror("make pipe error.\n");
14 }
15
16 // 从标准输入读取
17 while( fgets( buf, 64, stdin)){
18 len = strlen(buf);
19
20 // 写入管道
21 if(write( fd[1], buf, len) != len){
22 perror("write to pipe error.\n");
23 break;
24 }
25
26 memset( buf, 0x00, sizeof(buf));
27
28 // 从管道读取
29 if( ( len = read( fd[0], buf, 64)) == -1){
30 perror("read from pipe error.\n");
31 break;
32 }
33
34 // 往标准输出写
35 if( write( 1, buf, len) != len){
36 perror("read error.\n");
37 break;
38 }
39 }
40 return 0;
41 }
(2)例2:使用 fork 创建子进程,父子进程通过管道传输数据。父进程先创建管道,再创建子进程,子进程写时拷贝父进程的数据,所以就会看到同一个管道文件,父进程关闭读端,子进程关闭写端,这样就可以进行管道传输。大致如下图:
父进程创建管道
父进程创建子进程
父进程关闭读端,子进程关闭写端
// fork_pipe.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5
6 int main()
7 {
8 int pipe_fd[2];
9 if(pipe(pipe_fd) == -1){
10 // 创建管道失败
11 perror("make pipe error.\n");
12 }
13
14 pid_t pid;
15 pid = fork();
16 if(pid == -1){
17 // 创建子进程错误
18 perror("make fork error.\n");
19 }
20 if(pid == 0){
21 // 子进程,关掉读端,写入hello, world!之后关闭写端,退出
22 close(pipe_fd[0]);
23 write(pipe_fd[1], "hello, world!",13);
24 close(pipe_fd[1]);
25 exit(1);
26 }
27
28 //父进程关闭写端,将管道内容读取到缓冲区
29 close(pipe_fd[1]);
30 char buf[16] = {0};
31 read(pipe_fd[0], buf, 13);
32
33 // 打印缓冲区内容
34 printf("%s\n",buf);
35
36 return 0;
37 }
2. 管道的特点
- 单向传输数据
- 适用于有亲属关系的进程(父子进程、兄弟进程,要有共同的祖先)
- 自带同步互斥机制
- 管道生命周期随进程,进程结束,管道消失
-
管道提供面向字节流的服务
3. 管道的读写规则
(1)在读写操作进行时,linux会保证操作的原子性。
(2)当管道内没有数据可读,读端阻塞式等待数据
(3)当管道内已经写满时,写端阻塞等待,直到有进程读走数据,当写入数据量大于 PIPE_BUF 时,linux不在保证写入的原子性
(4)写端写一段时间后关闭,读端一直读完数据后关闭管道
(5)写端一直写,读端读一段时间后关闭,写端会被操作系统处理(发送13号信号)异常退出