c-在没有系统调用的情况下,如何处理信号



我读了几本关于信号的Linux书籍和教程,它们都说内核在内核从内核模式转换到用户模式的时候处理信号。这完全有意义,直到我看到并实验了以下代码:

>cat sig_timing.c
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
volatile bool done = false;
static void sig_handler(int signo)
{
printf("Received signal %d (%s), current errno:%sn", signo, strsignal(signo), strerror(errno));
done = true;
}
int main(int argc, char **argv)
{
signal(SIGALRM, sig_handler);
alarm(3);
while (!done) {
strlen("Hello World!");
}
return 0;
}
>gcc sig_timing.c
>./a.out
Received signal 14 (Alarm clock), current errno:Success

所以主函数在注册信号后进入无休止的循环,循环不调用任何系统调用,所以没有机会进入内核,那么就没有从内核到用户模式的转换,那么就应该没有机会调用信号处理程序,对吧?

稍后,谈话主持人解释了发生了什么(我做了一些调整(:

发送方内核线程发送cpu间消息以导致硬件CPU上运行目标进程的中断,导致它进入内核来处理中断,并返回到用户模式。

我不太相信:这个解释似乎是说信号发送器和信号接收器在两个CPU硬件线程上运行。但是没有超线程的CPU呢?进程只在一个CPU线程上运行。在这种情况下,当用户的代码运行无休止的循环时,信号是否有机会得到处理?

这将是一个复杂的答案,请耐心等待。

先发制人的调度

所有现代操作系统都使用先发制人的调度(与协作调度相反(,其中硬件定时器被编程为定期引发定时器中断(称为操作系统"滴答"(,然后进入操作系统调度器。因此,即使用户线程正在运行一个无限循环,每隔一段时间(可能是10ms(,硬件也会引发一个异常,导致内核陷入陷阱。这允许操作系统运行调度程序,以便决定下一步将运行哪个用户任务。在您的特定示例中,这不仅允许内核调度您的程序(sig_timing(,还允许内核注意何时应该触发用户空间警报。

先发制人的调度是即使在单核计算机上也能获得交互式多编程体验的主要原因。CPU在任务之间是时间共享的,调度器在每个节拍都会运行,以便调度不同的任务。

请注意,操作系统的定时器周期(10ms(远小于用户警报时间(3s(,操作系统几乎可以完美地以3s向进程发送信号。另一个相关的后果是,如果有另一个高优先级的用户空间任务占用CPU,不允许操作系统调度程序,那么操作系统可以在3s之后交付SIGALRM。

旁注:合作日程安排

在协作调度中,任务需要明确地将控制权交给调度器(通常使用类似yield的调用(。在这样的系统上,您的程序可能永远不会接收到信号。

精确异常和信号

与某些信号(SIGSEGV、SIGILL、SIGBUS(不同,报警信号(SIGALRM(不与特定的用户空间指令相关联,并且不需要是"0";精确";。换句话说,内核不需要非常精确地确定何时传递信号。请记住,对于CPU核心来说,一秒钟是非常长的。

然而,精确的异常是由特定指令引起的。SIGSEGV是由权限错误引起的,SIGILL是由非法指令引起的,等等。这些指令会导致硬件直接在指令上引发陷阱,操作系统开始运行。操作系统处理故障,并将相应的信号转发给应用程序,精确地显示指令上的状态。

最新更新