ECF机制:信号处理 | 您所在的位置:网站首页 › ecf机制 › ECF机制:信号处理 |
📜 本章目录: 0x00 观察接收信号 0x01 阻塞和解除阻塞信号 0x02 安全的信号处理 0x03 在信号处理器中使用安全的 I/O 函数 0x04 便携式信号处理 0x05 显式信号等待 0x00 观察接收信号假设内核从异常处理程序中返回,并准备将控制权交给进程 代码层级上看:假设内核从异常处理程序中返回,并准备将控制权交给进程 (进程 如果 pnb == 0: 将控制权传递给进程 否则: 在默认操作:每种信号类型都有预定义的默认操作,包括以下几种: 进程终止进程暂停,直到通过 SIGCONT 信号重新启动 进程忽略该信号安装信号处理程序 下面的函数可以修改与接收到信号signum相关联的操作(使用man命令获取详细信息): // 设置信号 signum 的处理函数,也称为信号处理程序或信号处理函数 sighandler_t signal(int signum, sighandler_t handler) // 设置信号 signum 的处理行为,并且可以获取先前的处理行为 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);signal() 函数中 handler 参数的可能取值: SIG_IGN:忽略类型为 signum 的信号SIG_DFL:恢复类型为 signum 的信号的默认操作否则,handler 是用户级别函数的地址:当进程接收到类型为 signum 的信号时调用该函数这被称为 "安装" 信号处理程序,执行 handler 被称为 "捕获" 或 "处理" 信号,当 handler 返回时,控制权会回到被信号中断的进程指令处。信号处理的例子: void sigint_handler(int sig) /* SIGINT handler */ { printf("So you think you can stop this with ctrl-c?\n"); sleep(2); } int main(int argc, char** argv) { /* Install the SIGINT handler */ if (signal(SIGINT, sigint_handler) == SIG_ERR) perror("signal error"); /* Wait for the receipt of a signal */ while(1); return 0; }接收信号: 信号处理器作为并发流,信号处理器是与主程序同时运行的独立逻辑流(而不是进程): 嵌套的信号处理器,处理器可以被其他处理器中断: 隐式阻塞机制 内核会阻塞当前正在处理的类型的任何未决信号例如,SIGINT处理器不能被另一个SIGINT中断显式阻塞和解除阻塞机制 使用 sigprocmask 函数支持的函数 sigemptyset - 创建一个空的信号集合sigfillset - 将所有信号号码添加到集合中sigaddset - 将信号号码添加到集合中sigdelset - 从集合中删除信号号码阻塞和解除阻塞的例子: sigset_t mask, prev_mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); /* Block SIGINT and save previous blocked set */ sigprocmask(SIG_BLOCK, &mask, &prev_mask); . . /* Code region that will not be interrupted by SIGINT */ . /* Restore previous blocked set, unblocking SIGINT */ sigprocmask(SIG_SETMASK, &prev_mask, NULL); 0x02 安全的信号处理处理器因为与主程序并发运行并共享相同的全局数据结构,所以比较棘手。 共享的数据结构可能会被破坏我们将在后面深入探讨并发性问题! 现在,以下是一些指导原则,以帮助您编写安全的信号处理器。 编写安全处理器的指导原则: G0:保持您的处理器尽可能简单 例如,设置一个全局标志并返回G1:在处理器中只调用异步信号安全的函数 如果函数是可重入的(例如,所有变量都存储在栈帧上,参见CS:APP 12.7.2),或者不会被信号中断,则该函数是异步信号安全的。常用的异步信号安全的函数有:_exit、write、wait、waitpid、sleep、kill 不安全的函数包括:exit、printf、sprintf、mallocG2:在进入和退出时保存和恢复 errno 值 这样,您的处理器不会影响主程序中观察到的 errno 值G3:通过临时阻塞所有信号来保护对共享数据结构的访问 防止可能的数据结构损坏,如何实现? 在处理器中调用 sigprocmask 函数(可能不稳定) 当使用sigaction函数时,可以指定在处理器执行时额外阻塞哪些信号G4:将全局变量声明为 volatile 防止编译器将其存储在寄存器中 如果变量存储在寄存器中,对该变量的更新可能对读取者不可见 0x03 在信号处理器中使用安全的 I/O 函数在信号处理器中考虑使用可重入的 SIO (Safe I/O) 库,例如 sio.c 中提供的库函数。 ssize_t sio_puts(char s[]) /* Put string */ ssize_t sio_putl(long v) /* Put long */ void sio_error(char s[]) /* Put msg & exit */SIO库是一个安全的I/O库,专门为信号处理器设计,用于处理信号处理器中的并发问题。它提供了一组可重入的I/O函数,用于在信号处理器中进行安全的I/O操作,避免了潜在的并发问题和数据损坏。 代码例子: ssize_t sio_puts(char s[]) { return write(STDOUT_FILENO, s, sio_strlen(s)); } void sio_error(char s[]) { sio_puts(s); _exit(1); } 0x04 便携式信号处理在不同的UNIX操作系统中,信号处理的实现可能会有所不同。 一些旧系统在捕捉信号后会将动作恢复为默认值 一些系统不会阻塞正在处理的类型的信号一些被中断的系统调用可能会返回errno == EINTR解决方案:使用 sigaction 函数 handler_t *Signal(int signum, handler_t *handler) { struct sigaction action, old_action; action.sa_handler = handler; sigemptyset(&action.sa_mask); /* Which signal will be additionally blocked */ action.sa_flags = SA_RESTART; /* Restart syscalls if possible */ if (sigaction(signum, &action, &old_action) < 0) unix_error("Signal error"); return (old_action.sa_handler); }常见错误: volatile int ccount = 0; void child_handler(int sig) { int olderrno = errno; pid_t pid; if ((pid = wait(NULL)) < 0) sio_error("wait error"); ccount--; sio_puts("Handler reaped child "); sio_putl((long)pid); sio_puts("\n"); sleep(1); errno = olderrno; } int main(void) { pid_t pid[N]; int i; ccount = N; signal(SIGCHLD, child_handler); for (i = 0; i < N; i++) { if ((pid[i] = fork()) == 0) { sleep(1); exit(0); /* Child exits */ } } while (ccount > 0); /* Parent spins */ }未决信号未排队:对于每种信号类型,一位指示信号是否处于未决状态,因此任何特定类型的至多一个未决信号。所以不能使用信号来计算事件,例如 child 的终止。 修复错误:必须等待所有终止的子进程,在循环中加入 wait 以获取所有终止的子进程。 void child_handler(int sig) { int olderrno = errno; pid_t pid; while ((pid = wait(NULL)) > 0) { ccount--; sio_puts("Handler reaped child "); sio_putl((long)pid); sio_puts("\n"); } if (errno != ECHILD) sio_error("wait error"); errno = olderrno; }另一个细微的错误:带有细微同步错误的简易 shell int main(void) { int pid; sigset_t mask_all, prev_all; Sigfillset(&mask_all); Signal(SIGCHLD, handler); initjobs(); /* Initialize the job list */ while (1) { if ((pid = Fork()) == 0) { /* Child */ Execve("/bin/date", argv, NULL); } /* Parent */ Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); addjob(pid); /* Add the child to the job list */ Sigprocmask(SIG_SETMASK, &prev_all, NULL); } exit(0); } void handler(int sig) { int olderrno = errno; sigset_t mask_all, prev_all; pid_t pid; Sigfillset(&mask_all); while ((pid = waitpid(-1, NULL, 0)) > 0) { /* Reap child */ Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); deletejob(pid); /* Delete child from the job list */ Sigprocmask(SIG_SETMASK, &prev_all, NULL); } if (errno != ECHILD) /* ECHILD: child does not exist */ sio_error("waitpid error"); errno = olderrno; }修复该错误: int main(void) { int pid; sigset_t mask_all, mask_one, prev_one; Sigfillset(&mask_all); Sigemptyset(&mask_one); Sigaddset(&mask_one, SIGCHLD); Signal(SIGCHLD, handler); initjobs(); /* Initialize the job list */ while (1) { Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); /* Block SIGCHLD */ if ((pid = Fork()) == 0) { /* Child process */ Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */ Execve("/bin/date", argv, NULL); } Sigprocmask(SIG_BLOCK, &mask_all, NULL); /* Parent process */ addjob(pid); /* Add the child to the job list */ Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */ } exit(0); } 0x05 显式信号等待显式地等待 SIGCHLD 到来的程序的处理程序。 int main(void) { sigset_t mask, prev; Signal(SIGCHLD, sigchld_handler); Signal(SIGINT, sigint_handler); Sigemptyset(&mask); Sigaddset(&mask, SIGCHLD); while (1) { Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */ if (Fork() == 0) /* Child */ exit(0); /* Parent */ pid = 0; Sigprocmask(SIG_SETMASK, &prev, NULL); /* Unblock */ /* Wait for SIGCHLD to be received (wasteful!) */ while (!pid); /* Do some work after receiving SIGCHLD */ printf("."); } exit(0); }如果程序是正确的,那么将会导致浪费,程序会进入忙等待循环: while (!pid);可能的竞争条件,可能在检查 pid 和启动暂停信号之间接收信号: while (!pid) /* Race! */ pause();安全,但速度慢:最多需要一秒钟才能做出响应 while (!pid) /* Too slow! */ sleep(1);解决方案:sigsuspend int sigsuspend(const sigset_t *mask)不间断版本: sigprocmask(SIG_SETMASK, &mask, &prev); pause(); sigprocmask(SIG_SETMASK, &prev, NULL);💬 代码演示:使用 sigsuspend 等待信号 int main(int argc, char** argv) { sigset_t mask, prev; Signal(SIGCHLD, sigchld_handler); Signal(SIGINT, sigint_handler); Sigemptyset(&mask); Sigaddset(&mask, SIGCHLD); while (1) { Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */ if (Fork() == 0) /* Child */ exit(0); /* Wait for SIGCHLD to be received */ pid = 0; while (!pid) Sigsuspend(&prev); /* Optionally unblock SIGCHLD */ Sigprocmask(SIG_SETMASK, &prev, NULL); /* Do some work after receiving SIGCHLD */ printf("."); } exit(0); }📜 参考资料 C++reference[EB/OL]. []. http://www.cplusplus.com/reference/. Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. . 百度百科[EB/OL]. []. https://baike.baidu.com/. 比特科技. Linux[EB/OL]. 2021[2021.8.31 xi |
CopyRight 2018-2019 实验室设备网 版权所有 |