首页 / 操作系统 / Linux / Linux Signal实现代码分析
本文介绍了Linux 信号处理的基本流程。关于信号处理的具体细节可以看ULK第三版第11章。
1. 基本数据结构 1.1 task_struct 中信号相关的域 struct signal_struct * signal; // Pointer to the process"s signal descriptor struct sighand_struct * sighand; // Pointer to the process"s signal handler descriptor sigset_t blocked; // Mask of blocked signals sigset_t real_blocked; // Temporary mask of blocked signals (used by the // rt_sigtimedwait( ) system call) struct sigpending pending; // Data structure storing the private pending signals unsigned long sas_ss_sp; // Address of alternative signal handler stack size_t sas_ss_size; // Size of alternative signal handler stack int (*) (void *) notifier; // Pointer to a function used by a device driver to block some // signals of the process void * 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 function l 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被用来给线程发消息。 kill kill函数最终会调用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。 tgkill tgkill有三个参数,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);
收藏该网址