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)。