【原理剖析】7-进程间通信方式之共享内存

概述:知识点:

  • Linux 共享内存方式原理介绍
  • Linux 共享内存相关 API 介绍

0x01 Linux 进程间通信方式之共享内存概述

在 Linux 中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。如下图所示:

当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。

对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。

0x02 Linux 共享内存使用步骤

  • 创建共享内存。也就是从内存中获得一段共享内存区域,这里用到的函数是 shmget()
  • 映射共享内存。也就是把这段创建的共享内存映射到具体的进程空间中,这里使用的函数是 shmat() 。到这一步就可以使用这段共享内存了,也就是可以使用不带缓冲的 I/O 读写命令对其进行操作。
  • 撤销映射。使用完共享内存就需要撤销,用到的函数是 shmdt()

其中用到的 Linux 系统函数介绍如下:

  1. shmget() 函数创建共享内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, int size, int shmflg);
/*
参数:
key:共享内存的键值,多个进程可以通过它访问同一个共享内存,其中 IPC_PRIVATE 用于创建当前进程私有的共享内存
size:共享内存的大小
shmflg:同 open() 函数的权限位
返回值:
成功:共享内存段标识符
失败:-1
*/
  1. shmat() 函数映射共享内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

char *shmat(int shmid, const void *shmaddr, int shmflg);
/*
参数:
shmid:要映射的共享内存标识符
shmaddr:将共享内存映射到指定地址,注意这里的是 void 型指针,如果是 0 则系统将自动分配地址并将共享内存映射到调用进程的地址空间
shmflg:SHM_RDONLY(共享内存只读),0(可读写)
返回值:
成功:被映射的段地址
失败:-1
*/

实验一 shmem_demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 256

int main(int argc, char *argv[])
{
pid_t pid;
int shmid;//共享内存标识符
char *shm_addr = NULL;
char flag[] = "SHIYANLOU";//标志字符串
char buf[BUFFER_SIZE] = {0};

/* 创建共享内存 */
shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666);
if(shmid < 0)
{
perror("shmget error");
exit(1);
}
printf("Create shared-memory: %d \n", shmid);

/* 显示共享内存情况 */
system("ipcs -m");

/* 创建子进程 */
pid = fork();
if(pid < 0)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{
/* 子进程 */
printf("[%d]Child prograss ...\n", getpid());
//映射共享内存
shm_addr = shmat(shmid, 0, 0);
if(shm_addr == (char *)-1)
{
perror("Child: shmat");
exit(1);
}
printf("[%d]Child : Attach shared-memory [%p]\n", getpid(), shm_addr);
/* 显示内存情况 */
system("ipcs -m");
while(strncmp(shm_addr, flag, strlen(flag)))
{
printf("[%d]Child : Wait for enable data...\n", getpid());
sleep(5);
}
/* 共享内存有效数据显示 */
strcpy(buf, shm_addr+strlen(flag));
printf("[%d]Child : Shared-memory: %s\n", getpid(), buf);

/* 解除共享内存映射 */
if((shmdt(shm_addr)) < 0)
{
perror("shmdt error");
exit(1);
}
printf("[%d]Child : Deattach shared-memory\n", getpid());
system("ipcs -m");

/* 删除共享内存 */
if(shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("Child : shmctl(IPC_RMID");
exit(1);
}
printf("[%d]Child : Delete shared-memory\n", getpid());
system("ipcs -m");

/* 子进程退出 */
exit(0);
}
else
{
/* 父进程 */
shm_addr = shmat(shmid, 0, 0);
if(shm_addr == (char *)-1)
{
perror("Parent: shmat");
exit(1);
}
printf("[%d]Parent : Attach shared-memory : [%p]\n", getpid(), shm_addr);
sleep(1);

printf("\nInput some string:\n");
fgets(buf, BUFFER_SIZE, stdin);
strncpy(shm_addr + strlen(flag), buf, strlen(buf));
strncpy(shm_addr, flag, strlen(flag));

/* 解除共享内存映射 */
if((shmdt(shm_addr)) < 0)
{
perror("Parent : shmdt error");
exit(1);
}
printf("[%d]Parent : Deattach shared-memory\n", getpid());
system("ipcs -m");
}

waitpid(pid, NULL, 0);
printf("\nFinished...\n");

return 0;
}

在 VS Code 平台下的终端窗口使用 gcc 工具完成可执行文件编译:

1
2
gcc shmem_demo.c -o shmem_demo
./shmem_demo

执行结果如下:

实验二——shmem_sem.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE 256

/* 定义联合体 */
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};



int init_sem(int sem_id, int init_val)
{
union semun sem_union;
sem_union.val = init_val;
if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
{
perror("init semaphore error.\n");
return -1;
}
return 0;
}

int del_sem(int sem_id)
{
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
{
// perror("Delete semaphore error.\n");
return -1;
}
return 0;
}

int sem_p(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;

if(semop(sem_id, &sem_b, 1) == -1)
{
perror("P error.\n");
return -1;
}
return 0;
}

int sem_v(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;

if(semop(sem_id, &sem_b, 1) == -1)
{
perror("P error.\n");
return -1;
}
return 0;
}

int main(int argc, char *argv[])
{
pid_t pid;
int sem_id;
int shmid;
char *shm_addr = NULL;
char buf[BUFFER_SIZE];

//创建信号量
sem_id = semget(ftok(".", 'a'), 1, 0666|IPC_CREAT);
init_sem(sem_id, 0);
//创建共享内存
shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666);
if(shmid < 0)
{
perror("shmget");
exit(1);
}
printf("Create Shared-memory : %d\n", shmid);

//显示共享内存情况
system("ipcs -m");
pid = fork();
if(pid < 0)
{
perror("fork() error");
exit(1);
}
else if(pid == 0)
{
//子进程
//映射共享内存
shm_addr = shmat(shmid, 0, 0);
if(shm_addr == (char *)-1)
{
perror("Child: shmat");
exit(1);
}
printf("[%d]Child : Attach shared-memory [%p]\n", getpid(), shm_addr);
/* 显示内存情况 */
system("ipcs -m");

printf("[%d]Child : Wait for enable data...\n", getpid());

/* 共享内存有效数据显示 */
while(1)
{
memset(buf, 0, BUFFER_SIZE);
sem_p(sem_id);
strcpy(buf, shm_addr);
memset(shm_addr, 0, BUFFER_SIZE);
printf("[%d]Child : Shared-memory: %s\n", getpid(), buf);

if((!strncmp(buf, "q", 1)) || (!strncmp(buf, "Q", 1)))
{
printf("[%d]Child Will Quit.\n", getpid());
break;
}
}

sem_v(sem_id);
del_sem(sem_id);
/* 解除共享内存映射 */
if((shmdt(shm_addr)) < 0)
{
perror("shmdt error");
exit(1);
}
printf("[%d]Child : Deattach shared-memory\n", getpid());
system("ipcs -m");

/* 删除共享内存 */
if(shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("Child : shmctl(IPC_RMID");
exit(1);
}
printf("[%d]Child : Delete shared-memory\n", getpid());
system("ipcs -m");

/* 子进程退出 */
exit(0);
}
else
{
//父进程
shm_addr = shmat(shmid, 0, 0);
if(shm_addr == (char *)-1)
{
perror("Parent: shmat");
exit(1);
}
printf("[%d]Parent : Attach shared-memory : [%p]\n", getpid(), shm_addr);
sleep(1);

while(1)
{
memset(buf, 0, BUFFER_SIZE);
printf("\nInput some string:\n");
fgets(buf, BUFFER_SIZE, stdin);
strncpy(shm_addr, buf, strlen(buf));
sem_v(sem_id);
if((!strncmp(buf, "q", 1)) || (!strncmp(buf, "Q", 1)))
{
printf("[%d]Parent Will Quit.\n", getpid());
break;
}
}

/* 解除共享内存映射 */
if((shmdt(shm_addr)) < 0)
{
perror("Parent : shmdt error");
exit(1);
}
printf("[%d]Parent : Deattach shared-memory\n", getpid());
system("ipcs -m");
}

waitpid(pid, NULL, 0);
printf("\nFinished...\n");

return 0;
}

【原理剖析】7-进程间通信方式之共享内存
https://hodlyounger.github.io/A_OS/Linux/Linux操作系统原理剖析/【原理剖析】7-进程间通信方式之共享内存/
作者
mingming
发布于
2023年12月27日
许可协议