Welcome 微信登录
编程资源 图片资源库 蚂蚁家优选 PDF转换器

首页 / 操作系统 / Linux / Linux Signal实现代码分析

本文介绍了Linux信号处理的基本流程。关于信号处理的具体细节可以看ULK第三版第11章。

1.     基本数据结构

1.1           task_struct中信号相关的域

struct signal_struct * signal;         // Pointer to the process"s signal descriptorstruct sighand_struct * sighand;    // Pointer to the process"s signal handler descriptorsigset_t blocked;                         // Mask of blocked signalssigset_t real_blocked;                  // Temporary mask of blocked signals (used by the                                                             // rt_sigtimedwait( ) system call)struct sigpending pending;            // Data structure storing the private pending signalsunsigned long sas_ss_sp;              // Address of alternative signal handler stacksize_t sas_ss_size;                        // Size of alternative signal handler stackint (*) (void *) notifier;               // Pointer to a function used by a device driver to block some                                                    // signals of the processvoid * notifier_data;                   // Pointer to data that might be used by the notifier function                                                      // (previous field of table)sigset_t * notifier_mask;                     // Bit mask of signals blocked by a device driver through a                                                        // notifier functionl         signal和sighand都是指针,因为同一个进程中所有线程都共享同一个signal和同一个sighand。l         sas_ss_sp和sas_ss_size可以用于定义信号处理函数使用的栈,但不是必须。

1.2           sigpending与sigqueue

sigpending代表目前等待处理的信号的集合。struct sigpending {       struct list_head list;              // sigqueue链表       sigset_t signal;                     // 待处理的信号集合};sigpending结构体中,signal域保存所有待处理信号的集合,每个信号占一位。list域指向sigqueue链表,对于非实时信号(1-31),每个信号在链表中只能拥有一个sigqueue;对于实时信号(32-63),如果接收到多个相同的信号,每个信号都会在链表中拥有一个sigqueue。struct sigqueue {       struct list_head list;       int flags;       siginfo_t info;       struct user_struct *user;};在每个task_struct结构中,有两个sigpending,分别是:       struct sigpending pending;       struct sigpending (struct signal_struct *signal)l->shared_pending;之所以有两个sigpending,是由于Linux的线程和进程在内核中都是一个task_struct表示。所以penging域代表线程本身待处理的信号,signal->shared_pending代表线程所属进程的待处理信号。sigqueue使用非常频繁,所以在内核中专门为其申请了kmem cache。可以在sigqueue_alloc和sigqueue_free中找到其分配、释放的实现代码。

2.     信号的发送

最常用的发送信号的函数有kill, tkill和tgkill等等。其中tkill已经过时,被tgkill代替。kill被用来给进程发消息;tgkill被用来给线程发消息。killkill函数最终会调用kill_something_info:       struct siginfo info;       info.si_signo = sig;       info.si_errno = 0;       info.si_code = SI_USER;       info.si_pid = task_tgid_vnr(current);       info.si_uid = current->uid;       return kill_something_info(sig, &info, pid); static int kill_something_info(int sig, struct siginfo *info, pid_t pid)       // 如果pid > 0,发信号给pid指定的进程              kill_pid_info(sig, info, find_vpid(pid));       // 否则,如果pid != -1 && pid != 0,发信号给由-pid指定的进程组              __kill_pgrp_info(sig, info, find_vpid(-pid);       // 否则,如果pid == 0,发信号给自己所属的进程组              __kill_pgrp_info(sig, info, task_pgrp(current));       // 否则 pid == -1),发信号给除自己所属进程之外的其它所有进程              for_each_process(p) {                     if (task_pid_vnr(p) > 1 && !same_thread_group(p, current))                            group_send_sig_info(sig, info, p);在以上所有情况下,最终都会调用send_signal,且改函数最后一个参数为1。tgkilltgkill有三个参数,tgid,pid和sig,其中tgid为进程,pid为线程。tgkill最终会调用do_tkill。static int do_tkill(pid_t tgid, pid_t pid, int sig)       struct siginfo info;       info.si_signo = sig;       info.si_errno = 0;       info.si_code = SI_TKILL;       info.si_pid = task_tgid_vnr(current);       info.si_uid = current->uid;       。。。       specific_send_sig_info(sig, &info, p);specific_send_sig_info最终会调用send_signal,且最后一个参数为0。send_signal有上面分析可以看出,无论是发信号给进程还是线程,最终都是调用send_signal函数,唯一区别在最后一个参数。int send_signal(int sig, struct siginfo *info, struct task_struct *t, int group)       struct sigpending *pending;       // 发消息给进程和线程的区别在这里       pending = group? &t->signal->shared_pending : &t->pending;              // 如果是非实时信号(<32),且该信号已经在等待队列中,则忽略       if (legacy_queue(pending, sig))              return 0;       // 注意如果传入的info指定为SEND_SIG_FORCED,那么不分配sigqueue       if (info != SEND_SIG_FORCED)              // 分配一个sigqueue              // 将sigqueue挂入sigpending队列              // 这里要注意除了SEND_SIG_FORCED之外还有几种特殊情况要考虑:              //    SEND_SIG_NOINFO              //     SEND_SIG_PRIV       // 通知signalfd       。。。       sigaddset(&pending->signal, sig);       // 将signal加入pengding->signal位掩码中       complete_signal(sig, t, group);complete_signal用于唤醒可能的正在等待信号的进程或线程static void complete_signal(int sig, struct task_struct *p, int group)       // 如果指定线程正在等待该信号,由该线程处理       if (wants_signal(sig, p))              t = p;       // 如果是发给指定线程的信号,或者是单线程进程,并且没有满足上面的条件,          // 可以直接返回,等待do_signal函数的最终处理       else if (!group || thread_group_empty(p))              return;       else  // 到这里,只能是发给进程的信号,且该进程是多线程的              // 从下面代码可以看出,如果是发给进程的信号,可以唤醒任意一个正在等待该                 // 信号的线程               t = signal->curr_target;              while (!wants_signal(sig, t)) {                     t = next_thread(t);                     if (t == signal->curr_target)                            return;              }              signal->curr_target = t;       // 判断是否是致命信号       if (sig_fatal(p, sig) &&           !(signal->flags & (SIGNAL_UNKILLABLE | SIGNAL_GROUP_EXIT)) &&           !sigismember(&t->real_blocked, sig) &&           (sig == SIGKILL || !tracehook_consider_fatal_signal(t, sig, SIG_DFL)))              if (!sig_kernel_coredump(sig))                     。。。       signal_wake_up(t, sig == SIGKILL);