当前位置:网站首页>进程间通信学习笔记
进程间通信学习笔记
2022-07-31 01:28:00 【小小酥诶】
进程间通信的目的
进程间通信的目的主要体现在5个方面:
- 传输数据:一个进程可能需要将它的数据传输给另外一个进程
- 资源共享:多个进程之间可能想共享同样的资源
- 通知事件:一个进程可能需要向另一个进程通知它们发生了某事件
- 进程控制:某些进程可能希望控制另一个进程的执行。
进程通信多种方式的原因
要实现进程间的相互通信就必须让这些进程“看到”同一份资源。但进程之间是相互独立的,一个进程无法看到另一个进程的资源。所以这份公共资源不能属于这些进程,而是由操作系统系统提供。操作系统提供公共资源的方式可以以文件的方式、队列的方式、也有可能是以原始内存块的方式,这也就导致了进程通信的方式有多种。
进程通信的多种方式
管道
- 匿名管道
- 命名管道
System V IPC
- System V 共享内存
- System V 消息队列
- System V 信号量
管道
匿名管道
匿名管道的五个特点:
- 1、只能单向通信的通信信道。
- 2、面向字节流。
- 3、只能“具有血缘关系”的进程进行通信,例如父子进程。
- 4、自带同步机制,写入是原子性的。
- 5、生命周期随通信进程。
站在文件描述符的角度来看管道(此处需要掌握文件描述符的知识)
匿名管道是单向通信的通信信道,且因为子进程会继承父进程相关信息,所以父进程需要先打开读端和写端,后面再根据需要关闭读端/关闭写端,否则子进程只拥有读端/写端。管道的本质就是文件。
创建匿名管道
#include <unistd.h>
int pipe(int fildes[2]);
//fildes是文件描述符数组,一个输出型参数,用于获取读端/写端的文件描述符
//fildes[0]是读端、fildes[1]是写端
//成功返回0
//失败返回错误代码
测试
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <sys/types.h>
6 #include <sys/wait.h>
7
8 int main(void)
9 {
10 int pipe_fd[2] = {
0};
11 if(pipe(pipe_fd) != 0) //创建匿名管道
12 {
13 return 1;
14 }
15 printf("pipe_fd[0] = %d, pipe_fd[1] = %d\n",pipe_fd[0], pipe_fd[1]);
16
17 pid_t id = fork();
18 //打算让子进程写入、父进程读取
19 if(id < 0)
20 {
21 perror("fork");
22 return 2;
23 }
24 else if(id == 0)
25 {
26 //child process
27 //写入,则关闭读端
28 close(pipe_fd[0]);
29
30 //写入,按照文件的方式
31 const char *msg = "hello parent, I am child";
32 while(1)
33 {
34 write(pipe_fd[1], msg, strlen(msg));
35 sleep(1);
36 }
37 close(pipe_fd[1]);
38 exit(0);
39 }
40 else
41 {
42
43 //father process
44 //读取,关闭写端
45 close(pipe_fd[1]);
46 char buf[64];
47 while(1)
48 {
49 buf[0] = 0;
50 ssize_t st = read(pipe_fd[0], buf, sizeof(buf));
51 if(st > 0)
52 {
53 buf[st] = 0;
54 printf("child send to pipe :%s\n", buf);
55 }
56 else if(st == 0)
57 {
58 printf("pipe file close, child quit\n");
59 break;
60 }
61 else
62 {
63 break;
64 }
65 }
66 //father wait
67 int status = 0;
68 pid_t ret = waitpid(-1, &status, 0);
69 if(ret > 0)
70 {
71 //wait success
72 //获取退出子进程退出状态
73 printf("exit code:%d, sig: %d\n", (status >> 8) & 0xFF, status & 0x7F);
74 }
75 close(pipe_fd[0]);
76 }
77
78 return 0;
79 }
管道通信的4种情况
- a.读端不读/读端读的慢,写端等待读端
- b.读端关闭,写端会收到SIGPIPE信号直接终止
- c.写端不写/写端写的慢,读端会等待写端
- d.写端关闭,读端会读完pipe内的数据,然后再读就会读到0,表明读到文件末尾,然后读端关闭。
管道是有大小的,我们可以通过程序知道当前我们创建的匿名管道的大小。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <sys/types.h>
6 #include <sys/wait.h>
7
8 //编写程序查看当前所创建的管道大小
9 int main(void)
10 {
11 int pipe_fd[2] = {
0};
12 if(pipe(pipe_fd) != 0)
13 {
14 return 1;
15 }
16
17 //计划子进程写入、父进程读取
18 pid_t id = fork();
19 if(id < 0)
20 {
21 perror("fork");
22 return 2;
23 }
24 else if(id == 0)
25 {
26 //child process
27 close(pipe_fd[0]);
28 char c = 'x';
29 int count = 0;
30 while(1)
31 {
32 write(pipe_fd[1], &c, 1);
33 count++;
34 printf("count: %d\n", count);
35 }
36
37 close(pipe_fd[1]);
38 exit(0);
39 }
40 else
41 {
42 //father process
43 close(pipe_fd[1]);
44 sleep(20); //父进程休眠时间根据子进程来定,要保证休眠时间内管道被写满
45
46 char buf[64];
47 while(1)
48 {
49 buf[0] = 0;//每读取一次,把缓冲区清零
50 ssize_t st = read(pipe_fd[0], buf, sizeof(buf));
51 if(st > 0)
52 {
53 buf[st] = 0;
54 printf("child send to pipe :%s\n", buf);
55 }
56 else if(st == 0)
57 {
58 printf("pipe file close, child quit\n");
59 break;
60 }
61 else
62 {
63 break;
64 }
65 }
66 //father wait
67 int status = 0;
68 pid_t ret = waitpid(-1, &status, 0);
69 if(ret > 0)
70 {
71 //wait success
72 //获取退出子进程退出状态
73 printf("exit code:%d, sig: %d\n", (status >> 8) & 0xFF, status & 0x7F);
74 }
75 close(pipe_fd[0]);
76 }
77
78 return 0;
79 }
得到的现象是,写端快速写入,知道写了很多次后,就暂时不写(实际上不是不写,而是在等待读端),
因为一次写入一字节,而写入65536字节后就开始等待读端了,说明此时所创建的匿名管道满了,可知大小为65536字节,也就是64KB大小。
命名管道
命名管道的5个特点:
- 1、只能单向通信的通信信道
- 2、面向字节流
- 3、用于“没有血缘关系”的进程通信
- 4、写端写入是原子性的,自带同步机制
- 5、管道生命周期随通信进程
命名管道与匿名管道的特点差异在于:
匿名管道只用于“具有血缘关系”的进程之间相互通信
命名管道用于“不具有血缘关系”的进程之间相互通信
创建命名管道
方式一:在命令行上创建,使用下面这个命令:
mkfifo filename
方式二:命名管道也可以从程序里创建,相关函数有:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename,mode_t mode);
//成功返回0,失败返回-1
//filename:创建出的管道文件,路径+文件名
//mode:管道文件的权限
1 #include <stdio.h>
2 #include <sys/stat.h>
3 #include <sys/types.h>
4 int main(void)
5 {
6 //创建命名管道,并判断是否创建成功
7 if(mkfifo("./fifo", 0666) < 0)
8 {
9 perror("mkfifo");
10 return 1;
11 }
12
13 return 0;
14 }
创建成功
但它的权限是rw-rw-r–,按8进制来显示则是0664,实际上它受到了权限掩码的影响。
//使用umask查看权限掩码
[[email protected]-0-2-centos fifo]$ umask
0002
上述例子:my_mode = 0666 & (~0002) = 0664.
解决办法之一:将权限掩码设置为0,即可不受权限掩码的影响。
//设置权限掩码
mode_t umask(mode_t mask)
命名管道的测试
上述已经给出了server.c文件,并创建出了一个命名管道。命名管道用于不具有血缘关系之间的进程进行相互通信,所以可以再创建出一个程序(运行后变成另外一个进程),以下是这次测试所创建的文件
[[email protected]-0-2-centos fifo]$ ls -l
total 16
-rw-rw-r-- 1 YDY YDY 572 Jul 27 14:56 client.c
-rw-rw-r-- 1 YDY YDY 123 Jul 27 14:26 comm.h
-rw-rw-r-- 1 YDY YDY 146 Jul 27 14:14 Makefile
-rw-rw-r-- 1 YDY YDY 970 Jul 27 14:59 server.c
Makefile文件内容
1 .PHONY:all
2 all: client server
3
4 client : client.c
5 gcc -o [email protected] $^
6 server:server.c
7 gcc -o [email protected] $^
8
9 .PHONY:clean
10 clean:
11 rm -f client server fifo
comm.h文件内容
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <unistd.h>
6 #include <string.h>
server.c文件内容
1 #include "comm.h"
2 #include <sys/wait.h>
3 #include <stdlib.h>
4
5 int main(void)
6 {
7 //创建命名管道,并判断是否创建成功
8 umask(0);
9 if(mkfifo("./my_fifo", 0666) < 0)
10 {
11 perror("mkfifo");
12 return 1;
13 }
14 //打开管道文件
15 int fd = open("./my_fifo", O_RDONLY);
16 if(fd < 0)
17 {
18 perror("open");
19 return 2;
20 }
21
22 //开始业务逻辑
23 while(1)
24 {
25 char buf[64];
26 ssize_t st = read(fd, buf, sizeof(buf) - 1);
27 if(st > 0)
28 {
29 buf[st] = 0;
30 if(strcmp(buf, "show") == 0)
31 {
32 if(fork() == 0)
33 {
34 //child
35 execl("/usr/bin/ls", "ls", "-l", NULL);
36 exit(0);
37 }
38 waitpid(-1, NULL, 0);
39 }
40 else
41 {
42 printf("server read# %s\n", buf);
43 }
44 }
45 else if(st == 0)
46 {
47 //读端、写端关闭
48 printf("client quit!\n");
49 break;
50 }
51 else
52 {
53 perror("read");
54 break;
55 }
56
57 }
58 close(fd);
59 return 0;
60 }
client.c文件内容
1 #include "comm.h"
2
3 int main(void)
4 {
5 //这里就不需要创建管道文件了,直接打开
6 int fd = open("./my_fifo", O_WRONLY);
7 if(fd < 0)
8 {
9 perror("open");
10 return 2;
11 }
12
13 //业务逻辑部分
14 while(1)
15 {
16 printf("输入# ");
17 fflush(stdout); //立即刷新
18
19 //先将标准输入里的数据拿到进程内部
20 char buf[64];
21 ssize_t st = read(0, buf, sizeof(buf) - 1);
22 if(st > 0)
23 {
24 buf[st - 1]= 0;
25 //将buf内的数据写入管道文件
26 write(fd, buf, strlen(buf));
27 }
28 }
29 close(fd);
30 return 0;
31 }
运行截图
管道文件的数据不刷新到磁盘上。
为了让不同的进程看到同一个文件,这个文件必须有文件名+文件路径。
System V标准
管道是基于文件的进程间通信方式,而Systm V标准的进程间通信方式是专门在OS层面定制的,是同一主机内的进程间通信方案。
System V进程间通信,一定存在专门用来通信的system call接口。
System V 通信的三种方式:
- System V 共享内存
- System V 消息队列
- System V 信号量
System V 共享内存
共享内存的原理:
1、通过某system call,在内存中创建一份内存空间。
2、通过某system call, 让参与通信的进程都“挂接”到这份内存。
这就使得不同的进程能够看到同一份资源,进而达到通信目的。
准备工作
1、共享内存可能同时存在多份,所以OS会对共享内存进行管理,管理的办法是“先描述,后组织”。共享内存描述的方法是将它的各种信息存放在结构体中
2、共享内存有唯一标识它的标识符,目的是让不同进程能够识别到同一份共享内存资源。这个标识符需要由用户设定。
共享内存部分system call接口
- 创建/获取共享内存
#include <sys/ipc.h>
#include <shm.h>
int shmget(key_t key, size_t size, int shmflag);
- 控制共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, shmid_ds *buf);
- 关联共享内存
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflag);
- 去关联共享内存
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
创建/获取共享内存
#include <sys/ipc.h>
#include <shm.h>
int shmget(key_t key, size_t size, int shmflag);
key是需要用户自己设定的,只要形成key的算法和原始数据相同,就能形成同一个ID,保证不同的进程能够看到同一份共享内存资源。
key可以使用函数ftok
生成
ftok作用:将路径名和项目标识符转换为System V IPC的key
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char* pathname, int proj_id);
//返回值,成功返回一个key值,失败返回-1
pathname必须是已经存在的可访问文件,proj_id由用户自己填写,虽然proj_id是一个int类型,但是到现在为止,它只使用低8位。如果使用同一个proj_id去标定同一个文件,ftok的返回值是一样的。
shmget中的参数size
获取的共享内存大小,建议为当前所在机器物理页面大小的整数倍。因为共享内存申请的单位是页。假设,当前机器的一个物理页面大小是4KB(4096字节),而我们申请了4097字节,OS就会申请2个页面,但它只会给我们4097个字节使用,那么就浪费了很大的一段内存空间。
shmget中的参数shmflag
可填的几种值:
- 0:创建一个共享内存,如果已经存在,返回当前已经存在的共享内存
- IPC_CREAT:创建一个共享内存,如果已经存在,返回当前已经存在的共享内存
- IPC_CREAT | IPC_EXCL:如果共享内存不存在,创建一个;如果共享内存已经存在,则返回错误。
- 还可以在前面的基础上
|
上共享内存段指明的权限
返回值
成功返回一个有效的shmid(共享内存标识符)
失败返回-1
key VS shmid
key:只是用于在系统层面进行标识唯一性的,不能用于管理共享内存
shmid:是OS给用户返回的ID,用于在用户层管理共享内存。
建议一定要在最后一个参数的基础上|
指明共享内存段的权限,否则后面访问不了共享内存段(访问不了,去访问就会出现段错误),那还如何利用共享内存段实现进程间的通信呢?
查看共享内存的命令ipcs -m
[[email protected]-0-2-centos shared_memory]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x33010c0a 10 YDY 666 4096 1
查看System V IPC资源的命令ipcs
[[email protected]-0-2-centos shared_memory]$ ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x33010c0a 10 YDY 666 4096 1
------ Semaphore Arrays --------
key semid owner perms nsems
删除共享内存的命令ipcrm -m shmid
[[email protected]-0-2-centos shared_memory]$ ipcrm -m 10
[[email protected]-0-2-centos shared_memory]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
控制共享内存(只了解共享内存的删除)
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, shmid_ds *buf);
共享内存的删除,cmd使用宏IPC_RMID
即可,第三个参数设置为NULL。
共享内存的关联和去关联。
#include <sys/types.h>
#include <sys/shm.h>
//关联
void *shmat(shmid, const void* shmaddr, int shmflag);
//去关联
int shmdt(const void *shmaddr);
如果关联成功, shmat
返回虚拟地址。
利用共享内存实现通信的操作,在共享内存关联成功之后,去关联之前。
共享内存的测试
共有如下测试文件
[[email protected]-0-2-centos shared_memory]$ ls -l
total 16
-rw-rw-r-- 1 YDY YDY 675 Jul 28 09:12 client.c
-rw-rw-r-- 1 YDY YDY 168 Jul 28 08:47 comm.h
-rw-rw-r-- 1 YDY YDY 141 Jul 27 16:43 Makefile
-rw-rw-r-- 1 YDY YDY 689 Jul 28 09:08 server.c
Makefile文件
1 .PHONY:all
2 all : client server
3
4 client : client.c
5 gcc -o [email protected] $^
6 server : server.c
7 gcc -o [email protected] $^
8
9 .PHONY:clean
10 clean:
11 rm -f client server
comm.h文件
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/shm.h>
4 #include <sys/ipc.h>
5 #include <unistd.h>
6
7
8 #define PATHNAME "./"
9 #define PROJ_ID 0X333
10 #define SIZE 4096
server.c文件
1 #include "comm.h"
2 #include <sys/shm.h>
3 int main(void)
4 {
5 //创建共享内存
6 key_t key = ftok(PATHNAME, PROJ_ID);
7 if(key < 0)
8 {
9 perror("ftok");
10 return 1;
11 }
12 int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL|0666);
13 if(shmid < 0)
14 {
15 perror("shmget");
16 return 2;
17 }
18 printf("key: %d, shmid :%d\n", key, shmid);
19 //关联共享内存
20 char* mem = (char*)shmat(shmid, NULL, 0);
21 if(mem == NULL)
22 {
23 perror("shmat");
24 return 1;
25 }
26 printf("shm attach success!\n");
27 sleep(10);
28 //通信
29 //读取
30 while(1)
31 {
32 sleep(1);
33 printf("%s\n", mem);
34 }
35
36 //共享内存去关联
37 shmdt(mem);
38 printf("shm detaches success!\n");
39
40 return 0;
41 }
client.c文件
1 #include "comm.h"
2
3 int main(void)
4 {
5 //映射到同一块共享内存
6 key_t key = ftok(PATHNAME, PROJ_ID);
7 if(key < 0)
8 {
9 perror("ftok");
10 return 1;
11 }
12 int shmid = shmget(key, SIZE, IPC_CREAT | 0666);//存在,则直接获取
13 if(shmid < 0)
14 {
15 perror("shmget");
16 return 2;
17 }
18 //关联共享内存
19 char* mem = (char*)shmat(shmid, NULL, 0);
20 printf("shm attches success!\n");
21
22 //写入
23 char c = 'A';
24 while(c <= 'Z')
25 {
26 mem[c - 'A'] = c;
27 c++;
28 mem[c - 'A'] = 0;
29 sleep(2);
30 }
31
32 //去关联
33 shmdt(mem);
34 printf("shm detaches success!\n");
35
36 shmctl(shmid, IPC_RMID, 0);
37 printf("shm delete success!\n");
38 return 0;
39 }
共享内存是目前所有进程间通信方式中最快的一种。
System V 信号量
准备知识:
1、临界资源:能够被多个执行流同时访问的资源。
2、临界区:用来访问临界资源的代码
3、原子性:一件事情要么一口气做完,要么不做。没有中间态。
4、互斥:任意时刻只允许有一个执行流(单核CPU)访问临界资源。
管道、共享内存、消息队列等是以传输数据为目的的,而信号量不是以传输数据为目的的,通过资源共享的方式,来达到进程同步和互斥的目的。信号量的本质,是一个计数器,用来衡量临界资源中资源数目的。
边栏推荐
- 内网渗透——提权
- ROS Action communication
- 想要写出好的测试用例,先要学会测试设计
- typescript13 - type aliases
- typescript17-函数可选参数
- Analyze the capabilities and scenarios of the cloud native message flow system Apache Pulsar
- 观察者(observer)模式(一)
- Basic Parameters of RF Devices 1
- typescript15- (specify both parameter and return value types)
- 剑指offer17---打印从1到最大的n位数
猜你喜欢
tensorflow与GPU版本对应安装问题
Chi-square distribution of digital image steganography
深度学习可以求解特定函数的参数么?
Bert usage and word prediction based on Keras_bert model
typescript16-void
软件测试工作3年了,谈谈我是如何从刚入门进阶到自动化测试的?
射频器件的基本参数1
VS warning LNK4099: No solution found for PDB
Meta元宇宙部门第二季度亏损28亿 仍要继续押注?元宇宙发展尚未看到出路
"Real" emotions dictionary based on the text sentiment analysis and LDA theme analysis
随机推荐
tensorflow与GPU版本对应安装问题
ROS Action通信
九州云获评云计算标准化优秀成员单位
typescript18-对象类型
蓝牙mesh系统开发二 mesh节点开发
In Google Cloud API gateway APISIX T2A and T2D performance test
PDF split/merge
1.非类型模板参数 2.模板的特化 3.继承讲解
ShardingSphere's public table combat (7)
prometheus 监控概述
Google官方控件ShapeableImageView使用
斩获BAT、TMD技术专家Offer,我都经历了什么?
网站频繁出现mysql等数据库连接失败等信息解决办法
typescript9-常用基础类型
验证 XML 文档
typescript11 - data types
打印任务排序 js od华为
勾股数元组 od js
android的webview缓存相关知识收集
Dispatch Center xxl-Job