信号

由内核向进程发出,或者由其他进程发出等

异步的操作系统机制

除零会触发exception,也会使得内核发出SIGFPE这个信号

多个同时发出的信号可能只会触发一次处理程序,处理程序需要考虑这种情况

defer

sigprocmask()允许进程忽视信号,即暂时屏蔽信号的处理,并不会忽略或丢弃信号

通过阻止异步事件的处理,创建了一个临界区,保证了临界区和信号处理程序不会形成竞争

等待

// simplesh-all-better.c
// wait之前已保证屏蔽了SIGCHILD
static void waitForForegroundProcess(pid_t pid) {
  fgpid = pid;
  sigset_t empty;
  sigemptyset(&empty);
  while (fgpid == pid) {
    sigsuspend(&empty);
  }
  unblockSIGCHLD();
}

SIGCHLD信号在子进程结束时发出,我们希望程序在检查fgpid(foreground pid,shell需要等待前台进程完成)时不发生数据竞争

sigsuspend()会使进程休眠,并设置新的屏蔽mask,在捕捉到信号后处理信号,并恢复原来的屏蔽mask,然后return

类似于条件变量提供了原子地解锁并等待

在这里休眠后才开始接收SIGCHILD信号,return后又屏蔽SIGCHILD信号

信号处理语义

信号不是function call

信号处理不会立刻执行,可以有delay。多次信号只会有一次处理

异步

信号如何实现

栈处理,sigreturn

linux规定信号处理程序运行期间信号会被block

static const size_t kNumChildren = 5;
static size_t numDone = 0;
 
int main(int argc, char *argv[]) {
  printf("Let my five children play while I take a nap.\n");
  signal(SIGCHLD, reapChild);
  for (size_t kid = 1; kid <= 5; kid++) {
    if (fork() == 0) {
      sleep(3 * kid); // sleep emulates "play" time
      printf("Child #%zu tired... returns to dad.\n", kid);
      return 0;
    }
  }
 
  while (numDone < kNumChildren) {
    printf("At least one child still playing, so dad nods off.\n");
    sleep(5);
    printf("Dad wakes up! ");
  }
  printf("All children accounted for.  Good job, dad!\n");
  return 0;
}
 
static void reapChild(int unused) {
  while (true) {
    pid_t pid = waitpid(-1, NULL, WNOHANG);
    if (pid <= 0) break; // note the < is now a <=
    numDone++;
  }
}

异步信号安全

可以在异步的信号处理程序中安全调用的

printf不是重入安全的,但是是线程安全的

CPU机制

interrupt

外部,异步

exception

处理器内部,同步

relaxed

保证了单个变量的一致性

保证了只会观察到一个统一的修改顺序

释放获取