对信号的理解

  1. 调用 alarm(secs) 进程调用 alarm(secs) 时,内核会立即检查并取消之前设置的任何待处理闹钟(如果有的话),并返回上次闹钟剩余的秒数。

    • 如果 secs0,则不设置新闹钟,只取消旧的。
    • 如果 secs 是正数,内核会在 secs 秒后向该进程发送 SIGALRM 信号。
  2. 内核调度闹钟 内核记录下闹钟的触发时间,并在计时到期时,修改该进程的 pending 信号位图,标记 SIGALRM 待处理。

  3. 进程响应 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;
}

运行流程是:

  1. 程序启动后,调用 alarm(5),内核开始计时 5 秒。
  2. 程序进入 while(1) 循环,每隔 1 秒打印一次 "等待..."。
  3. 5 秒后,内核向当前进程发送 SIGALRM 信号。
  4. 进程收到信号,执行 sig_handler 函数,打印结束信息并退出。

调用 alarm(5) 时,内核并不会直接把 SIGALRM 放到进程的 pending 信号里,而是做了一个 "预约":

  • 内核记录下当前时间,加上 5 秒,得到闹钟的触发时间。
  • 然后内核把这个闹钟 "挂" 在当前进程上,等待时间到期。
  • 这时候进程的 pending 信号里是没有 SIGALRM 的,因为信号还没到触发时间。

等到5 秒时间到了,内核才会:

  1. SIGALRM 信号放到进程的 pending 信号队列中。
  2. 等进程下次陷入内核态(比如系统调用结束、时间片用完)时,内核检查到 pending 里的 SIGALRM
  3. 暂停当前程序执行,去调用 sig_handler

简单来说:alarm(5)预约 (不立即发信号)5 秒后 → 内核放入 pending → 触发处理函数

内核在收到 alarm(secs) 调用时,并不会立刻把 SIGALRM 放到 pending 里,而是先在内部设置一个定时器

流程是这样的:

  1. 调用 alarm(5) → 内核记下 "5 秒后要发 SIGALRM"。
  2. 这时候 pending 里没有 SIGALRM,进程继续正常运行。
  3. 5 秒后 → 内核才会把 SIGALRM 信号放到 pending 队列。
  4. 进程下一次进入内核态(比如系统调用结束、时间片切换)时,内核检测到 pending 中的 SIGALRM,触发信号处理。

在 Linux 内核和进程信号处理的语境里,pending 通常翻译成 **"待处理""挂起"**。

它指的是已经发送给进程、但进程还没来得及处理的信号 ,保存在进程的 pending 信号位图里。只有当进程进入内核态时,内核才会检查这个位图,并处理其中的信号。

  1. 触发信号处理一定会执行 sig_handler :只要内核检测到 pending 中的 SIGALRM,且你已经通过 signalsigaction 为该信号注册了 sig_handler 作为处理函数,就会暂停当前程序执行,转而调用 sig_handler
  2. sig_handler 是在用户态执行的 :信号处理函数由用户自己编写,内核不会在核心态执行用户代码 ------ 内核会先完成 "暂停当前进程、保存上下文" 等核心态操作,然后切换到用户态,再去执行 sig_handlersig_handler 执行完后,会切回内核态恢复原进程上下文,让原程序继续运行。

sig_hsnder的执行会单独再起一个进程吗?

不会,sig_handler(信号处理函数)不会单独启动新进程 ,它是在当前进程的上下文里执行的。

具体流程可以拆解为 3 步,更清晰理解为什么不用新进程:

  1. 当内核检测到 pending 中的 SIGALRM 时,会先暂停当前进程正在执行的代码,并保存当前进程的上下文(比如 CPU 寄存器值、程序计数器等,记录 "暂停时执行到哪了")。
  2. 然后内核会切换到用户态,在当前进程的地址空间里 ,直接调用 sig_handler 执行信号处理逻辑(相当于 "插队" 执行,用的还是原进程的资源)。
  3. sig_handler 执行完后,内核会恢复之前保存的进程上下文,让原进程从暂停的位置继续往下执行。

简单说:sig_handler 是当前进程的 "临时任务",不是新进程 ------ 就像你写代码时,中途停下来处理一个消息,处理完再接着写,不会重新开一个新的代码文件去处理消息。

如果信号处理要启动新进程,需要你在 sig_handler 里主动调用 fork() 函数,否则默认就是在当前进程里执行。

其他信号触发时,是不是没有时间等待,系统直接往对应接收信号的进程的上下文中中的pending中添加信号事件?

是的,当信号触发时,系统会直接往对应接收信号的进程的上下文中的pending(未决信号集)中添加信号事件。

信号产生后,内核并不会立即将其递送给进程处理,而是先将信号标记为 "待处理(pending)",存放在进程的进程控制块(PCB)即task_struct结构体中。pending是一个位图,每个比特位对应一个信号,当信号触发时,内核会在pending位图中设置相应的比特位,表示该信号已产生但未被处理。如果信号没有被阻塞,内核会在进程从用户态切换到内核态后,返回用户态前检查并递送该信号;如果信号被阻塞,则会一直留在pending集合中,直到阻塞解除。

相关推荐
Mr.H0127几秒前
多线程文件拷贝:从原理到实现的完整指南
linux·运维
橘子真甜~19 分钟前
C/C++ Linux网络编程5 - 网络IO模型与select解决客户端并发连接问题
linux·运维·服务器·c语言·开发语言·网络·c++
e***749538 分钟前
Nginx 常用安全头
运维·nginx·安全
oushaojun21 小时前
Linux内核KGDB进阶:源码级调试实战演练(转)
linux·运维·kgdb
余俊晖1 小时前
英伟达开源多模态视觉语言模型-Nemotron Nano V2 VL模型架构、训练方法、训练数据
人工智能·算法·语言模型·自然语言处理·多模态
2501_941111461 小时前
C++中的原型模式
开发语言·c++·算法
高洁011 小时前
国内外具身智能VLA模型深度解析(2)国外典型具身智能VLA架构
深度学习·算法·aigc·transformer·知识图谱
船长㉿1 小时前
vim常用命令
linux·编辑器·vim
一只会写代码的猫1 小时前
C# 性能优化:从垃圾回收到多线程并发
jvm·算法
大聪明-PLUS1 小时前
Linux 系统中的 CPU。文章 2:平均负载
linux·嵌入式·arm·smarc