Linux signal 图文详解(三)信号处理

发布于:2025-09-06 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

一、信号处理的时机

二、信号处理实现原理

        2.1)整体流程

        2.2)获取信号

        2.3)handle signal

        2.4)信号返回

三、系统调用被信号打断后的restart处理

四、任务上有多个待处理的pending信号

五、用户态信号处理函数修改程序的运行轨迹

六、代码实现

七、总结


(代码:linux 6.3.1,架构:arm64)

One look is worth a thousand words.  —— Tess Flanders

相关链接:

Linux signal 图文详解(一)信号简介、信号注册

Linux signal 图文详解(二)信号发送

阅读本文前,让我们一起思考几个问题:

        1)任务一般什么时候会响应并处理信号?

        2)对于SIGKILL的信号,其处理流程是怎样的?

        3)若信号没有注册任何处理函数,任务接收到这类信号后的处理逻辑是什么?

        4)若任务上同时有多个待处理的信号,其执行流程是怎样的?

        5)任务执行系统调用时,被信号打断后,该如何处理?

        6)信号处理函数,能否改变程序原本的执行轨迹?什么场景下会这么干?

一、信号处理的时机

        在linux中,信号的处理时机:任务从内核态返回用户态前夕不论任务是由于系统调用、data_abort同步异常、中断等任何哪种方式陷入的内核,在其返回用户态前夕,内核代码都会检查当前任务上是否存在待处理的信号!

// 任何从EL0陷入EL1的方式, 在处理完后, 都会调用exit_to_user_mode, 该函数中会去检查是否有信号需要处理!
entry_handler
    el0t_64_sync_handler / el0t_64_irq_handler / el0t_64_fiq_handler / el0t_64_error_handler
        ...
        exit_to_user_mode(regs)   //<<<<<< 其中会判断是否有pending信号需要处理
    .if \el == 0
    b	ret_to_user

        在exit_to_user_mode中,会检查当前任务的很多待处理的事项,其中有一项就是:是否存在pending的信号需要处理。

/* 返回用户态前夕 */
exit_to_user_mode(struct pt_regs *regs) {
    prepare_exit_to_user_mode(struct pt_regs *regs = regs) {
        local_daif_mask()

        flags = read_thread_flags();
        
        if (unlikely(flags & _TIF_WORK_MASK))        
            do_notify_resume(struct pt_regs *regs = regs, unsigned long thread_flags = flags)
            {
                do {
                    local_daif_restore(DAIF_PROCCTX)
                    
                    if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
                        do_signal(regs)
                        {
                            ### Detail see below
                        }//do_signal
                    
                    local_daif_mask()
                    thread_flags = read_thread_flags()
                } while (thread_flags & _TIF_WORK_MASK);
            }//do_notify_resume
    }//prepare_exit_to_user_mode
    
    __exit_to_user_mode()
}//exit_to_user_mode

二、信号处理实现原理

        内核中,信号处理的流程比较复杂,所以接下来会将其拆解成 4 部分,各自详细介绍。

        2.1)整体流程

        信号在内核中的处理流程如下:

        1)判断当前任务是否处于syscall系统调用中;

        2)若处于的话,说明当前任务的系统调用被信号打断了,可能需要执行syscall restart的动作,设置对应的寄存器(这块后面章节会详细介绍)

        3)获取pending的信号,可能是线程私有的pending信号、也可能是线程共享的pending信号;(下面会详细分析)

        4)若pending的信号,其注册时不支持syscall restart的话,恢复原本的task上下文中的pc,并且设置系统调用返回值(即:x[0])为-EINTR,代表系统调用被信号打断的了,需要用户态进行处理;

        5)handle_signal中主要做的事情是:设置任务返回用户态,执行信号处理函数时,所需要的用户栈、寄存器等内容(下面会详细分析)

        6)当为本次获取到的pending信号做好准备工作后,接着尝试获取任务上其他pending信号,直到任务不再置位TIF_SIGPENDING;

        7)若任务上没有可以处理的pending信号,则从do_signal返回,最终返回用户态去执行信号处理函数;

        注意:在do_signal中,是会循环调用get_signal、handle_signal,这就说明如果当任务上有多个可处理的pending信号,在内核的do_signal中,会为它们一一分配好对应的用户栈和上下文,这样任务返回用户态后,会依次执行所有准备好的pending信号!(细节见第4章)

        2.2)获取信号

        get_signal 的处理流程如下:

        1)判断当前进程是否有置位SIGNAL_GROUP_EXIT;

        2)若置位了SIGNAL_GROUP_EXIT,且当前pending信号是需要执行coredump流程的信号的话,则调用do_coredump执行coredump流程(具体实现在Linux coredump原理 图文详解一文中有详细介绍)

        3)若不是需要coredump的信号,则调用do_exit 执行线程退出流程;

        4)尝试获取pending信号,依次查找synchronous signal、线程私有信号pending队列(task->pending)、线程共享信号pending队列(task->signal->pending),找到一个pending信号即可;

        5)判断当前任务是否处于gdb调试状态,若是的话,调用ptrace_signal,给父进程ptracer发送SIGCHLD,并唤醒父进程,随后将自身挂起(详细内容可参考:ptrace系列文章);

        6)获取pending信号,在内核中记录的k_sigaction对象;

        7)若信号的处理函数为SIG_IGN,则代表该信号当前进程不做处理直接忽略,则返回第1步重新尝试获取其他pending信号;

        8)若信号的处理函数为SIG_DFL,且是SIG_STOP类型的信号,则将所有其他线程都唤醒,去执行sig_stop的处理,随后当前线程也调用schedule调度挂起,直到之后的SIGCONT会将其唤醒;

        9)若信号的处理函数为SIG_DFL,且不属于SIG_STOP,则默认这类信号的处理与SIGKILL信号的处理一致,将当前进程杀死。于是会给进程置位SIGNAL_GROUP_EXIT,并给每个线程的私有pending信号队列置位SIGKILL,并唤醒其他线程,最后自身调用do_exit 执行线程退出流程;

        10)若获取的pending信号,注册了非SIG_DFL、SIG_IGN的其他处理函数,则将k_sigaction对象设置到入参“ksig->ka”中,并返回signr;

        【get_signal针对一些特殊信号处理】

        1)对于需要coredump的信号,不会返回到用户态了,而是在内核中调用do_coredump,走coredump流程;

        2)对于STOP类型的信号,会将其他线程置位SIG_STOP并唤醒它们去立刻挂起;

        3)对于处理函数为SIG_DFL的信号,其行为与SIGKILL一致,都是将整个进程杀死,走退出流程;

        【获取pending信号的优先级】

        从上述流程可以看到,get_signal的时候,会按照一定顺序去拿去pending信号,顺序如下:

        synchronous信号 ——> 线程私有pending信号 ——> 线程共享pending信号

        其中,synchronous信号,需要在其它信号之前被传递和处理,以应对严重的运行时错误,如:SIGSEGV, SIGILL 等

        2.3)handle signal

        handle_signal 的处理流程如下:

        1)设置用户态信号处理时的用户栈空间;

        2)首先将当前task在内核中记录的“用户态上下文”,保存到用户栈中,这是为了用户态信号处理函数执行完毕并返回到内核后,重新恢复原本task需要返回的上下文!

        3)既然原本的task上下文已经备份好了,那么就可以重新设置task的用户态上下文了,主要涉及几个寄存器:x[0]、sp、x29、pc、x30。这里的x30寄存器十分重要,当用户态的信号处理函数执行完毕后,需要再次陷入内核,这里就是借助x30执行的函数去完成这个动作的后面会详细介绍);

        4)若注册的信号处理函数需要siginfo信息,则将siginfo的信息也设置到用户栈中,并设置好x[1]、x[2]这两个用作信号处理函数入参的寄存器;

        5)若在setup_rt_frame设置用户栈的期间发生了错误,例如用户栈溢出等问题,就认定当前进程异常了,无法继续正常执行,所以给当前进程发送SIGSEGV信号,让其走coredump流程;

        6)设置任务在用户态执行信号处理函数时的信号屏蔽掩码,由:“当前待处理的信号”、以及“当前信号的处理函数注册时指定的屏蔽信号集” 组成;

        7)若进程中,还存在pending的进程信号(即:线程共享pending队列中的信号),且这些共享的pending信号当前任务由于信号屏蔽掩码的原因无法处理的话,就尝试唤醒进程中其他可以处理这些shared pending信号的线程去做信号处理。这么做也是为了保证信号尽快得到处理!

        以上都做完后,当任务从内核态恢复上下文,并eret返回到用户态后,就会跳转到信号处理函数去执行了。

        2.4)信号返回

        到这里,我们知道了内核是如何让task返回用户态执行信号处理函数的了,但有一个问题还没有解释清楚:用户态sig_handler执行完后,程序如何跑回原本的代码执行流程中?

        这就是信号返回(sigreturn)所需要负责的事情了!

        在介绍内核sigreturn的实现之前,先看一下C库中注册信号处理函数时的一些行为,这里以Musl-C为例:

int __libc_sigaction(int sig, const struct sigaction *restrict sa, struct sigaction *restrict old) {
    struct k_sigaction ksa, ksa_old;
    
    ksa.handler = sa->sa_handler
    ksa.flags = sa->sa_flags
    ksa.flags |= SA_RESTORER
    ksa.restorer = (sa->sa_flags & SA_SIGINFO) ? __restore_rt : __restore
    
    memcpy(&ksa.mask, &sa->sa_mask, _NSIG/8)
    
    int r = __syscall(SYS_rt_sigaction, sig, sa?&ksa:0, old?&ksa_old:0, _NSIG/8)
    if (old && !r) {
        old->sa_handler = ksa_old.handler
        old->sa_flags = ksa_old.flags
        memcpy(&old->sa_mask, &ksa_old.mask, _NSIG/8)
    }
    return __syscall_ret(r)
}

        这里,Musl-C将 ksa.restorer 的地址,设置为了 __restore_rt 或 __restore。ksa.restorer的值,在do_signal中,就会被设置到task上下文中的x[30]中,也就是用户态信号处理函数执行完毕后,执行ret的函数返回地址!

        那么我们来看一下__restore_rt 、__restore 的实现是什么:

.global __restore
.hidden __restore
.type __restore,%function
__restore:
.global __restore_rt
.hidden __restore_rt
.type __restore_rt,%function
__restore_rt:
	mov x8,#139 // SYS_rt_sigreturn
	svc 0

        可以看到,__restore_rt 、__restore 的实现是一样的,都是调用 svc 触发系统调用陷入内核,同时,系统调用号设置为 139,即:SYS_rt_sigreturn;

        在内核中,针对rt_sigreturn的实现如下:

        内核中,rt_sigreturn做的主要事情如下:

        1)恢复原本的task信号屏蔽掩码;

        2)从用户栈中,恢复task原本的用户态上下文;

        当rt_sigreturn执行完毕,并返回时,就是返回到信号处理前,task原本的用户态返回地址去执行!这样一来,用户态正常执行的代码就能无感的照常执行原本的代码逻辑了!

        整个一套流程如下图所示:

三、系统调用被信号打断后的restart处理

        在linux中,不是所有的系统调用都能在内核中执行完毕后会立刻返回的,对于一些可能会将当前任务挂起阻塞的系统调用(如:futex、wait 等),在挂起期间,若收到了一个信号被唤醒,那么在内核中会从挂起的地方唤醒继续执行,但是可能此时的系统调用并未执行成功(或者叫执行完毕),那么在从内核的系统调用路径层层返回后,最终同样会在返回用户态前夕检查是否有pending信号需要处理;这种情况就是所谓的被信号打断的syscall调用。

        那么有什么办法,可以让程序自动的再次触发一次系统调用,使得用户对本次被信号打断的系统调用行为无感呐?

        答案是有的!对于一些允许被信号打断后restart的系统调用而言,其会设置一些特定的返回值,如:ERESTARTNOHAND、ERESTART_RESTARTBLOCK、ERESTARTSYS;

        当打断系统调用的信号,在注册期间也指定了SA_RESTART这个标志位的话,就好办了。说明这种情况下,内核可以手动的修改task上下文中的寄存器,让代码在处理完信号后,再次触发一次系统调用。其实现原理,就是在do_signal中,针对被打断的syscall场景,修改task寄存器上下文中的pc、x[0]。

        其中,pc修改为“pc-4”、x[0]修改为orig_x0;

        这里解释下,为什么仅仅修改PC、x[0],就能达到 restart syscall 的效果。在arm64中,执行svc指令后,会触发同步异常,陷入内核,同时PC会被CPU自动加4,执行svc的下一条指令,那么只要我们将PC减4,就能使得task处理完信号返回用户态执行的代码,再次指向svc 0,这样就能再一次的触发系统调用陷入内核了。

        当然,仅仅修改pc是不够的,还需要配合修改x[0],细心的小伙伴可能会问:“那又为什么只需要修改x[0]呐?” 哈哈哈哈,那是因为其他x[1]~x[30]的内容,都有保存在task的寄存器上下文中,而唯独x[0],是会在内核系统调用执行期间,被修改为系统调用的返回值的。因此,这里需要将x[0]恢复成系统调用首次陷入时缓存在task中的orig_x0;

四、任务上有多个待处理的pending信号

思考2个问题:

        1)do_notify_resume中,为什么需要 do-while (thread_flags & _TIF_WORK_MASK) 去调用 do_signal ?

do_notify_resume(struct pt_regs *regs = regs, unsigned long thread_flags = flags)
{
    do {
        local_daif_restore(DAIF_PROCCTX)
        
        if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
            do_signal(regs)         // <<<<<<<<<<
        
        local_daif_mask()
        thread_flags = read_thread_flags()
    } while (thread_flags & _TIF_WORK_MASK);
}//do_notify_resume

        因为,此时此刻,任务上可能存在不止一个pending的信号,所以需要一直调用do_signal去获取pending信号,并准备好用户栈的布局、以及相应的寄存器上下文,直到task上不再置位TIF_SIGPENDING为止!

        2)对于存在多个pending信号的场景,用户栈的构造布局又是怎样的?

        有点写不动了。。。直接上图!

五、用户态信号处理函数修改程序的运行轨迹

        在handle_signal的代码中,setup_sigframe函数会将pc、sp设置到一个用户态信号处理函数能够访问到的数据结构uc_mcontext中,同时在sigreturn时,内核也是从uc_mcontext中恢复原本任务的用户态返回地址的!

setup_sigframe(&user, regs, set) {
    struct rt_sigframe __user *sf = user->sigframe
    
    __put_user_error(regs->sp, &sf->uc.uc_mcontext.sp, err)    // <<<<<<<<<<
    __put_user_error(regs->pc, &sf->uc.uc_mcontext.pc, err)    // <<<<<<<<<<

    __put_user_error(regs->pstate, &sf->uc.uc_mcontext.pstate, err)
    
    __put_user_error(current->thread.fault_address, &sf->uc.uc_mcontext.fault_address, err)
    
    err |= __copy_to_user(&sf->uc.uc_sigmask, set, sizeof(*set))
    
    return err
}

        那么问题来了,如果用户态的信号处理函数,修改了uc_mcontext中的pc,不就会导致任务处理完信号sigreturn陷入内核后,最终返回的用户态地址被篡改了么!

        的确,在Musl-C的pthread_cancel中,就这么干了,其中会修改uc_mcontext->pc,使得task处理完信号后,返回的用户态执行地址,被篡改到了其他地方。代码实现如下:

        1)pthread_cancel 实现

int pthread_cancel(pthread_t t) {
    static int init
    if (!init) {
        init_cancellation();
        init = 1;
    }

    a_store(&t->cancel, 1);
    if (t == pthread_self()) {
        if (t->canceldisable == PTHREAD_CANCEL_ENABLE && t->cancelasync)
            pthread_exit(PTHREAD_CANCELED);
        return 0;
    }
    
    return pthread_kill(t, SIGCANCEL)   // <<<< 发送信号给目标线程
}

        2)init_cancellation 函数

static void init_cancellation()
{
	struct sigaction sa = {
		.sa_flags = SA_SIGINFO | SA_ONSTACK,
		.sa_sigaction = cancel_handler
	};
	memset(&sa.sa_mask, -1, _NSIG/8);
	__libc_sigaction(SIGCANCEL, &sa, 0);
}

        这个函数主要就是为当前进程注册一个SIGCANCEL信号的处理函数:cancel_handler;

        3)cancel_handler 函数

static void cancel_handler(int sig, siginfo_t *si, void *ctx)
{
	pthread_t self = __pthread_self();
	ucontext_t *uc = ctx;
	uintptr_t pc = uc->uc_mcontext.MC_PC;

	a_barrier();
	if (!self->cancel || self->canceldisable == PTHREAD_CANCEL_DISABLE) return;

	_sigaddset(&uc->uc_sigmask, SIGCANCEL);

	if (self->cancelasync) {
		pthread_sigmask(SIG_SETMASK, &uc->uc_sigmask, 0);
		__cancel();
	}

	if (pc >= (uintptr_t)__cp_begin && pc < (uintptr_t)__cp_end) {
		uc->uc_mcontext.MC_PC = (uintptr_t)__cp_cancel;    // <<<<<<< 修改了PC!
#ifdef CANCEL_GOT
		uc->uc_mcontext.MC_GOT = CANCEL_GOT;
#endif
		return;
	}

	__syscall(SYS_tkill, self->tid, SIGCANCEL);
}

        我们看到,在cancel_handler中,会将uc->uc_mcontext.MC_PC,修改为__cp_cancel!

        4)__cp_cancel 函数

__cp_cancel:
	b __cancel


long __cancel()
{
	pthread_t self = __pthread_self();
	if (self->canceldisable == PTHREAD_CANCEL_ENABLE || self->cancelasync)
		pthread_exit(PTHREAD_CANCELED);   // <<<<<<< 调用pthread_exit退出
	self->canceldisable = PTHREAD_CANCEL_DISABLE;
	return -ECANCELED;
}

        最终,__cp_cancel会调用pthread_exit执行线程退出流程!

        Musl-C中,在SIGCANCEL信号处理函数中修改了uc_mcontext->pc的目的,是为了让被pthread_cancel杀死的线程,在处理完pthread_cancel发送的SIGCANCEL信号后,自己在用户态调用pthread_exit,执行线程退出流程!

六、代码实现

1、do_signal实现

do_signal(struct pt_regs *regs) {
    unsigned long continue_addr = 0, restart_addr = 0
    struct ksignal ksig
    bool syscall = in_syscall(regs) {
        return regs->syscallno != NO_SYSCALL
    }
    
    /* 1) 判断当前任务是否正在执行系统调用, 若是的话说明本次系统调用被信号打断了 */
    if (syscall) {
        /* 2) 若处于系统调用中, 则记录返回用户态的指令地址、原本触发系统调用的指令地址、原本的系统调用返回值 */
        continue_addr = regs->pc
        restart_addr = continue_addr - (compat_thumb_mode(regs) ? 2 : 4)
        retval = regs->regs[0]
        
        forget_syscall(regs) {
            regs->syscallno = NO_SYSCALL
        }
        
        /* 3) 若处于系统调用中, 先修改“task上下文”中的regs[0]、pc, 为syscall restart作准备 */
        switch (retval) {
            case -ERESTARTNOHAND:
            case -ERESTARTSYS:
            case -ERESTARTNOINTR:
            case -ERESTART_RESTARTBLOCK:
                regs->regs[0] = regs->orig_x0
                regs->pc = restart_addr
                break
        }//switch (retval)
    }//if (syscall)
    
    /* 4) 获取一个需要处理的pending信号, 并将其k_sigaction记录到入参ksig中; 对于SIGSTOP、fatal类型的信号, 做一些相应处理 */
    has_pending_signal = get_signal(&ksig)
    {
        struct sighand_struct *sighand = current->sighand
        struct signal_struct *signal = current->signal
        
        if (!task_sigpending(current))
            return false
        
        /* can't return to user-mode if freezing() == T */
        try_to_freeze()
    
    relock:
        spin_lock_irq(&sighand->siglock)
        
        /* 4.1) 若是SIGSTOP/SIGCONT类型的信号, 则发送SIGCHLD信号给父进程, 并唤醒父进程 */
        if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
            if (signal->flags & SIGNAL_CLD_CONTINUED)
                why = CLD_CONTINUED
            else
                why = CLD_STOPPED
            
            signal->flags &= ~SIGNAL_CLD_MASK
            
            spin_unlock_irq(&sighand->siglock)
            
            do_notify_parent_cldstop(current, false, why)
            
            goto relock
        }
        
        for (;;) {
            struct k_sigaction *ka
            enum pid_type type
            
            /* 4.2) 若当前进程需要走退出流程, 则清除当前任务私有pending信号队列中的SIGKILL位 */
            if ((signal->flags & SIGNAL_GROUP_EXIT) || signal->group_exec_task) {
                ksig->info.si_signo = signr = SIGKILL
                sigdelset(&current->pending.signal, SIGKILL)
                recalc_sigpending() {
                    if (!recalc_sigpending_tsk(current) && !freezing(current))
                        clear_thread_flag(TIF_SIGPENDING)
                }//recalc_sigpending
                goto fatal
            }
            
            if (unlikely(current->jobctl & JOBCTL_STOP_PENDING) && do_signal_stop(0))
                goto relock
            
            type = PIDTYPE_PID
            
            /* 4.3) 获取当前task的私有pending信号队列中的synchronous信号(若存在) */
            signr = dequeue_synchronous_signal(kernel_siginfo_t *info = &ksig->info)
            if (!signr)
                /* 4.4) 若不存在synchronous信号, 则尝试获取私有pending信号队列中的普通信号、或还没有则尝试获取线程共享的pending信号队列中的信号 */
                signr = dequeue_signal(current, &current->blocked, &ksig->info, &type)
                {
                    *type = PIDTYPE_PID
                    signr = __dequeue_signal(&tsk->pending, mask, info, &resched_timer)
                    if (!signr) {
                        *type = PIDTYPE_TGID
                        signr = __dequeue_signal(&tsk->signal->shared_pending, mask, info, &resched_timer)
                    }
                    
                    /* 重新计算当前任务上是否还有pending待处理的信号 */
                    recalc_sigpending()
                    
                    return signr
                }//dequeue_signal
            
            /* 4.5) 若当前任务处于ptraced状态、且pending信号不是SIGKILL, 则调用ptrace_signal将当前任务挂起并通知父进程 */
            if (unlikely(current->ptrace) && (signr != SIGKILL) && !(sighand->action[signr -1].sa.sa_flags & SA_IMMUTABLE)) {
                signr = ptrace_signal(signr, &ksig->info, type)
                if (!signr)
                    continue
            }
            
            ka = &sighand->action[signr-1]
            
            if (ka->sa.sa_handler == SIG_IGN)
                continue
            
            /* 4.6) 若待处理的信号, 有注册信号处理函数, 则将信号对应的k_sigaction对象记录到入参ksig中, 并从get_signal函数返回 */
            if (ka->sa.sa_handler != SIG_DFL) {
                ksig->ka = *ka
                
                if (ka->sa.sa_flags & SA_ONESHOT)
                    ka->sa.sa_handler = SIG_DFL
                
                break
            }
            
            if (sig_kernel_ignore(signr))
                continue
            
            /* 4.7) 若是SIGSTOP类型的信号, 则唤醒所有其他线程去同样执行信号处理并挂起, 然后当前task也将自身挂起调度其他任务运行 */
            if (sig_kernel_stop(signr)) {
                if (likely(do_signal_stop(ksig->info.si_signo))) {      // <<<<<  这里面会调用schedule()挂起!待之后的SIGCONT唤醒当前task
                    goto relock
                }
                continue
            }
        fatal:
            spin_unlock_irq(&sighand->siglock)
            
            current->flags |= PF_SIGNALED
            
            /* 4.8) 若信号属于需要产生coredump的信号类型, 则执行coredump流程 */
            if (sig_kernel_coredump(signr)) {
                do_coredump(&ksig->info)
            }
            
            /* 4.9) 若信号不需要产生coredump, 仅仅是Death signal, 则唤醒给进行中每个线程置位SIGKILL pending位, 并唤醒所有其他线程, 最后走do_exit退出流程 */
            do_group_exit(ksig->info.si_signo) {
                struct signal_struct *sig = current->signal
                if (sig->flags & SIGNAL_GROUP_EXIT)
                    exit_code = sig->group_exit_code
                else if (sig->group_exec_task)
                    exit_code = 0
                else {
                    struct sighand_struct *const sighand = current->sighand
                    spin_lock_irq(&sighand->siglock)
                    if (sig->flags & SIGNAL_GROUP_EXIT)
                        exit_code = sig->group_exit_code
                    else if (sig->group_exec_task)
                        exit_code = 0
                    else {
                        sig->group_exit_code = exit_code
                        sig->flags = SIGNAL_GROUP_EXIT
                        zap_other_threads(struct task_struct *p = current) {
                            struct task_struct *t = p
                            
                            while_each_thread(p, t) {
                                task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK)
                                if (t->exit_state)  /* Don't bother with already dead threads */
                                    continue
                                sigaddset(&t->pending.signal, SIGKILL)
                                signal_wake_up(t, 1)
                            }
                        }//zap_other_threads
                    }
                }
                do_exit(exit_code)
            }//do_group_exit
        }//for (;;)
        
        spin_unlock_irq(&sighand->siglock)
    out:
        ksig->sig = signr
        
        return ksig->sig > 0
    }//get_signal
    
    if (has_pending_signal) {
        /* 5) 若本次信号处理位于系统调用中, 且信号注册时不支持syscall被打断后restart的话, 则恢复原本的用户态返回地址, 并设置返回系统调用的范湖一直为-EINTR */
        if (regs->pc == restart_addr && (retval == -ERESTARTNOHAND || retval == -ERESTART_RESTARTBLOCK ||
            (retval == -ERESTARTSYS &&!(ksig.ka.sa.sa_flags & SA_RESTART))))
        {
            syscall_set_return_value(current, regs, -EINTR, 0)
            regs->pc = continue_addr
        }
        
        /* 6) 设置用户态sig_handler执行时需要的栈空间内容、上下文(pc、lr、x0、x1、x2、sp), 并在用户栈中备份task原本返回用户态的上下文 */
        handle_signal(&ksig, regs)
        {
            sigset_t *oldset = sigmask_to_save()
            int usig = ksig->sig
            
            /* 6.1) 设置用户态sig_handler函数执行时的栈空间内容, 并保存当前task的用户态上下文到用户栈中
             * Set up the stack frame */
            ret = setup_rt_frame(usig, ksig, oldset, regs)
            {
                struct rt_sigframe_user_layout user
                
                /* 6.1.1) 设置信号处理函数的栈指针, 以及用于保存task原本返回用户态上下文的栈地址 */
                res = get_sigframe(&user, ksig, regs) {
                    init_user_layout(user)
                    
                    /* Determine the layout of optional records in the signal frame */
                    setup_sigframe_layout(struct rt_sigframe_user_layout *user = user, false)
                    
                    sp = sp_top = sigsp(regs->sp, ksig)
                    
                    sp = round_down(sp - sizeof(struct frame_record), 16)
                    user->next_frame = (struct frame_record __user *)sp
                    
                    sp = round_down(sp, 16) - sigframe_size(user)
                    user->sigframe = (struct rt_sigframe __user *)sp
                    
                    if (!access_ok(user->sigframe, sp_top - sp))
                        return -EFAULT
                    return 0
                }//get_sigframe
                if (res)
                    return 1
                
                frame = user.sigframe
                
                __put_user_error(0, &frame->uc.uc_flags, err)
                __put_user_error(NULL, &frame->uc.uc_link, err)
                
                /* 6.1.2) 为需要使用信号栈做准备, 记录信号栈的信息 */
                err |= __save_altstack(stack_t __user *uss = &frame->uc.uc_stack, regs->sp) {
                    struct task_struct *t = current
                    int err = __put_user((void __user *)t->sas_ss_sp, &uss->ss_sp) |
                              __put_user(t->sas_ss_flags, &uss->ss_flags) |
                              __put_user(t->sas_ss_size, &uss->ss_size)
                    return err
                }
                
                /* 6.1.3) 将当前task原本返回用户态的上下文信息先保存到用户态的栈空间中, 并将sp、pc记录到uc_mcontext中,
                 *        用户态sig_handler可能会修改pc、sp来改变task处理完信号后返回到用户态的地址(如: pthread_cancel的实现中会这么干) */
                err |= setup_sigframe(&user, regs, set) {
                    struct rt_sigframe __user *sf = user->sigframe
                    
                    __put_user_error(regs->regs[29], &user->next_frame->fp, err)
                    __put_user_error(regs->regs[30], &user->next_frame->lr, err)
                    
                    for (i = 0; i < 31; i++)
                        __put_user_error(regs->regs[i], &sf->uc.uc_mcontext.regs[i], err)
                    
                    __put_user_error(regs->sp, &sf->uc.uc_mcontext.sp, err)
                    __put_user_error(regs->pc, &sf->uc.uc_mcontext.pc, err)
                    __put_user_error(regs->pstate, &sf->uc.uc_mcontext.pstate, err)
                    
                    __put_user_error(current->thread.fault_address, &sf->uc.uc_mcontext.fault_address, err)
                    
                    err |= __copy_to_user(&sf->uc.uc_sigmask, set, sizeof(*set))
                    
                    return err
                }
                
                if (err == 0) {
                    /* 6.1.4) 设置task上下文里用于返回用户态执行信号处理函数的相关寄存器(参数x[0]、栈指针sp、栈帧x29、函数入口地址pc、函数返回地址x30) */
                    setup_return(regs, struct k_sigaction *ka = &ksig->ka, &user, usig)
                    {
                        __sigrestore_t sigtramp
                        
                        regs->regs[0]   = usig
                        regs->sp        = (unsigned long)user->sigframe
                        regs->regs[29]  = (unsigned long)&user->next_frame->fp
                        regs->pc        = (unsigned long)ka->sa.sa_handler
                        
                        if (ka->sa.sa_flags & SA_RESTORER)
                            sigtramp = ka->sa.sa_restorer
                        else
                            sigtramp = VDSO_SYMBOL(current->mm->context.vdso, sigtramp)
                        
                        regs->regs[30] = (unsigned long)sigtramp
                    }//setup_return
                    
                    /* 6.1.5) 若注册的信号处理函数需要提供SIGINFO信息, 则设置好siginfo对象并将相关地址设置给x1、x2作为信号处理函数的入参 */
                    if (ksig->ka.sa.sa_flags & SA_SIGINFO) {
                        err |= copy_siginfo_to_user(&frame->info, &ksig->info)
                        regs->regs[1] = (unsigned long)&frame->info
                        regs->regs[2] = (unsigned long)&frame->uc
                    }
                }
            }//setup_rt_frame
            
            /* Check that the resulting registers are actually sane. */
            ret |= !valid_user_regs(struct user_pt_regs *regs = &regs->user_regs, struct task_struct *task = current)
            {
                user_regs_reset_single_step(regs, task)
                
                return valid_native_regs(regs) {
                    regs->pstate &= ~SPSR_EL1_AARCH64_RES0_BITS
                    
                    if (user_mode(regs) && !(regs->pstate & PSR_MODE32_BIT) &&
                        (regs->pstate & PSR_D_BIT) == 0 &&
                        (regs->pstate & PSR_A_BIT) == 0 &&
                        (regs->pstate & PSR_I_BIT) == 0 &&
                        (regs->pstate & PSR_F_BIT) == 0)
                    {
                        return 1
                    }
                    regs->pstate &= PSR_N_BIT | PSR_Z_BIT | PSR_C_BIT | PSR_V_BIT
                    return 0
                }
            }//valid_user_regs
            
            signal_setup_done(failed = ret, ksig, int stepping = test_thread_flag(TIF_SINGLESTEP)) {
                /* 6.2) 若上面准备信号执行函数的栈空间内容时, 发生了错误(如栈溢出等), 则给当前进程发送SIGSEGV触发coredump */
                if (failed)
                {
                    force_sigsegv(ksig->sig) {
                        if (sig == SIGSEGV)
                            force_fatal_sig(SIGSEGV)
                        else
                            force_sig(SIGSEGV)
                    }
                } else {
                    /* 6.3) 更新当前task的信号屏蔽掩码(信号掩码为: 当前待处理的信号、以及当前信号的处理函数注册时指定的屏蔽信号集) */
                    signal_delivered(ksig, stepping) {
                        clear_restore_sigmask()
                        sigorsets(&blocked, &current->blocked, &ksig->ka.sa.sa_mask)
                        
                        set_current_blocked(&blocked) {
                            sigdelsetmask(newset, sigmask(SIGKILL) | sigmask(SIGSTOP))
                            
                            __set_current_blocked(newset) {
                                if (sigequalsets(&tsk->blocked, newset))
                                    return
                                
                                spin_lock_irq(&tsk->sighand->siglock)
                                __set_task_blocked(tsk, newset) {
                                    if (task_sigpending(tsk) && !thread_group_empty(tsk)) {
                                        sigset_t newblocked
                                        sigandnsets(&newblocked, newset, &current->blocked)
                                        
                                        /* 6.4) 唤醒其他可处理的线程去处理当前任务无法处理的shared pending信号 */
                                        retarget_shared_pending(tsk, &newblocked) {
                                            sigandsets(&retarget, &tsk->signal->shared_pending.signal, which)
                                            if (sigisemptyset(&retarget))
                                                return
                                            
                                            t = tsk
                                            while_each_thread(tsk, t) {
                                                if (t->flags & PF_EXITING)
                                                    continue
                                                if (!has_pending_signals(&retarget, &t->blocked))
                                                    continue
                                                /* Remove the signals this thread can handle. */
                                                sigandsets(&retarget, &retarget, &t->blocked)
                                                
                                                if (!task_sigpending(t))
                                                    signal_wake_up(t, 0)
                                                
                                                if (sigisemptyset(&retarget))
                                                    break
                                            }
                                        }//retarget_shared_pending
                                    }
                                }//__set_task_blocked
                                spin_unlock_irq(&tsk->sighand->siglock)
                            }//__set_current_blocked
                        }//set_current_blocked
                    }//signal_delivered
                }
            }//signal_setup_done
        }//handle_signal
        
        return
    }
    restore_saved_sigmask()
}//do_signal

2、rt_sigreturn实现

SYSCALL_DEFINE0(rt_sigreturn)
{
    struct pt_regs *regs = current_pt_regs()
    
    struct rt_sigframe __user *frame
    
    current->restart_block.fn = do_no_restart_syscall
    
    frame = (struct rt_sigframe __user *)regs->sp
    
    if (!access_ok(frame, sizeof (*frame)))
        goto badframe
    
    ret = restore_sigframe(regs, struct rt_sigframe __user *sf = frame) {
        /* 1) 恢复原本的task信号屏蔽掩码 */
        err = __copy_from_user(&set, &sf->uc.uc_sigmask, sizeof(set))
        if (err == 0)
            set_current_blocked(&set)
        
        /* 2) 恢复当前task原本返回用户态的上下文 */
        for (i = 0; i < 31; i++)
            __get_user_error(regs->regs[i], &sf->uc.uc_mcontext.regs[i], err)
        __get_user_error(regs->sp, &sf->uc.uc_mcontext.sp, err)
        __get_user_error(regs->pc, &sf->uc.uc_mcontext.pc, err)
        __get_user_error(regs->pstate, &sf->uc.uc_mcontext.pstate, err)
        
        forget_syscall(regs)
            regs->syscallno = NO_SYSCALL
    }//restore_sigframe
    
    if (ret)
        goto badframe
    
    ret = restore_altstack(&frame->uc.uc_stack) {
        copy_from_user(&new, uss, sizeof(stack_t))
        do_sigaltstack(&new, NULL, current_user_stack_pointer(), MINSIGSTKSZ)
    }//restore_altstack
    
    if (ret)
        goto badframe
    
    /* 返回后, 最终回到用户态信号处理前原本返回的位置 */
    return regs->regs[0]

badframe:
    arm64_notify_segfault(regs->sp)
    return 0
}

七、总结

        信号处理在内核中的实现,主要就是获取pending信号,为pending信号设置好用户栈、寄存器上下文。

        当用户态信号处理完毕后,会借助sigreturn通过svc的方式再次陷入内核,进而恢复到原本的用户态上下文。


网站公告

今日签到

点亮在社区的每一天
去签到