You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于C POSIX IPC信号量与共享内存实现计数信号量遇阻塞问题

问题分析与修复方案

核心错误点

  • LOCK信号量初始值错误:原代码中将用于挂起等待进程的LOCK信号量初始值设为1,不符合设计逻辑。该信号量初始值应为0,仅当计数从0变为1时才触发一次up操作唤醒等待进程。
  • sem_up共享内存泄漏:原代码中仅在*data ==1的分支执行了dettach_n,其余分支未释放共享内存映射,会导致引用计数异常、资源泄漏。
  • sem_down逻辑漏洞:进程因计数为0阻塞在LOCK上被唤醒后,没有对共享计数器执行减1操作,直接返回导致计数逻辑完全混乱。
  • 临界区保护不完整:阻塞被唤醒后的计数器操作没有纳入临界区保护,存在竞态风险。

修复后的核心代码

1. 信号量初始化修正

int sem_init(Sem *s, int n) {
    key_t key;
    Args arg;
    int *data;

    /* sem */
    if ((key = ftok(PATH, SID)) == -1) {
        return -1;
    }
    if ((s->semid = semget(key, SEMN, 0600 | IPC_CREAT)) == -1) {
        return -1;
    }
    // CRIT信号量初始为1,用于保护临界区
    arg.val = 1;
    if (semctl(s->semid, CRIT, SETVAL, arg) == -1) {
        return -1;
    }
    // LOCK信号量初始为0,用于挂起等待计数的进程
    arg.val = 0;
    if (semctl(s->semid, LOCK, SETVAL, arg) == -1) {
        return -1;
    }

    /* 共享内存初始化部分不变 */
    if ((key = ftok(PATH, SHMID)) == -1) {
        return -1;
    }
    if ((s->shmid = shmget(key, SHM_SIZE, 0664 | IPC_CREAT)) == -1) {
        return -1;
    }

    data = shmat(s->shmid, (void *)0, 0);
    if (data == (int *)(-1)) {
        return -1;
    }
    *data = n;
    if (shmdt(data) == -1) {
        return -1;
    }
    return 0;
}

2. sem_down 逻辑修正

int sem_down(Sem *s) {
    int *data;
    enter_crit(s);
    data = get_n(s);

    printf("[%d] checked %d\n", getpid(), *data);
    if (*data == 0) {
        dettach_n(data);
        exit_crit(s);
        // 阻塞到LOCK上,被唤醒说明已有可用计数
        acquire_lock(s);
        // 被唤醒后重新进入临界区操作计数器
        enter_crit(s);
        data = get_n(s);
    }
    // 所有分支统一执行计数器减1
    (*data)--;
    dettach_n(data);
    exit_crit(s);
    return 0;
}

3. sem_up 逻辑修正

int sem_up(Sem *s) {
    int *data;
    enter_crit(s);
    data = get_n(s);
    printf("[%d] read %d\n", getpid(), *data);
    (*data)++;
    // 判断是否需要唤醒等待进程
    int need_wake = (*data == 1);
    // 所有分支统一释放共享内存映射
    dettach_n(data);
    exit_crit(s);
    
    if (need_wake) {
        release_lock(s);
    }
    return 0;
}

逻辑说明

  1. CRIT二元信号量始终用于保护共享计数器的读写操作,保证同一时间只有一个进程访问计数器,避免竞态。
  2. 当计数器为0时,后续申请信号量的进程会阻塞在LOCK信号量上,直到有进程释放信号量使得计数器从0变为1,触发一次LOCK的up操作唤醒一个阻塞进程。
  3. 被唤醒的进程需要重新进入临界区,完成计数器减1操作,确保计数逻辑正确。
  4. 每次操作共享内存后都统一执行解除映射操作,避免资源泄漏。

测试时可设置SLOTS=2、NPROC=5,可以观察到同一时间最多2个进程进入working状态,所有5个进程都能正常退出,不会出现永久阻塞的问题。如果测试过程中出现异常退出导致System V IPC资源残留,可以用ipcs查看残留的信号量、共享内存,用ipcrm命令手动清理。

内容的提问来源于stack exchange,提问作者argot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 00:39:03