linux 信号处理

1 linux中的信号

Linux中的信号是用来通知进程发生了异步事件。在原理上,一个进程收到一个信号与一个处理器收到一个中断请可以看成一致的。信号是linux进程间通信机制中的唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也无法提前感知信号什么时候到达由谁发送。当然内核也会因为内部事件给进程发送信号。信号机制除了基本通知功能外,还可以传递附加信息。

常用的信号发送函数有raise kill等 常用的信号注册函数有signal、sigaction等。

2 源码分析

2.1 信号发送

此处拿raise函数分析

raise函数实际调用的是tgkill系统调用,tgkill系统调用实际上是调用的do_tkill函数

3289 static int do_tkill(pid_t tgid, pid_t pid, int sig)

3290 {

3291         struct siginfo info;

3292

3293         clear_siginfo(&info);

3294         info.si_signo = sig;

3295         info.si_errno = 0;

3296         info.si_code = SI_TKILL;

3297         info.si_pid = task_tgid_vnr(current);

3298         info.si_uid = from_kuid_munged(current_user_ns(), current_uid());

3299

3300         return do_send_specific(tgid, pid, sig, &info);

3301 }

上述代码的3293-3298行可以看到初始化了一个info数据结构,其中3294是保存sig信号值,3295是设置错误码,3296行设置是发送的此信号的途径,这里是由tkill发送,3297-3298行是设置pid和uid。3300行可以看到接着调用do_send_specifi。

do_send_specific(pid_t tgid, pid_t pid, int sig, struct siginfo *info)

3261 {

3262         struct task_struct *p;

3263         int error = -ESRCH;

3264

3265         rcu_read_lock();

3266         p = find_task_by_vpid(pid);

3267         if (p && (tgid <= 0 || task_tgid_vnr(p) == tgid)) {

3268                 error = check_kill_permission(sig, info, p);

3269                 /*

3270                  * The null signal is a permissions and process existence

3271                  * probe.  No signal is actually delivered.

3272                  */

3273                 if (!error && sig) {

3274                         error = do_send_sig_info(sig, info, p, false);

3275                         /*

3276                          * If lock_task_sighand() failed we pretend the task

3277                          * dies after receiving the signal. The window is tiny,

3278                          * and the signal is private anyway.

3279                          */

3280                         if (unlikely(error == -ESRCH))

3281                                 error = 0;

3282                 }

3283         }

3284         rcu_read_unlock();

3285

3286         return error;           

3287 }

上述代码3266行可以看到根据传入的pid获取目标进程的进程描述符p,如果3268行传入的参数允许则error肯定是0,且传入的sig肯定大于0(之前的函数会对传入参数做出检查),则3273条件在此次调用上下文肯定成立,则会执行3274行执行信号传递。

1163 int do_send_sig_info(int sig, struct siginfo *info, struct task_struct *p,

1164                         bool group)

1165 {

1166         unsigned long flags;

1167         int ret = -ESRCH;

1168

1169         if (lock_task_sighand(p, &flags)) {

1170                 ret = send_signal(sig, info, p, group);

1171                 unlock_task_sighand(p, &flags);

1172         }

1173

1174         return ret;

1175 }

上述代码可以看到do_send_sig_info函数调用的是send_signal函数,接着send_signal函数(这里的实现代码不展开了)会继续调用__send_signal函数,此处对__send_signal主要代码进行展示

1000 static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,

1001                         int group, int from_ancestor_ns)

...

1015         pending = group ? &t->signal->shared_pending : &t->pending;

...

1042         if (sig < SIGRTMIN)

1043                 override_rlimit = (is_si_special(info) || info->si_code >= 0);

1044         else

1045                 override_rlimit = 0;

1046

1047         q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);

1048         if (q) {

1049                 list_add_tail(&q->list, &pending->list);

1050                 switch ((unsigned long) info) {

1051                 case (unsigned long) SEND_SIG_NOINFO:

1052                         clear_siginfo(&q->info);

1053                         q->info.si_signo = sig;

1054                         q->info.si_errno = 0;

1055                         q->info.si_code = SI_USER;

1056                         q->info.si_pid = task_tgid_nr_ns(current,

1057                                                         task_active_pid_ns(t));

1058                         q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid());

1059                         break;

1060                 case (unsigned long) SEND_SIG_PRIV:

1061                         clear_siginfo(&q->info);

1062                         q->info.si_signo = sig;

1063                         q->info.si_errno = 0;

1064                         q->info.si_code = SI_KERNEL;

1065                         q->info.si_pid = 0;

1066                         q->info.si_uid = 0;

1067                         break;

1068                 default:

1069                         copy_siginfo(&q->info, info);

1070                         if (from_ancestor_ns)

1071                                 q->info.si_pid = 0;

1072                         break;

1073                 }

1074

1075                 userns_fixup_signal_uid(&q->info, t);

1076

1077         } else if (!is_si_special(info)) {

1078                 if (sig >= SIGRTMIN && info->si_code != SI_USER) {

1079                         /*

1080                          * Queue overflow, abort.  We may abort if the

1081                          * signal was rt and sent by user using something

1082                          * other than kill().

1083                          */

1084                         result = TRACE_SIGNAL_OVERFLOW_FAIL;

1085                         ret = -EAGAIN;

1086                         goto ret;

1087                 } else {

1088                         /*

1089                          * This is a silent loss of information.  We still

1090                          * send the signal, but the *info bits are lost.

1091                          */

1092                         result = TRACE_SIGNAL_LOSE_INFO;

1093                 }

1094         }

1095

1096 out_set:

1097         signalfd_notify(t, sig);

1098         sigaddset(&pending->signal, sig);

1099         complete_signal(sig, t, group);

1100 ret:

1101         trace_signal_generate(sig, info, t, group, result);

1102         return ret;

1103 }

对于上述代码此次调用上下文由于传入的group是false则1015行肯定是t->pending的地址也就是共享信号数据结构(在下一节信号响应可以看到共享与不共享的区别)。1047行分配一个sigqueue结构体,在此次调用上下文我们传入的info数据结构有效则结合1050-1073行代码可以确定执行到1068-1071行保存传入的info数据结构,在1098行则是调用sigaddset函数去设置对应的函数标志位记录有信号递送过来。1099行的函数整体complete_signal会调用signal_wake_up函数会间接调用 set_tsk_thread_flag去设置目标进程的TIF_SIGPENDING标志。其中对于非排队信号处理是在上述1022-1023行处理的,legacy_queue函数发现信号值是非排队信号以及pengding中已经存在了信号位则直接跳转到ret标签处执行返回。 函数执行流程图如下图。

linux 信号处理_第1张图片

 2.2 信号处理函数注册

先看下signal函数对应的系统调用如下

3889 SYSCALL_DEFINE2(signal, int, sig, __sighandler_t, handler)

3890 {

3891         struct k_sigaction new_sa, old_sa;

3892         int ret;

3893

3894         new_sa.sa.sa_handler = handler;

3895         new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;

3896         sigemptyset(&new_sa.sa.sa_mask);

3897

3898         ret = do_sigaction(sig, &new_sa, &old_sa);

3899

3900         return ret ? ret : (unsigned long)old_sa.sa.sa_handler;

3901 }

上述代码3839行则是定义两个sigaction变量new_sa\old_sa。3894-3895初始化了新定义的变量new_sa,将用户态的处理函数对应的虚拟地址保存在handler中,并且清空new_sa里sa.sa_mask。然后调用do_sigaction

3452 int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)

3453 {      

3454         struct task_struct *p = current, *t;

3455         struct k_sigaction *k;

3456         sigset_t mask;

3457

3458         if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))

3459                 return -EINVAL;

3460

3461         k = &p->sighand->action[sig-1];

3462

3463         spin_lock_irq(&p->sighand->siglock);

3464         if (oact)

3465                 *oact = *k;

3466

3467         sigaction_compat_abi(act, oact);

3468        

3469         if (act) {

3470                 sigdelsetmask(&act->sa.sa_mask,

3471                               sigmask(SIGKILL) | sigmask(SIGSTOP));

3472                 *k = *act;

3473                 /*

3474                  * POSIX 3.3.1.3:

3475                  *  "Setting a signal action to SIG_IGN for a signal that is

3476                  *   pending shall cause the pending signal to be discarded,

3477                  *   whether or not it is blocked."

3478                  *

3479                  *  "Setting a signal action to SIG_DFL for a signal that is

3480                  *   pending and whose default action is to ignore the signal

3481                  *   (for example, SIGCHLD), shall cause the pending signal to

3482                  *   be discarded, whether or not it is blocked"

3483                  */

3484                 if (sig_handler_ignored(sig_handler(p, sig), sig)) {

3485                         sigemptyset(&mask);

3486                         sigaddset(&mask, sig);

3487                         flush_sigqueue_mask(&mask, &p->signal->shared_pending);

3488                         for_each_thread(p, t)

3489                                 flush_sigqueue_mask(&mask, &t->pending);

3490                 }

3491         }

3492

3493         spin_unlock_irq(&p->sighand->siglock);

3494         return 0;

3495 }

上述代码3454-3460行是变量定义以及参数检查,第3461行是获取对应的k_sigaction保存handler的内存地址,第3464-3465将旧的k_sigaction保存到传入参数oact中,第3469行判断传入的act非空,则进入if语句,3470行则将新的act中的sigmask里的SIGKILL和SIGKILL删除,3472行将新传入的act保存到p->sighand->action[sig-1]变量中。3484-3490行对SIG_IGN和SIG_DFL情况做处理这里不展开说明,感兴趣可以去看看源码’kernel/signal.c’。

函数流程如下图

linux 信号处理_第2张图片

 2.3 信号响应

信号处理是在退出异常处理程序或者中断处理程序返回用户态之前会去检查是否存在信号需要处理,可以再ret_from_exception汇编代码段看到调用prepare_exit_to_usermode函数后续代码我就不贴了可以结合下面流程图看代码:

linux 信号处理_第3张图片

在信号执行过程中内核态和用户态之间转换图如下:

linux 信号处理_第4张图片

你可能感兴趣的:(linux,信号处理,内核,linux)