在 Linux 系统中,进程间通信(IPC)是实现多任务环境中不同进程协作的关键技术。在众多的 IPC 机制中,共享内存(Shared Memory)是一种高效的进程间通信(IPC)机制。它允许多个进程共享一个给定的存储区,这些进程可以是父子进程关系或完全无关的进程。共享内存是最快的一种 IPC 形式,因为它允许进程直接对内存进行读写,而不需要数据在内核空间和用户空间之间复制。
一、System V 共享内存概述
进程间通信的本质是让不同进程看到同一个资源。System V 共享内存是一种古老的但依然广泛使用的 IPC 机制,它允许多个进程共享同一块物理内存区域(类似C语言动态库的加载)。这种机制的优势在于,进程可以直接读写内存,无需数据在用户空间和内核空间之间的复制,从而大大提高了数据交换的速度。
二、共享内存的使用方法
核心函数:
- **shmget()**:创建或获取一个共享内存段。
- **shmat()**:将共享内存段附加到进程的地址空间。
- **shmdt()**:将共享内存段从进程的地址空间分离。
- **shmctl()**:控制共享内存段(如设置权限)。
1. shmget():创建或获取共享内存段
shmget
函数用于创建一个新的共享内存段或获取对现有共享内存段的访问。如果共享内存段不存在,并且
IPC_CREAT
标志被设置,那么
shmget
将创建一个新的共享内存段。
#include <sys/ipc.h> #include <sys/shm.h> int shmid = shmget(key, size, IPC_CREAT | 0666);
- key:这是一个键值,用于唯一地标识共享内存段。可以通过
ftok
函数从文件路径和项目 ID 生成。key_t ftok(const char *pathname, int proj_id) // key_t key = ftok("/tmp", 12345);
- size:这是要创建的共享内存段的大小,单位为字节。
- IPC_CREAT:这个标志表示如果共享内存段不存在,则创建它。
- 0666:这是共享内存段的权限设置,表示所有用户都可以读写共享内存。
2. shmat():将共享内存附加到进程地址空间
一旦共享内存段被创建,进程可以使用
shmat
函数将其附加到自己的地址空间中。这样,进程就可以通过指针直接访问共享内存。
char *ptr = (char *)shmat(shmid, NULL, 0);
- shmid:由
shmget
返回的共享内存段的标识符。- NULL:这个参数指定了共享内存附加的地址。如果传递
NULL
,系统会自动选择一个合适的地址。- 0:这个标志用于指定附加操作的行为,通常设置为 0。
3. shmdt():将共享内存从进程地址空间分离
当进程不再需要访问共享内存时,可以使用
shmdt
函数将其从地址空间中分离。这不会删除共享内存段,只是断开了进程与共享内存的连接。
int shmdt((const void *)ptr);
- ptr:这是通过
shmat
函数获得的指向共享内存的指针。
4. shmctl():控制共享内存段
shmctl
函数用于控制共享内存段,如获取其状态信息、设置权限或删除共享内存段。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- shmid:共享内存段的标识符。
- cmd:这是要执行的控制操作的命令,如
IPC_STAT
用于获取状态信息,IPC_RMID
用于删除共享内存段。- buf:这是一个指向
shmid_ds
结构的指针,用于存储共享内存的状态信息或控制命令的参数。
三、共享内存的使用场景
共享内存适用于以下场景:
- 速度快:因为进程可以直接访问内存,无需数据复制。
- 简单:使用内存映射文件(memory-mapped files)或系统调用实现。
- 容量限制:受限于系统内存和进程地址空间。
四、实践指南与注意事项
- 同步机制:由于共享内存本身不提供同步机制,因此在访问共享内存时,应使用信号量或其他同步手段来避免竞态条件。
- 内存管理:确保在不再需要共享内存时,及时使用
shmctl
函数删除,避免内存泄漏。 - 权限控制:合理设置共享内存的权限,防止未授权访问。
五、示例代码:展示如何使用 System V 共享内存获得其他进程放入共享内存的数据
a. 演示代码头文件
mem.hpp
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
const char* pathname = "./main.cc";
using namespace std;
b. 创建和使用共享内存
#include"mem.hpp"
int main()
{
// 生成内存唯一key值
key_t key = ftok(pathname, 0666);
// 创建共享内存段
int shmid = shmget(key, sizeof(int), 0666 | IPC_CREAT);
if (shmid < 0)
{
cerr << "shmget error" << endl;
return 1;
}
// 将共享内存加载到进程地址空间
int *ptr = (int *)shmat(shmid, nullptr, 0);
if (ptr == (int *)(-1))
{
cerr << "shmat error" << endl;
return 2;
}
// 分离共享内存
*ptr = 123;
cout << "process 1 write> ptr = " << *ptr << endl;
if(shmdt(ptr) == -1)
{
cerr << "shmdt error " << endl;
return 3;
}
sleep(5);
// 删除共享内存
if(shmctl(shmid, IPC_RMID, nullptr) == -1)
{
cerr << "shmctl error" << endl;
return 4;
}
return 0;
}
c. 另一个进程访问共享内存
#include"mem.hpp"
int main()
{
// 生成唯一key值
key_t key = ftok(pathname, 0666);
// 获取共享内存
int shmid = shmget(key, sizeof(int), 0666);
if (shmid < 0)
{
cerr << "shmget error " << endl;
return 1;
}
// 将共享内存加载到程序中
int *ptr = (int *)shmat(shmid, nullptr, 0);
if (ptr == (int *)-1)
{
cerr << "shmat error " << endl;
return 2;
}
//
// 使用共享内存(获得共享内存里的数据)
cout << *ptr << endl;
//
// 分离共享内存
if(shmdt(ptr) == -1)
{
cerr << "shmdt error " << endl;
return 3;
}
return 0;
}
版权归原作者 koi li 所有, 如有侵权,请联系我们删除。