Linux进程间通讯
Linux进程间通讯(一)信号(上)
一、信号概述
在Linux中,当遇到某些紧急的情况,可以给进程发送信号,紧急处理一些事情
在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
……
进程对相应的信号都有默认的处理,当然我们也可以自定义某个信号的处理函数
一旦有信号产生,用户进程有以下几种处理方式
- 执行默认方式:进程对每种信号都有默认的处理方式,例如上面的 Term 表示终止进程;Core 表示终止进程后,通过 Core Dump 将当前进程的运行状态保存到文件中,可以给程序员分析
- 捕抓信号:我们可以自定义信号的处理函数,当进程接收到相应的信号时,会调用我们定义的信号处理函数
- 忽略信号:当我们不希望处理某些信号的时候,可以选择忽略信号,不做任何处理。有两个信号是进程无法忽略的,SIGKILL 和 SEGSTOP,它们作用是结束或中断进程
信号的处理流程主要有两步,一个是注册信号,一个是发送信号,这篇文章先讲解注册信号,下篇文章再讲解发送信号
二、注册信号
如果我们不想让某一个信号执行默认的处理操作,我们可以通过 signal 函数来进程注册,其定义如下
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
- signum:表示处理的信号,这个对应 kill -l 命令得到的列表中的信号编号
- handler:自定义信号处理函数
此外,还可以通过 sigaction 来注册信号
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
- signum:信号编号
- act:信号的处理动作
- oldact:返回旧的处理动作
这两种方式有什么区别呢?
其实是类似的,signal 直接将信号和一个处理函数相关联,sigaction 将信号和一个动作相关联,这个动作由 struct sigaction 表示,定义如下
struct sigaction {
__sighandler_t sa_handler;
unsigned long sa_flags;
__sigrestore_t sa_restorer;
sigset_t sa_mask; /* mask last for extensibility */
};
和 signal 类似,这里面包含有 sa_handler 信号处理函数,其它变量是让你可以更加精细的控制信号的处理行为(例如 sa_flags 可以设置一些标志来控制信号处理的行为;sa_mask 可以设置在信号处理的过程中屏蔽某些中断)
而 signal 却无法设置这些,signal 不是一个系统调用,而是 glibc 封装的一个函数,其定义如下
# define signal __sysv_signal
__sighandler_t
__sysv_signal (int sig, __sighandler_t handler)
{
struct sigaction act, oact;
......
act.sa_handler = handler;
__sigemptyset (&act.sa_mask);
act.sa_flags = SA_NOMASK | SA_INTERRUPT;
act.sa_flags &= ~SA_RESTART;
if (__sigaction (sig, &act, &oact) < 0)
return SIG_ERR;
return oact.sa_handler;
}
weak_alias (__sysv_signal, sysv_signal)
可以看到,signal 其实也是定义一个动作 struct sigaction 来和信号关联,只不过这个动作的一部分参数被默认设置了
在 sa_flags 设置了 SA_NOMASK、SA_INTERRUPT,清除了 SA_RESTART
- SA_NOMASK:这个表示表示不屏蔽信号,对应上面调用的 __sigemptyset (&act.sa_mask),将 sa_mask 设置为空。也就是,在中断处理函数中,允许处理其它的信号
- SA_INTERRUPT:表示在信号到来的时候,如果当前进程正在进程系统调用,可以中断系统调用
- SA_RESTART:如果设置了此标志,表示如果系统调用被中断后,在处理完信号后,会自动重新调用系统调用。如果将该标志清空,那么系统调用会直接返回 -EINTR
在设置完动作后,会调用 __sigaction,这个我们后面再继续分析
接下来看 sigaction 做了什么
glibc 里面有一个文件 syscall.list。这里定义了库函数对应的系统调用,在这里面找到 sigaction 函数
sigaction - sigaction i:ipp __sigaction sigaction
它对应的系统调用是 __sigaction,发现没有,signal 函数里面调用的也是 __sigaction
所以 signal 和 signaction 是一样的,只不过 signal 多了一层封装,设置了 struct sigaction 的一些默认参数,而 sigaction 更加底层,所以也更加灵活
接下来我们分析 __sigaction,在 glibc 中,__sigaction 会调用 __libc_sigaction,并最终调用的系统调用是 rt_sigaction,其定义如下
int
__sigaction (int sig, const struct sigaction *act, struct sigaction *oact)
{
......
return __libc_sigaction (sig, act, oact);
}
int
__libc_sigaction (int sig, const struct sigaction *act, struct sigaction *oact)
{
int result;
struct kernel_sigaction kact, koact;
if (act)
{
kact.k_sa_handler = act->sa_handler;
memcpy (&kact.sa_mask, &act->sa_mask, sizeof (sigset_t));
kact.sa_flags = act->sa_flags | SA_RESTORER;
kact.sa_restorer = &restore_rt;
}
result = INLINE_SYSCALL (rt_sigaction, 4,
sig, act ? &kact : NULL,
oact ? &koact : NULL, _NSIG / 8);
if (oact && result >= 0)
{
oact->sa_handler = koact.k_sa_handler;
memcpy (&oact->sa_mask, &koact.sa_mask, sizeof (sigset_t));
oact->sa_flags = koact.sa_flags;
oact->sa_restorer = koact.sa_restorer;
}
return result;
}
真正的系统调用 rt_sigaction 定义如下啊
SYSCALL_DEFINE4(rt_sigaction, int, sig,
const struct sigaction __user *, act,
struct sigaction __user *, oact,
size_t, sigsetsize)
{
struct k_sigaction new_sa, old_sa;
int ret = -EINVAL;
......
if (act) {
if (copy_from_user(&new_sa.sa, act, sizeof(new_sa.sa)))
return -EFAULT;
}
ret = do_sigaction(sig, act ? &new_sa : NULL, oact ? &old_sa : NULL);
if (!ret && oact) {
if (copy_to_user(oact, &old_sa.sa, sizeof(old_sa.sa)))
return -EFAULT;
}
out:
return ret;
}
rt_sigaction 函数中,将用户空间的 sigaction 拷贝到内核空间,然后调用 do_sigaction
do_sigaction 的定义如下
int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
{
struct task_struct *p = current, *t;
struct k_sigaction *k;
sigset_t mask;
......
k = &p->sighand->action[sig-1];
spin_lock_irq(&p->sighand->siglock);
if (oact)
*oact = *k;
if (act) {
sigdelsetmask(&act->sa.sa_mask,
sigmask(SIGKILL) | sigmask(SIGSTOP));
*k = *act;
......
}
spin_unlock_irq(&p->sighand->siglock);
return 0;
}
首先要知道,每个进程的 task_struct 中,都有一个 struct sighand_struct,它里面有一个 struct sigaction 数组,每一项表示一个信号对应的动作,数组的下标就是对应信号的编号
do_sigaction 就是找到信号对应的 struct sigaction,然后设置它
到这里,信号的注册就已经完成了
三、总结
- 在用户程序中,可以调用两个函数注册信号的处理函数,分别为 signal 和 sigaction
- signal 和 sigaction 最终调用的系统调用都是 rt_sigaction,signal 可以看作比 sigaction 多了一层封装,它用起来不如 sigaction 灵活
- 每个进程的 task_struct 中都有一个 struct sighand_struct,它里面有一个 struct sigaction 数组,每一项对应一个信号的处理动作,下标对应着信号的编号。rt_sigaction 最终会把用户程序设置的 struct sigaction 拷贝到 struct sigaction 数组中对应的项