Signals
1. Introduction
电子计算机中, interrupt(中断, 也称为trap)会中断CPU正在执行的代码, 以便及时处理事件. 若interrupt被接受, CPU会暂停当前活动, 保存其状态, 并执行一个interrupt handler来处理该事件. interrupt通常比较短暂, 允许中断处理完毕后恢复原本的程序. Interrupt通常由硬件设备生成, 用于标识外部设备的状态变化.
Signal是软件层面的interrupt, 其是一种处理异步事件的方式, 大多数应用程序都需要处理signal. UNIX系统的早期版本就支持signal, 但并不可靠, signal会丢失, 且进程执行某些关键代码时无法屏蔽特定signal. 4.3BSD和SVR3对signal model做出了调整, 让其变得可靠. POSIX.1标准化了signal model.
假设存在两个进程A和B, A无法直接向B发送signal, 必须通过kernel转发. 之所以需要通过kernel转发, 是出于安全考虑. Kernel收到signal后会检查A是否有权限向B发送signal, 若通过检查, 则会在B的PCB(进程控制块)中的信号表设置该signal, 并向B发送中断请求, B进入内核态, Kernel会查看该signal对应的处理函数, 并跳回到用户态执行该函数. 以下是signal的一些特性:
- Signal可由异常(非法内存访问), 硬件(Ctrl-C), 或程序产生(
kill
) - Signal是异步的, signal的触发时间无法确定, 也无法确定产生何种signal
- Signal的处理不是即时的, 发送方发送signal时, 接收方不会立即收到
- Signal没有队列: 若某个进程同一时间收到多次同一类型的signal, 该进程只处理一次该signal
2. Signal Concepts
- 每个signal都有一个名字, 以SIG开头. 例如:
SIGABRT
: 进程调用abort()
时产生的abort signalSIGALRM
:alarm()
设置的timer响起产生的alarm signal
FreeBSD 8.0支持32种不同的signal. Mac OS X 10.6.8和Linux 3.2.0支持31种signal, 而Solaris 10支持40种signal. FreeBSD, Linux, 和Solaris支持应用程序自定义的signal.
- signal以正整数常数的形式定义在
<signal.h>
中- 实现上, 不同signal定义在不同header文件中, 但都包含在
<signal.h>
中 - kernel不应包含应用程序的header文件, 因此signal包含在kernel的header文件中, 再包含在用户级的header文件中:
<sys/signal.h>
: FreeBSD 8.0和Mac OS X 10.6.8<bits/signum.h>
: Linux 3.2.0<sys/iso/signal_iso.h>
: Solaris 10
- 实现上, 不同signal定义在不同header文件中, 但都包含在
- signal不能为0,
kill()
的参数0表示process group中的所有进程, POSIX.1将其命名为null signal - 以下是生成signal的几种方式:
- 当用户按下某些按键时, 会生成terminal-generated signal. 例如: DELETE或Control-C会生成
SIGINT
- 硬件异常生成的signal. 硬件检测到异常后会通知kernel, kernel生成相应的signal. 例如, 访问无效内存地址时生成
SIGSEGV
kill()
允许一个进程向另一个进程发送signal, 通常用于中止background process.- 软件层面检测到异常会生成signal, 例如:
SIGURG
: out-of-band数据通过网络连接成功传输时产生该signalSIGPIPE
: 进程向pipe写入数据, 但没有reader时生成该signalSIGALRM
: 进程设置的alamrm clock到时间时生成该signal
- 当用户按下某些按键时, 会生成terminal-generated signal. 例如: DELETE或Control-C会生成
Signal作为一种经典的异步事件, 随时都可能发生, 因此进程无法通过检查一个变量(如error
)来判断signal是否到来; 进程必须告诉kernel: 出现signal时应如何处理.
2.1 Signal Disposition
以下是处理signal的三种方式:
- 无视signal: 除了
SIGKILL
和SIGSTOP
, 其他signal均可被忽略或捕获- 上面两个signal为kernel和superuser提供了中止进程的方式, 因此无法被忽略
- 无视硬件异常产生的signal可能导致undefined behavior.
- 捕获signal: 为捕获signal, 程序必须传给kernel一个signal-catching function(signal捕获函数), 该函数用于处理某一signal的情况
- signal通常表示该进程之外突发的事件, 例如: 当进程接收到
SIGCHLD
, 说明其child process已中止, 因此signal-catching function中需调用waitpid()
获取child process的PID和termination status - 当进程创建临时文件时, 需在
SIGTERM
(kill()
发出的termination signal)的signal-catching function内清理临时文件
- signal通常表示该进程之外突发的事件, 例如: 当进程接收到
- 默认操作: 每个signal都有一个默认操作, 绝大多数signal的默认操作为中止进程
2.2 UNIX System Signals
以下是所有signal的详细信息:
Name | Description | Default Action |
---|---|---|
SIGABRT | abort() 生成, 进程异常退出 |
terminate+core |
SIGALRM | alarm() 或setitimer() 创建的timer超时后生成 |
terminate |
SIGBUS | 表示硬件错误, 通常由某些类型的内存故障生成 | terminate+core |
SIGCHLD | 进程暂停或中止时向parent process发送该signal, 若parent process需要得知child process的状态变化, 可在handler内调用wait() |
ignore |
SIGCONT | 让暂停的进程继续运行, 进程无法阻塞该signal | continue/ignore |
SIGFPE | 算数异常引发的signal, 例如: 除零活浮点溢出 | terminate+core |
SIGHUP | 与terminal中断时向session leader发送该signal. session leader进程终止时会向foreground process group中的所有进程发送该signal | terminate |
SIGEMT | hardware fault触发的signal, 视系统而定 | terminate+core |
SIGILL | 进程执行非法硬件指令时发送该signal | terminate+core |
SIGINFO | 当用户按下status key(通常为Control-T)时, terminal driver向foreground process group中的所有进程发送该signal, 通常用于获取foreground process group中所有进程的状态信息 | ignore |
SIGINT | 当用户按下interrupt key(通常为Control-C)时, terminal driver向foreground process group中的所有进程发送该signal, 用于中止失控的程序 | terminate |
SIGQUIT | 当用户按下terminal quit key(通常为Control-backslash)时, terminal driver向foreground process group中的所有进程发送该signal, 通常会中止foreground process group的所有进程, 并生成core文件 | terminate+core |
SIGIO | 异步I/O事件(例如file descriptor准备写入或输出) | terminate/ignore |
SIGIOT | 硬件故障 | terminate+core |
SIGKILL | kill进程, 无法被捕获或无视 | terminate |
SIGPIPE | 进程向pipe或FIFO写入数据, 但pipe的reader已中止 | terminate |
SIGPWR | 断电时uninterruptible power supply(UPS)提供电力, 或电量过低 | terminate/ignore |
SIGSEGV | 进程进行了无效内存引用 | terminate+core |
SIGSTOP | 用于暂停进程, 无法被捕获或无视 | stop process |
SIGSYS | 进程调用非法的system call | terminate+core |
SIGTERM | kill触发, 通知进程终止运行, 用于调用结束前的清理工作 | terminate |
SIGTRAP | hardware fault触发的signal, 视系统而定 | terminate+core |
SIGTSTP | 按下terminal suspend key(Control-Z)触发该signal, 用于停止进程运行 | stop process |
SIGTTIN | background process group中的进程尝试读取controlling terminal的数据 | stop process |
SIGTTOU | background process group中的进程尝试写入controlling terminal | stop process |
SIGURG | 表示发生紧急情况, 接收到out-of-band data时也生成该signal | ignore |
SIGUSR1 | 用户自定义的signal | terminate |
SIGUSR2 | 用户自定义的signal | terminate |
SIGVTALRM | setitimer() 创建的virtual interval timer超时 |
terminate |
SIGWINCH | 进程调用ioctl() 修改terminal的window size时, kernel向foreground process group中的所有进程发送该signal |
ignore |
SIGXCPU | 进程超过soft CPU time limit | terminate or terminate+core |
SIGXFSZ | 进程超过soft file size limit | terminate or terminate+core |
表格中terminate
表示中止进程; core表示在进程的工作目录中创建一个名为core的文件, 其中保存进程的内存映像, 该文件用于debugger检查进程被中止时的状态; stop表示暂停当前进程.
以下情况不会生成core文件:
- 程序设置了set-user-ID, 但当前用户不是程序所有者
- 程序设置了set-group-ID, 但当前用户不是程序所有者
- 用户无权写入当前目录
- core文件已存在, 且用户无权写入
- core体积过大
3. signal Function
typedef void (*sighandler_t)(int); |
3.1 Program Start-Up
启动程序时, 所有signal都是默认行为. 调用exec
时, 会有三种情况:
- 进程忽略某个signal,
exec
执行的程序仍忽略该signal - 进程捕获某个signal,
exec
执行的程序将其改为默认行为 - 进程采用默认行为处理signal,
exec
执行的程序仍采用默认行为
由于exec
不保存原程序的内存空间, 因此任何自定义的signal handler都会改为默认行为. 若shell不支持job control, 用户执行background process时(如cc main.c &
), shell会将interrupt signal和quit signal的行为设置为ignore, 这样用户按下interrupt character时, background process不会受到影响.signal()
存在一个限制: 在不修改当前signal handler的前提下, 无法得知该signal的handler, 因此引入了sigaction()
.
3.2 Process Creation
当进程调用fork()
时, child process会继承parent process的signal handler. 由于child process会复制parent process的内存映像, signal handler的地址也会复制到child process中.
4. Unreliable Signals
早期UNIX系统的signal不可靠: 生成signal, 但进程没有接收到, 其中一个问题在于: 生成signal后, 会将handler重置为默认动作, 因此handler被调用后需调用signal()
重新设置. 由于signal handler并不是一个原子操作, 因此会产生竞争:
int sig_int(); /* signal handler */ |
上述代码中, 调用sig_int()
时会立刻调用signal()
重新设置signal handler, 但存在一个特殊情况: 调用sig_int()
之后与调用signal()
之前存在一个时间间隙, 此时若生成SIGINT
, 会使用默认行为处理该signal, 而不是调用sig_int()
.
早期UNIX系统还存在一个问题: 进程没有一个atomic pause function. 以下面代码为例:
int sig_int(); /* my signal handling function */ |
进程调用pause()
进入睡眠状态, 捕获到SIGINT
时进程被唤醒, signal handler将sig_int_flag
设置为非零值, 因此不会再次陷入休眠. 但存在一种特殊情况: 进入while循环之后和调用pauae()之前存在一个时间间隙, 若此时接收到SIGINT
, 则sig_int_flag
变为非零值, 且调用pause()
后进程永远不会醒来.
5. Interrupted System Calls
早期UNIX系统中, 若进程被一个较慢的system call阻塞时收到signal, 该system call会被中断, 返回错误并将errno
设置为EINTR
.
5.1 Slow System Calls
system call分为两种: slow system call和fast system call. slow system call会一直阻塞进程, 直到完成操作, 如下:
- 读取文件但当前没有数据(pipes, terminal device, 和network device)
- 写入文件但数据不能立即被接受(buffer溢出或其他原因)
- 打开文件但需要满足某些条件(modem响应后才可打开terminal device)
- 调用
pause()
或wait()
- 部分
ioctl()
操作 - 部分IPC函数
Disk I/O并不算作slow system call, 虽然读取或写入一个磁盘文件会暂时阻塞进程, 但除非硬件出错, I/O操作会很快返回.
可被中断的system call存在一个问题: 我们必须显式处理返回的错误. 假设被中断时需要重新数据, 如下:
again: |
5.2 Automatic Restarts of Interrupted System Calls
4.2BSD引入了新机制: 自动重启被中断的system call. 以下是可自动重启的system call:
- 只在slow device上才能被signal中断的函数:
ioctl
read
readv
write
writev
- 总会被signal中断的函数:
wait
waitpid
有些应用程序不想在signal打断时自动重试, 4.3BSD允许进程关闭该特性. POSIX.1要求system call只有设置了SA_RESTART
时才会在signal中断时重试. 该flag可通过调用sigaction()
设置.
对于可被signal中断的system call, 不同系统上的实现各不相同: System V默认不会重试被中断的system call; BSD则默认重试被中断的system call. FreeBSD 8.0, Linux 3.2.0和Mac OS X 10.6.8只会重试部分被signal打断的system call, 这些signal必须调用signal()
注册了handler.
4.2BSD之所以选择自动重试system call, 因为有时用户并不清楚输入或输出设备是否为slow device. 以下是不同UNIX系统实现下的signal函数及其语义.
6. Reentrant Functions
进程收到signal后, 正在运行的指令会被signal handler中断. handler运行结束后会继续执行被暂停的命令, 与硬件中断类似.
在signal handler中, 我们无法得知当前进程执行到哪一步:
- 若进程在运行
malloc()
时收到signal, 并将执行权转移给signal handler, signal handler中再次调用malloc()
, 从而导致进程中的malloc()
维护的列表被中途修改, 进而导致无法预测的异常. - 进程正在执行某个函数, 如
getpwnam()
, 该函数会将运行结果保存在一个静态位置, 而signal handler内也会执行该函数, 从而导致进程的运行结果被signal handler覆盖.
The Single UNIX Specification规定了signal handler内可安全使用的函数, 这些函数都是reentrant(可重入的), 被称为async-signal safe. 以下是所有的reentrant functions:
大部分函数都没有囊括在内, 主要因为以下几点原因:
- 使用了static data structure
- 调用了
malloc()
或free()
- standard I/O library的一部分
使用上述函数时需主要几点:
- standard I/O library的大多数实现都会用到global data structure
- 即使在signal handler内使用安全函数, 也需要注意: 每个线程只有一个
errno
变量, 例如, signal handler内调用read()
可能会修改main()
的errno
值. 因此在signal handler内调用函数时要保存并之后恢复errno
longjmp
和siglongjmp
不是安全函数, 因为进程收到signal时可能正在更新某个data structure. 调用siglongjmp
会让进程执行其他操作, 而不是继续未完成的data structure.
7. SIFCLD Semantics
SIGCLD
和SIGCHLD
存在一些混淆: SIGCLD
源自System V, 与BSD的SIGCHLD
有不同的语义. POSIX.1也命名为SIGCHLD
.
当child process状态发生变化时, parent process会收到SIGCHLD
, parent process可通过调用wait()
获取child process的termination status.
System V处理SIGCLD
的方式则不同:
- SIG_DFL:
SIGCLD
的默认动作为无视该signal, 但也不会丢弃该signal, parent process可用wait()
来获取child parent的信息, 否则child process变为zombie. - SIG_IGN: 若将
SIGCLD
的handler设置为SIG_IGN
, 则child process的信息会被无视并丢弃.wait()
会阻塞parent process, 直到child process中止, 并将errno
设为ECHILD
, child process不会变为zombie. - 自定义处理方式: kernel会在
signal()
执行后立即检查是否存在child process状态改变.
8. Reliable-Signal Terminology and Semantics
以下是有关signal的一些名词定义:
- 发生某些事件时, 会向进程generate(生成)对应的signal, 以下是可触发signal的事件:
- 硬件故障
- 软件状况(alarm timer到时间)
- terminal-generated signal
- 调用
kill()
- 当需要对signal采取行动时, 会向进程deliver(发送)signal
- signal的generate和delivery之间的时间称为pending(待定)
- 进程可以选择block(阻塞)signal的delivery. 若signal被阻塞, 且signal的handler为默认行为或捕获signal, 则signal维持pending状态, 直到:
- 进程unblock(解除阻塞)signal
- 将signal handler改为
SIG_IGN
(忽略该signal)
- 只有delivery时系统才判断是否将signal阻塞, 而不是generate, 这么做允许进程在signal发送前修改signal的handler.
sigpending()
可可查看哪些signal被block或处于pending状态. - 若进程解除阻塞signal前, 已存在多个被阻塞signal, POSIX.1允许系统向该进程传递一次或多次signal. 若系统传递signal多次, 我们可以说signal被queue(排队). 绝大多数UNIX系统不会对signal排队, 只会传递一次signal.
- POSIX.1没有规定signal的传递顺序, 因此, 与进程当前状态相关的signal会比其他signal提前到达.
- 每个进程都有一个signal mask, 其决定了哪些signal可以被传递给当前进程, 哪些signal被阻塞. 每种signal占一个bit, 若bit为1, 表示该signal被阻塞. 进程可调用
sigprocmask()
来检查和修改signal mask. 由于signal数量超出integer的bit数, POSIX.1规定了一种新的数据类型:sigset_t
.
9. kill and raise Functions
/** |
若kill()
的参数signo
为0, kill()
不会发送任何signal, 但会检查其他错误, 通常用于检查目标进程是否存在; 若目标进程不存在, kill()
返回-1, 并将errno
设为ESRCH
.
10. alarm and pause Functions
/** |
- 每个进程的线程共享一个timer, 因此
alarm()
会覆盖当前正在运行的timer, 并返回之前timer剩余的时间 alaram()
和setitimer()
共享一个timer, 调用其中一个函数会影响另一个函数.execve()
会保留alarm()
生成的timer, 但fork()
不会保留sleep()
可能使用SIGALRM
, 因此不要混用sleep()
和alarm()
- 由于
SIGALRM
的默认中止进程, 因此大多数进程都会选择捕获该信号
10.1 sleep1 example
使用alarm()
和pause()
可实现一个简易sleep function
|
上述代码存在三个问题:
- 调用
alarm()
会替代进程设置的timer, 需保存alarm()
的返回值并依情况而定:- 若返回值小于
seconds
, 则需等待之前timer - 若返回值大于
seconds
, 则在sleep1()
返回前调用alarm()
完成接下来的倒计时
- 若返回值小于
sleep1()
调用signal()
修改SIGALRM
的handler, 需保存signal()
的返回值, 并在sleep1()
返回前恢复之前的handler- 第一次调用
alarm()
与pause()
之间存在竞争条件, 若调用pause()
之前进程收到signal, 进程会被一直挂起
10.2 sleep2 example
以下是修改过后的sleep function:
|
上述代码还存在一个隐患: 若SIGALRM
还存在其他handler, 处理完signal后调用longjmp()
不会还原进程设置的handler.alarm()
一般用于中断I/O操作, 如下:
int main(void) |
某些UNIX系统的read()
会自动启动, 则SIGALRM
无法打断read()
, 仍需调用longjmp()
11. Signal Sets
signal set是一种表示多个signal的数据类型, 用于sigprocmask()
阻塞某些signal. 以下是设置sigset_t
的函数:
/** |
以下是<signal.h>
的实现:
|
12. sigprocmask Function
/** |
13. sigpending Function
/** |
14. sigaction Function
struct sigaction { |
若sigaction()
的参数act
为NULL, 用于查询当前signal handler. 以下是sigaction struct
中sa_flags
的可选项:
Option | Description |
---|---|
SA_INTERRUPT | 不会重试被目标signal打断的system call |
SA_NOCLDSTOP | 若signum为SIGCHLD , child process停止或恢复运行时不产生SIGCHLD |
SA_NOCLDWAIT | 若signum为SIGCHLD , child process中止时不将其变为zombie |
SA_NODEFER | 进程执行目标signal的signal handler时, 不将该signal加入signal mask中 |
SA_ONSTACK | 若存在可用的alternative signal stack(sigaltstack() 生成), 则使用该stack的signal handler. |
SA_RESETHAND | 将目标signal的handler改为SIG_DFL (默认行为), 并清除signal handler的SA_SIGINFO flag |
SA_RESTART | 自动重试被目标signal打断的system call |
SA_SIGINFO | act 使用sa_sigaction , 而不是sa_handler |
通常signal handler的类型如下:
void handler(int signo); |
若sigaction struct中设置SA_SIGINFO
, 则signal handler的函数类型变为:
void handler(int signo, siginfo_t *info, void *context); |
siginfo
struct包含signal如何生成的原因. 所有遵循POSIX.1的UNIX系统都至少包含si_signo
和si_code
. 以下是siginfo
struct的定义:
struct siginfo { |
- 若handler的
signo
为SIGCHLD
, 将会设置si_pid
,si_status
, 和si_uid
- 若handler的
signo
为SIGBUS
,SIGILL
,SIGFPE
, 或SIGSEGV
, 将会在si_addr
设置出错的地址 si_errno
包含导致该signal的错误码
context
参数是一个无类型指针, 可转换为ucontext_t
struct, 在signal传递时标识上下文:
/* pointer to context resumed when this context returns */ |
14.1 Example: signal Function
使用sigaction()
可实现signal()
功能:
Sigfunc *signal(int signo, Sigfunc *func) |
- 必须使用
sigemptyset
初始化sa_mask
, 因为sa_mask = 0
不一定表示空白的signal mask - 上述代码为除
SIGALRM
之外的signal设置SA_RESTART
flag, 因此所有被signal终端的system call都会自动重试. - 部分UNIX系统定义了
SA_INTERRUPT
flag, 系统会自动重试被中断的system call, 只有设置该flag才能放弃重试.
14.2 Example: signal_intr Function
以下代码可防止被signal中断的system call重试:
Sigfunc *signal_intr(int signo, Sigfunc *func) |
15. sigsetjmp and siglongjmp Functions
longjmp()
通常用于从signal handler跳转至main()
, 但longjmp()
存在一个缺陷: 当进程收到signal时, 会开始执行handler, 并自动该signal加入signal mask, 以避免后续signal打断handler. 调用longjmp()
从signal handler跳出时, 由于POSIX.1没有定义setjmp()
和longjmp()
是否处理signal mask, 不同UNIX系统会有不同行为:
- FreeBSD 8.0, Mac OS X 10.6.8:
setjmp()
保存当前signal mask,longjmp()
恢复signal mask - Linux 3.2.0, Solaris 10:
setjmp()
和longjmp()
不保存和恢复signal mask - FreeBSD和Mac OS X提供了
_setjmp()
和_longjmp()
, 这两个函数不会保存和恢复signal mask
为统一行为, POSIX.1提供了专门用于signal handler内的跳转函数:
/** |
16. sigsuspend Function
之前我们讨论了如何block或unblock某个signal, 该方法可保证关键代码区不被signal中断, 但如果想要unblock某个signal, 如何收到之前被block的signal? 假设目标signal为SIGINT
, 以下为错误代码:
sigset_t newmask, oldmask; |
对于上述代码, 被阻塞的signal可能在调用sigprocmask(SIG_SETMASK, &oldmask, NULL)
后立即传递给进程, pause()
没有收到signal, 让进程一直陷入挂起状态. 因此, 我们需要一种方法, 将恢复signal mask和挂起进程变为一个原子操作:
/** |
- 由于进程不能阻塞
SIGKILL
和SIGSTOP
, 因此将这两个signal作为sigsuspend()
的参数没有意义 sigsuspend()
通常与sigprocmask()
一起使用, 用于防止某段代码被signal中断
16.1 Example of sigsuspend to protect a critial region
|
以下是代码的运行结果:
$ ./a.out |
16.2 Example of sigsuspend to wait for a signal handler to set a global variable
以下代码捕获interrupt signal和quit signal, 进程收到quit signal时才唤醒main()
:
|
以下是输出结果:
$ ./a.out |
16.3 Example of signals that synchronize a parent and child
以下代码通过signal同步parent process和child process:
|
17. abort Function
/** |
- ISO C没有规定
abort()
是否应flush buffered data, 是否关闭open stream, 或是否删除temporary files - POSIX.1允许
abort()
在进程终止前调用fclose()
关闭standard I/O streams.
以下是POSIX.1的abort()
实现:
|
18. system Function
- POSIX.1要求
system()
忽略SIGINT
和SIGQUIT
: 因为system()
的调用者并不是该函数的进程,system()
会调用fork()
, 其child process才是执行者. parent process和child process会一并收到SIGINT
或SIGQUIT
, 实际上只需要child process接收该signal. - POSIX.1要求
system()
阻塞SIGCHLD
: 避免parent process过早收到SIGCHLD
18.1 Example of system invoking ed editor
以下代码使用system()
启动ed editor, 会捕获interrupt signal和quit signal并输出:
|
以下为输出:
$ ./a.out |
- 中止editor时, kernel向parent process发送
SIGCHLD
, parent process收到该signal, 并执行signal handler. - POSIX.1要求
system()
期间阻塞SIGCHLD
.
再次运行上述代码, 这次向editor发送interrupt signal:
$ ./a.out |
下图为运行editor时的进程安排如下:
当用户按下interrupt key时, terminal driver向foreground process group中的所有进程发送SIGINT
, /bin/sh
忽略该signal, a.out
和/bin/ed
应收到该signal; 但由于a.out
正在执行system()
, 因此只有/bin/ed
收到SIGINT
和SIGQUIT
, a.out
只有在system()
运行结束后才能收到这两个signal.
18.2 Implementation of system with signal handling
以下代码使用signal handling实现system()
|
- 调用
system()
的进程在执行system()
期间不会收到interrupt或quit signal - 命令执行结束后,
system()
会调用sigprocmask
解除SIGCHLD
的阻塞
19. sleep, nanosleep and clock_nanosleep Functions
/** |
sleep()
会在以下两种情况停止睡眠:
- 已经过
seconds
参数指定的时间, 返回0 - 进程收到signal并执行signal handler, 返回剩余未睡眠的秒数
以下是POSIX.1中sleep()
的实现:
static void sig_alrm(int signo) |
上述代码中不会影响进程SIGALRM
的signal handler, 且没有用到longjmp()
或siglongjmp()
|
- POSIX.1规定,
nanosleep()
应使用CLOCK_REALTIME
测量时间 - 若
nanosleep()
被signal中断, 且执行signal handler, 则将剩余未睡眠时间写入rem
指向的结构体(若rem
不为NULL). - 若UNIX系统不支持nanosecond, 则四舍五入
req
.
20. sigqueue Function
大部分UNIX系统不支持对signal排队, 但还是存在部分系统支持该特性. 为实现queue signal, 需满足以下条件:
- 调用
sigaction()
设置signal handler时, 需指定SA_SIGINFO
; 若不指定该flag, 可能不会queue signal. sigaction
struct中使用sa_sigaction
, 而不是sa_handler
.
- 使用
sigqueue()
发送signal
union sigval { |
POSIX.1规定了SIGQUEUE_MAX
表示signal队列的长度上限.
21. Job Control Signals
POSIX.1规定以下6种signal作为job-control signal:
SIGCHLD
: child process停止运行或中止SIGCONT
: 若进程已被停止, 继续运行进程SIGSTOP
: 停止目标进程(无法被handler处理或无视)SIGTSTP
: interactive stop signalSIGTTIN
: background process group中的进程从controlling terminal读取数据SIGTTOU
: background process group中的进程向controlling terminal写入数据
除了SIGCHLD
, 大部分进程都不会接触上述signal, 因为shell负责处理剩下5个signal:
- 当用户按下suspend character时, terminal driver向foreground process group中的所有进程发送
SIGTSTP
- 当恢复某个job时, 会向指定process group中的所有进程发送
SIGCONT
- 当backgroup process group中的进程尝试从terminal读取或写入数据时, background process group中的所有进程都收到
SIGTTIN
或SIGTTOU
- job-control signals之间也存在交互: 当stop signals(SIGTSTP, SIGSTOP, SIGTTIN, SIGTTOU)中的某一个发送给进程, 被阻塞的
SIGCONT
会被直接丢弃; 当SIGCONT
发送至进程时, 所有被阻塞的stop signal也会被丢弃. - 当进程处于停止状态时,
SIGCONT
会让进程恢复运行; 但若进程处于运行状态,SIGCONT
会被忽略.
22. Signal Names and Numbers
/** |
上述两个函数会将signal的描述输出到stderr
, 若不想输出到stderr
, 可调用以下函数:
/** |
不同UNIX系统的strsignal()
输出不同: 若无法识别sig
:
- Solaris 10返回一个null pointer
- FreeBSD 8.0, Linux 3.2.0, Mac OS X 10.6.8返回一个string表示
sig
无法识别
除此之外Solaris还提供了一组函数用于signal number和signal name的互相转换:
int sig2str(int signo, char *str); |