对信号的理解

  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集合中,直到阻塞解除。

相关推荐
OKkankan4 小时前
模板的进阶
开发语言·数据结构·c++·算法
weixin_307779134 小时前
Linux 下 Docker 与 ClickHouse 的安装配置及 MySQL 数据同步指南
linux·数据库·mysql·clickhouse·运维开发
qq_297075674 小时前
vmware和kali linux安装和搭建
linux·安全测试
zhaotiannuo_19984 小时前
【Linux kali 更换yum源】
linux·运维·服务器
会灭火的程序员4 小时前
银河麒麟V10 SP3 升级GCC环境
linux·c++·supermap
RTC老炮4 小时前
webrtc弱网-PccBitrateController类源码分析与算法原理
网络·算法·webrtc
草莓熊Lotso4 小时前
模板进阶:从非类型参数到分离编译,吃透 C++ 泛型编程的核心逻辑
linux·服务器·开发语言·c++·人工智能·笔记·后端
和芯星通unicore4 小时前
扩展RTCM消息
人工智能·算法
草莓熊Lotso4 小时前
《算法闯关指南:优选算法--前缀和》--25.【模板】前缀和,26.【模板】二维前缀和
开发语言·c++·算法