每种信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD、SIGINT等,它们在系统头文件<signal.h>中定义。
信号由内核(kernel)管理,产生方式多种多样:
可以由内核自身产生,比如出现硬件错误、内存读取错误,分母为0的除法等,内核需要通知相应进程。
也可以由其他进程产生并发送给内核,再由内核传递给目标进程。
信号传递的过程:
内核中针对每一个进程都有一个表来保存信号。
当内核需要将信号传递给某个进程时,就在该进程对应的表中写入信号,这样就生成了信号。
当该进程由用户态陷入内核态,再次切换到用户态之前,会查看表中的信号。如果有信号,进程就会首先执行信号对应的操作,此时叫做执行信号。
从生成信号到将信号传递给对应进程这段时间,信号处于等待状态。
我们可以编写代码,让进程阻塞(block)某些信号,也就是让这些信号始终处于等待的状态,直到进程取消阻塞(unblock)或者忽略信号。
信号种类
下表列出了一些常见信号:
信号名称 | 数字表示 | 说明 |
---|---|---|
SIGHUP | 1 | 终端挂起或控制进程终止。当用户退出Shell时,由该进程启动的所有进程都会收到这个信号,默认动作为终止进程。 |
SIGINT | 2 | 键盘中断。当用户按下<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。 |
SIGQUIT | 3 | 键盘退出键被按下。当用户按下<Ctrl+D>或<Ctrl+\>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为退出程序。 |
SIGFPE | 8 | 发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。 |
SIGKILL | 9 | 无条件终止进程。进程接收到该信号会立即终止,不进行清理和暂存工作。该信号不能被忽略、处理和阻塞,它向系统管理员提供了可以杀死任何进程的方法。 |
SIGALRM | 14 | 定时器超时,默认动作为终止进程。 |
SIGTERM | 15 | 程序结束信号,可以由 kill 命令产生。与SIGKILL不同的是,SIGTERM 信号可以被阻塞和终止,以便程序在退出前可以保存工作或清理临时文件等。 |
通过 kill -l 命令可以查看系统支持的所有信号:
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT
17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN
35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4
39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6
59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
上面仅是一个演示,不同的Linux发行版支持的信号可能不同。
每种信号都会有一个默认动作。默认动作就是脚本或程序接收到该信号所做出的默认操作。常见的默认动作有终止进程、退出程序、忽略信号、重启暂停的进程等,上表中也对部分默认动作进行了说明。
发送信号
有多种方式可以向程序或脚本发送信号,例如按下<Ctrl+C>组合键会发送SIGINT信号,终止当前进程。
还可以通过 kill 命令发送信号,语法为:
$ kill -signal pid
signal为要发送的信号,可以是信号名称或数字;pid为接收信号的进程ID。例如:
$ kill -1 1001
将SIGHUP信号发送给进程ID为1001的程序,程序会终止执行。
又如,强制杀死ID为1001的进程:
$ kill -9 1001
捕获信号
通常情况下,直接终止进程并不是我们所希望的。例如,按下<Ctrl+C>,进程被立即终止,不会清理创建的临时文件,带来系统垃圾,也不会保存正在进行的工作,导致需要重做。
可以通过编程来捕获这些信号,当终止信号出现时,可以先进行*和保存处理,再退出程序。
用户程序可以通过C/C++等代码捕获信号,这将在Linux C编程中进行讲解,这里仅介绍如果通过Linux命令捕获信号。
通过 trap 命令就可以捕获信号,语法为:
$ trap commands signals
commands为Linux系统命令或用户自定义命令;signals为要捕获的信号,可以为信号名称或数字。
捕获到信号后,可以有三种处理:
执行一段脚本来做一些处理工作,例如清理临时文件;
接受(恢复)信号的默认操作;
忽略当前信号。
1) 清理临时文件
脚本捕获到终止信号后一个常见的动作就是清理临时文件。例如:
$ trap "rm -f $WORKDIR/work1$$ $WORKDIR/dataout$$; exit" 2
当用户按下<Ctrl+C>后,脚本先清理临时文件 work1$$ 和 dataout$$ 再退出。
注意:exit 命令是必须的,否则脚本捕获到信号后会继续执行而不是退出。
修改上面的脚本,使接收到 SIGHUP 时进行同样的操作:
$ trap "rm $WORKDIR/work1$$ $WORKDIR/dataout$$; exit" 1 2
几点注意:
如果执行多个命令,需要将命令用引号包围;
只有脚本执行到 trap 命令时才会捕获信号;
再次接收到信号时还会执行同样的操作。
上面的脚本,执行到 trap 命令时就会替换 WORKDIR 和 $$ 的值。如果希望接收到 SIGHUP 或 SIGINT 信号时再替换其值,那么可以将命令放在单引号内,例如:
$ trap 'rm $WORKDIR/work1$$ $WORKDIR/dataout$$; exit' 1 2
2) 忽略信号
如果 trap 命令的 commands 为空,将会忽略接收到的信号,即不做任何处理,也不执行默认动作。例如:
$ trap '' 2
也可以同时忽略多个信号:
$ trap '' 1 2 3 15
注意:必须被引号包围,不能写成下面的形式:
$ trap 2
3) 恢复默认动作
如果希望改变信号的默认动作后再次恢复默认动作,那么省略 trap 命令的 commands 即可,例如:
$ trap 1 2
将恢复SIGHUP 和 SIGINT 信号的默认动作。