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

共享内存实现子进程通信时随机触发SIGSEGV错误求助

排查共享内存子进程通信中的SIGSEGV问题

首先,你的代码里有几个关键问题导致了随机的段错误(SIGSEGV),我会逐个拆解并给出修复方案:


1. 共享内存重复Attach且未检查返回值

你的init、invite_sent_check、handle_signal函数里都调用了shmat,但完全没检查返回值——如果shmat失败(比如系统内存不足),会返回(void*)-1,后续访问这个无效指针直接触发段错误。而且重复attach会让进程持有多个共享内存映射,既浪费资源,还可能引发不可预测的内存访问行为。

修复方案:

  • 只在main里attach一次共享内存,把有效指针通过参数传递给其他函数,或者用全局变量存储(fork后的子进程会复制全局变量,这里是安全的)。
  • 每次调用shmat后必须检查返回值,失败则立即处理错误并退出。

2. 共享内存访问未加锁导致竞态条件

invite_sent_check函数直接读写共享内存的invite_sent数组,但没有用信号量加锁。多个子进程同时操作这个数组时,会出现竞态条件:比如一个进程在写数组元素,另一个进程在读,可能读到半写的无效数据,甚至触发段错误。

修复方案:

  • 所有访问共享内存的代码(包括invite_sent_check)都要先加锁,操作完成后再解锁。
  • 绝对不能在未加锁的情况下读写共享内存区域。

3. 信号处理函数中的不安全操作

你的SIGALRM信号处理函数里调用了wait、shmctl等函数,这些函数不是异步信号安全的——在信号处理过程中调用它们,可能导致进程状态混乱,甚至触发段错误。另外,信号处理函数里再次shmat共享内存完全没必要,还增加了出错风险。

修复方案:

  • 信号处理函数只做最小化操作:设置一个全局的退出标志,让主进程或子进程自己检测标志并优雅退出,不要在信号处理里直接调用kill、wait这类复杂函数。
  • 避免在信号处理函数中执行任何非必要的复杂逻辑。

4. 子进程循环逻辑的死循环问题

子进程的循环条件while(exit_loop==0 || exit_loop==1)是永远为真的——exit_loop被设置为1后,条件依然成立,导致子进程无限循环执行LOCK/UNLOCK和invite_sent_check,大幅增加了竞态条件发生的概率。

修复方案:

  • 调整循环条件,比如当exit_loop变为1并完成业务逻辑后,设置为2来退出循环,或者用全局退出标志终止循环。

修改后的示例代码

这里是修复了上述问题的最小可复现代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

#define SHMEMORY
#define NUM_SEMS 2
#define LOCK \
    do { \
        sops.sem_num = 1; \
        sops.sem_op = -1; \
        semop(sem_Id, &sops, 1); \
    } while(0)
#define UNLOCK \
    do { \
        sops.sem_num = 1; \
        sops.sem_op = 1; \
        semop(sem_Id, &sops, 1); \
    } while(0)
#define TEST_ERROR if (errno) {dprintf(STDERR_FILENO, \
    "%s:%d: PID=%5d: Error %d (%s)\n", \
    __FILE__, \
    __LINE__, \
    getpid(), \
    errno, \
    strerror(errno));}
#define POP_SIZE 100 //number of child
#define TRUE 1

struct shared_data {
    /* index where next write will happen */
    unsigned long cur_idx;
    int invite_sent[POP_SIZE][POP_SIZE];
};

static void init(struct shared_data* corso);
static int invite_sent_check(struct shared_data* corso, int stud);
int maxMin_rand(int max,int min);
void handle_signal(int sig);

int sim_time = 10;
unsigned long next_num;
struct sembuf sops;
pid_t *kid_pids;
int mem_Id, sem_Id;
volatile sig_atomic_t g_exit_flag = 0; // 信号安全的全局退出标志

int main() {
    int i = 0;
    int status;
    struct shared_data* corso;
    pid_t child_pid;
    int stud = 0;
    int exit_loop = 0;

    /*********************************************************/
    struct sigaction sa;
    sigset_t my_mask;
    /* handler SIGALRM */
    sa.sa_handler = handle_signal;
    sa.sa_flags = 0;
    sigemptyset(&my_mask);
    sa.sa_mask = my_mask;
    sigaction(SIGALRM, &sa, NULL);
    /**********************************************************/

    mem_Id = shmget(IPC_PRIVATE, sizeof(*corso), 0600);
    TEST_ERROR;

    /* Attach the shared memory to a pointer */
    corso = shmat(mem_Id, NULL, 0);
    if (corso == (void*)-1) {
        TEST_ERROR;
        exit(EXIT_FAILURE);
    }
    corso->cur_idx = 0;

    /*********************************************************/
    sem_Id = semget(IPC_PRIVATE, NUM_SEMS, 0600);
    TEST_ERROR;

    /* Sem 0 to syncronize the start of child processes */
    semctl(sem_Id, 0, SETVAL, 0);
#ifdef SHMEMORY
    semctl(sem_Id, 1, SETVAL, 1);
#endif
    TEST_ERROR;

    sops.sem_num = 0; /* check the 0-th semaphore */
    sops.sem_flg = 0; /* no flag */

    init(corso); // 传递共享内存指针

    kid_pids = malloc(POP_SIZE*sizeof(*kid_pids));
    if (!kid_pids) {
        TEST_ERROR;
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < POP_SIZE; i++) {
        switch (kid_pids[i] = fork()) {
            case -1: /* Handle error */
                TEST_ERROR;
                break;
            case 0: /* Wait for the green light */
                sops.sem_op = -1;
                semop(sem_Id, &sops, 1);

                while(!g_exit_flag && exit_loop != 2){
                    LOCK;
                    if(exit_loop == 0){
                        stud = corso->cur_idx;
                        printf("%d %d\n",stud,getpid());
                        corso->cur_idx++;
                        exit_loop = 1;
                    }
                    if(exit_loop == 1){
                        if(invite_sent_check(corso, stud) == 1){
                            // 这里添加你的业务逻辑
                        }
                        exit_loop = 2; // 完成后退出循环
                    }
                    UNLOCK;
                }
                shmdt(corso); // 子进程 detach 共享内存
                exit(0);
                break;
            default:
                break;
        }
    }

    alarm(sim_time);

    sops.sem_op = POP_SIZE;
    semop(sem_Id, &sops, 1);

    /* Waiting for all child processes to terminate */
    while ((child_pid = wait(&status)) != -1) {
        dprintf(2,"PID=%d. Sender (PID=%d) terminated with status 0x%04X\n", getpid(), child_pid, status);
    }

    /* 子进程都退出后,释放共享内存和信号量 */
    shmdt(corso);
    shmctl(mem_Id, IPC_RMID, NULL);
    semctl(sem_Id, 0, IPC_RMID);

    free(kid_pids);
    exit(0);
}

static void init(struct shared_data* corso){
    printf("INIT\n");
    corso->cur_idx=0;
    int j, q;
    for(j = 0; j < POP_SIZE; j++){
        for(q = 0; q < POP_SIZE; q++){
            corso->invite_sent[j][q] = -1;
        }
    }
}

int maxMin_rand(int max, int min){
    // 修复未初始化的变量,建议后续替换为标准rand()逻辑
    int reset = 5;
    int randomics=12345;
    int w=0;
    while(w<reset) {
        randomics++;
        w++;
    }
    next_num = next_num+randomics;
    next_num = next_num*1103515245 +12345;
    unsigned int result=(unsigned int) ((next_num*65536)%(max+1))+min;
    int reload = 5;
    w=0;
    while(w<reload) {
        next_num++;
        w++;
    }
    return result;
}

static int invite_sent_check(struct shared_data* corso, int stud){
    int i, q;
    // 调用方已加锁,此处无需重复加锁
    for(i = 0; i < POP_SIZE; i++){
        if(i == stud){
            for(q = 0; q < POP_SIZE; q++){
                if(corso->invite_sent[i][q] != -1){
                    return 1;
                }
            }
            break;
        }
    }
    return 0;
}

void handle_signal(int signal){
    switch (signal) {
        case SIGALRM:
            g_exit_flag = 1; // 设置全局退出标志,让子进程自行退出
            break;
    }
}

额外说明

  • 用do-while(0)包裹LOCK/UNLOCK宏,避免宏展开时的语法问题。
  • 修复了随机数生成函数中未初始化的reset和reload变量,这也可能导致未定义行为。
  • 主进程不再提前标记共享内存删除,因为子进程还在使用,提前删除会导致子进程访问出错。
  • 子进程退出前调用shmdt detach共享内存,避免资源泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 05:15:58