4. 共享内存实现数据共享
任务目标
创建一段命名共享内存,由两个独立进程分别写入和读取。写入进程持续向共享内存写入递增计数器,读取进程读取并校验数据,同时通过信号量机制保证读写互斥,避免数据竞争。
环境与依赖
- QNX 7.x 或以上版本(支持 POSIX 共享内存与命名信号量)
- 编译工具:qcc 或 gcc(QNX 目标平台)
- 链接参数:
-lrt(共享内存与信号量所需实时库)
核心实现
以下代码包含两个程序:shm_writer 与 shm_reader。二者通过同一路径名 /demo_shm 访问共享内存,通过命名信号量 /demo_sem 实现互斥。共享内存中存放一个 int 计数器,写入进程每次加 1,读取进程校验数据是否为递增序列。
/* shm_demo.c — 编译为两个可执行文件 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>
#include <errno.h>
#define SHM_NAME "/demo_shm"
#define SEM_NAME "/demo_sem"
#define SHM_SIZE sizeof(int)
/* 写入进程:持续递增计数器 */
void run_writer(int iterations) {
int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) { perror("shm_open"); exit(1); }
ftruncate(shm_fd, SHM_SIZE);
int *counter = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);
if (counter == MAP_FAILED) { perror("mmap"); exit(1); }
*counter = 0;
sem_t *sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
if (sem == SEM_FAILED) { perror("sem_open"); exit(1); }
for (int i = 1; i <= iterations; i++) {
sem_wait(sem);
*counter = i;
sem_post(sem);
usleep(10000); /* 10ms,便于观察 */
}
/* 清理 */
sem_close(sem);
munmap(counter, SHM_SIZE);
close(shm_fd);
printf("Writer done. Final value: %d\n", iterations);
}
/* 读取进程:校验递增序列 */
void run_reader(int expected_count) {
int shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);
if (shm_fd == -1) { perror("shm_open"); exit(1); }
int *counter = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);
if (counter == MAP_FAILED) { perror("mmap"); exit(1); }
sem_t *sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
if (sem == SEM_FAILED) { perror("sem_open"); exit(1); }
int last = 0, errors = 0;
for (int i = 0; i < expected_count * 20; i++) { /* 超时保护 */
sem_wait(sem);
int val = *counter;
sem_post(sem);
if (val != last) {
if (val != last + 1) {
printf("Sequence error: %d -> %d\n", last, val);
errors++;
}
last = val;
}
if (last == expected_count) break;
usleep(5000);
}
printf("Reader final: %d, errors: %d\n", last, errors);
sem_close(sem);
munmap(counter, SHM_SIZE);
close(shm_fd);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s [writer|reader] [iterations]\n", argv[0]);
return 1;
}
if (strcmp(argv[1], "writer") == 0) {
int iters = (argc > 2) ? atoi(argv[2]) : 100;
run_writer(iters);
} else if (strcmp(argv[1], "reader") == 0) {
int iters = (argc > 2) ? atoi(argv[2]) : 100;
run_reader(iters);
} else {
fprintf(stderr, "Unknown mode: %s\n", argv[1]);
return 1;
}
return 0;
}
编译与运行
# 编译(生成两个可执行文件)
qcc -o shm_writer shm_demo.c -lrt
qcc -o shm_reader shm_demo.c -lrt
# 终端1:启动写入进程(写100次)
./shm_writer writer 100
# 终端2:启动读取进程(校验100次)
./shm_reader reader 100
验证方法
- 预期输出:写入进程打印
Writer done. Final value: 100;读取进程打印Reader final: 100, errors: 0。 - 判定通过:读取进程的
errors为 0,且最终读到的值与写入次数一致,说明共享内存数据正确同步。 - 异常排查:若
errors非零,说明读写互斥失效或共享内存初始化有误;若shm_open失败,检查/dev/shm是否存在且有写权限。
关键说明
- 共享内存路径名必须以
/开头,且长度不超过NAME_MAX(通常 255)。 - 信号量初始值为 1,保证同一时刻只有一个进程访问共享内存中的计数器。
- 读取进程使用超时轮询(
expected_count * 20次)避免无限等待;实际应用中可根据需要调整。 - 运行结束后,共享内存与信号量仍保留在系统中,可用
rm /dev/shm/demo_shm和rm /dev/shm/sem.demo_sem手动清理(QNX 中命名信号量文件位于/dev/shm)。