SIGCHLD信号处理时Global指针偶现空指针问题排查
我编写了一段用于启动进程的C代码,每个进程的元数据会被注册到结构体链表中,SIGCHLD信号处理函数负责更新该元数据。代码结构如下,但偶发失败:update_proc无法找到匹配的ProcessNode,排查发现SIGCHLD处理函数执行时head指针为NULL,而执行前后head均非空。我每次读写head时都会阻塞SIGCHLD,且head已声明为volatile,原以为这两种措施足以规避竞态条件,想请教还有哪些可能的问题点?
// block/unblock utilities static sigset_t block () { sigset_t sigchld_mask, old_mask; sigemptyset(&sigchld_mask); sigaddset(&sigchld_mask, SIGCHLD); if(sigprocmask(SIG_BLOCK, &sigchld_mask, &old_mask)) exit_with_error(); return old_mask; } static void unblock (sigset_t* old_mask) { if(sigprocmask(SIG_SETMASK, old_mask, NULL)) exit_with_error(); } typedef struct ChildProcess { pid_t pid; int* status; } ChildProcess; // ProcessNode is a linked list node containing process metadata typedef struct ProcessNode { ChildProcess* proc; volatile struct ProcessNode* next; } ProcessNode; volatile ProcessNode * head = NULL; int launch_process ( ... ) { block(); // block SIGCHLD ... // launch process with posix_spawn() register_proc(...); // register all metadata unblock(); // unblock SIGCHLD } void register_proc (pid_t pid, int* status) { ChildProcess* child = (ChildProcess*)malloc(sizeof(ChildProcess)); child->pid = pid; child->status = status; volatile ProcessNode* node = (ProcessNode*)malloc(sizeof(ProcessNode)); new_node->proc = child; new_node->next = head; head = new_node; return 0; } int update_proc (pid_t pid, int status) { volatile ProcessNode* cursor = head; while(cursor != NULL && curr->proc->pid != pid) { cursor = cursor->next; } if(cursor != NULL) { *(cursor->proc->status) = status; } } void sigchld_handler (int sig) { int status; pid_t pid; while((pid = waitpid(-1, &status, WNOHANG))) { update_proc(pid, status); } } // API function for checking the status pointer // if we are to wait for the process to terminate, we pselect until sigchld eventually updates the status pointer int get_state (int* status, bool wait_for_termination) { sigset_t old_mask = block(); // block SIGCHLD, store old mask bool state_unknown = true; bool done = false; while(state_unknown) { if(WIFEXITED(*status) || WIFSIGNALED(*status)) { done = true; } state_unknown = false; if(wait_for_termination && done) { if(pselect(0, NULL, NULL, NULL, NULL, &old_mask)) { if(errno == EINTR) { state_unknown = true; // check status again on interrupt } } } else if(!wait_for_termination) { state_unknown = false; } } unblock(); // unblock SIGCHLD return 0; } int main () { // ... allocate some memory for other tasks //Setup SIGCHLD handler sigset_t sigchld_mask; sigemptyset(&sigchld_mask); sigaddset(&sigchld_mask, SIGCHLD); struct sigaction sa; sa.sa_handler = sigchld_handler; sa.sa_mask = sigchld_mask; sa.sa_flags = SA_RESTART; sigaction(SIGCHLD, &sa, NULL); }
可能的问题点:
register_proc中存在变量名笔误,触发未定义行为
代码里register_proc函数内,明明分配了node变量,但后续操作却使用了未定义的new_node:volatile ProcessNode* node = (ProcessNode*)malloc(sizeof(ProcessNode)); new_node->proc = child; // new_node未声明,属于非法内存访问 new_node->next = head; head = new_node;这种未定义行为会导致随机内存写入,可能将
head设置为NULL或其他非法值,这是最可能引发你描述的偶发失败的原因。update_proc遍历链表时未保护并发访问update_proc在SIGCHLD信号处理函数中被调用,遍历链表时没有阻塞SIGCHLD信号。虽然head和next被声明为volatile,但链表遍历是多步操作:读取head、读取next、比较PID等。如果此时有另一个launch_process在unblock后立即触发信号处理,链表结构的变化可能导致遍历异常,极端情况下会出现head临时为NULL的错觉。waitpid循环未处理错误场景sigchld_handler中的waitpid调用可能返回-1(比如无存活子进程时,errno为ECHILD),此时循环会直接终止,但代码未处理这种错误。如果waitpid因错误提前退出,可能导致部分子进程的退出状态未被处理,间接引发连锁问题。get_state函数逻辑倒置get_state中,当wait_for_termination为true时,当前逻辑是**进程已终止(done为true)**才调用pselect等待,这与函数注释的“等待进程终止”逻辑完全相反。正确逻辑应该是当进程未终止(!done)时才等待信号,否则直接返回。这种逻辑错误可能导致不必要的阻塞,影响其他流程的正确性。内存分配失败未做处理
register_proc中malloc可能返回NULL(内存不足时),如果child或node分配失败,后续对其成员的访问会触发未定义行为,可能导致head被设置为NULL或非法值。
内容的提问来源于stack exchange,提问作者trpnd

