假设我们在C中有一个使用sleep()函数的程序
该程序执行并进入睡眠状态。然后,我们输入Ctrl+ C将SIGINT信号发送到该进程。
Ctrl
C
我们知道接收到SIGINT时的默认操作是终止该进程,我们也知道每当睡眠进程收到信号时,sleep()函数都会恢复该进程。
我的教科书上说,为了允许sleep()函数返回,我们必须安装一个SIGINT处理程序,如下所示:
void handler(int sig){ return; /* Catch the signal and return */ } ... int main(int argc, char **argv) { ... if (signal(SIGINT, handler) == SIG_ERR) /* Install SIGINT handler */ unix_error("signal error\n"); ... sleep(1000) }
虽然代码看起来很简单,但是如果我想更深入地研究,我仍然有疑问:
背景:当进程处于hibernate状态时,我们键入Ctrl+ C发送SIGINT
问题1:我的理解是,内核通过更新在插入位向量中SIGINT的对应挂起位将SIGINT发送到进程,我的理解正确吗?
问题2:处理器检测到SIGINT的存在,但是由于我们覆盖了处理程序以使其返回而不是终止进程,因此我们的处理程序被执行,然后内核清除SIGINT的对应挂起位,我的理解正确吗?
Q3-由于SIGINT的相应挂起位被清除,那么sleep()函数如何返回?我认为应该仍然处于睡眠状态,因为从理论上讲,sleep()函数无法知道SIGINT的存在(已清除)
Q1: 内核检查进程是否阻塞了接收到的信号,如果是,它将更新进程条目中的挂起信号位(在具有可靠信号的系统上不可靠,这应该是一个计数器),以调用信号处理程序当信号再次被解除阻塞时(见下文)。如果未阻塞,则系统调用将准备返回值和errno值,并返回到用户模式,并在程序的虚拟堆栈中安装特殊代码,从而使该syscall代码在从通用代码返回之前调用信号处理程序(已在用户模式下)。系统调用返回给-1调用者代码,并且errno变量设置为EINTR。这要求该进程已安装信号处理程序,因为默认情况下该操作是中止该进程,因此它不会从正在等待的系统调用中返回。认为当说 内核 实际执行的代码在系统调用中时就被唤醒并通知特殊情况(接收到信号)中断的调用,检测到要调用信号处理程序,并准备用户堆栈跳转从syscall()包装返回之前,将其放置到适当的位置(用户代码中的中断处理程序)。
errno
syscall
-1
EINTR
syscall()
Q2: 挂起位 仅 用于保存要调用的挂起信号处理程序,因此不是这种情况。在该过程的执行部分中,unix程序加载器在从系统调用返回之前,安装一些基本代码以跳转到信号处理程序。这是因为信号处理程序必须在用户模式下执行(而不是在内核模式下执行),所以一切都会在系统调用终止时发生。执行的信号处理程序是SIGINT,但被中断的代码是系统调用,在系统调用返回之前(返回代码和errno变量已经固定),什么也不会发生
SIGINT
问题3: 好吧,您的推论是基于错误的前提,也就是说, 中断待处理标志表明已接收到中断 。该位 仅 预示着未处理中断已被标记为交付 只要你解锁 ,而这只有在其他系统调用发生(解除阻塞信号)。一旦信号被解除阻塞,sigsetmask(2)系统调用的返回代码将执行信号处理程序。在这种情况下,信号将在计时器经过后立即传递到进程,系统调用将被中断,并且,如果您尚未为信号安装信号处理程序 SIGALRM(但sleep(2)实现则至少在以前这样做)实施)程序将被中止。
sigsetmask(2)
SIGALRM
sleep(2)
当我说该程序被内核中止时,但在两种情况下,所涉及的信号(SIGINT和SIGALRM)都不会使它转储核心文件。该程序被中止而不生成core。 这与abort()发送a 的例程的行为不同SIGABRT,因此,它使de kernel转储进程的核心文件。
core
abort()
SIGABRT