一、共享内存(SHM)
像上图所示,当进程 P1 向其虚拟内存中的区域 1 写入数据时,进程 2 就能同时在其虚拟内存空间的区域 2 看见这些数据,中间没有经过任何的转发,效率极高。
使用共享内存的一般步骤是:
1,获取共享内存对象的 ID
2,将共享内存映射至本进程虚拟内存空间的某个区域
3,当不再使用时,解除映射关系
4,当没有进程再需要这块共享内存时,删除它。
方法总结
共享内存是进程间通信(IPC)的重要方式之一,可以让多个进程访问相同的内存区域,从而实现数据共享。实现共享内存的方式有多种,主要包括以下几种:
POSIX 共享内存 (POSIX Shared Memory)
使用 shm_open 和 mmap 等函数来创建和管理共享内存段。
POSIX 共享内存可以通过名字标识,多个进程可以通过名字来访问同一个共享内存段。
需要使用 shm_unlink 来删除共享内存对象。
mmap:它是一个系统调用,用于在用户空间和内核空间之间进行文件映射。
System V 共享内存 (System V Shared Memory)
使用 shmget、shmat、shmdt 和 shmctl 等函数来创建和管理共享内存段。
共享内存段通过一个整数键来标识,进程通过这个键来访问共享内存段。
System V 共享内存提供了更多的控制选项,但编程接口相对复杂。
shmat是Linux系统中的一个重要的内存管理函数
匿名映射(Anonymous Mapping)
使用 mmap 函数,可以在多个进程之间创建匿名共享内存区域。
通常通过 fork 创建子进程时,共享的匿名内存区域会自动继承。
内存映射文件(Memory Mapped Files)
使用 mmap 函数将文件映射到进程的地址空间,不同进程可以通过映射同一个文件来实现共享内存。
文件映射提供了一种持久化的数据共享方式,但需要文件系统的支持。
Windows 共享内存
使用 Windows API 提供的 CreateFileMapping 和 MapViewOfFile 函数来创建和管理共享内存段。
通过命名的内存映射文件对象可以在不同进程间共享内存。
需要使用 CloseHandle 来释放资源。
Boost.Interprocess (C++ 库)
Boost 库提供了跨平台的共享内存实现,使用 boost::interprocess::shared_memory_object 和 boost::interprocess::mapped_region 等类来管理共享内存。
提供了更高层次的封装,简化了共享内存的使用。
Python 的 multiprocessing 模块
Python 的 multiprocessing 模块提供了共享内存的支持,如 multiprocessing.Array 和 multiprocessing.Value。
适用于 Python 的多进程编程,简单易用。
每种共享内存方式都有其特点和适用场景,选择具体方式时需要根据系统平台、应用需求和编程语言等因素进行权衡。
举例:内存映射文件(Memory Mapped Files)
先用open函数打开一个文件,然后调用mmap函数把得到的描述符映射到当前进程地址空间中。这种方式访问速度相对较慢,因为需要内核同步或异步更新到文件系统中。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
int main() {
// 打开文件
int fd = open("example.txt", O_RDWR);
if (fd == -1) {
perror("Error opening file");
exit(EXIT_FAILURE);
}
// 获取文件大小
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("Error getting the file size");
exit(EXIT_FAILURE);
}
off_t length = sb.st_size; // 文件大小,单位是字节
// 映射内存到进程的地址空间
char* map = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
perror("Error mmapping the file");
exit(EXIT_FAILURE);
}
// 打印映射的内存内容,即文件内容
for (off_t i = 0; i < length; i++) {
printf("%c", map[i]); // 打印每个字符,即文件内容
}
printf("\n");
// 释放内存映射和文件描述符
if (munmap(map, length) == -1) {
perror("Error un-mmapping the file");
exit(EXIT_FAILURE);
}
close(fd);
return 0;
}
- open函数用于打开文件,其返回值是文件描述符。如果打开失败,则返回-1。第二个参数O_RDWR表示以读写模式打开文件。
- fstat函数用于获取文件的大小,其返回值是stat结构体,其中st_size成员表示文件大小(单位是字节)。如果获取失败,则返回-1。
- mmap函数用于将文件映射到进程的地址空间。第一个参数是映射区域的起始地址,通常为NULL。第二个参数是映射区域的长度。第三个参数是保护标志,这里设置为读、写和共享(可读、可写、可被其他进程共享)。第四个参数是映射对象的类型,这里设置为共享内存。第五个参数是文件描述符。第六个参数是文件映射的偏移量。如果映射成功,则返回映射区域的指针;否则返回MAP_FAILED。
- munmap函数用于释放内存映射。第一个参数是映射区域的指针。第二个参数是映射区域的长度。如果释放成功,则返回0;否则返回-1。
举例:POSIX 共享内存
先用shm_open打开一个Posix IPC名字(也可以是文件系统中的一个路径名),然后调用mmap将返回的描述符映射到当前进程的地址空间。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define SHM_SIZE 1024 // 共享内存大小
int main() {
int fd;
void *map_ptr;
// 打开共享内存对象,以读写模式打开,不创建新对象,如果对象不存在则返回-1
fd = shm_open("/Posix IPC", O_RDWR | O_CREAT, 0666);
if (fd == -1) {
perror("shm_open");
exit(EXIT_FAILURE);
}
// 调整共享内存对象的大小,这里将其设置为1024字节
if (ftruncate(fd, SHM_SIZE) == -1) {
perror("ftruncate");
exit(EXIT_FAILURE);
}
// 将共享内存对象的描述符映射到当前进程的地址空间,map_ptr指向的就是这块内存的起始地址
map_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_ptr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
// 现在可以在map_ptr指向的内存区域进行读写操作了,这就像操作普通的内存一样简单
// ... 写入数据到 map_ptr 指向的内存区域 ...
// ... 从 map_ptr 指向的内存区域读取数据 ...
// 当进程不再需要访问共享内存时,