从零到负一

Linux编程基础4 - 进程间通信(共享内存)

2020/06/24

Linux下的几个IPC(信号量、共享内存、消息队列)都使用了相似的系统调用,上一章介绍了信号量,这一节来说说共享内存。

背景知识

共享内存是内核在物理内存中预留了一片空间用于进程间的通信,这部分内存空间不属于任何一个进程,但每个进程都可以访问它们(通过建立映射)。共享内存是进程间通信最简单的方式,但缺点在于没有进程间同步的功能,因此我们需要使用其他技术进行共享内存的同步。

相关系统调用

shmget()

semget()类似,shmget()也是用于创建或者获取共享内存。它的函数原型如下:

1
2
// 成功返回共享内存的标识符,否则返回-1
int shmget(key_t key, size_t size, int shmflg);

size是我们需要共享内存的大小,shmflg是设置共享内存的标志位。同semget()一样,最后9位是访问权限,要创建新的共享内存需要使用shmflg | IPC_CREA | IPC_EXCL,如果返回EEXIST说明已经创建了同key的共享内存。

shmat()/shmdt()

创建好共享内存后,我们就需要将其映射到物理内存空间。使用shmat()系统调用建立映射,shmdt()解除这种映射。shmat()的函数原型如下:

1
2
// 成功返回共享内存的映射的地址,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);

shmaddr一般设置成NULL,这样内核会自动分配一块空间(也便于移植);shmflg可以设置为SHM_RDONLYSHM_RND等,但一般什么都不用设置,只填0就行了。

shmdt()函数原型如下:

1
2
// 成功返回0,否则返回-1
int shmdt(const void *shmaddr);

shmdt()函数只是断开进程和共享内存的映射,并没有销毁共享内存,要销毁共享内存需要使用函数shmctl()

shmctl()

shmctl()函数原型如下:

1
2
// 大部分命令成功返回0,所有命令失败都返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmctl()可以用的命令有很多,要销毁之前创建的共享内存,我们需要使用IPC_RMID命令。使用IPC_RMID时,buf参数会被忽略,因此直接使用shmctl(shmid, IPC_RMID, NULL)即可。必须是创建或者拥有这个共享内存的进程才能调用shmctl()来销毁它。

共享内存实例

下面这个实例来自博文 - Linux 高级编程 - 共享内存 Shared Memory,该例子没有牵涉进程同步的内容。

写操作

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
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
// 1. 创建 SHM
int shm_id = shmget(13, 2048, IPC_CREAT | 0666);
if (shm_id != -1) {
// 2. 映射 SHM
void* shm = shmat(shm_id, NULL, 0);
if (shm != (void*)-1) {
// 3. 写 SHM
char str[] = "I'm share memory";
memcpy(shm, str, strlen(str) + 1);
// 4. 关闭 SHM
shmdt(shm);
} else {
perror("shmat:");
}
} else {
perror("shmget:");
}
return 0;
}

读操作

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
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
// 1. 获取 SHM
int shm_id = shmget(13, 2048, IPC_CREAT | 0666);

if (shm_id != -1) {
// 2. 映射 SHM
void* shm = shmat(shm_id, NULL, 0);
if (shm != (void*)-1) {
// 3. 读取 SHM
char str[50] = { 0 };
memcpy(str, shm, strlen("I'm share memory"));
printf("shm = %s\n", (char *)shm);
// 4. 关闭 SHM
shmdt(shm);
} else {
perror("shmat:");
}
} else {
perror("shmget:");
}
if (0 == shmctl(shm_id, IPC_RMID, NULL))
printf("delete shm success.\n");
return 0;
}
CATALOG
  1. 1. 背景知识
  2. 2. 相关系统调用
    1. 2.1. shmget()
    2. 2.2. shmat()/shmdt()
    3. 2.3. shmctl()
  3. 3. 共享内存实例
    1. 3.1. 写操作
    2. 3.2. 读操作