【进程通信】 Linux下使用共享内存实现跨进程通信:基于C++的完整示例
在 Linux 系统中,不同进程间无法直接共享变量,但我们可以通过 共享内存(Shared Memory) 这种高效的进程间通信方式(IPC)实现数据共享。本文将通过一个基于 C++ 的示例,带你从零理解如何使用共享内存配合信号量实现跨进程通信,并详细解释关键函数如 ftok()
的原理和使用。
一、为什么选择共享内存?
在常见的进程通信方式中,比如管道、消息队列、Socket 等,都存在一定的系统调用开销。而共享内存是最快的IPC方式之一,它允许多个进程将同一块内存段映射到各自的地址空间,从而实现内存级别的数据共享。缺点是它本身不具备同步机制,因此通常需要结合信号量或互斥锁使用。
二、示例代码概览
我们将实现两个进程:
- 生产者(Producer):将数据写入共享内存。
- 消费者(Consumer):从共享内存读取数据。
为了避免同时访问造成数据混乱,示例中还使用了 POSIX 信号量(sem_t
) 来进行互斥访问控制。
代码文件名为:shared_memory_demo.cpp
三、共享内存结构定义
我们定义了一个结构体 SharedData
,用于在进程间传递数据:
struct SharedData {
int counter;
char message[128];
};
该结构体将在共享内存中作为数据容器,用于写入和读取。
四、共享内存的创建与连接(含 key 生成机制详解)
在 Linux 中使用共享内存前,需通过 shmget()
创建共享内存段。这个函数需要一个 key 来标识这块共享内存,生成这个 key 的常用方法是使用 ftok()
:
key_t key = ftok(SHM_KEY_PATH, 'R');
🔍 ftok()
原理解析
ftok()
函数用于从一个已有文件路径生成一个唯一的 key。它的原型如下:
key_t ftok(const char* pathname, int proj_id);
pathname
:指定一个存在的文件路径(必须存在!)。proj_id
:一个项目ID,通常为字符(如'R'
、'X'
等),用于区分不同用途的共享内存。
工作机制:
ftok()
会将指定文件的 inode编号 和 设备号 与 proj_id
组合,生成一个唯一的 key_t
值。例如:
touch /tmp/shm_demo
ls -i /tmp/shm_demo
执行上述命令可以查看该文件的 inode 编号。
使用该 key 创建共享内存:
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
成功后即可通过 shmat()
将共享内存映射到当前进程的地址空间。
五、使用信号量进行访问控制
由于共享内存没有内建的同步机制,我们使用 POSIX 信号量 sem_t
来保证互斥访问:
sem_t* sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
sem_wait(sem);
// 操作共享内存
sem_post(sem);
sem_open()
:打开或创建信号量。sem_wait()
:进入临界区,信号量减一。sem_post()
:退出临界区,信号量加一。
六、生产者进程实现
void producer() {
int shmid = create_shared_memory();
SharedData* data = (SharedData*)shmat(shmid, NULL, 0);
sem_t* sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
sem_wait(sem);
data->counter = 0;
strcpy(data->message, "Hello from Producer, I am T, use the shm of IPC firstly !");
sem_post(sem);
std::cout << "Producer wrote: " << data->message << std::endl;
std::cout << "Press Enter to exit..." << std::endl;
std::cin.get();
// 清理资源
shmdt(data);
sem_close(sem);
shmctl(shmid, IPC_RMID, NULL); // 删除共享内存
sem_unlink(SEM_NAME); // 删除信号量
}
七、消费者进程实现
void consumer() {
int shmid = create_shared_memory();
SharedData* data = (SharedData*)shmat(shmid, NULL, 0);
sem_t* sem = sem_open(SEM_NAME, 0);
sem_wait(sem);
std::cout << "Consumer received: \n"
<< "Counter: " << data->counter << "\n"
<< "Message: " << data->message << std::endl;
sem_post(sem);
shmdt(data);
sem_close(sem);
}
八、程序入口与运行方式
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " [producer|consumer]\n";
return EXIT_FAILURE;
}
if (strcmp(argv[1], "producer") == 0) {
producer();
} else if (strcmp(argv[1], "consumer") == 0) {
consumer();
} else {
std::cerr << "Invalid argument\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
✅ 编译和运行:
# 编译
g++ -o shm_demo shared_memory_demo.cpp -pthread
# 创建路径文件供 ftok 使用
touch /tmp/shm_demo
# 终端1:运行生产者
./shm_demo producer
# 终端2:运行消费者
./shm_demo consumer
九、总结
共享内存提供了一种高效的进程间通信机制,但需要开发者自己管理同步问题。本文展示了如何使用 shmget
、shmat
等函数创建和使用共享内存,并结合 POSIX 信号量实现互斥访问。同时,通过对 ftok()
机制的讲解,帮助理解共享内存 key 的生成原理和潜在的错误排查方向。
这种方式非常适合用于:
- 实时通信(如传感器数据共享)
- 大数据量传输(如图像帧、视频块)
- 轻量级进程协作场景