赞
踩
在 Linux 操作系统中,为了响应各种各样得事件,定义了非常多得信号。可以通过命令 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
信号的作用可以通过命令 man 7 signal
命令查看。
Signal Value Action Comment ────────────────────────────────────────────────────────────────────── SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process SIGINT 2 Term Interrupt from keyboard SIGQUIT 3 Core Quit from keyboard SIGILL 4 Core Illegal Instruction SIGABRT 6 Core Abort signal from abort(3) SIGFPE 8 Core Floating point exception SIGKILL 9 Term Kill signal SIGSEGV 11 Core Invalid memory reference SIGPIPE 13 Term Broken pipe: write to pipe with no readers SIGALRM 14 Term Timer signal from alarm(2) SIGTERM 15 Term Termination signal SIGUSR1 30,10,16 Term User-defined signal 1 SIGUSR2 31,12,17 Term User-defined signal 2 ……
一旦信号产生,就需要对信号进行处理,对信号的处理有三种方式:
执行默认的操作。Linux 对每种信号都规定了默认操作
捕捉信号。可以为信号定义一个信号处理函数。当信号发生时,就执行相应的信号处理函数。
忽略信号。不希望处理某些信号的时候,就可以忽略该信号。但有两个信号是无法捕捉和忽略的,即 SIGKILL 和 SEGSTOP, 它们用于在任何时候中断和结束某一进程。
信号处理最常见的流程分为两步:第一步是注册信号处理函数;第二步时发送信号。
如果不想某个信号执行默认操作,一种方法就是对特定的信号注册相应的信号处理函数,设置信号处理方式的是 signal 函数。
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
其实就是定义一个方法,将这个方法和某个信号关联起来。当这个进程遇到这个信号的时候,就执行这个方法。
注意,signal 不是系统调用,而是 glibc 封装的一个函数。这样就像 man signal 里面写的一样,不同的实现方式,设置的参数会不同,会导致行为的不同。
在 Linux 下面执行 man signal
的话,会发现 Linux 不建议直接使用这个方法,而是改用 sigaction。定义如下:
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
这两者的区别在哪里呢?其实它还是将信号和一个动作进行关联,只不过这个动作由一个结构 struct sigaction 表示了。
glibc 里面有个文件 syscalls.list。这里面定义了库函数调用哪些系统调用,在这里找到 sigaction。
sigaction - sigaction i:ipp __sigaction sigaction
接下来,在 glibc 中,__sigaction 会调用 __libc_sigaction,并最终调用的系统调用是 rt_sigaction。
在用户程序里面,有两个信号处理函数可以调用,一个是 signal, 一个是 sigaction, 推荐使用 sigaction。
用户程序调用的是 Glibc 里面的函数, signal 调用的是 __sysv_signal, 里面默认设置了一些参数,使得 signal 的功能收到了限制,sigaction 调用的是 __sigaction, 参数用户可以任意设定。
无论是 __sysv_signal 还是 __sigaction, 调用的都是统一的一个系统调用 rt_sigaction.
在内核中, rt_sigaction 调用的是 do_sigaction 设置信号处理函数。在每一个进程的 task_struct 里面,都有一个 sighand 指向 struct sighand_struct,里面是一个数组,下标是信号,里面的内容是信号处理函数。
有时候,在终端输入某些组合的时候,会给进程发送信号,例如, CTRL+C 产生 SIGINT 信号, CTRL+Z 产生 SIGTSTP 信号。
有的时候,硬件异常也会产生信号。比如,执行了除以 0 的指令,CPU 就会产生异常,然后把 SIGFPE 信号发送给进程。再如,进程访问了非法内存,内存管理模块就会产生异常,然后把信号 SIGSEGV 发送给进程。
中断和信号都可以由硬件产生,对于中断和信号还是要加以区别。中断要注册中断处理函数,但是中断处理函数是在内核驱动里面的,信号也是注册信号处理函数,信号处理函数是在用户态进程里面的。
我们可以通过 kill 或者 sigqueue 系统调用,发送给某个进程,也可以通过 tkill 或者 tgkill 发送信号给某个进程。虽然方式多种多样,但是最终都是调用了 do_send_sig_info 函数,将信号放在相应的 task_struct 的信号数据结构中。
kill->kill_something_info->kill_pid_info->group_send_sig_info->do_send_sig_info
tkill->do_tkill->do_send_specific->do_send_sig_info
tgkill->do_tkill->do_send_specific->do_send_sig_info
rt_sigqueueinfo->do_rt_sigqueueinfo->kill_proc_info->kill_pid_info->group_send_sig_info->do_send_sig_info
do_send_sig_info 会调用 send_signal, 进而调用 __send_signal。
参考文章:
信号:项目组A完成了,如何及时通知项目组B?
Linux 进程学习(四)------ sigaction 函数
Linux kill命令
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。