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

目录

一、信号处理的时机

二、信号处理实现原理

2.1)整体流程

2.2)获取信号

[2.3)handle signal](#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同步异常、中断等任何哪种方式陷入的内核,在其返回用户态前夕,内核代码都会检查当前任务上是否存在待处理的信号!

cpp 复制代码
// 任何从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的信号需要处理。

cpp 复制代码
/* 返回用户态前夕 */
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,并且设置系统调用返回值(即:x0)为-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的用户态上下文了,主要涉及几个寄存器:x0、sp、x29、pc、x30。这里的x30寄存器十分重要,当用户态的信号处理函数执行完毕后,需要再次陷入内核,这里就是借助x30执行的函数去完成这个动作的(后面会详细介绍);

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

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

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

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

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

2.4)信号返回

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

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

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

cpp 复制代码
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上下文中的x30中,也就是用户态信号处理函数执行完毕后,执行ret的函数返回地址!

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

cpp 复制代码
.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、x0

其中,pc修改为"pc-4"、x0修改为orig_x0;

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

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

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

思考2个问题:

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

cpp 复制代码
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中恢复原本任务的用户态返回地址的!

cpp 复制代码
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 实现

cpp 复制代码
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 函数

cpp 复制代码
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 函数

cpp 复制代码
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 函数

cpp 复制代码
__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实现

cpp 复制代码
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实现

cpp 复制代码
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的方式再次陷入内核,进而恢复到原本的用户态上下文。

相关推荐
HLC++5 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室6 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy9 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕9 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫10 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生11 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1112 小时前
linux基础IO
linux·运维·服务器
BelongPanda14 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯14 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983414 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器