共享内存实现子进程通信时随机触发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变量,这也可能导致未定义行为。 - 主进程不再提前标记共享内存删除,因为子进程还在使用,提前删除会导致子进程访问出错。
- 子进程退出前调用
shmdtdetach共享内存,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Reverendo
相关产品推荐
相关产品推荐

