linux中脚本扑捉(trap)信号问题
扑捉ctrl+c信号:
1 #!/bin/bash 2 trap "trap" 2; 3 function trap() 4 { 5 echo "You press Ctrl+C."; 6 echo "Exiting,please wait..."; 7 exit; 8 } 9 sleep 20s;
按下ctrl+c:
1 root@ubuntu:/home/dyx/linux 16:13:10 107# bash ex_trap.sh 2 ^CYou press Ctrl+C. 3 Exiting,please wait...
但是如果把trap函数发在前面就不可以了:
1 root@ubuntu:/home/dyx/linux 16:13:58 109# bash ex_trap.sh 2 You press Ctrl+C. 3 Exiting,please wait...
直接出现函数的内容了。
为什么????
一、trap捕捉到信号之后,可以有三种反应方式:
(1)执行一段程序来处理这一信号
(2)接受信号的默认操作
(3)忽视这一信号
二、trap对上面三种方式提供了三种基本形式:
第一种形式的trap命令在shell接收到signal list清单中数值相同的信号时,将执行双
引号中的命令串。
trap 'commands' signal-list
trap "commands" signal-list
为了恢复信号的默认操作,使用第二种形式的trap命令:
trap signal-list
第三种形式的trap命令允许忽视信号
trap " " signal-list
三、注意:
(1) 对信号11(段违例)不能捕捉,因为shell本身需要捕捉该信号去进行内存的转储。
(2) 在trap中可以定义对信号0的处理(实际上没有这个信号), shell程序在其终止(如
执行exit语句)时发出该信号。
(3) 在捕捉到signal-list中指定的信号并执行完相应的命令之后, 如果这些命令没有
将shell程序终止的话,shell程序将继续执行收到信号时所执行的命令后面的命令,这样将
很容易导致shell程序无法终止。
另外,在trap语句中,单引号和双引号是不同的,当shell程序第一次碰到trap语句时,将
把commands中的命令扫描一遍。此时若commands是用单引号括起来的话,那么shell不会
对commands中的变量和命令进行替换, 否则commands中的变量和命令将用当时具体的值来替
换。
四、例子:忽略Ctrl+C:
在有些情况下,我们不希望自己的shell脚本在运行时刻被中断,比如说我们写得shell脚
本设为某一用户的默认shell,使这一用户进入系统后只能作某一项工作,如数据库备份,
我们可不希望用户使用ctrl+C之类便进入到shell状态,做我们不希望做的事情。这便用到
了信号处理。
kill -l可以列出系统的信号名称,如下:
-bash-3.00# 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 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
通常我们需要忽略的信号有四个,即:HUP, INT, QUIT, TSTP,也就是信号1, 2, 3, 24
使用这样的语句可以使这些中断信号被忽略:
trap "" 1 2 3 24 或 trap "" HUP INT QUIT TSTP
用 trap :1 2 3 24 或 trap HUP INT QUIT TSTP使其回复默认值。
用stty -a可以列出中断信号与键盘的对应,分别执行上面的命令后,运行
tail -f /etc/passwd, 然后尝试用键盘中断,试试两种情况(默认和忽略)下有何不同。
更方便的是我们可以用在shell中用trap定义我们自己的信号处理程序,就象在c中用
signal一样,
如:trap “echo 'GO Away'” INT
信号是传送给进程的一种事件通知,生成信号的事件有三大类:
1. 程序错误:除零,非法内存访问…
2. 外部信号:终端Ctrl-C产生SGINT信号,定时器到期产生SIGALRM…
3. 显示请求:kill函数允许进程发送任何信号给其他进程或进程组。
信号生成既可以是同步的(信号与程序中的某个具体操作相关并在那个操作同时产生),也可以是异步的。通常程序错误生成信号为同步的,进程显式请求给自己的信号也是同步的。
外部事件总是异步的,来自其他进程的显示请求也是异步的。
信号发生时,我们可以告诉unix内核采取下面三种动作中的任一种:
1. 忽略信号:大部分信号可被忽略,除SIGSTOP和SIGKILL信号外(这是超级用户杀掉或停掉任意进程的手段)。
2. 捕获信号:注册信号处理函数,它对产生的特定信号做处理。
3. 让信号默认动作起作用:unix内核定义的默认动作,有5种情况:
a) 流产abort:终止进程并产生core文件。
b) 终止stop:终止进程但不生成core文件。
c) 忽略:忽略信号。
d) 挂起suspend:挂起进程。
e) 继续continue:若进程是挂起的,则resume进程,否则忽略此信号。
任意时刻,进程可以为信号指定动作。
信号处理涉及两个过程,生成与交付。
信号生成出现在事件发生时,此时内核检查接收进程的相关数据结构,此结构中记录了信号的布局,悬挂信号集和处理动作。如果信号是要被忽 略的,内核不做任何动作就返回。否则,将此信号加入悬挂信号集合中。(悬挂信号集合通常用位串表示,每位对应一个信号,内核无法记录同一信号的多个实 例)。
如果进程处于可中断的睡眠状态,并且该信号非阻塞,内核唤醒进 程。被唤醒进程一旦运行则在返回用户态前优先处理悬挂信号,当有悬挂信号并且非阻塞时,内核查看是否有处理句柄,如果没有注册句柄,则采取默认动作(通常 为终止进程)。如果有句柄,则将此信号加入阻塞信号屏蔽中。
最后内核安排进程返回到用户态并执行信号句柄,同时保证句柄执行完时,进程从被中断处代码执行。
由异步事件产生的信号可能在任一条指令后发生,当信号句柄完成时,进程从中断之处起执行。如果信号是在进程处于系统调用期间到达的,内核通常abort此系统调用并返回错误码EINTR。
进程可以有选择的阻塞信号交付,当一个被阻塞的信号生成时,如果进程指定的动作为默认或者捕获,则此信号一直悬挂于该进程直到对此信号的阻塞放开,或者信号动作改为忽略。 系统对阻塞信号的判定是在信号交付时而非生成时,这样可以允许进程在信号被交付前改变信号动作。
每个进程有一个阻塞信号屏蔽,它定义当前被阻塞交付的那些信号。可认为它是一个位串,每位对应一个信号。如果某信号对应的位被设置,则该信号当前阻塞,进程可调用
sigprocmask函数来检查或设置屏蔽。
程序错误类信号:默认动作使进程流产,产生core文件。
SIGABRT: 调用abort函数生成的信号。
SIGFPE: 浮点计算错误。
SIGILL: 非法指令错误。
SIGBUS/SIGSEGV: 硬件错误-非法地址访问。
SIGEMT: 硬件错误
SIGSYS: 非法系统调用。
SIGTRAP: 硬件错误(通常为断点指令)。
程序终止类信号:默认动作使进程终止,我们通常要处理这类信号,做一些清理工作,句柄函数应在结束时为此信号指定默认动作,然后再次生成该信号,使得程序终止。
SIGHUP:终端断开连接时,生成此信号给控制进程。
SIGINT:Ctrl-C或Delete按下时,由终端驱动生成,并发送给前台进程组中的所有进程。
SIGKILL:使程序立即终止,不能被捕获或忽略,也不能被阻塞。
SIGQUIT:Ctrl-\,如SIGINT,并且产生core。
SIGTERM:该信号使程序终止,但是可以阻塞、捕获、忽略。
闹钟类信号:通知定时器到期,默认动作是终止程序,但通常会设置句柄。
SIGALRM:alarm/setitimer函数设置定时到期后,会产生此信号。
SIGPROF:
SIGVTALRM:
I/O类信号:通知进程在描述字上发生了感兴趣事件,支持信号驱动IO。
SIGIO: fd准备执行输入输出时发送此信号。
SIGPOLL:异步I/O信号。
SIGURG:网络收到带外数据时可选择生成此信号。
作业控制类信号:
SIGCHLD: 进程终止或停止时会向其父进程发送该信号,默认动作为忽略。
SIGCONT: 使停止的进程恢复运行。
SIGSTOP: 停止进程。
SIGTSTP/SIGTTIN/SIGTTOU:
操作错误类信号:默认动作终止程序。
SIGPIPE: 管道破裂。
SIGXCPU/SIGXFSZ:
signal函数:
void (* signal(int sig, void (*func)(int)))(int);
sig指明是哪一种信号。
func指明动作:SIG_DFL, SIG_IGN,或者信号句柄地址。
当信号发生时,如果func指向信号句柄,系统在将控制转往句柄前,先将该信号动作置为DFL,或者阻塞该信号直到句柄完成。
Signal函数返回值指向前一次有效动作指针:SIG_DFL,SIG_IGN,或信号地址,这提供了恢复信号动作的机制。 如果signal调用出错,返回SIG_ERR并设置errno。
进程初启时的信号动作:
fork:继承父进程的动作
exec:所有信号动作要么是忽略要么是默认。
不可靠信号:
早期版本Unix中使用signal,每当信号交付时,其动作总是由系统重置为默认动作,因此为了使信号句柄执行期间,仍能对同一信号后续做反应,需要再次调用signal。
Catch_Signal(){
// 如果第二次信号刚好在此时发生,将导致进程终止core掉。
signal(SIGQUIT,Catch_Signal);
}
Main(){
signal(SIGQUIT, Catch_Signal);
…
}
使用signal的另一个问题是,对于信号,进程要么忽略,要么捕获,无法在一段时间内阻塞信号(推迟信号的交付),为了克服signal兼容性问题,现代unix均实现了POSIX定义的sigaction函数,该函数采用一个sigaction结构,除定义信号交付时要采取的动作外,还包含其他一些动作控制信息。
sigaction还允许调用进程检测或指定与特定信号相关的动作。
int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);
sig指定信号-除SIGKILL和SIGSTOP。
如果act为NULL,则不改变信号动作,只查询当前动作。
成功返回0,失败则不安装新信号动作,返回-1,设置errno。
struct sigaction{
void (*sa_handler)(int); // 同signal的第二参数func。
void (*sa_sigaction)(int, siginfo_t*, void*); // 仅当flags设置SA_SIGINFO起作用。
sigset_t sa_mask; // 指明信号执行期间要阻塞的一组信号,除此之外导致信号句柄执
行的信号也自动阻塞,除非指定了SA_NODEFER。信号句柄正常返回时,屏蔽恢复到原先状态。
int sa_flags; //
};
sa_flags是一个位串,可以通过或运算生成。可设置下列标志:
SA_NOCLDSTOP: 只对CHLD信号起作用,子进程暂停时不发信号给父进程。
SA_RESTART: 信号句柄返回时,自动恢复被该信号中断的系统调用。否则该系统调用将中断返回-1,并设置errno为EINTR。
SA_ONSTACK:
SA_RESETHAND:信号句柄入口,系统将重置信号动作为SIG_DFL.
SA_NODEFER:句柄执行期间,不自动阻塞该信号。
SA_NOCLDWAIT:只对CHLD起作用,调用进程的所有子进程在终止时不会成为Zombe。这种情况下,父进程无需要wait子进程,并且子进程终止也不向父进程发SIGCHLD信号。
如果父进程调用wait,将阻塞到所有子进程终止,并返回-1,errno设为ECHILD.
SA_SIGINFO:如果未设置此标志,则信号句柄原型为:
void func(int signo);
如果设置此标志,则句柄原型为:
void func(int signo, singinfo_t* info, void* context);
Info- 解释信号生成的原因。
Context-信号被交付时所中断进程的上下文。
一旦用sigaction为特定信号建立了动作,该动作就一直保持,直到另一次调用sigaction,或者调用exec,或者因设置了SA_RESETHAND导致系统自动改变动作为默认为止。
除了外部中断产生信号外,程序可以显式的调用raise函数给他自己发送信号,或调用kill向自己或其他进程发送信号。
阻塞信号意味着保持该信号并推迟它的交付,可以防止程序中的关键代码被信号中断。
信号集操作:
int sigemptyset(sigset_t* set); // 清空信号集
int sigfillset(sigset_t* set); // 包含所有信号集
sigaddset/sigdelset/sigismember;
sigprocmask用来检测或改变调用进程的信号屏蔽。
int sigprocmask(int how, const sigset_t* set, sigset_t* oset);
How: SIGBLOCK SIG_UNBLOCK SIG_SETMASK
如果调用sigprocmask放开某个信号而导致任何悬挂信号被解除阻塞,则函数返回前,这些信号中至少有一个被交付。
检查悬挂信号:int sigpending(sigset_t* set);
等待信号:int pause(void);
悬挂调用进程直到有一个信号到达。仅当句柄执行并返回时,pause函数才返回:此时返回-1,并设置errno为EINTR。所有其他情况下pause不返回。
如果多个相同信号在信号句柄运行前发给了进程,则句柄只被运行一次。换句话说,默认情况下unix信号是非排队的,只有当实现支持实时信号并且sa_flags设置SA_SIGINFO时,由sigqueue生成的后续信号才排队。
I/O执行期间,有可能到达信号,此时有两种情况:重新开始系统调用还是返回失败.
早期unix特征为,进程执行慢系统调用期间捕获信号时,该调用被中断并设置errno为EINTR。现代unix增加了sa_flags选项SA_RESTART可对单个信号要求自动恢复被中断的系统调用。
原则如下:如果进程阻塞于慢系统调用,并且进程捕获信号且该信号句柄返回,系统调用可能返回EINTR。 虽然有些unix能自动恢复系统调用,但是为了兼容性,我们必须准备慢系统调用返回EINTR,当检测到EINTR,要么重新开始系统调用,要么做其他处理。
Again:
if (n=read(fd,buff, BUFSIZE) < 0) {
if (errno ==EINTR)
goto Again;
else
…}
参考:http://blog.chinaunix.net/uid-20648182-id-1907477.html
SIGINT 终止进程 中断进程
SIGQUIT 建立CORE文件 终止进程,并且生成core文件
SIGILL 建立CORE文件 非法指令
SIGTRAP 建立CORE文件 跟踪自陷
SIGBUS 建立CORE文件 总线错误
SIGSEGV 建立CORE文件 段非法错误
SIGFPE 建立CORE文件 浮点异常
SIGIOT 建立CORE文件 执行I/O自陷
SIGKILL 终止进程 杀死进程
SIGPIPE 终止进程 向一个没有读进程的管道写数据
SIGALARM 终止进程 计时器到时
SIGTERM 终止进程 软件终止信号
SIGSTOP 停止进程 非终端来的停止信号
SIGTSTP 停止进程 终端来的停止信号
SIGCONT 忽略信号 继续执行一个停止的进程
SIGURG 忽略信号 I/O紧急信号
SIGIO 忽略信号 描述符上可以进行I/O
SIGCHLD 忽略信号 当子进程停止或退出时通知父进程
SIGTTOU 停止进程 后台进程写终端
SIGTTIN 停止进程 后台进程读终端
SIGXGPU 终止进程 CPU时限超时
SIGXFSZ 终止进程 文件长度过长
SIGWINCH 忽略信号 窗口大小发生变化
SIGPROF 终止进程 统计分布图用计时器到时
SIGUSR1 终止进程 用户定义信号1
SIGUSR2 终止进程 用户定义信号2
SIGVTALRM 终止进程 虚拟计时器到时