-
调用
alarm(secs)进程调用alarm(secs)时,内核会立即检查并取消之前设置的任何待处理闹钟(如果有的话),并返回上次闹钟剩余的秒数。- 如果
secs是0,则不设置新闹钟,只取消旧的。 - 如果
secs是正数,内核会在secs秒后向该进程发送SIGALRM信号。
- 如果
-
内核调度闹钟 内核记录下闹钟的触发时间,并在计时到期时,修改该进程的
pending信号位图,标记SIGALRM待处理。 -
进程响应
SIGALRM当进程下一次进入内核态(比如系统调用结束、时间片切换)时,内核会检查到SIGALRM信号,调用对应的处理函数(默认是终止进程)。
简单来说,alarm 函数相当于让内核在指定时间后 "唤醒" 进程并给它一个 SIGALRM 信号。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void sig_handler(int signum) {
printf("收到 SIGALRM 信号,程序结束\n");
exit(0);
}
int main() {
// 注册信号处理函数
signal(SIGALRM, sig_handler);
// 设置 5 秒后发送 SIGALRM 信号
alarm(5);
printf("程序开始运行,5 秒后会自动结束\n");
// 让程序在这里循环,直到信号触发
while (1) {
printf("等待...\n");
sleep(1);
}
return 0;
}
运行流程是:
- 程序启动后,调用
alarm(5),内核开始计时 5 秒。 - 程序进入
while(1)循环,每隔 1 秒打印一次 "等待..."。 - 5 秒后,内核向当前进程发送
SIGALRM信号。 - 进程收到信号,执行
sig_handler函数,打印结束信息并退出。
调用 alarm(5) 时,内核并不会直接把 SIGALRM 放到进程的 pending 信号里,而是做了一个 "预约":
- 内核记录下当前时间,加上 5 秒,得到闹钟的触发时间。
- 然后内核把这个闹钟 "挂" 在当前进程上,等待时间到期。
- 这时候进程的
pending信号里是没有SIGALRM的,因为信号还没到触发时间。
等到5 秒时间到了,内核才会:
- 把
SIGALRM信号放到进程的pending信号队列中。 - 等进程下次陷入内核态(比如系统调用结束、时间片用完)时,内核检查到
pending里的SIGALRM。 - 暂停当前程序执行,去调用
sig_handler。
简单来说:alarm(5) → 预约 (不立即发信号)5 秒后 → 内核放入 pending → 触发处理函数
内核在收到 alarm(secs) 调用时,并不会立刻把 SIGALRM 放到 pending 里,而是先在内部设置一个定时器。
流程是这样的:
- 调用
alarm(5)→ 内核记下 "5 秒后要发SIGALRM"。 - 这时候
pending里没有SIGALRM,进程继续正常运行。 - 5 秒后 → 内核才会把
SIGALRM信号放到pending队列。 - 进程下一次进入内核态(比如系统调用结束、时间片切换)时,内核检测到
pending中的SIGALRM,触发信号处理。
在 Linux 内核和进程信号处理的语境里,pending 通常翻译成 **"待处理"或"挂起"**。
它指的是已经发送给进程、但进程还没来得及处理的信号 ,保存在进程的 pending 信号位图里。只有当进程进入内核态时,内核才会检查这个位图,并处理其中的信号。
- 触发信号处理一定会执行
sig_handler:只要内核检测到pending中的SIGALRM,且你已经通过signal或sigaction为该信号注册了sig_handler作为处理函数,就会暂停当前程序执行,转而调用sig_handler。 sig_handler是在用户态执行的 :信号处理函数由用户自己编写,内核不会在核心态执行用户代码 ------ 内核会先完成 "暂停当前进程、保存上下文" 等核心态操作,然后切换到用户态,再去执行sig_handler;sig_handler执行完后,会切回内核态恢复原进程上下文,让原程序继续运行。
sig_hsnder的执行会单独再起一个进程吗?
不会,sig_handler(信号处理函数)不会单独启动新进程 ,它是在当前进程的上下文里执行的。
具体流程可以拆解为 3 步,更清晰理解为什么不用新进程:
- 当内核检测到
pending中的SIGALRM时,会先暂停当前进程正在执行的代码,并保存当前进程的上下文(比如 CPU 寄存器值、程序计数器等,记录 "暂停时执行到哪了")。 - 然后内核会切换到用户态,在当前进程的地址空间里 ,直接调用
sig_handler执行信号处理逻辑(相当于 "插队" 执行,用的还是原进程的资源)。 sig_handler执行完后,内核会恢复之前保存的进程上下文,让原进程从暂停的位置继续往下执行。
简单说:sig_handler 是当前进程的 "临时任务",不是新进程 ------ 就像你写代码时,中途停下来处理一个消息,处理完再接着写,不会重新开一个新的代码文件去处理消息。
如果信号处理要启动新进程,需要你在 sig_handler 里主动调用 fork() 函数,否则默认就是在当前进程里执行。
其他信号触发时,是不是没有时间等待,系统直接往对应接收信号的进程的上下文中中的pending中添加信号事件?
是的,当信号触发时,系统会直接往对应接收信号的进程的上下文中的pending(未决信号集)中添加信号事件。
信号产生后,内核并不会立即将其递送给进程处理,而是先将信号标记为 "待处理(pending)",存放在进程的进程控制块(PCB)即task_struct结构体中。pending是一个位图,每个比特位对应一个信号,当信号触发时,内核会在pending位图中设置相应的比特位,表示该信号已产生但未被处理。如果信号没有被阻塞,内核会在进程从用户态切换到内核态后,返回用户态前检查并递送该信号;如果信号被阻塞,则会一直留在pending集合中,直到阻塞解除。