内核并不真正关心 process-1 是否“等待 process-2 完成”(特别是它对“为什么”它处于当前状态不感兴趣,只是它处于某种状态:在这种情况下,在内核中空闲等待某个事件)。对于典型的1捕获信号,信号发送方基本上只是在信号接收方的进程/线程状态中设置一些位,然后如果合适,安排该进程/线程运行以便它可以看到这些位。如果接收器在内核中空闲等待某个事件,那就是“调度运行”的情况之一。(其他常见情况包括:接收器处于 STOP 状态,除SIGCONT
信号;或者,接收器在用户模式下运行,它被设置为转换到内核模式,以便注意待处理的信号。)
两者都不能被捕获或忽略SIGKILL
,SIGSTOP
所以,不,你不能为这些提供处理程序。(通常进程通过SIGTSTP
、SIGTTIN
或进入停止状态SIGTTOU
,所有这些都可以被捕获或忽略。)
如果系统调用被设置为在用户信号处理程序返回后重新启动(通过 的SA_RESTART
标志sigaction()
),这是通过为操作设置“返回地址”来实现的sigreturn()
,实际上是重新进行系统调用。也就是说,如果 process-1 在 中,则从初始 的点到接收到捕获的信号,然后返回到更多等待waitpid()
的操作序列(从 process-1 的角度来看)是:waitpid()
s
- 系统调用:
waitpid()
- 让自己入睡等待事件
- 唤醒:检查唤醒事件
- 事件是信号并且信号被捕获,所以:
- 为每个
sigaction()
设置设置新的信号掩码(请参阅sigaction()
)
- 将信号帧推入堆栈(参见
SA_ONSTACK
和sigaltstack()
)
- 设置用户代码(程序计数器)进入“信号蹦床”
- 返回用户代码(进入蹦床)
(此时 process-1 又回到了用户模式。剩下的步骤没有编号,因为我不能让 SO 从 9 开始。:-))
- 调用用户处理程序例程(仍在上面选择的堆栈上)
- 当用户例程返回时
sigreturn()
,使用设置时存储的帧执行系统调用,可能由用户例程修改
(此时进程进入内核态,执行sigreturn()
系统调用)
- 系统调用::设置参数
sigreturn()
指定的信号掩码sigreturn()
- 设置其他寄存器,包括堆栈指针和程序计数器,由
sigreturn()
参数指定
- 返回用户代码
(程序现在回到用户模式,寄存器设置为 enter waitpid
)
- 系统调用:
waitpid()
此时,进程返回到接收到捕获信号之前的相同状态:waitpid
使其进入睡眠状态以等待事件(步骤 2)。一旦被唤醒(步骤 3),它等待的事件已经发生(例如,正在执行的进程waitpid()
已完成)并且它可以正常返回,或者另一个捕获的信号已经发生并且它应该重复这个序列,或者它正在被被杀了,它应该清理,或者其他什么。
这个顺序就是为什么一些系统调用(例如一些类似read()
的系统调用)如果被信号中断会“提前返回”:它们在“第一次”进入内核和信号处理程序的时间之间做了一些不可逆的事情。运行。在这种情况下,在第 6 步推送的信号帧不得具有导致整个系统调用重新启动的程序计数器值。如果是这样,那么在进程进入睡眠状态之前完成的不可逆转的工作将会丢失。因此,它被设置为返回检测到成功的系统调用的指令,寄存器值设置为返回短read()
计数,或其他。
当系统调用设置为不重启(SA_RESTART
未设置)时,第6步推送的信号帧也不同。它不是返回到执行系统调用的指令,而是返回到检测到失败的系统调用的指令,并设置寄存器值以指示EINTR
错误。
(通常,但并非总是如此,这些指令是相同的,例如,用于测试成功/失败的条件分支。在我最初的 SPARC 端口中,我在大多数情况下为它们制作了不同的指令。因为叶例程返回%o6+8
时没有寄存器或堆栈操作,我只是设置了一点指示成功返回应该返回叶例程的返回地址。所以大多数系统调用只是“将系统调用号和 ret-on-success 标志放入%g1
,然后陷阱到内核,然后跳转到-错误处理,因为如果我们到达这里,系统调用肯定失败了。”)
1与排队信号。