写点什么

Android C++ 系列:Linux 信号(三)

作者:轻口味
  • 2021 年 12 月 01 日
  • 本文字数:2386 字

    阅读完需:约 8 分钟

可重入函数



  • 不含全局变量和静态变量是可重入函数的一个要素

  • 可重入函数见 man 7 signal

  • 在信号捕捉函数里应使用可重入函数

  • 在信号捕捉函数里禁止调用不可重入函数


例如:strtok 就是一个不可重入函数,因为 strtok 内部维护了一个内部静态指针,保存上一 次切割到的位置,如果信号的捕捉函数中也去调用 strtok 函数,则会造成切割字符串混乱, 应用 strtok_r 版本,r 表示可重入。

信号引起的竞态和异步 I/O

时序竞态

  • int pause(void)

  • 使调用进程挂起,直到有信号递达,如果递达信号是忽略,则继续挂起

  • int sigsuspend(const sigset_t *mask)

  • 以通过指定 mask 来临时解除对某个信号的屏蔽,

  • 然后挂起等待,

  • 当被信号唤醒 sigsuspend 返回时,进程的信号屏蔽字恢复为原来的值


mysleep 实现,这种实现方式是否存在 BUG?


#include <unistd.h> #include <signal.h> #include <stdio.h>void sig_alrm(int signo) {  /* nothing to do */ }unsigned int mysleep(unsigned int nsecs) {  struct sigaction newact, oldact;   unsigned int unslept;  newact.sa_handler = sig_alrm;   sigemptyset(&newact.sa_mask);   newact.sa_flags = 0;   sigaction(SIGALRM, &newact, &oldact);  alarm(nsecs);   pause();  unslept = alarm(0);   sigaction(SIGALRM, &oldact, NULL);  return unslept;   }int main(void) {  while(1){    mysleep(2);    printf("Two seconds passed\n");   }  return 0; }
复制代码


mysleep 改进版


unsigned int mysleep(unsigned int nsecs) {  struct sigaction newact, oldact;  sigset_t newmask, oldmask, suspmask;  unsigned int unslept;  /* set our handler,save previous information */  newact.sa_handler = sig_alrm;  sigemptyset(&newact.sa_mask);   newact.sa_flags = 0;   sigaction(SIGALRM, &newact, &oldact);  /* block SIGALRM and save current signal mask */   sigemptyset(&newmask);  sigaddset(&newmask, SIGALRM);   sigprocmask(SIG_BLOCK, &newmask, &oldmask);  alarm(nsecs);  suspmask = oldmask;   sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't blocked */   sigsuspend(&suspmask);/* wait for any signal to be caught */  /* some signal has been caught,SIGALRM is now blocked */  unslept = alarm(0);  sigaction(SIGALRM, &oldact, NULL);/* reset previous action */   /* reset signal mask, which unblocks SIGALRM */   sigprocmask(SIG_SETMASK, &oldmask, NULL);   return(unslept);}
复制代码

全局变量异步 I/O

可重入函数

  1. 不含全局变量和静态变量是可重入函数的一个要素

  2. 可重入函数见 man 7 signal

  3. 在信号捕捉函数里应使用可重入函数

避免异步 I/O 的类型

  • sig_atomic_t 平台下的原子类型

  • volatile 防止编译器开启优化选项时,优化对内存的读写

SIGCHLD 信号处理

SIGCHLD 的产生条件

  • 子进程终止时

  • 子进程接收到 SIGSTOP 信号停止时

  • 子进程处在停止态,接受到 SIGCONT 后唤醒时


代码实现


#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h> #include <signal.h>void sys_err(char *str) {  perror(str);  exit(1); }void do_sig_child(int signo) {  int status;  pid_t pid;  while ((pid = waitpid(0, &status, WNOHANG)) > 0) {    if (WIFEXITED(status))      printf("child %d exit %d\n", pid, WEXITSTATUS(status));    else if (WIFSIGNALED(status))      printf("child %d cancel signal %d\n", pid, WTERMSIG(status));  }}int main(void) {  pid_t pid;  int i;  //阻塞SIGCHLD  for (i = 0; i < 10; i++) {    if ((pid = fork()) == 0)       break;    else if (pid < 0)       sys_err("fork");  }  if (pid == 0) {    int n = 18; while (n--) {      printf("child ID %d\n", getpid());      sleep(1);     }    return i;   }else if (pid > 0) { //先设置捕捉    //再解除对SIGCHLD的阻塞     struct sigaction act;    act.sa_handler = do_sig_child;     sigemptyset(&act.sa_mask); act.sa_flags = 0;     sigaction(SIGCHLD, &act, NULL);     while (1) {      printf("Parent ID %d\n", getpid());      sleep(1);     }  }  return 0; }
复制代码

status 处理方式

pid_t waitpid(pid_t pid, int *status, int options)


  • options

  • WNOHANG 没有子进程结束,立即返回

  • WUNTRACED

  • 如果子进程由于被停止产生的 SIGCHLD, waitpid 则立即返回

  • WCONTINUED 如果子进程由于被 SIGCONT 唤醒而产生的 SIGCHLD, waitpid 则立即返回

  • 获取 status

  • WIFEXITED(status) 子进程正常 exit 终止,返回真 WEXITSTATUS(status)返回子进程正常退出值

  • WIFSIGNALED(status) 子进程被信号终止,返回真

  • WTERMSIG(status)返回终止子进程的信号值 WIFSTOPPED(status)

  • 子进程被停止,返回真 WSTOPSIG(status)返回停止子进程的信号值

  • WIFCONTINUED(status) 子进程由停止态转为就绪态,返回真

向信号捕捉函数传参

sigqueue

int sigqueue(pid_t pid, int sig, const union sigval value) union sigval {int sival_int;void *sival_ptr; };
复制代码

sigaction

void (*sa_sigaction)(int, siginfo_t *, void *) siginfo_t {  int si_int;   void *si_ptr;   sigval_t si_value;  ...}sa_flags = SA_SIGINFO/* POSIX.1b signal */ /* POSIX.1b signal */ /* Signal value */
复制代码


实例


  • 进程自己收发信号,在同一地址空间

  • 不同进程间收发信号,不在同一地址空间,不适合传地址

信号中断系统调用

read 阻塞时,信号中断系统调用:


  1. 返回部分读到的数据

  2. read 调用失败,errno 设成 EINTER

总结

本文介绍了可重入函数,信号引起的竞态和异步 I/O,SIGCHLD 信号处理,向想好捕捉函数传参,信号中断系统调用。

发布于: 2021 年 12 月 01 日阅读数: 22
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017.10.17 加入

Android、音视频、AI相关领域从业者。 邮箱:qingkouwei@gmail.com

评论

发布
暂无评论
Android C++系列:Linux信号(三)