现在的位置: 首页 > 综合 > 正文

Linux Signal实现代码分析

2013年08月25日 ⁄ 综合 ⁄ 共 4572字 ⁄ 字号 评论关闭

本文介绍了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         signalsighand都是指针,因为同一个进程中所有线程都共享同一个signal和同一个sighand

l         sas_ss_spsas_ss_size可以用于定义信号处理函数使用的栈,但不是必须。

1.2           sigpendingsigqueue

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 *signall->shared_pending;

之所以有两个sigpending,是由于Linux的线程和进程在内核中都是一个task_struct表示。所以penging域代表线程本身待处理的信号,signal->shared_pending代表线程所属进程的待处理信号。

sigqueue使用非常频繁,所以在内核中专门为其申请了kmem cache。可以在sigqueue_allocsigqueue_free中找到其分配、释放的实现代码。

2.     信号的发送

2.1           普通发送

最常用的发送信号的函数有kill, tkilltgkill等等。其中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有三个参数,tgidpidsig,其中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;

       /* prepare_signal,会调用ignore_signal判断信号是否需要被忽略,

        如果是,则立即返回*/

       if (!prepare_signal(sig, t))

           return 0;

       // 发消息给进程和线程的区别在这里

       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

抱歉!评论已关闭.