Linux:进程间通信技术

一、什么是共享内存?

共享内存就像是一块大家都能直接访问的公共黑板,不同的进程可以直接在这块黑板上读写数据。和传统的进程间通信方式相比,共享内存不需要在内核和进程之间反复拷贝数据,大大减少了数据传输的开销。想象一下,传统方式是把资料从一个部门复印一份再送到另一个部门,而共享内存则是让两个部门直接看同一份资料,效率自然高得多。所以,在实时数据处理、数据库缓存、图像处理这些对数据传输速度要求极高的场景中,共享内存就成了开发者们的首选。

二、共享内存是如何工作的?

Linux 系统为例,常见的共享内存实现方式有System V共享内存和POSIX共享内存,它们各有特点,下面我们分别来看其实现步骤。

1. System V 共享内存

创建或获取共享内存段:首先,我们需要用ftok函数生成一个唯一的 “钥匙”(键值),它根据一个已存在的文件路径和一个项目 ID 来生成,这个键值就像进入共享内存房间的钥匙。例如:

1
2
3
#include <sys/types.h>
#include <sys/ipc.h>
key_t key = ftok(".", 'a');

有了 “钥匙” 后,就可以用shmget函数创建或获取共享内存段。如果是创建新的,需要指定共享内存的大小和权限:

1
2
3
#include <sys/ipc.h>
#include <sys/shm.h>
int shmid = shmget(key, 1024, IPC_CREAT | 0666);

这里1024表示共享内存大小为 1024 字节,IPC_CREAT | 0666表示如果不存在就创建,并且设置读写权限为 0666。

附加共享内存段:创建好共享内存后,还需要用shmat函数把它 “连接” 到当前进程的地址空间,这样进程就能访问这块内存了:

1
2
3
#include <sys/types.h>
#include <sys/shm.h>
char *shmaddr = (char *)shmat(shmid, NULL, 0);

shmidshmget返回的共享内存标识符,NULL表示让系统自动分配映射地址,0表示读写权限。

访问共享内存:连接成功后,就可以像操作普通内存一样,直接通过指针在共享内存里读写数据。

分离共享内存段:当进程不再需要使用共享内存时,要用shmdt函数把它从进程地址空间中分离出来。如果想要彻底删除共享内存段,可以使用shmctl函数,IPC_RMID命令会标记共享内存段为删除状态,等所有进程都分离后,系统就会真正删除它。

2. POSIX 共享内存

创建或打开共享内存对象:使用shm_open函数,通过一个名称来创建或打开共享内存对象,同时需要指定权限和文件模式:

1
2
3
4
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
int shm_fd = shm_open("/my_shared_memory", O_CREAT | O_RDWR, 0666);

这里"/my_shared_memory"是共享内存对象的名称,O_CREAT | O_RDWR表示如果不存在则创建且以读写模式打开,0666是文件权限。

设置共享内存大小:使用ftruncate函数设置共享内存的大小:

1
ftruncate(shm_fd, 1024);

这里将共享内存大小设置为 1024 字节。

映射共享内存到进程地址空间:使用mmap函数将共享内存对象映射到进程的地址空间:

1
void *shmaddr = mmap(0, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

0表示让系统自动选择映射地址,1024是共享内存大小,PROT_READ | PROT_WRITE表示可读可写,MAP_SHARED表示共享映射,shm_fdshm_open返回的文件描述符,最后的0是偏移量。

访问共享内存:映射成功后,即可对共享内存进行读写操作。

解除映射与关闭共享内存对象:使用munmap函数解除共享内存的映射,使用shm_unlink函数删除共享内存对象名称,当所有进程都解除映射后,系统会自动释放共享内存资源:

1
2
munmap(shmaddr, 1024);
shm_unlink("/my_shared_memory");

三、System V 与 POSIX 共享内存对比

对比项 System V 共享内存 POSIX 共享内存
创建方式 通过ftok生成键值,再用shmget创建,依赖文件系统路径和项目 ID 直接用shm_open通过名称创建,类似文件操作,更直观
函数接口 函数命名风格较传统,操作步骤多,如shmgetshmat等多个函数配合 函数接口更接近文件操作函数,如shm_openmmap,概念更清晰
生命周期管理 删除需显式调用shmctl设置IPC_RMID,且要等待所有进程分离 使用shm_unlink删除名称,所有进程解除映射后自动释放,管理更简单
同步机制 本身不提供同步机制,需额外借助信号量等 可结合 POSIX 信号量、互斥锁等,与共享内存接口兼容性更好
跨平台性 主要在 UNIX 和 Linux 系统,跨平台性差 在 Linux、Mac 等多种系统广泛支持,跨平台性好

四、共享内存的优势与应用场景

优势

  1. 高效性:少了数据在内核和进程之间拷贝的步骤,读写速度极快,能大幅提升数据传输效率。

  2. 大数据量友好:对于需要传输大量数据的场景,共享内存完全能轻松应对,不会出现性能瓶颈。

  3. 灵活自由:作为公共缓冲区,进程可以根据自己的需求随意读写,方便进程间协作。

应用场景

  1. 实时数据处理:在股票交易、期货行情分析、传感器数据采集等实时性要求高的场景中,共享内存能快速传递数据,保证系统及时响应。

  2. 图像处理与视频处理:多个进程需要共享图像或视频数据时,共享内存能让数据共享变得高效,加快处理速度。

  3. 高性能计算:在大型模型训练、AI 计算等大规模数据共享场景下,共享内存能显著提升计算效率。

  4. 数据库系统:数据库通过共享内存可以快速交换数据,减少对硬盘的频繁访问,提升整体性能。

五、使用共享内存的注意事项

虽然共享内存很强大,但使用时也有不少 “坑” 需要注意:

  1. 数据同步与互斥:因为多个进程都能访问共享内存,就像多个人同时用一块黑板写字,如果不加以控制,就会出现数据混乱。所以需要使用信号量、互斥锁等同步机制,保证数据的一致性,避免竞态条件。

  2. 内存管理:共享内存的创建、连接、分离和删除都要妥善处理,不然容易出现内存泄漏,浪费系统资源,甚至引发程序崩溃。

六、示例代码

1. System V 共享内存示例代码

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

const int SHM_SIZE = 1024; // 共享内存大小

int main() {
// 创建或获取共享内存
key_t key = ftok(".", 'a');
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
return 1;
}

// 附加共享内存
char *shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
return 1;
}

// 写入数据到共享内存
std::string message = "Hello, shared memory!";
std::strcpy(shmaddr, message.c_str());

// 分离共享内存
if (shmdt(shmaddr) == -1) {
perror("shmdt");
return 1;
}

// 标记共享内存段为删除状态(所有进程分离后真正删除)
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl");
return 1;
}

return 0;
}

2. POSIX 共享内存示例代码

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
#include <iostream>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string>

const int SHM_SIZE = 1024; // 共享内存大小

int main() {
// 创建或打开共享内存对象
int shm_fd = shm_open("/my_shared_memory", O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) {
perror("shm_open");
return 1;
}

// 设置共享内存大小
if (ftruncate(shm_fd, SHM_SIZE) == -1) {
perror("ftruncate");
return 1;
}

// 映射共享内存到进程地址空间
void *shmaddr = mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (shmaddr == MAP_FAILED) {
perror("mmap");
return 1;
}

// 写入数据到共享内存
std::string message = "Hello, POSIX shared memory!";
std::strcpy((char *)shmaddr, message.c_str());

// 解除映射
if (munmap(shmaddr, SHM_SIZE) == -1) {
perror("munmap");
return 1;
}

// 关闭共享内存对象
if (close(shm_fd) == -1) {
perror("close");
return 1;
}

// 删除共享内存对象名称
if (shm_unlink("/my_shared_memory") == -1) {
perror("shm_unlink");
return 1;
}

return 0;
}