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

SIGCHLD信号处理时Global指针偶现空指针问题排查

进程元数据链表与SIGCHLD信号处理的竞态问题

我编写了一段用于启动进程的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);
}

可能的问题点:

  1. 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或其他非法值,这是最可能引发你描述的偶发失败的原因。

  2. update_proc遍历链表时未保护并发访问
    update_proc在SIGCHLD信号处理函数中被调用,遍历链表时没有阻塞SIGCHLD信号。虽然head和next被声明为volatile,但链表遍历是多步操作:读取head、读取next、比较PID等。如果此时有另一个launch_process在unblock后立即触发信号处理,链表结构的变化可能导致遍历异常,极端情况下会出现head临时为NULL的错觉。

  3. waitpid循环未处理错误场景
    sigchld_handler中的waitpid调用可能返回-1(比如无存活子进程时,errno为ECHILD),此时循环会直接终止,但代码未处理这种错误。如果waitpid因错误提前退出,可能导致部分子进程的退出状态未被处理,间接引发连锁问题。

  4. get_state函数逻辑倒置
    get_state中,当wait_for_termination为true时,当前逻辑是**进程已终止(done为true)**才调用pselect等待,这与函数注释的“等待进程终止”逻辑完全相反。正确逻辑应该是当进程未终止(!done)时才等待信号,否则直接返回。这种逻辑错误可能导致不必要的阻塞,影响其他流程的正确性。

  5. 内存分配失败未做处理
    register_proc中malloc可能返回NULL(内存不足时),如果child或node分配失败,后续对其成员的访问会触发未定义行为,可能导致head被设置为NULL或非法值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 09:14:56