10.1 介绍
Signals 提供了一种 处理异步事件的 方式, 例如用户在中断 键入ctrl + C 来终止一个程序.
我们即将描述的就是 POSIX.1 标准化的 reliable-signal routines .
10.2 Signal Concepts 信号概念
首先每个信号都有一个名字。这些名字以SIG开头。
例如: SIGABRT 是 abort signal ,当一个程序调用abort 函数时生成。
SIGALRM 是 alarm signal .
Linux 3.2.0 支持 31种不同的信号。注意信号都是正整数常量, 没有值为0的信号。
Linux 3.2.0 定义了信号在 <bits/signum.h>
下列多种情况(numerous conditions) 可以生成一个信号:
a: 终端生成: 当用户按 特定的 终端键。
如 DELETE 键, Control -C 键 会引起 interrupt signal (SIGINT) 产生。 #18章 说 如 何映射这个信号 给任何 字符。
b: 硬件异常产生信号:被零除, 非法的内存引用,等。
通常由硬件(hardware)侦测到,并 且内核被通知, 内核然后 产生合适的信号 给 那个引起这种情况的进程。
如 SIGSEGV 的产生就是因为 进程执行了非法的内存引用。
c: kill(2) 函数 : 允许一个进程发送 任何信号 给 另外的进程或进程组。
自然地,有前提 条件: 我们要发送信号 给的那个进程,我们要么是这个进程的拥有者,要么我们 是超级用户。
d: kill(1) 命令: 允许我们发送信号给其它进程。它是kill 函数的接口。
这个命令通常用于 终止 后台运行的进程
e: 软件情况 可以产生信号:
例如 SIGURG( 当网络连接上的out-of-band data 达到了)
SIGPIPE(当一个进程试着去往一个pipe中写入,这个pipe没有 reader)
SIGALRM( 由进程设置的 alarm clock 过期时)
信号来了,进程怎么处理?
signal 是典型的异步事件的例子。它们发生在进程的任何可能的时间。进程不能简单地 测试一个变量(如errno) 来看是否一个信号已经产生;替代地,进程不得不告诉kernel “如果并且合适信号产生, 做如下的...”
当信号来了,告诉内核 做三件事其一:
1. 忽略signal。 这对大多数信号都是有效的,除了2个信号(它们从不被忽略):SIGKILL 和SIGSTOP。 不能被忽略的原因:是为了提供给内核和超级用户 一种必定成功执行(要么杀死,要么停止进程)的一种方式。
2. 捕获信号。 为了这样做,需要告诉内核 去调用 函数,这个函数中我们可以做任何我想要去处理的事。注意有两个信号不能被捕获“SIGKILL, SIGSTOP”
3. 应用默认的动作(action): 每个信号都有 一个默认的action, 注意大多数默认的action是终止进程。
信号 及 其 默认的action, 及所被支持的系统。表
信号名称 | 描述 | Linux3.2.0 | 默认 action |
SIGABRT | abnormal termination (abort) | 支持 | terminate+core |
SIGALRM | timer expired(alarm) | 支持 | terminate |
SIGBUS | hardware fault | 支持 | terminate+core |
SIGCANCEL | threads library internal use | \ | ignore |
SIGCHLD | change in status of child | 支持 | ignore |
SIGCONT | continue stopped process | 支持 | continue/ ignore |
SIGEMT | hardware fault | 支持 | terminate + core |
SIGFPE | arithmetic exception | 支持 | terminate + core |
SIGFREEZE | checkpoint freeze | \ | ignore |
SIGHUP | hangup | 支持 | terminate |
SIGILL | illegal instruction | 支持 | terminate + core |
SIGINFO | status request from keyboard | \ | ignore |
SIGINT | terminal interrupt character | 支持 | terminate |
SIGIO | asynchronous I/O | 支持 | terminate / ignore |
SIGIOT | hardware fault | 支持 | terminate + core |
SIGJVM1 | Java virtual machine internal use | \ | ignore |
SIGJVM2 | Java virtual machine internal use | \ | ignore |
SIGKILL | termination | 支持 | terminate |
SIGLOST | resource lost | \ | terminate |
SIGLWP | threads library internal use | \ | terminate/ignore |
SIGPIPE | write to pipe with no readers | 支持 | terminate |
SIGPOLL | pollable event(poll) | 支持 | terminate |
SIGPROF | profifiling time alarm (setitimer) | 支持 | terminate |
SIGPWR | power fail / restart | terminate /ignore | |
SIGQUIT | terminal quit character | 支持 | terminate+core |
SIGSEGV | invalid memory reference | 支持 | terminate+core |
SIGSTKFLT | coprocessor stack fault | 支持 | terminate |
SIGSTOP | stop | 支持 | stop process |
SIGSYS | invalid system call | 支持 | terminate+core |
SIGTERM | termination | 支持 | terminate |
SIGTHAW | checkpoint thaw | \ | ignore |
SIGTHR | threads library internal use | \ | terminate |
SIGTRAP | hardware fault | 支持 | terminate+core |
SIGTSTP | terminal stop character | 支持 | stop process |
SIGTTIN | background read from control tty | 支持 | stop process |
SIGTTOU | background write to control tty | 支持 | stop process |
SIGURG | urgent condition(sockets) | 支持 | ignore |
SIGUSR1 | user-defined signal | 支持 | terminate |
SIGUSR2 | user-defined signal | 支持 | terminate |
SIGVTALRM | virtual time alarm(setitimer) | 支持 | terminate |
SIGWAITING | threads library internal use | \ | ignore |
SIGWINCH | terminal window size change | 支持 | ignore |
SIGXCPU | CPU limit exceeded (setrlimit) | 支持 | terminate or terminate+core |
SIGXFSZ | fifile size limit exceeded (setrlimit) | 支持 | terminate or terminate+core |
SIGXRES | resource control exceeded | \ | ignore |
在以下情况中,core file 将不会被生成,
a: 进程 被 set-user-ID 并且当前的用户不是程序文件的拥有者。
b: 进程被 set-group-ID 并且当前用户不是程序文件的 组拥有者。
c: 在当前工作目录下,用户没有写权限。
d: file 已经存在 并且 用户没有写权限去 写它。
e: file 是太大了。
core file 的权限 通常 是 user-read , user-write.
概述每一种信号。
SIGABRT :
通过调用abort 函数 产生。
SIGALRM :
当 timer(使用alarm 设置) 过期;
interval timer ( 使用setitimer设置)过期;
SIGBUS :
硬件错误;通常 在特定类型的内存错误时 产生。
SIGCANCEL :
这个信号 在Solaris threads library 内部中使用
SIGCHLD :
不论何时 进程终止 或停止, SIGCHLD 信号就被发送给 parent。默认地,信号被忽略。
信号捕获函数的正常的action 是:调用 其中一个wait函数来 获取child的进程ID和 终止状态。
SIGCONT :
job-control signal 要被发送给 一个停止的进程,以使它可以继续。默认的action 是继续进程, 但是 如果这个进程不是停止的,他就会忽略它。
SIGEMT :
硬件错误; 来自PDP-11 “emulator trap “指令;并不是所有的平台都支持这个信号。在 Linux 上,只有某些架构支持 如: SPARC, MIPS, and PA-RISC
SIGFPE :
这个信号 是一个 算法异常(arithmetic exception): 如 被0 除,浮点数overflow 等等
SIGHUP :
如果terminal interface 检测到连接断了,这种信号就会 被发送给 控制进程(即,session leader; 它关联了一个 控制终端)。
注意,session leader 接收这个信号 可能是处于background; 和 正常的 terminal-generated 信号(interrupt, quit, suspend)不同的是,它们总是被发送给 前台进程组。
如果session leader 终止了, 这个信号也会产生,在这种情况下:信号会被发送给 前台进程组的 每个进程。
这个信号 常常被用来 通知守护进程 去重新加载它们的配置文件。选择SIGHUP 做这项任务的原因是:守护进程 不应该有一个 控制终端 并且 正常情况下从不接收这个信号。
SIGILL
进程已经执行了非法的 软件指令。
SIGINFO
Linux 不支持SIGINFO
SIGINT
当我们按了中断键(通常是 DELETE 或 Control-C), terminal driver 就会生成这个信号。
这个信号 通常被发送给 前台进程组的 所有进程。这个信号被通常用于 终止一个 跑 偏的程序。
SIGIO
这个信号 指明了 一个 异步 I/O 事件
Linux 3.2.0 定义SIGIO 是和 SIGPOLL 相同的值,所以它的默认行为是:终止进程。
SIGIOT
硬件错误
在Linux3.2.0 Mac OS X 10.6.8 被定义 和 SIGABRT 有相同的值。
SIGJVM1
在Solaris 使用java 虚拟机 时会用到
SIGJVM2
在Solaris 使用java 虚拟机 时会用到
SIGKILL
不能被捕获 或 忽略。它提供给系统管理员 一种确定无疑的 杀死任何进程的 途径。
SIGLOST
在Solaris 上使用
SIGLWP
在Solaris 的线程库中 使用。
SIGPIPE
如果我们向一个管道写,当这个reader 已经终止了,SIGPIPE 就会产生了。
当一个进程向 一个 类型为SOCK_STREAM 的socket (并且它不再connected) 写入时, 信号就会产生。
SIGPOLL
这个信号在SUSv4 被标记为废弃的, 所以在未来版本有可能被移除。当在pollable 设备上一个特定的事件发送时,信号会产生。
SIGPROF
这个信号在SUSv4 被标记为废弃的, 所以在未来版本有可能被移除。
SIGPWR
这个信号是 依赖系统。它主要用于 一种系统(它用一个不中断的电源供给 UPS).如果power 失败,则UPS接管并且软件会得到通知。在这个时间点,不需要做什么,因为系统继续运行在电池电源上。但是如果电池 电量很低,软件通常会被通知;在这个时间点。在大多数系统,init process 会收到SIGPWR 信号,来处理 system shutdown.
在Linux 上 默认是 终止进程。
SIGQUIT
当我们按quit键(通常是Control-backslash),terminal driver 会生成这个信号。这个信号被发送给前台进程组中的所有进程。这个信号不仅终止 前台进程组,还会产生 core file.
SIGSEGV
这个信号 表明了 进程 有一个非法的内存引用(这通常是程序的bug,如使用了一个未初始化的指针)
SEGV 名 是“ segmentation violation”
SIGSTKFLT
Linux 定义的。出现在早期的Linux版本。 这个信号不再被内核生成了,保留它是为了 向后兼容
SIGSTOP
job-control 信号,来停止一个进程。他和 SIGTSTP 相似,但是SIGSTOP 不能够被捕获 或忽略。
SIGSYS
非法的系统调用。 如:你构建了一个程序它使用了一个新的系统调用,然后你把程序放在老版本的操作系统上运行,它没有某些系统调用。
SIGTERM
kill(1)命令默认地 产生的 termination signal. 由于它可以被捕获,程序就有机会去 做一些清理工作后愉快地终止。 对比SIGKILL, 它(SIGKILL)不能被捕获或忽略。
SIGTHAW
Solaris 定义的
SIGTHR
FreeBSD 使用的线程库
SIGTRAP
硬件错误; 名字来源于 PDP-11 TRAP 指令。实现常常使用这个信号 去传递控制给debugger(当断点指令被执行时)。
SIGTSTP
当我们按 terminal suspend key (通常时Control-Z)时,terminal driver 生成信号。这个信号被发送给 前台进程组 的所有进程。
SIGTTIN
当一个处于 后台进程组的 进程 尝试去从他的 控制终端 读取时,terminal driver 就会产生这个信号。
有特殊情况 a: 读进程忽略或 阻塞了 这个信号
b: 读进程的进程组 是孤儿时
这两种情况都不会生成改信号;代替地read操作失败,并设置errno 为EIO
SIGTTOU
当一个处于后台进程组中的进程 尝试去写它的控制终端时,信号就会有terminal driver 产生。 不像 后台 读, 一个进程 可以选择 去允许 后台写 到 控制终端。
如果后台写 不被允许 则有SIGTTIN 信号产生。
这有两种特殊情况:
a: 要写 的进程 忽略或阻塞了信号
b: 写进程的 进程组是 孤儿的
这两种情况下 都不产生信号;替代地,写操作返回一个error 并且errno 被设置为EIO
忽略是否 后台写是 被允许的, 其它终端操作(不是写)也可能产生SIGTTOU 信号。
如: tcsetattr, tcdrain, tcflush, tcflow, tcsetpgrp.
SIGURG
这个信号通知进程一个紧急的情况发生了。当out-of-band data 在网络连接中 收到时, 这个信号 是可选的 会生成。
SIGUSR1
这是用户定义 的信号,用在应用程序中。
SIGUSR2
这是用户定义 的信号,用在应用程序中。
SIGVTALRM
当 一个 virtual interval timer(由setitimer 设置) 过期时,产生信号
SIGWAITING
Solaris 线程库使用
SIGWINCH
内核维护每个终端 或 伪终端的窗口大小。一个进程 可以使用 ioctl 函数来获取或设置窗口size。进程 可以改变窗口size (从之前的值),内核就会生成 SIGWINCH 信号给前台进程组。
SIGXCPU
如果进程超出了它的 soft CPU time limit, 信号就会产生。
Linux 3.2.0 支持 一个默认的action : 终止并且有core file.
SIGXFSZ
如果进程超出了 soft file size limit
Linux 3.2.0 支持 一个默认的action : 终止并且有core file.
SIGXRES
仅仅 Solaries
10.3 signal Function
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
Returns: previous disposition of signal (see following) if OK, SIG_ERR on error
由于signal的语义在 各个实现是不同的,我们必须使用sigaction 函数替代。
所以 不要用<signal.h> 中的这个signal函数,用也是用sigaction实现的signal( 覆盖系统的signal 函数)
signo 参数 是 信号名对应的值。
参数func 可以是:
a: 常量 SIG_IGN
b: 常量 SIG_DFL
c: 一个函数的地址
SIG_IGN : 告诉系统 忽略信息
SIG_DFL: 设置和信号关联的action 为默认值。
一个函数的地址: 我们安排去“catch”信号。我们叫这个函数为 signal handler 或 signal-catching function.
signal 返回一个指针,这个指针指向 之前的signal handler .
使用typedef 可以简化signal 的定义。
typedef void Sigfunc(int);
然后原型 变成如下:
SigFunc * signal(int, Sigfunc *);
在<signal.h> 将会找到 SIG_IGN , SIG_DFL 的声明
#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)())0
#define SIG_IGN (void (*)())1
例子:10.2 简单的程序,用来捕获SIGUSR1 和 SIGUSR2
1.#include "apue.h"
2.
3.static void sig_usr(int); /* one handler for both signals */
4.
5.int
6.main(void)
7.{
8. if (signal(SIGUSR1, sig_usr) == SIG_ERR)
9. err_sys("can't catch SIGUSR1");
10. if (signal(SIGUSR2, sig_usr) == SIG_ERR)
11. err_sys("can't catch SIGUSR2");
12. for ( ; ; )
13. pause();
14.}
15.
16.static void
17.sig_usr(int signo) /* argument is signal number */
18.{
19. if (signo == SIGUSR1)
20. printf("received SIGUSR1\n");
21. else if (signo == SIGUSR2)
22. printf("received SIGUSR2\n");
23. else
24. err_dump("received signal %d\n", signo);
25.}
26.
27.
28./**
29.[xingqiji@work78 signals]$ ./sigusr &
30.[1] 51592
31.[xingqiji@work78 signals]$ kill -USR1 51592
32.received SIGUSR1
33.[xingqiji@work78 signals]$ kill -USR2 51592
34.received SIGUSR2
35.[xingqiji@work78 signals]$ kill 51592
36.[xingqiji@work78 signals]$
37.[1]+ 已终止 ./sigusr
38.[xingqiji@work78 signals]$
39. *
40. */
Program Start-Up 程序启动
许多交互式的程序,捕获这两个信号 有如下类似的代码:
void sig_int(int), sig_quit(int);
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
signal(SIGINT, sig_int);
if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
signal(SIGQUIT, sig_quit);
进程创建:
当一个进程调用 fork, child 会继承parent的 signal dispositions.
10.4 Unreliable Signals (不可靠的信号)
10.5 Interrupted System Calls (中断系统调用)
为了支持这个功能,系统调用 系统调用被分为 两类:slow 系统调用 和 其它的。
这些 slow 系统调用可能会 永远阻塞。永远阻塞的有如下类:
a: 读(reads) 也可能永远阻塞调用者( 读特定的文件读不数据,这类文件类型 如:pipes, terminal devices, 和network devices)
b: 写(writers ) 也可能会永远阻塞调用者
c: 打开特定的文件类型 阻塞了 调用者, 直到 一些情况发生
d: pause 函数(它使调用者进程 sleep,直到收到一个信号); wait 函数;
e: 特定的ioctl 操作
f: 一些 进程间通信的 函数(15章)
值得记录的 slow 系统调用 的异常 就是 那些 和 disk I/O 相关的。尽管 对文件的读或写可能会 临时性的 阻塞 调用者(当硬盘驱动排队请求,然后请求被执行)。除非硬件错误,I/O 操作通常会 很快 返回和不阻塞调用。
当一个进程 从 终端device 初始化了read ,并且在终端的用户 离开了很长一段时间。这种情况进程可能被阻塞 数小时,甚至数天,除非系统坏掉。
有中断系统调用这种问题,我们就不得不显式地处理返回的错误了。典型的代码写法是(假设有 读操作,如果它被中断了,我们想继续读)
again:
if ((n = read(fd, buf, BUFFSIZE)) < 0) {
if (errno == EINTR)
goto again; /* just an interrupted system call */
/* handle other errors */
}
为了 避免 如上这样的 处理 中断的系统调用,4.2BSD 产生了 一些特定的的 可以 自动 restart 的中断的系统调用。自动restart 的系统调用是:ioctl, read, readv, write, writev, wait, waitpid.
POSIX.1 要求,实现去 restart 系统调用,前提是 中断的信号 设置了SA_RESTART flag
在10.14部分中,flag 通常被用于sigaction 函数, 允许应用程序去 请求 中断的系统调用 重新开始。On FreeBSD 8.0, Linux 3.2.0, and Mac OS X 10.6.8 中, 当信号handler 使用signal 函数,被安装进去, 中断系统调用 将 被 restart. 在Solaris 10 中 不是这样。。。。我们自己实现signal 函数就会 避免处理这些不同。
10.18 我们尝试提供我们自己的signal 版本,它会自动地restart 中断的系统调用(除了SIGALRM signal) 10.19 我们实现了signal_intr, 它从不restart.
10.3 总结 各个实现下 signal 函数 和 他们的 语义
10.6 Reentrant Functions 重入函数
当一个 信号被捕获并处理,那么 正常的执行序列 被signal handler 临时中断。进程然后 继续执行,但是在 signal handler 中的指令 现在被 执行。但是在signal handler 中,我们不能告诉进程正执行到哪里,何时信号被捕获。如果是..怎么办:一个进程正在它的heap 调用malloc 来分配额外的内存,并且signal handler 也正在使用 malloc 怎么办?
又或者是:进程正调用 一个函数间 如getpwnam(存储结果到一个static 区),并且我们在signal handler 也调用这个函数,会怎么样?
在malloc的例子,进程可能会发生灾难,因为:malloc 通常维护了一个它分配的 一个链表,并且它可能处于 改变list 的中间态。在 getpwnam例子,则 正常程序中调用返回的信息可能会被覆盖(被signal handler 返回的信息)。
UNIX 规范指出 :一些函数 在signal handler 中保证 是调用安全地,这些是可重入的,被叫做UNIX 规范 叫做 async-signal safe的。除了是 可重入的,它们阻塞操作期间 的任何信号。
10.4 列表列出了这些 async-signal safe 函数。大多数没有出现是因为:a: 它们使用了 static data structures, b: 它们调用了malloc, free, c: 它们是 标准I/O 的一部分。 大多数标准I/O 使用 全局 data structures( 以非重入的方式)。 注意尽管 我们从 signal handler 中调用printf, 它也不保证产生 期望的结果, 是由于signal handler 可能中断 来自主程序中的printf调用。
abort | faccessat | linkat | select | socketpair |
accept | fchmod | listen | sem_post | stat |
access | fchmodat | lseek | send | symlink |
aio_error | fchown | lstat | sendmsg | symlinkat |
aio_return | fchownat | mkdir | sendto | tcdrain |
aio_suspend | fcntl | mkdirat | setgid | tcflow |
alarm | fdatasync | mkfifo | setpgid | tcflush |
bind | fexecve | mkfifoat | setsid | tcgetattr |
cfgetispeed | fork | mknod | setsockopt | tcgetpgrp |
cfgetospeed | fstat | mknodat | setuid | tcsendbreak |
cfsetispeed | fstatat | open | shutdown | tcsetattr |
cfsetospeed | fsync | openat | sigaction | tcsetpgrp |
chdir | ftruncate | pause | sigaddset | time |
chmod | futimens | pipe | sigdelset | timer_getoverrun |
chown | getegid | poll | sigemptyset | timer_gettime |
clock_gettime | geteuid | posix_trace_event | sigfillset | timer_settime |
close | getgid | pselect | sigismember | times |
connect | getgroups | raise | signal | umask |
create | getpeername | read | sigpause | uname |
dup | getpgrp | readlink | sigpending | unlink |
dup2 | getpid | readlinkat | sigprocmask | unlinkat |
execl | getppid | recv | sigqueue | utime |
execle | getsockname | recvfrom | sigset | utimensat |
execv | getsockopt | recvmsg | sigsuspend | utimes |
execve | getuid | rename | sleep | wait |
_Exit | kill | renameat | sockatmark | waitpid |
_exit | link | rmdir | socket | write |
10.4 Reentrant functions that may be called from a signal handler
当心:即便是我们调用了10.4 中列出的函数,由于每个线程只有一个 errno 变量,并且可能潜在地修改它的值。考虑到一个情况,一个信号signal handler 在main 刚设置完errno后就被调用。如果signal handler 调用read, 例如 ,这个调用可能会改变 errno 的值。
因此,作为一个通常的规则, 当我们在 signal handler 中调用10.4 列表中的函数时,我们应该 save 并 restore errno的值。(注意当心, 信号SIGCHLD, 它的signal handler 通常调用 wait系列之一的函数,所有的wait 函数都可能改变errno)。
例子: 10.5 展示在 signal handler 中 每秒调用 getpwnam 函数 ,使用alarm 函数来生成 SIGALRM signal
10.7 SIGCLD semantics (SIGCLD 含义)
1.#include "apue.h"
2.#include <sys/wait.h>
3.
4.static void sig_cld(int);
5.
6.int
7.main()
8.{
9. pid_t pid;
10.
11. if (signal(SIGCLD, sig_cld) == SIG_ERR)
12. perror("signal error");
13. if ((pid = fork()) < 0) {
14. perror("fork error");
15. } else if (pid == 0) { /* child */
16. sleep(2);
17. _exit(0);
18. }
19.
20. pause(); /* parent */
21. exit(0);
22.}
23.
24.static void
25.sig_cld(int signo) /* interrupts pause() */
26.{
27. pid_t pid;
28. int status;
29.
30. printf("SIGCLD received\n");
31.
32. if (signal(SIGCLD, sig_cld) == SIG_ERR) /* reestablish handler 为什么要重新定义呢???*/
33. perror("signal error");
34.
35. if ((pid = wait(&status)) < 0) /* fetch child status */
36. perror("wait error");
37.
38. printf("pid = %d\n", pid);
39.}
40.
41./**
42.当用signal对某个信号设定信号处理函数的时候,有些信号的处理函数会被重置,有些则不会!!!
43.信号处理程序会被重置的信号:
44.SIGALRM
45.SIGCHLD信号(SIGCLD)
46.
47.信号处理程序不会被重置的信号:
48.SIG_USR1和SIG_USR2
49.
50. *
51.[xingqiji@localhost signals]$ ./child
52.SIGCLD received
53.pid = 105041
54.[xingqiji@localhost signals]$
55. *
56. */
注意:只有Linux 3.2.0 和 Solaris 10 定义了 SIGCLD, 在其它平台,SIGCLD 是等效于 SIGCHLD。
10.8 Reliable-Signal Terminology and Semantics 可靠的信号术语和 语义
一些术语:
一个signal 被 “generated” 对于一个进程。
当事件引起信号产生 。
事件 可能是 一个 硬件异常,(如 被0 整除),软件情况(如,alarm timer 过期), 一个终端生成信号,或 kill函数 生成。当信号产生,内核通常 在 进程的 table 中设置 一个 flag
我们说 一个 signal 被 “delivered” 给一个进程,这时针对signal的action就会被采取了。
在 生成信号 和 它被投送 之间的时段,被叫做 signal 正在 pending.
一个进程 有 “blocking” 的选项 ,这个选项关系到 信号的投送。
如果一个 信号(被 blocked) 被生成 对于一个进程,并且 如果 此信号的action 要么是
default action 要么是 捕获signal , 则 signal 保持 pending 对此进程,直到进程 要么 unblocks 此信号,要么将 action 改变为 “ignore the signal”。system决定 在当信号被delivered时 做些什么事情;而不是当信号生成 时,就决定对信号做些什么事情。这就决定了/允许 进程 在信号被投递之前,就改变 action.
sigpending 函数 可以被一个进程调用,用来决定哪个signal 被 blocked and pending.
如果.. 会发生什么:如果一个 信号(blocked) 产生了超过一次,在进程unblocks signals 之前。 发生什么: 大多数的unix 系统, 不会 排队signals (除非他们支持 real-time extensions to POSIX.1)。
替代地,unix 内核会简单地 投递一次信号。
如果 有超过一个的信号 准备投递给 一个进程,POSIX.1 没有指定 信号的 先后被投递的顺序。然而,那些和当前 进程状态相关的信号 应该 先于其它信号。(SIGSEGV 就是一个这样的信号).
每个进程 有 一个 “signal mask” ,它定义了 当前信号blocked 的集合。
“signal mask” 每个bit 对应 相应的一个信号。如果 给定信号的 bit 是 on ,那么这个信号就是 “currently blocked” . 一个进程 可以 检查或改变 他的“signal mask”(通过 sigprocmask, 这我们描述在10.12) .
由于信号的个数 有可能超过integer 的 位数,POSIX.1 定义了一个 数据类型,叫 sigset_t ,它包含了 一个“signal set” .
10.9 kill and raise Functions
kill 发送一个信号 给 进程 或 进程组,raise 允许 一个进程发送 信号给它自己。
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
Both return: 0 if OK, -1 on error
调用raise(signo)
相当于kill(getpid(), signo)
pid > 0 信号被 投给 哪个它的进程ID 是”pid”的 进程。
pid == 0 : 发给 那些进程(“all processes”),它们的进程组ID 等于调用者的进程组ID , 并且发送者还要有发送权限。 注意 “all processes” 是 排除了 系统进程set。 对大多数unix 系统, 系统进程set 包括了 kernel 进程和 init (pid 为1)
pid < 0 : 发给 那些进程(“all processes”), 它们的进程组ID 等于 “pid” 的绝对值,并且发送者有权限去发送。 也是,“all processes” 要排除 系统进程set。 就像前面描述的。
pid == -1 : 信号被发送给所有进程,并且发送者要有权限发送。 像前边说的,排除 那些 系统进程。
对超级用户 来说,它可以发送一个 信号给任何 进程。对于其它用户, 基础的规则是:
发送者的 real 或 effective user ID 必须等于 接收者的 real 或 effective user ID。如果 实现 支持 _POSIX_SAVED_IDS, 则会检查 接收者的 saved set-user-ID (而不是检查他的 effective user ID ). 一个 关于权限检查的 特殊的情况 是:被发送的信号是 SIGCONT,一个进程可以发送 给它 给 其它的进程(这些进程在相同的session 即可)。
signal 数字 0 为 null signal .如果 signo 参数是 0,那么调用kill ,会执行正常的错误检查,但不发送信号。这个技术通常 用来去决定 一个 特定的进程 是否 仍然存在。如果我们发送给进程 null signal 并且 它 不存在, 则kill 返回 -1, 并且信号为 ESRCH。 当心!unix 在一段事件后 会回收进程IDs。 所以使用此方法( 给定的ID) 返回了说存在这个进程,那也不意味者 是 你以为的那个进程。
还有一点要理解就是 测试进程存在性 不是原子性的。在kill返回答案给调用者是,进程可能已经exited。 所以答案是 limited value。
10.10 alarm and Pause Functions
当 timer 过期,SIGALRM signal 信号就会产生。如果我们忽略或 不捕获这个信号,它的默认action 就是去终止进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
Returns: 0 or number of seconds until previously set alarm
每个进程 只有一个 alarm clocks。如果 我们调用alarm, 一个先前注册的alarm clock 还没有过期, 那么就会返回 那个alarm clock 剩余的seconds。之前注册的alarm clock 就会被新的 取代。
pause 函数 挂起调用进程直到 一个信号被捕获
#include <unistd.h>
int pause(void);
Returns: -1 with errno set to EINTR
例子: 使用alarm 和 pause ,我们可以 让 一个进程 sleep 指定的时间。
sleep1.c
1.#include <signal.h>
2.#include <unistd.h>
3.
4.static void
5.sig_alrm(int signo)
6.{
7. /* nothing to do, just return to wake up the pause */
8.}
9.
10.unsigned int
11.sleep1(unsigned int seconds)
12.{
13. if (signal(SIGALRM, sig_alrm) == SIG_ERR)
14. return(seconds);
15. alarm(seconds); /* start the timer */
16. pause(); /* next caught signal wakes us up */
17. return(alarm(0)); /* turn off timer, return unslept time */
18.}
10.7 Simple incomplete implementation of sleep
说明:这个函数有 3个问题:
1. 如果在调用者之前 已经 有一个 alarm 设置, 那这个alarm 就会被 第一个调用sleep1 (内部调用alarm) 的被擦除。我们可以纠正这点,通过看alarm的返回值。如果这个秒数 小于 参数,那么我们应该wait,直到存在的alarm 过期。如果 先前设置的 alarm 将在我们设置的 后边停止,那么在改函数return 之前,我们应该 重新设置这个 alarm 以让它在未来 产生。
2. 我们已经修改了 SIGALRM 的安排(disposition) 。如果我们写的一个函数是为了让别人调用,那么我们应该: 当调用function 时保存 disposition ,当我们做完时,重新存储它。我们可以 纠正这点 通过 保存从signal 返回的值,并且在函数返回前 重新设置下 disposition.
3. 这里有一个竞争条件: 在第一次调用alarm 和 pause 之间。在以busy的系统上,alarm 过期时,signal handler的调用 比 pause的调用早。如果这个情况发生了,那么调用者就会被永远suspended在pause的调用上(假定一些其它的信号没有被捕获)。
第3个问题 有两种方法解决,一是使用 setjmp, 二:使用 sigprocmask 和 sigsuspend
例子 10.8 使用 setjmp 和 longjmp 来避免之前例子中的问题3。这个例子没有处理1,2问题。
sleep2.c
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
static jmp_buf env_alrm;
static void
sig_alrm(int signo)
{
printf("sig_alrm of sleep2 starting\n");
longjmp(env_alrm, 1);
printf("sig_alrm of sleep2 finished\n");
}
unsigned int
sleep2(unsigned int seconds)
{
printf("sleep2 starting\n");
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(seconds);
if (setjmp(env_alrm) == 0) {
alarm(seconds); /* start the timer */
pause(); /* next caught signal wakes us up */
}
printf("sleep2 finished\n");
return(alarm(0)); /* turn off timer, return unslept time */
}
说明:
1. sleep2 函数避免了10.7 中出现的 race condition 。 即便 pause 是从来不被执行,当SIGALRM信号 发生的时候,sleep2 也能够 return。
2. 这里,然而有另一个 不易察觉和微妙(subtle)的问题, 就是关于和其它信号的交互。
如果SIGALRM 中断了 一些其它 signal handler ,那么 我们调用 longjmp,我们就会abort 其它的signal handler 。图10.9 展示了这种场景。
SIGINT handler 执行的时间超过5秒。我们只是简单的想让它执行的时间超过 sleep2的参数指定的。整型k 被声明为 volatile 来阻止 编译器优化 废弃掉loop.
10.9 一个程序调用sleep2 , 这个程序 还捕获其它信号。(测试sleep2)
tsleep2.c
#include "apue.h"
unsigned int sleep2(unsigned int);
static void sig_int(int);
int
main(void)
{
unsigned int unslept;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
unslept = sleep2(5);
printf("sleep2 returned: %u\n", unslept);
exit(0);
}
static void
sig_int(int signo)
{
int i, j;
volatile int k;
/*
* Tune these loops to run for more than 5 seconds
* on whatever system this test program is run.
*/
printf("\nsig_int starting\n");
for (i = 0; i < 3000010; i++)
for (j = 0; j < 40020; j++)
k += i * j;
printf("sig_int finished\n");
}
/**
*
[xingqiji@work78 signals]$ ./tsleep2
sleep2 starting
^C
sig_int starting
sig_alrm of sleep2 starting
sleep2 finished
sleep2 returned: 0
[xingqiji@work78 signals]$
[xingqiji@work78 signals]$ ./tsleep2
sleep2 starting
sig_alrm of sleep2 starting
sleep2 finished
sleep2 returned: 0
[xingqiji@work78 signals]$
*
*/
说明:
sleep1, sleep2 展示了 如果天真地处理signals 就会存在陷阱(pitfalls)。下面的部分展示了围绕这些问题的解决办法,以使我们可以 可靠地处理signals(不会有其它的代码片段干扰)。
例子: 一个常见的alarm 使用,除了实现 sleep 函数,也能 放一个上限时间 到那些可能阻塞的操作上。例如,如果我们有一个 read operation 在设备上,它可能阻塞(block), 我们想要 read 在 一些时间后 超时。这个程序10.10就做这些, 从标准输入上读,并且写到标准输出上。
10.10 调用 read ,带超时 ; alarm 的应用之一:设置超时
read1.c
#include "apue.h"
static void sig_alrm(int);
int
main(void)
{
int n;
char line[MAXLINE];
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
alarm(180);
if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
err_sys("read error");
alarm(0);
write(STDOUT_FILENO, line, n);
exit(0);
}
static void
sig_alrm(int signo)
{
/* nothing to do, just return to interrupt the read */
printf("sig_alrm finished\n");
}
/**
*
[xingqiji@bogon signals]$ ./read1
^C
[xingqiji@bogon signals]$ make //注释掉alarm后
gcc -ansi -I../include -Wall -DLINUX -D_GNU_SOURCE read1.c -o read1 -L../lib -lapue
[xingqiji@bogon signals]$ ./read1
read error: Interrupted system call
[xingqiji@bogon signals]$
ps aux | grep read 查询到进程ID, 使用 kill -USR1 20784 发送 SIGUSR1 信号,则:
[xingqiji@work78 signals]$ ./read1
用户定义信号 1
[xingqiji@work78 signals]$
*
*/
说明:
这些代码在unix applications 中很常见,但是这个程序有两个问题:
1. 注意10.10 中也存在和10.7 相同的 race condition ; 就是在 第一次调用 alarm 和调用read, 如果内核在两个函数之间 长时间block ,这个时间超过了 alarm period, 则read 可能会永远 block。 大多数这种类型的操作 会使用更长的 alarm period, 例如: 一分钟或更长,使这种 坏情况 不可能发生。无论如何,这确实 是个 race condition。
2. 如果系统调用 自动重新 开始,那么当SIGALRM signal handler 返回时,read就不会被中断。在这种情况下,timeout 不起作用了。
在我的work78上测试显然 不会自动重新 开始。
例子:使用longjmp 重新做之前的例子。
10.11 调用read 带超时,使用longjmp;
read2.c
#include "apue.h"
#include <setjmp.h>
static void sig_alrm(int);
static jmp_buf env_alrm;
int
main(void)
{
int n;
char line[MAXLINE];
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
if (setjmp(env_alrm) != 0)
err_quit("read timeout");
alarm(10);
if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
err_sys("read error");
alarm(0);
write(STDOUT_FILENO, line, n);
exit(0);
}
static void
sig_alrm(int signo)
{
longjmp(env_alrm, 1);
}
/**
*
[xingqiji@bogon signals]$ ./read2
read timeout
[xingqiji@bogon signals]$
*
*/
说明:
这个版本工作是像期望那样的,不用管 是否系统调用是restart interrupted 系统调用。然而,在与其它 signal handlers 的交互上 仍然有问题。
另一个 可选的做法 是 使用 select 或 poll 函数,
10.11 Signal Sets 信号集
我们需要一个数据类型 来代表 众多的信号- - 这就是 信号集。就像前面提到的,信号的数量在各个系统是不同的,这个数量可能超出了integer 的位数。所以不能用 integer来代表信号集。posix.1 定义了 数据类型 sigset_t 来包含信号集,下面的5个函数就是来维护 信号集的。
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
All four return: 0 if OK, -1 on error
int sigismember(const sigset_t *set, int signo);
Returns: 1 if true, 0 if false, -1 on error
所有应用不得不调用 要么 sigemptyset 或者 sigfillset 一次 。
一旦 我们初始化了 一个 signal set, 我们可以添加 并且删除 特定的signal 。函数 sigaddset 添加 一个 signal 给 一个已存在的signal set, sigdelset 从一个 signal set 中移除一个signal。
实现:
如果实现 有信号 的数量 少于 一个 整型的bit, 那么 一个 signal set 就可以实现为 一位代表一个信号的方式。下面的部分,假定 有一个实现 它有 31个signals 用 32bit 整型。则 可以用宏实现如下:
#define sigemptyset(ptr) (*(ptr) = 0)
#define sigfillset(ptr) (*(ptr) = ~(sigset_t)0, 0)
10.12 sigaddset, sigdelset, sigismember 的实现
10.12 sigprocmask Function
一个进程的 signal mask 就是 当前对进程blocked 的 signals set. 一个进程可以检查她的 signal mask ,改变它的 signal mask , 或者 两步并做一步走(通过下面的函数)。
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
Returns: 0 if OK, −1 on error
首先, oset 是 非空pointer, how 指明了 当前的signal mask 怎么被修改。SIG_BLOCK 是一个 inclusive-OR 操作, SIG_SETMASK 是一个 赋值操作。注意:SIGKILL 和 SIGSTOP 不能被 blocked.
如果set 是个 null 指针, 进程的 signal mask 不会被改变,how 会被忽略。
sigprocmask 函数 被定义 仅仅为 单线程的进程。对于多线程的进程,有另外的函数。
例子:10.14 展示了一个函数,它打印调用进程的 signal mask 中的信号名。
10.14 为进程 打印 signal mask
lib/prmask.c
1.#include "apue.h"
2.#include <errno.h>
3.
4.void
5.pr_mask(const char *str)
6.{
7. sigset_t sigset;
8. int errno_save;
9.
10. errno_save = errno; /* we can be called by signal handlers */
11. if (sigprocmask(0, NULL, &sigset) < 0) {
12. err_ret("sigprocmask error");
13. } else {
14. printf("%s", str);
15. if (sigismember(&sigset, SIGINT))
16. printf(" SIGINT");
17. if (sigismember(&sigset, SIGQUIT))
18. printf(" SIGQUIT");
19. if (sigismember(&sigset, SIGUSR1))
20. printf(" SIGUSR1");
21. if (sigismember(&sigset, SIGALRM))
22. printf(" SIGALRM");
23.
24. /* remaining signals can go here */
25.
26. printf("\n");
27. }
28.
29. errno = errno_save; /* restore errno */
}
10.13 sigpending Function
sigpending 返回 调用进程 的 被blocked 的且 pending 的信号集。通过信号集通过 set 参数来返回。
#include <signal.h>
int sigpending(sigset_t *set);
Returns: 0 if OK, -1 on error
例子:
10.15 展示了许多信号功能
1.#include "apue.h"
2.
3.static void sig_quit(int);
4.
5.int
6.main(void)
7.{
8. sigset_t newmask, oldmask, pendmask;
9.
10. if (signal(SIGQUIT, sig_quit) == SIG_ERR)
11. err_sys("can't catch SIGQUIT");
12.
13. /*
14. * Block SIGQUIT and save current signal mask.
15. */
16. sigemptyset(&newmask);
17. sigaddset(&newmask, SIGQUIT);
18. if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
19. err_sys("SIG_BLOCK error");
20.
21. sleep(5); /* SIGQUIT here will remain pending */
22.
23. if (sigpending(&pendmask) < 0)
24. err_sys("sigpending error");
25. if (sigismember(&pendmask, SIGQUIT))
26. printf("\nSIGQUIT pending\n");
27.
28. /*
29. * Restore signal mask which unblocks SIGQUIT.
30. */
31. if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
32. err_sys("SIG_SETMASK error");
33. printf("SIGQUIT unblocked\n");
34.
35. sleep(5); /* SIGQUIT here will terminate with core file */
36. exit(0);
37.}
38.
39.static void
40.sig_quit(int signo)
41.{
42. printf("caught SIGQUIT\n");
43. if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
44. err_sys("can't reset SIGQUIT");
45.}
46.
47.
48./**
49. *
50.
51. [xingqiji@bogon signals]$ ./critical
52.^\
53.
54.SIGQUIT pending
55.caught SIGQUIT
56.SIGQUIT unblocked
57.[xingqiji@bogon signals]$
58.[xingqiji@bogon signals]$ ./critical
59.^\
60.
61.SIGQUIT pending
62.caught SIGQUIT
63.SIGQUIT unblocked
64.^\退出(吐核)
65.[xingqiji@bogon signals]$ ./critical
66.^\
67.^\
68.^\
69.
70.SIGQUIT pending
71.caught SIGQUIT
72.SIGQUIT unblocked
73.[xingqiji@bogon signals]$
74.
1. [xingqiji@bogon signals]$ ./critical
2.^\
3.^\
4.^\
5.^\
6.
7.SIGQUIT pending
8.caught SIGQUIT
9.SIGQUIT unblocked
10.^\退出(吐核)
11.[xingqiji@bogon signals]$
75.
76. *
77. *
78. */
有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数。这种情况是通过阻塞信号实现的
2、信号阻塞和忽略信号的区别
操作系统在信号被进程解除阻塞之前不会将信号传递出去,被阻塞的信号也不会影响进程的行为,信号只是暂时被阻止传递。当进程忽略一个信号时,信号会被传递出去但进程会将信号丢弃。
10.14 sigaction Function
sigaction 函数允许我们去 检查 或 修改 (或both) 特定信号关联的 action。
一旦我们对一个给定的signal 安装一个 action, 这个action 就会保持一直安装状态直到 我们通过调用sigaction 显式地改变。
#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
Returns: 0 if OK, -1 on error
这个函数 使用如下的 结构体:
struct sigaction {
void (*sa_handler)(int); /* addr of signal handler, or SIG_IGN, or SIG_DFL*/
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; /* additional signals to block */
int sa_flags; /* signal options, Figure 10.16 */
void (*sa_restorer)(void);
};
sa_sigaction 是一个额外的信号handler。 当SA_SIGINFO flag 被设置时使用
Option | Linux3.2.0 | 描述 |
SA_INTERRUPT | 支持 | System calls interrupted by this signal are not automatically restarted |
SA_NOCLDSTOP | 支持 | 如果signo 是SIGCHLD, 当child进程停止时不会生成这个信号(job control). ..... |
SA_NOCLDWAIT | 支持 | 如果signo 是SIGCHLD, 这个选项会阻止系统创建zombie processes. 如果它随之调用wait, 调用进程会阻塞直到所有它的子进程终止并且 返回-1 、errno 被设置为ECHLD |
SA_NODEFER | 支持 | |
SA_ONSTACK | 支持 | |
SA_RESETHAND | 支持 | 对于这个信号会被重置为SIG_DFL, 并且SA_SIGINFO flag 会被清除。 |
SA_RESTART | 支持 | 被这个信号 中断的 系统调用会 自动 重新开始 |
SA_SIGINFO | 支持 | 这个选项提供额外的信息给signal handler: 一个指针指向 一个siginfo structure, 一个指针指向进程context 的identifier |
10.16 sa_flags 选项
正常地, signal handler 被调用 像:
void handler(int signo)
但是如果 SA_SIGINFO flag 被设置, signal handler 被调用像如下:
void handler(int signo, siginfo_t *info, void *context);
siginfo 结构体包含了 为什么signal 被生成的 信息。
struct siginfo {
int si_signo; /* signal number */
int si_errno; /* if nonzero, errno value from errno.h */
int si_code; /* additional info (depends on signal) */
pid_t si_pid; /* sending process ID */
uid_t si_uid; /* sending process real user ID */
void *si_addr; /* address that caused the fault */
int si_status; /* exit value or signal number */
union sigval si_value; /* application-specific value */
/* possibly other fields also */
};
sigval union 包含如下的字段
int sival_int;
void *sival_ptr;
context 结构体 有如下字段:
ucontext_t *uc_link; /* pointer to context resumed when */
/* this context returns */
sigset_t uc_sigmask; /* signals blocked when this context */
/* is active */
stack_t uc_stack; /* stack used by this context */
mcontext_t uc_mcontext; /* machine-specific representation of */
/* saved context */
uc_stack 字段描述了 当前context 使用的 stack。它包含了至少如下的成员:
void *ss_sp; /* stack base 或者 pointer*/
size_t ss_size; /* stack size */
int ss_flags; /* flags */
siginfo_t code value 如下表:10.17
signal | Code | 原因 |
SIGILL | ILL_ILLOPC | illegal opcode |
ILL_ILLOPN | illegal operand | |
ILL_ILLADR | illegal addressing mode | |
ILL_ILLTRP | illegal trap | |
ILL_PRVOPC | privileged opcode | |
ILL_PRVREG | privileged register | |
ILL_COPROC | coprocessor error | |
ILL_BADSTK | internal stack error | |
SIGFPE | FPE_INTDIV | integer divide by zero |
FPE_INTOVF | integer overflow | |
FPE_FLTDIV | floating-point divide by zero | |
FPE_FLTOVF | floating-point overflow | |
FPE_FLTUND | floating-point underflow | |
FPE_FLTRES | floating-point inexact result | |
FPE_FLTINV | invalid floating-point operation | |
FPE_FLTSUB | subscript out of range | |
SIGSEGV | SEGV_MAPERR | address not mapped to object |
SEGV_ACCERR | invalid permissions for mapped object | |
SIGBUS | BUS_ADRALN | invalid address alignment |
BUS_ADRERR | nonexistent physical address | |
BUS_OBJERR | object-specific hardware error | |
SIGTRAP | TRAP_BRKPT | process breakpoint trap |
TRAP_TRACE | process trace trap | |
SIGCHLD | CLD_EXITED | child has exited |
CLD_KILLED | child has terminated abnormally(no core) | |
CLD_DUMPED | child has terminated abnormally with core | |
CLD_TRAPPED | traced child has trapped | |
CLD_STOPPED | child has stopped | |
CLD_CONTINUED | stopped child has continued | |
ANY | SI_USER | signal sent by kill |
SI_QUEUE | signal sent by sigqueue | |
SI_TIMER | expiration of a timer set by timer_settime | |
SI_ASYNCIO | completion of asynchronous I/O request | |
SI_MESGQ | arrival of a message on message queue(real-time extension) | |
例子:使用 sigaction 来实现 signal 函数。很多平台都这样做。
1.#include "apue.h"
2.
3./* Reliable version of signal(), using POSIX sigaction(). */
4.Sigfunc *
5.signal(int signo, Sigfunc *func)
6.{
7. struct sigaction act, oact;
8.
9. act.sa_handler = func;
10. sigemptyset(&act.sa_mask);
11. act.sa_flags = 0;
12. if (signo == SIGALRM) {
13.#ifdef SA_INTERRUPT
14. act.sa_flags |= SA_INTERRUPT;
15.#endif
16. } else {
17. act.sa_flags |= SA_RESTART;
18. }
19. if (sigaction(signo, &act, &oact) < 0)
20. return(SIG_ERR);
21. return(oact.sa_handler);
22.}
说明:
注意:我们必须使用 sigemptyset 去初始化sa_mask成员。我们不能保证 act.sa_mask=0 能做同样的事情。
我们 打算 对除了SIGALRM外的信号设置SA_RESTART flag , 这样任何系统调用 如果被这些信号(除了SIGALRM) 打断了 都将能自动 restart。 不想让 SIGALRM restart 的原因是,我们想对I/O 操作 设置超时(可以回顾下10.10的例子)。
一些老的系统,如 SunOS, 定义了 SA_INTERRUPT flag。 这些系统默认就是 restart, 所以设置了这个标志,就会中断(interrupted) 系统调用。Linux 定义 SA_INTERRUPT flag 是为了兼容那些使用这个flag的应用,但是默认地Linux 不会restart 系统调用。
Unix规范指出:sigaction函数不应该 restart 中断的系统调用(除非SA_RESTART 标志被指定)。Linux 符合这个规范
10.19 展示了signal函数的一个版本,这个版本 试着去 阻止 那些被中断的系统调用 被重新开始(restart).
1.#include "apue.h"
2.
3.Sigfunc *
4.signal_intr(int signo, Sigfunc *func)
5.{
6. struct sigaction act, oact;
7.
8. act.sa_handler = func;
9. sigemptyset(&act.sa_mask);
10. act.sa_flags = 0;
11.#ifdef SA_INTERRUPT
12. act.sa_flags |= SA_INTERRUPT;
13.#endif
14. if (sigaction(signo, &act, &oact) < 0)
15. return(SIG_ERR);
16. return(oact.sa_handler);
17.}
10.15 sigsetjmp and siglongjmp Functions
在调用longjmp 上有问题: 当一个信号被捕获,进入信号捕获函数内, 当前的信号会自动地被添加到进程 的 signal mask。 如果 我们用longjmp 跳出signal handler, 那么对于进程的signal mask 会产生什么影响?
在 freeBSD8.0 和 Mac OS X 10.6.8, setjmp 和 longjmp save 和 restore signal mask。Linux 3.2.0 和 Solaris 10, 然而 不这样做 (尽管Linux 支持一个选择来支持BSD行为)。FreeBSD和 Mac OS X 提供了函数_setjmp 和_longjmp, 它们 不会 save 和 restore signal mask。
POSIX ,在 signal handler 中应该总是 使用 sigsetjmp 和siglongjmp。
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
Returns: 0 if called directly, nonzero if returning from a call to siglongjmp
void siglongjmp(sigjmp_buf env, int val);
10.20 说明了 sigsetjmp 和 siglongjmp 的用法。
signal/mask.c
1.#include "apue.h"
2.#include <setjmp.h>
3.#include <time.h>
4.
5.static void sig_usr1(int);
6.static void sig_alrm(int);
7.static sigjmp_buf jmpbuf;
8.static volatile sig_atomic_t canjump;
9.
10.int
11.main(void)
12.{
13. if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
14. err_sys("signal(SIGUSR1) error");
15. if (signal(SIGALRM, sig_alrm) == SIG_ERR)
16. err_sys("signal(SIGALRM) error");
17.
18. pr_mask("starting main: "); /* {Prog prmask} */
19.
20. if (sigsetjmp(jmpbuf, 1)) {
21.
22. pr_mask("ending main: ");
23.
24. exit(0);
25. }
26. canjump = 1; /* now sigsetjmp() is OK */
27.
28. for ( ; ; )
29. pause();
30.}
31.
32.static void
33.sig_usr1(int signo)
34.{
35. time_t starttime;
36.
37. if (canjump == 0)
38. return; /* unexpected signal, ignore */
39.
40. pr_mask("starting sig_usr1: ");
41.
42. alarm(3); /* SIGALRM in 3 seconds */
43. starttime = time(NULL);
44. for ( ; ; ) /* busy wait for 5 seconds */
45. if (time(NULL) > starttime + 5)
46. break;
47.
48. pr_mask("finishing sig_usr1: ");
49.
50. canjump = 0;
51. siglongjmp(jmpbuf, 1); /* jump back to main, don't return */
52.}
53.
54.static void
55.sig_alrm(int signo)
56.{
57. pr_mask("in sig_alrm: ");
58.}
59.
60.
61./**
62.
63.[xingqiji@work78 signals]$ ./mask &
64.[1] 46377
65.[xingqiji@work78 signals]$ starting main:
66.
67.[xingqiji@work78 signals]$ kill -USR1 46377
68.starting sig_usr1: SIGUSR1
69.[xingqiji@work78 signals]$ in sig_alrm: SIGUSR1 SIGALRM
70.finishing sig_usr1: SIGUSR1
71.ending main:
72.
73.[1]+ 完成 ./mask
74.[xingqiji@work78 signals]$
75.
76. */
给这个程序 画一个 时序图:
10.21
10.16 sigsuspend Function
我们知道如何 给一个进程 的 signal mask 使用 block 和 unlock 选择的信号。我们可以使用这个技术去 保护 极重要的代码区 ,这个代码区我们不想让信号来中断。
但是,如果我们想unblock 一个信号并且 然后pause 来等待之前被blocked的信号被投递, 这个情况会发生什么?
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/* block SIGINT and save current signal mask */
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
/* critical region of code */
/* restore signal mask, which unblocks SIGINT */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
/* window is open */
pause(); /* wait for signal to occur */
/* continue processing */
如果信号 被 blocked , 这个信号将要被发送 给进程,则信号的投递 将被延迟,直到signal 被 unlocked。 对于这个应用, 这看起来 好像 signal 发生在 unblock 和 pause之间,这么我们有个问题。 任何在这个时间窗口来的 signal 都会 丢失,意味着我们可能再也看不到这个信号, 在这种情况下, pause 将无限制 阻塞。另外一个问题就是 早期的 不可靠的信号。
为了纠正这个问题, 我们需要一个方法 来 去 重新存储 signal mask 并且 放进程去sleep ,这两个动作要是原子性操作。 这个功能可以用 sigsuspend 函数来完成。
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
Returns: -1 with errno set to EINTR
例子:正确保护极重要代码区域 以防止信号的影响 的方法如下:
10.22
1.#include "apue.h"
2.
3.static void sig_int(int);
4.
5.int
6.main(void)
7.{
8. sigset_t newmask, oldmask, waitmask;
9.
10. pr_mask("program start: ");
11.
12. if (signal(SIGINT, sig_int) == SIG_ERR)
13. err_sys("signal(SIGINT) error");
14. sigemptyset(&waitmask);
15. sigaddset(&waitmask, SIGUSR1);
16. sigemptyset(&newmask);
17. sigaddset(&newmask, SIGINT);
18.
19. /*
20. * Block SIGINT and save current signal mask.
21. */
22. if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
23. err_sys("SIG_BLOCK error");
24.
25. /*
26. * Critical region of code.
27. */
28. pr_mask("in critical region: ");
29.
30. /*
31. * Pause, allowing all signals except SIGUSR1.
32. * 用wait 信息号集 替代 new 信号集。 过来 SIGUSR1 信号,阻塞掉,程序继续挂起; 过来其他信号(如SIGINT),则会唤醒。 当函数被信息中断要返回前会 恢复调用之前进程的 signal mask
33. */
34. if (sigsuspend(&waitmask) != -1)
35. err_sys("sigsuspend error");
36.
37. pr_mask("after return from sigsuspend: ");
38.
39. /*
40. * Reset signal mask which unblocks SIGINT.
41. */
42. if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
43. err_sys("SIG_SETMASK error");
44.
45. /*
46. * And continue processing ...
47. */
48. pr_mask("program exit: ");
49.
50. exit(0);
51.}
52.
53.static void
54.sig_int(int signo)
55.{
56. pr_mask("\nin sig_int: ");
57.}
58.
59./**
60. *
61.[xingqiji@work78 signals]$ ./suspend1
62.program start:
63.in critical region: SIGINT
64.^C
65.in sig_int: SIGINT SIGUSR1
66.after return from sigsuspend: SIGINT
67.program exit:
68.[xingqiji@work78 signals]$
69.
70. *
71. */
说明:当我们调用sigsuspend 时 参数waitmask 的作用是 设置 SIGUSR1 到 mask, 给这样的参数目的是:当 signal handler 运行时 ,我们可以 告诉/看出 mask 确实已被改变了。
2.可以看到当 sigsuspend returns, 它(sigsuspend会重新 使用调用前的mask(里边有SIGINT) 去设置进程的mask.
10.23 使用sigsuspend 来 等待signal handler 去设置 一个global 变量。
这个程序:我们 捕获 两个信号 interrupt signal 和 quit signal , 但是我们只想捕获到quit signal 时 去唤醒 main routine.
signals/suspend2.c
#include "apue.h"
volatile sig_atomic_t quitflag; /* set nonzero by signal handler */
static void
sig_int(int signo) /* one signal handler for SIGINT and SIGQUIT */
{
if (signo == SIGINT)
printf("\ninterrupt\n");
else if (signo == SIGQUIT)
quitflag = 1; /* set flag for main loop */
}
int
main(void)
{
sigset_t newmask, oldmask, zeromask;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
if (signal(SIGQUIT, sig_int) == SIG_ERR)
err_sys("signal(SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
/*
* Block SIGQUIT and save current signal mask.
*/
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
while (quitflag == 0)
sigsuspend(&zeromask);
/*
* SIGQUIT has been caught and is now blocked; do whatever.
*/
quitflag = 0;
/*
* Reset signal mask which unblocks SIGQUIT.
*/
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
exit(0);
}
/**
[xingqiji@bogon signals]$ ./suspend2
^C
interrupt
^C
interrupt
^\[xingqiji@bogon signals]$
[xingqiji@work78 signals]$
*
*
*/
10.24 例子:展示 信号 如何来同步 parent 和 child.
TELL_WAIT, TELL_PARENT, TELL_CHILD, WAIT_PARENT, and WAIT_CHILD 用信号来实现。
lib/tellwait.c
#include "apue.h"
static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */
static sigset_t newmask, oldmask, zeromask;
static void
sig_usr(int signo) /* one signal handler for SIGUSR1 and SIGUSR2 */
{
sigflag = 1;
}
void
TELL_WAIT(void)
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR1) error");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR2) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
/* Block SIGUSR1 and SIGUSR2, and save current signal mask */
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
}
void
TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2); /* tell parent we're done */
}
void
WAIT_PARENT(void)
{
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for parent */
sigflag = 0;
/* Reset signal mask to original value */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
void
TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1); /* tell child we're done */
}
void
WAIT_CHILD(void)
{
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for child */
sigflag = 0;
/* Reset signal mask to original value */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
说明:
在15.7 中,使用pipe 实现了 这5个函数。
当我们 想 sleep 并且等待着信号的发生,那么sigsuspend 函数就完美的实现这个需求。但是如果是 :正在waitting 时要是我们想 调用其它系统函数,我们该怎么办? 不幸的是这个问题没有解决方案,除非我们使用 多线程threads 并且指定一个独立的线程 去处理信号(参考12.8)。
在没有使用线程的情况下, 我们最好的做法就是:在signal handler set一个全局变量.
例子:
我们 捕获 两个信号(SIGINT, SIGALRM) ,并且 用signal_intr函数 安装 signal handler, 信号会中断 任何的 被阻塞的 慢系统调用( slow system call)。当 从 slow device read 时会被blocked, 这时信号就很可能发生。(典型地 SIGALRM的 做法就很对 :它阻止了我们 永远地 卡在 读取 输入上)。代码看起来如下:(伪代码)
if (intr_flag) /* flag set by our SIGINT handler */
handle_intr();
if (alrm_flag) /* flag set by our SIGALRM handler */
handle_alrm();
/* signals occurring in here are lost */
while (read( ... ) < 0) {
if (errno == EINTR) {
if (alrm_flag)
handle_alrm();
else if (intr_flag)
handle_intr();
} else {
/* some other error */
}
} else if (n == 0) {
/* end of file */
} else {
/* process input */
}
}
10.17 abort Function
#include <stdlib.h>
void abort(void);
This function never returns
这个函数 发送 SIGABRT 信号给 caller 。进程不应该忽略此信号。
ISO C 要求 如果信号被 捕获,signal handler返回, abort 仍然不会返回到它的 caller.
让进程捕获SIGABRT的意图是:允许它允许任何的 cleanup。如果进程没有从这signal handler中终止它自己,POSIX.1 说,当signal handler return ,abort 终止 这个进程。
这个函数的 ISO C规范 留给具体的实现来决定 是否 刷新 output streams、 是否删除临时文件。
POSIX.1 允许 abort实现 在终止进程前对 标准I/O streams 调用 fclose
历史的原因, abort 的实现 对如何处理标准I/O 是不同的。所以为了 编程的严谨性 和提高兼容性, 我们想要 标准I/O 被 flushed, 我们可以在调用abort之前做这些。
例子: POSIX.1 abort 函数的一种实现。
10.25 signals/abort.c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void
abort(void) /* POSIX-style abort() function */
{
sigset_t mask;
struct sigaction action;
/* Caller can't ignore SIGABRT, if so reset to default */
sigaction(SIGABRT, NULL, &action);
if (action.sa_handler == SIG_IGN) {
action.sa_handler = SIG_DFL;
sigaction(SIGABRT, &action, NULL);
}
if (action.sa_handler == SIG_DFL)
fflush(NULL); /* flush all open stdio streams */
/* Caller can't block SIGABRT; make sure it's unblocked */
sigfillset(&mask);
sigdelset(&mask, SIGABRT); /* mask has only SIGABRT turned off */
sigprocmask(SIG_SETMASK, &mask, NULL);
kill(getpid(), SIGABRT); /* send the signal */
/* If we're here, process caught SIGABRT and returned */
fflush(NULL); /* flush all open stdio streams
这里又flush一次,是由于进程可能已经生成了 更多output, 唯一的情况不需要处理是:
进程捕获到信号且调用了_exit 或_Exit. 在这种情况下在内存中的任何的没刷新的标准I/O 都会被丢弃。
*/
action.sa_handler = SIG_DFL;
sigaction(SIGABRT, &action, NULL); /* reset to default */
sigprocmask(SIG_SETMASK, &mask, NULL); /* just in case ... */
kill(getpid(), SIGABRT); /* and one more time */
exit(1); /* this should never be executed ... */
}
说明:fflush 不等效于fclose, 它仅仅 flush open streams,它不关闭 他们,但是 当进程终止,系统关闭所有的文件。
kill: kill 引起生成信号,并且如果 signal 没有被 block,那么signal 会被投递给进程,之后kill 返回。
10.18 system Function
POSIX.1 要求 system 忽略 SIGINT 和 SIGQUIT 并且 block SIGCHLD.
为什么需要 去担心 signal handling
例子:
使用来自8.13 的 system 版本 调用ed(1) 的问题展示在10.26 中。 ed(1) 这个编辑器是 unix 系统的一部分。我们使用它在这,是因为它是一个交互式程序,它捕获interrupt和quit 信号。如果我们 从shell 中调用 ed 并且 键入 interrupt character, 它捕获 interrupt signal 并且 打印 一个 question mark ( ?号)。 ed程序 也会设置 quit signal的disposition,目的是它被忽略)。
8.22 proc/system.c
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
int
system(const char *cmdstring) /* version without signal handling */
{
printf("in proc/system.c\n");
pid_t pid;
int status;
if (cmdstring == NULL)
return(1); /* always a command processor with UNIX */
if ((pid = fork()) < 0) {
status = -1; /* probably out of processes */
} else if (pid == 0) { /* child */
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127); /* execl error */
} else { /* parent */
while (waitpid(pid, &status, 0) < 0) {
if (errno != EINTR) {
status = -1; /* error other than EINTR from waitpid() */
break;
}
}
}
return(status);
}
10.26代码:signals/systest2.c
#include "apue.h"
static void
sig_int(int signo)
{
printf("caught SIGINT\n");
}
static void
sig_chld(int signo)
{
printf("caught SIGCHLD\n");
}
int
main(void)
{
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
if (signal(SIGCHLD, sig_chld) == SIG_ERR)
err_sys("signal(SIGCHLD) error");
if (system("/bin/ed") < 0) /* 使用了同目录下的 system 函数, 需要拿出铁证来,gdb调试下 */
err_sys("system() error");
exit(0);
}
/*
修改makefile system.o 使用../proc/system.o
[root@bogon signals]# ./systest2
in proc/system.c
a
here is
.
1,$p
here is
w temp.foo
8
q
caught SIGCHLD
[root@bogon signals]#
修改makefile system.o 使用 ../signals/sytem.o
[root@bogon signals]# ./systest2
in siganl/system.c
a
hello,world
.
1,$p
hello,world
w temp.foo
12
^C
? // 与书上结果不一样, "caught SIGINT and so does the parent process "
q
caught SIGCHLD
[root@bogon signals]#
[root@bogon signals]# ./systest2
in siganl/system.c
^C
?
^\
?
a
这是here
.
.
这是here
1,$p
这是here
w aaa.txt
11
q
caught SIGCHLD
[root@bogon signals]#
*/
使用 ../proc/system.c ,调用10.26程序,我么get
$ ./a.out
a append text to the editor’s buffer ;a 追加文本
Here is one line of text
. period on a line by itself stops append mode ; 停止追加模式
1,$p print fifirst through last lines of buffer to see what’s there ;打 印最后一行
Here is one line of text
w temp.foo write the buffer to a fifile ;将buffer中内容写到文件中
25 editor says it wrote 25 bytes ;编辑器告诉我们它写了25个字节
q and leave the editor ;退出编辑器
caught SIGCHLD
当 编辑器终止,system 发送 SIGCHLD 给 parent( a.out) 进程。
当system函数正在执行,在parent 中的SIGCHLD信号投递 应该被blocked。(这是POSIX.1的要求)。如果不被阻塞,则 当 由system创建的child 结束时, 它(child)将欺骗 system的调用者“ 让调用者认为是它的 一个child 终止了。调用者将使用 wait 类 函数 去获取 the child的终止状态。因此 “不阻塞这种做法”(不好的) 会 阻止system 函数 return the child的状态。
回想起9.6部分,键入 interrupt character 会引起 interrupt signal 被发送到 在前台进程组重的所有进程。10.27 展示了 当the editor 正在running 时的进程布局。
由于system 的调用者 放弃了 对正在执行程序(ed)的控制,控制指的是 waitting for ed 去结束。所以system的调用者 不应该 接受 这两个 terminal-generated signals. 出于这个原因,POSIX.1 规范说 system 函数应该 ignore 这两个信号(当waitting the command去完成时)。
例子:
10.28 展示了 一个 带必要的signal handling 的 system 系统函数版本。
10.28 正确的 POSIX.1标准的 system函数实现。
1.#include <sys/wait.h>
2.#include <errno.h>
3.#include <signal.h>
4.#include <unistd.h>
5.
6.int
7.system(const char *cmdstring) /* with appropriate signal handling */
8.{
9. pid_t pid;
10. int status;
11. struct sigaction ignore, saveintr, savequit;
12. sigset_t chldmask, savemask;
13.
14. if (cmdstring == NULL)
15. return(1); /* always a command processor with UNIX */
16.
17. ignore.sa_handler = SIG_IGN; /* ignore SIGINT and SIGQUIT */
18. sigemptyset(&ignore.sa_mask);
19. ignore.sa_flags = 0;
20. if (sigaction(SIGINT, &ignore, &saveintr) < 0)
21. return(-1);
22. if (sigaction(SIGQUIT, &ignore, &savequit) < 0)
23. return(-1);
24. sigemptyset(&chldmask); /* now block SIGCHLD */
25. sigaddset(&chldmask, SIGCHLD);
26. if (sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0)
27. return(-1);
28. /* 在fork 之前 就应该忽略掉SIGINT和 SIGQUIT 放在fork后 会出现race condition*/
29. if ((pid = fork()) < 0) {
30. status = -1; /* probably out of processes */
31. } else if (pid == 0) { /* child */
32. /* restore previous signal actions & reset signal mask */
33. sigaction(SIGINT, &saveintr, NULL);
34. sigaction(SIGQUIT, &savequit, NULL);
35. sigprocmask(SIG_SETMASK, &savemask, NULL);
36.
37. execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); //这里为啥不 直接 execl(cmdstring) ?
38. _exit(127); /* exec error */
39. } else { /* parent */
40. while (waitpid(pid, &status, 0) < 0)
41. if (errno != EINTR) {
42. status = -1; /* error other than EINTR from waitpid() */
43. break;
44. }
45. }
46.
47. /* restore previous signal actions & reset signal mask */
48. if (sigaction(SIGINT, &saveintr, NULL) < 0)
49. return(-1);
50. if (sigaction(SIGQUIT, &savequit, NULL) < 0)
51. return(-1);
52. if (sigprocmask(SIG_SETMASK, &savemask, NULL) < 0) /* 恢复,不再block SIGCHLD 信号 */
53. return(-1);
54.
55. return(status);
}
来自system 的return value:
从system 返回的值,是 the shell 的终止状态,这不总是command string 的终止状态。
10.19 sleep, nanosleep, and clock_nanosleep Functions
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
Returns: 0 or number of unslept seconds
这个函数 引起调用进程 挂起(suspended) 直到
- 被seconds 指定的 时间 到了。
- 一个 signal 被 进程 捕获 并且 signal handler 返回了
第一种情况 return 值 是 0; 第二种情况: return 值 是 未sleep的秒数
FreeBSD 8.0 ,Linux 3.2.0, Mac OS X 10.6.8 实现 sleep ,通过使用 nanosleep 函数。
10.29
lib/sleep.c 可靠的sleep 实现
1.#include "apue.h"
2.
3.static void
4.sig_alrm(int signo)
5.{
6. /* nothing to do, just returning wakes up sigsuspend() */
7.}
8.
9.unsigned int
10.sleep(unsigned int seconds)
11.{
12. struct sigaction newact, oldact;
13. sigset_t newmask, oldmask, suspmask;
14. unsigned int unslept;
15.
16. /* set our handler, save previous information */
17. newact.sa_handler = sig_alrm;
18. sigemptyset(&newact.sa_mask);
19. newact.sa_flags = 0;
20. sigaction(SIGALRM, &newact, &oldact); /* 设置SIGALRM 信号的 handler */
21.
22. /* block SIGALRM and save current signal mask */
23. sigemptyset(&newmask);
24. sigaddset(&newmask, SIGALRM);
25. sigprocmask(SIG_BLOCK, &newmask, &oldmask);
26.
27. alarm(seconds);
28. suspmask = oldmask;
29.
30. /* make sure SIGALRM isn't blocked */
31. sigdelset(&suspmask, SIGALRM);
32.
33. /* wait for any signal to be caught */
34. sigsuspend(&suspmask);
35.
36. /* some signal has been caught, SIGALRM is now blocked */
37.
38. unslept = alarm(0);
39.
40. /* reset previous action */
41. sigaction(SIGALRM, &oldact, NULL);
42.
43. /* reset signal mask, which unblocks SIGALRM */
44. sigprocmask(SIG_SETMASK, &oldmask, NULL);
45. return(unslept);
}
#include <time.h>
int nanosleep(const struct timespec *reqtp, struct timespec *remtp);
Returns: 0 if slept for requested time or -1 on error
因为nanosleep 函数不涉及 任何信号的生成,我们可以使用它,不用担心和其他函数的交互。
随着 多系统clocks 的介绍(回想下6.10),我们需要一种方式 来挂起 调用线程(使用一个 相对于 特定clock 的 delay time )。clock_nanosleep 函数 提供给我们 这样的能力。
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flflags, const struct timespec *reqtp, struct timespec *remtp);
Returns: 0 if slept for requested time or error number on failure
clock_id 参数 指定了 要延迟的 clock .
flag 通常去控制 是否 延迟是 绝对的还是 相对的。TIMER_ABSTIME 。
然而 当使用 一个绝对时间,remtp 参数 是没用的,因为它不被需要;
一个relative sleep time 可能导致 sleep的比 期望的 更长的时间。
使用 绝对时间,可以提高 精度(precision)
10.20 sigqueue Function
大多数的unix system 不支持queue signals. 对于POSIX.1 的real-time extension ,一些系统开始支持 queueing signals . SUSv4. 已经把real-time extension 移到了base specification.
一般地, 一个信号 使用 一个bit 信息: signal它自己。另外,queueing signals , 这些扩张允许应用 在投递时 传递更多的信息。这些信息被 嵌入到 siginfo structure. 根据系统提供的信息,应用可以 传递 一个 integer 或 pointer (它 执行一个buffer,这个buffer 包含了更多的信息)。
为了使用 queued signal 我们不得不做如下的事:
- 指定 SA_SIGINFO flag ,当我们 使用sigaction 函数 install 一个 signal handler
- 提供一个 signal handler 给 sigaction structure的sa_sigaction 成员(而不是 sa_handler).
实现可能允许我们去使用 sa_handler字段,但是我们不能够 通过sigqueue 函数获取额外的信息。
3. 使用 sigqueue 函数 去 send signals
#include <signal.h>
int sigqueue(pid_t pid, int signo, const union sigval value)
Returns: 0 if OK, −1 on error
sigqueue 函数与kill 函数很相似。
value 参数 ,那么是 integer 要么是 一个指针(指向signal handler)
signals 不可能无限地来排队。回忆下: SIGQUEUE_MAX , 当限制达到了,sigqueue 可能失败,且 设置errno 为EAGAIN.
10.21 Job-Control Signals
六个作业控制信号
SIGCHLD child进程 已经stopped 或 terminated
SIGCONT 继续进程 (如果stopped)
SIGSTOP stop信号 不可能被捕获 或忽略
SIGTSTP 交互式 stop signal
SIGTTIN 后台进程组的成员 从控制终端 读
SIGTTOU 后台进程组的成员 写 到 控制终端
除了SIGCHLD, 大多数应用程序不会处理这些信号:交互式shells 通常做一些事情来处理他们。
一个例外 是 这个进程正管理着 终端-- 例如 vi(1) 编辑器。 它需要去知道 何时user 想去 挂起它 以致 它 可以 重新存储 终端的state
例子: 10.31 例子 演示了 当一个进程处理 job control 是 通常的 代码序列。
signals/sigtstp.c
10.31 如何处理 SIGTSTP
#include "apue.h"
#define BUFFSIZE 1024
static void
sig_tstp(int signo) /* signal handler for SIGTSTP */
{
sigset_t mask;
/* ... move cursor to lower left corner, reset tty mode ... */
printf("in sig_tstp\n");
/*
* Unblock SIGTSTP, since it's blocked while we're handling it.
*/
sigemptyset(&mask);
sigaddset(&mask, SIGTSTP);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
signal(SIGTSTP, SIG_DFL); /* reset disposition to default */
kill(getpid(), SIGTSTP); /* and send the signal to ourself */
/* we won't return from the kill until we're continued */
signal(SIGTSTP, sig_tstp); /* reestablish signal handler */
/* ... reset tty mode, redraw screen ... */
}
int
main(void)
{
int n;
char buf[BUFFSIZE];
/*
* Only catch SIGTSTP if we're running with a job-control shell.
*/
if (signal(SIGTSTP, SIG_IGN) == SIG_DFL)
signal(SIGTSTP, sig_tstp);
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < 0)
err_sys("read error");
exit(0);
}
/**
*
[xingqiji@bogon signals]$ ./sigtstp
^Zin sig_tstp
[1]+ 已停止 ./sigtstp
[xingqiji@bogon signals]$ ps aux | grep sigtstp
xingqiji 69899 0.0 0.0 4228 352 pts/1 T 17:33 0:00 ./sigtstp
xingqiji 70617 0.0 0.0 112832 988 pts/1 S+ 17:46 0:00 grep --color=auto sigtstp
[xingqiji@bogon signals]$ jobs
[1]+ 已停止 ./sigtstp
[xingqiji@bogon signals]$ fg 1
./sigtstp
nihao
nihao
^C
[xingqiji@bogon signals]$
[xingqiji@bogon signals]$ ./sigtstp
^Zin sig_tstp
[1]+ 已停止 ./sigtstp
[xingqiji@bogon signals]$ jobs
[1]+ 已停止 ./sigtstp
[xingqiji@bogon signals]$ fg 1
./sigtstp
nihaoya
nihaoya
^Zin sig_tstp
[1]+ 已停止 ./sigtstp
[xingqiji@bogon signals]$ jobs
*
*/
键入 suspend character (control + Z), 进程s 收到SIGTSTP 信号
当10.31的程序开始,
仅当signal的 disposition 是SIG_DFL时,才去安排着去捕获SIGTSTP signal 。这样写的原因是,当是不支持作业控制的shell 执行时,这个signal 的disposition 会被设置为SIG_IGN。
init 设置 3个信号(SIGTSTP, SIGTTIN, and SIGTTOU) 到 SIG_IGN。这种disposition 会被所有的login shells 继承。 仅 job-control shell 应该 重置这3个信号 为 SIG_DFL。
10.22 Signal Names and Numbers
在这个部分,我们描述 signal numbers 和 names 是如何map。
Linux3.2.0, 和 Mac OS X 10.6.8 提供了array :
extern char *sys_siglist[];
数组的索引是 signal number, 给一个指针,指向signal的字符串name
为了打印 signal number 对应的 字符串,可以使用 psignal 函数
#include <signal.h>
void psignal(int signo, const char *msg);
如果你有一个 来自 signal handler 的 siginfo structure ,你可以用 psiginfo 函数打印 signal 信息。这个函数类似perror(1.7部分)
#include <signal.h>
void psiginfo(const siginfo_t *info, const char *msg);
它的操作 和 psignal 函数很类似,尽管这个函数 访问了更多信息(不仅是signal number), 实际上不同的平台会有额外的信息被打印。
如果你仅需要 signal 的字符描述,且没必要去写到standard error( 你可能想写这些到一个log file) 你可以使用 strsignal 函数。这个函数 类似于 strerror
#include <string.h>
char *strsignal(int signo);
Returns: a pointer to a string describing the signal
这个函数类似strerror (1.7部分)