首页 文章

在Linux中接收SIGINT和异常句柄

提问于
浏览
1

假设我们在C中有一个使用sleep()函数的程序

程序执行并进入休眠状态 . 然后我们输入ctrl-c以向进程发送SIGINT信号 .

我们知道收到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)
}

代码似乎很简单,如果我想深入挖掘,我仍有疑问:

背景:当进程处于休眠状态时,我们输入ctrl-c来发送SIGINT

Q1-我的理解是,内核通过更新挂起位向量中的SIGINT相应的挂起位来向进程发送SIGINT,我的理解是否正确?

Q2-处理器检测到SIGINT的存在,但是因为我们覆盖处理程序以使其返回而不是终止进程,所以我们的处理程序被执行,然后内核清除SIGINT的相应的待处理位,我的理解是否正确?

Q3-由于SIGINT的corresponging挂起位被清除,那么sleep()函数如何返回?我认为它应该处于睡眠状态,因为理论上,sleep()函数无法知道SIGINT的存在(已被清除)

3 回答

  • 1

    你的理解是正确的 .

    想一想 . 该进程在内核中被阻止 . 我们需要返回用户空间来运行处理程序 . 我们如何在不中断任何阻塞内核调用的情况下执行此操作?我们这里只有一个进程/线程上下文 . 该过程不能同时处于休眠状态和运行信号处理程序 .

    顺序是:

    • 某些阻塞内核调用中的进程块 .

    • 信号被发送给它 .

    • 位已设置,进程已准备好运行 .

    • 进程恢复在内核模式下运行,检查挂起的非阻塞信号 .

    • 调用信号调度程序 .

    • 修改了进程上下文,以便在恢复时执行信号处理程序 .

    • 进程在用户空间中恢复

    • 信号处理程序运行 .

    • 信号处理程序返回 .

    • 内核由信号处理程序结束调用 .

    • 内核决定是恢复系统调用还是返回中断错误 .

  • 2

    Q3-由于SIGINT的corresponging挂起位被清除,那么sleep()函数如何返回?

    想象一下内核中的 sleep() 函数是一个函数:

    • 以某种"timer event"结构分配和设置字段

    • 将"timer event"添加到计时器的IRQ处理程序的计时器事件列表中,以便稍后担心(到期时间已过)

    • 将任务从"RUNNING"状态移动到"SLEEPING"状态(因此调度程序知道不给任务CPU时间),导致调度程序执行任务切换到其他任务

    • 配置用户空间的返回参数(剩余时间或 0 ,如果时间到期)

    • 弄清楚为什么调度程序再次给它CPU时间(时间到期还是由信号中断睡眠?)

    • 可能会使堆栈变得有点乱(如果 sleep() 被信号中断而不是返回到调用 sleep() 的代码,内核将返回信号处理程序)

    • 返回用户空间

    还想象那里's a second function (that I' m会因为没有特别的原因而打电话给 wake() ):

    • 从计时器事件列表中删除"timer event"(为计时器的IRQ处理程序担心)

    • 将任务从"SLEEPING"状态移动到"READY TO RUN"状态(因此调度程序知道该任务可以再次获得CPU时间)

    当然,如果定时器's IRQ handler notices that the 483190 has expired then the timer'的IRQ处理程序将调用 wake() 函数来再次唤醒任务 .

    现在假设's a third function (that I' m将调用 send_signal() ),这可能被其他函数调用(例如由 kill() 调用) . 此函数可能为应该接收信号的任务设置"pending signal"标志,然后检查接收任务所处的状态;如果接收任务处于"SLEEPING"状态,它会调用 wake() 函数将其唤醒(然后让 sleep() 函数的后半部分担心只要调度程序感觉给任务CPU时间,就会将信号传回用户空间后来) .

  • 0
    • Q1: 内核检查进程是否阻塞了接收信号,如果是,它会在进程条目中更新待处理信号位(不可靠,在具有可重复信号的系统上,这应该是一个计数器),以便调用信号处理程序当信号再次被解锁时(见下文) . 如果未被阻止,系统调用将准备返回值和 errno 值并返回到用户模式,并在程序的虚拟堆栈中安装特殊代码,使其在从通用 syscall 代码返回之前调用信号处理程序(已在用户模式下) . 系统调用的返回给 -1 调用者代码, errno 变量设置为 EINTR . 这需要进程安装信号处理程序,因为默认情况下操作是中止进程,因此它不会从它正在等待的系统调用返回 . 认为当一个人说内核时,执行的实际代码是在唤醒系统调用并通知特殊情况(收到信号)中断调用,检测到要调用信号处理程序,并准备用户堆栈跳转从 syscall() 包装器返回之前到适当的位置(用户代码中的中断处理程序) .

    • Q2: pending bit仅用于保存要调用的挂起信号处理程序,因此情况并非如此 . 在进程的执行部分,unix程序加载器安装一些基本代码,以便在从系统调用返回之前跳转到信号处理程序 . 这是因为信号处理程序必须在用户模式下执行(而不是在内核模式下),所以一切都在系统调用终止时发生 . 执行的信号处理程序是 SIGINT ,但是中断的代码是系统调用,在系统调用返回之前没有任何反应(返回代码和 errno 变量已经修复)

    • Q3: 好吧,你的推理基于一个错误的前提,即中断挂起标志表示已收到中断 . 该位 only 表示已经标记了未处理的中断以进行传送 as soon as you unblock it ,这仅在另一个系统调用中发生(以解除对信号的阻塞) . 一旦信号被解除阻塞, sigsetmask(2) 系统调用的返回码将执行信号处理程序 . 在这种情况下,一旦计时器结束,信号将被传递给过程,系统调用将被中断,如果你还没有安装 SIGALRM 信号的信号处理程序(但 sleep(2) 实现这样做 - 至少,旧的实现确实)该程序将被中止 .

    注意

    当我说程序被内核中止但在两种情况下,所涉及的信号( SIGINTSIGALRM )都没有使它转储核心文件 . 程序中止而不生成 core . 这与发送 SIGABRTabort() 例程的行为不同,因此,它使得de kernel转储进程的核心文件 .

相关问题