linux kernel signal机制(X86_64)

1. 概述

应用程序注册信号,信号事件发生后,内核将信号置为pending状态,在中断返回或者系统调用返回时,查看pending的信号,内核在应用程序的栈上构建一个信号处理栈帧,然后通过中断返回或者系统调用返回到用户态,执行信号处理函数。执行信号处理函数之后,再次通过sigreturn系统调用返回到内核,在内核中再次返回到应用程序被中断打断的地方或者系统调用返回的地方接着运行。在这里插入图片描述

如果应用程序没有注册对应的信号处理函数,那么信号发生后,内核按照内核默认的信号处理方式处理该信号,比如访问非法内存地址,发生SIGSEGV,内核将进程终止。

2. 基本数据结构

  • task_strcut
struct task_struct {
   
	...
    struct signal_struct *signal; // 指向线程组的struct signal结构体
    struct sighand_struct *sighand; // 描述的action,即信号发生后,如何处理
    sigset_t blocked, real_blocked; // 该线程阻塞的信号,信号被阻塞,但是可以pending
                                    // 当取消阻塞后,就可以执行信号处理
    sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
    struct sigpending pending; // 该线程的pending信号
    unsigned long sas_ss_sp;  // 用户执行信号执行栈、栈大小、flag
    size_t sas_ss_size;       // 可以通过系统调用sigaltstack指定
    unsigned sas_ss_flags;
    ...
};
  • signal_struct
struct signal_struct {
   
    ...
	  /* shared signal handling: */
	  struct sigpending   shared_pending; // 线程组的pending信号
	  ...
}
  • sighand_struct
struct sighand_struct {
   
    atomic_t        count;
    struct k_sigaction  action[_NSIG]; // 描述信号的action,即信号发生时,如何处理该信号
    spinlock_t      siglock;
    wait_queue_head_t   signalfd_wqh;
};
  • sigpending
struct sigpending {
   
    struct list_head list; // sigqueue的链表头,用来链接处于pending状态的信号
    sigset_t signal; // 处于Pending的sig号集合
};
  • sigset_t
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
typedef struct {
   
    unsigned long sig[_NSIG_WORDS];  // 是一个位掩码,对应的信号会置位/清零
} sigset_t;

这些数据结构组织在一起,如下图。每个线程有一个自己私有的信号pending链表,链表上是发送给该线程,需要该线程自己去处理的信号。struct sigpending的成员head_list是pending信号sigqueue的链表头,成员signal是这些pending信号的信号编号掩码。

另外,属于同一个进程的线程组共享一个信号pending链表,这个链表上的信号没有绑定特定的线程,该线程租中的任意线程都可以处理。

线程组还共享sighand数据结构,里面有64个信号的action。每个action用来描述如何处理该信号,比如忽略、默认方式处理、或者执行用户注册的信号handler。

在这里插入图片描述

3. 注册信号

用户态的应用程序通过系统调用函数signal或者函数sigaction注册一个信号服务,当进程或者线程接收到信号后,调用注册的信号服务函数。

虽然现在内核源码中kernel/signal.c中有定义signal系统调用,但是libc库中使用系统调用sigaction实现应用程序中使用的signal函数。

3.1 内核中sigaction系统调用

内核kernel/signal.c中定义了系统调用sigaction函数。

该系统调用的功能是为信号sig注册新的信号action,同时返回旧的信号action。如果输入参数act为空,则不注册新的信号action,如果输出参数oact为空,则不返回老的信号action。

sigaction系统调用函数代码主要流程:

  • 如果输入参数act不为空,检查act的合法性,并读取act的各个成员,然后赋值个局部变量new_ka
  • 调用do_sigaction做底层的工作
  • 如果输出参数oact不为空,检查oact的合法性,并将旧的信号action通过oact返回
#ifdef CONFIG_COMPAT_OLD_SIGACTION
COMPAT_SYSCALL_DEFINE3(sigaction, int, sig,
        const struct compat_old_sigaction __user *, act,
            struct compat_old_sigaction __user *, oact)
{
   
    struct k_sigaction new_ka, old_ka;
    int ret;
    compat_old_sigset_t mask;
    compat_uptr_t handler, restorer;

    if (act) {
   
        if (!access_ok(VERIFY_READ, act, sizeof(*act)) ||
            __get_user(handler, &act->sa_handler) ||
            __get_user(restorer, &act->sa_restorer) ||
            __get_user(new_ka.sa.sa_flags, &act->sa_flags) ||
            __get_user(mask, &act->sa_mask))
            return -EFAULT;

#ifdef __ARCH_HAS_KA_RESTORER
        new_ka.ka_restorer = NULL;
#endif
        new_ka.sa.sa_handler = compat_ptr(handler);
        new_ka.sa.sa_restorer = compat_ptr(restorer);
        siginitset(&new_ka.sa.sa_mask, mask);
    }

    ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL);

    if (!ret && oact) {
   
        if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) ||
            __put_user(ptr_to_compat(old_ka.sa.sa_handler),
                   &oact->sa_handler) ||
            __put_user(ptr_to_compat(old_ka.sa.sa_restorer),
                   &oact->sa_restorer) ||
            __put_user(old_ka.sa.sa_flags, &oact->sa_flags) ||
            __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask))
            return -EFAULT;
    }
    return ret;
}
#endif

do_sigaction函数代码流程:

  • 检查sig是否是合法值。这里除了检查sig数值的合法性,也要求用户不能注册SIGKILLSIGSTOP信号action,这两种信号由内核处理,应用程序不能拦截。
  • 将信号sig的action注册到sighand数组中,并返回旧的action
  • 如果新注册的信号为可忽略信号,那么丢弃线程组共享的pending链表和线程组内各线程私有的pending链表中已经pending的sigqueue.
int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
{
   
    struct task_struct *p = current, *t;
    struct k_sigaction *k;
    sigset_t mask;

  	// 检查sig是否是合法值:
    // 1. 1 <= sig <= _NSIG
    // 2. sig不是Kernel特有的:SIGKILL, SIGSTOP, 
    //    内核不允许注册SIGKILL, SIGSTOP的信号处理函数,这两种信号由内核自己处理
    if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))
        return -EINVAL;

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

    spin_lock_irq(&p->sighand->siglock);
    // 返回旧的信号action
    if (oact)
        *oact = *k;
    sigaction_compat_abi(act, oact);

    if (act) {
   
        // 确保act->sa.sa_mask中没有SIGKILL和SIGSTOP
        sigdelsetmask(&act->sa.sa_mask,
                  sigmask(SIGKILL) | sigmask(SIGSTOP));
        *k = *act; //  赋值新的信号action
        /*
         * POSIX 3.3.1.3:
         *  "Setting a signal action to SIG_IGN for a signal that is
         *   pending shall cause the pending signal to be discarded,
         *   whether or not it is blocked."
         *
         *  "Setting a signal action to SIG_DFL for a signal that is
         *   pending and whose default action is to ignore the signal
         *   (for example, SIGCHLD), shall cause the pending signal to
         *   be discarded, whether or not it is blocked"
         */
        // 如果当前进程忽略该信号:1. handler == IGNR
        //   或者
        //   2. handler == DFLT && (sig为SIGCONT | SIGCHLD | SIGWINCH |SIGURG之一)
        // 那么,丢弃已经pending的信号
        if (sig_handler_ignored(sig_handler(p, sig), sig)) {
   
            sigemptyset(&mask);
            sigaddset(&mask, sig);
            // 清除共享信号中该sig的pending信号
            flush_sigqueue_mask(&mask, &p->signal->shared_pending);
            // 清除线程组中其他新线程该sig的pending信号
            for_each_thread(p, t)
                flush_sigqueue_mask(&mask, &t->pending);
        }
    }

    spin_unlock_irq(&p->sighand->siglock);
    return 0;
}

注:创建进程时,会将该进程的所有线程添加到task_struct中signal->thread_head链表中

4. send_signal 发送信号

发送信号,即在进程共享信号pending链表或者线程信号pending链表中添加一个信号sigqueue,并置信号接收者TIF_SIGPENDING位。如果信号接收者在此时没有在运行,那么直接唤醒;如果正在运行,有可能在用户态运行,那么kick_process给信号接收者发送一个IPI,让其进入一次kernel。最终,信号处理是在线程由内核返回用户态的时候,检查线程的TIF_SIGPENDING是否有置位,如果有,就执行信号的服务函数。

static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
            enum 
  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值