在 Linux 操作系统中,为了响应各种各样的事件,也是定义了非常多的信号
# 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
这些信号作用:
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 ……
用户进程对信号的处理方式:
1.执行默认操作。Linux 对每种信号都规定了默认操作,例如,上面列表中的 Term,就是终止进程的意思。Core 的意思是 Core Dump,也即终止进程后,通过 Core Dump 将当前进程的运行状态保存在文件里面,方便程序员事后进行分析问题在哪里
2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数
3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SEGSTOP,它们用于在任何时候中断或结束某一进程
这个过程主要是分成两步,第一步是注册信号处理函数,第二步是发送信号
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction { __sighandler_t sa_handler; unsigned long sa_flags; __sigrestore_t sa_restorer; sigset_t sa_mask; /* mask last for extensibility */ };
注意:signal 不是系统调用,而是 glibc 封装的一个函数。这样就像 man signal 里面写的一样,不同的实现方式,设置的参数会不同,会导致行为的不同
# 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_ONESHOT | 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)
接下来,在 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; }
函数调用链: __libc_sigaction -> rt_sigaction -> do_sigaction 设置 sighand 里的信号处理函数
do_send_sig_info 会调用 send_signal,进而调用 __send_signal
1 SYSCALL_DEFINE2(kill, pid_t, pid, int, sig) 2 { 3 struct siginfo info; 4 5 info.si_signo = sig; 6 info.si_errno = 0; 7 info.si_code = SI_USER; 8 info.si_pid = task_tgid_vnr(current); 9 info.si_uid = from_kuid_munged(current_user_ns(), current_uid()); 10 11 return kill_something_info(sig, &info, pid); 12 } 13 14 15 static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, 16 int group, int from_ancestor_ns) 17 { 18 struct sigpending *pending; 19 struct sigqueue *q; 20 int override_rlimit; 21 int ret = 0, result; 22 ...... 23 pending = group ? &t->signal->shared_pending : &t->pending; 24 ...... 25 if (legacy_queue(pending, sig)) 26 goto ret; 27 28 if (sig < SIGRTMIN) 29 override_rlimit = (is_si_special(info) || info->si_code >= 0); 30 else 31 override_rlimit = 0; 32 33 q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE, 34 override_rlimit); 35 if (q) { 36 list_add_tail(&q->list, &pending->list); 37 switch ((unsigned long) info) { 38 case (unsigned long) SEND_SIG_NOINFO: 39 q->info.si_signo = sig; 40 q->info.si_errno = 0; 41 q->info.si_code = SI_USER; 42 q->info.si_pid = task_tgid_nr_ns(current, 43 task_active_pid_ns(t)); 44 q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid()); 45 break; 46 case (unsigned long) SEND_SIG_PRIV: 47 q->info.si_signo = sig; 48 q->info.si_errno = 0; 49 q->info.si_code = SI_KERNEL; 50 q->info.si_pid = 0; 51 q->info.si_uid = 0; 52 break; 53 default: 54 copy_siginfo(&q->info, info); 55 if (from_ancestor_ns) 56 q->info.si_pid = 0; 57 break; 58 } 59 60 userns_fixup_signal_uid(&q->info, t); 61 62 } 63 ...... 64 out_set: 65 signalfd_notify(t, sig); 66 sigaddset(&pending->signal, sig); 67 complete_signal(sig, t, group); 68 ret: 69 return ret; 70 }
在上面的代码里面,我们先是要决定应该用哪个 sigpending。这就要看我们发送的信号,是给进程的还是线程的。如果是 kill 发送的,也就是发送给整个进程的,就应该发送给 t->signal->shared_pending。这里面是整个进程所有线程共享的信号;如果是 tkill 发送的,也就是发给某个线程的,就应该发给 t->pending。这里面是这个线程的 task_struct 独享的。
struct sigpending 里面有两个成员,一个是一个集合 sigset_t,表示都收到了哪些信号,还有一个链表,也表示收到了哪些信号。它的结构如下:
struct sigpending { struct list_head list; sigset_t signal; };
当信号挂到了 task_struct 结构之后,最后我们需要调用 complete_signal。这里面的逻辑也很简单,就是说,既然这个进程有了一个新的信号,赶紧找一个线程处理一下吧
在找到了一个进程或者线程的 task_struct 之后,我们要调用 signal_wake_up,来企图唤醒它,signal_wake_up 会调用 signal_wake_up_state。
1 void signal_wake_up_state(struct task_struct *t, unsigned int state) 2 { 3 set_tsk_thread_flag(t, TIF_SIGPENDING); 4 5 6 if (!wake_up_state(t, state | TASK_INTERRUPTIBLE)) 7 kick_process(t); 8 }
signal_wake_up_state 里面主要做了两件事情。第一,就是给这个线程设置 TIF_SIGPENDING,这就说明其实信号的处理和进程的调度是采取这样一种类似的机制
signal_wake_up_state 的第二件事情,就是试图唤醒这个进程或者线程。wake_up_state 会调用 try_to_wake_up 方法
无论是从系统调用返回还是从中断返回,都会调用 exit_to_usermode_loop
1 static void exit_to_usermode_loop(struct pt_regs *regs, u32 cached_flags) 2 { 3 while (true) { 4 ...... 5 if (cached_flags & _TIF_NEED_RESCHED) 6 schedule(); 7 ...... 8 /* deal with pending signal delivery */ 9 if (cached_flags & _TIF_SIGPENDING) 10 do_signal(regs); 11 ...... 12 if (!(cached_flags & EXIT_TO_USERMODE_LOOP_FLAGS)) 13 break; 14 } 15 }
如果在前一个环节中,已经设置了 _TIF_SIGPENDING,我们就调用 do_signal 进行处理
1 void do_signal(struct pt_regs *regs) 2 { 3 struct ksignal ksig; 4 5 if (get_signal(&ksig)) { 6 /* Whee! Actually deliver the signal. */ 7 handle_signal(&ksig, regs); 8 return; 9 } 10 11 /* Did we come from a system call? */ 12 if (syscall_get_nr(current, regs) >= 0) { 13 /* Restart the system call - no handlers present */ 14 switch (syscall_get_error(current, regs)) { 15 case -ERESTARTNOHAND: 16 case -ERESTARTSYS: 17 case -ERESTARTNOINTR: 18 regs->ax = regs->orig_ax; 19 regs->ip -= 2; 20 break; 21 22 case -ERESTART_RESTARTBLOCK: 23 regs->ax = get_nr_restart_syscall(regs); 24 regs->ip -= 2; 25 break; 26 } 27 } 28 restore_saved_sigmask(); 29 }
do_signal 会调用 handle_signal。按说,信号处理就是调用用户提供的信号处理函数,但是这事儿没有看起来这么简单,因为信号处理函数是在用户态的。
1 static void 2 handle_signal(struct ksignal *ksig, struct pt_regs *regs) 3 { 4 bool stepping, failed; 5 ...... 6 /* Are we from a system call? */ 7 if (syscall_get_nr(current, regs) >= 0) { 8 /* If so, check system call restarting.. */ 9 switch (syscall_get_error(current, regs)) { 10 case -ERESTART_RESTARTBLOCK: 11 case -ERESTARTNOHAND: 12 regs->ax = -EINTR; 13 break; 14 case -ERESTARTSYS: 15 if (!(ksig->ka.sa.sa_flags & SA_RESTART)) { 16 regs->ax = -EINTR; 17 break; 18 } 19 /* fallthrough */ 20 case -ERESTARTNOINTR: 21 regs->ax = regs->orig_ax; 22 regs->ip -= 2; 23 break; 24 } 25 } 26 ...... 27 failed = (setup_rt_frame(ksig, regs) < 0); 28 ...... 29 signal_setup_done(failed, ksig, stepping); 30 }
我们就需要干预和自己来定制 pt_regs 了。这个时候,我们要看,是否从系统调用中返回。如果是从系统调用返回的话,还要区分我们是从系统调用中正常返回,还是在一个非运行状态的系统调用中,因为会被信号中断而返回
原文:https://www.cnblogs.com/mysky007/p/12335905.html