一、信号捕捉这件事到底怎么发生
如果某个信号的处理动作是我们自己写的函数,信号递达时就会去执行这个函数,这就是'捕捉'信号。流程比很多人第一次想的要绕一点:进程先在用户态执行,遇到系统调用、异常或者中断之类的情况进入内核;内核处理完后,如果发现有需要递达的信号,并且这个信号配置成了自定义处理函数,就会在从内核返回用户态之前把它安排进去。然后进程回到用户态执行 handler,处理结束后再回到内核,最后继续原来的执行流。
这不是普通函数调用。main 和信号处理函数各跑各的栈,彼此之间没有那种'谁调用谁'的常规关系。很多人第一次看这段逻辑会下意识把它当成回调,其实不太像,信号更像是进程执行途中被硬插进来的一段异步流程。
用户态和内核态的切换由操作系统负责,用户程序不能自己随便切权限。早期 x86 上用户态进入内核态常见的是 int 80,本质上就是把 CPU 的特权级从用户态切到内核态。信号的检测和递达,也是在这个切换回来的时机做的。
二、用户态、内核态和地址空间

每个进程都有自己的 PCB,里面会有 mm_struct,也就是进程地址空间。我们平时说的虚拟地址,实际上就是这套地址空间里的东西。虚拟地址靠页表和 MMU 映射到物理内存,页表就是这中间的桥。
但这事只对用户空间这么分。内核空间的页表是统一的,内核空间的数据也是共享的。不同进程的用户区代码和数据可以完全不同,进程一切换,看到的用户空间就换了一套;内核空间却还是同一份。
所以从进程的角度看,系统调用是在自己的地址空间里发起的;从操作系统的角度看,任何时刻都会有进程运行,内核代码随时可以被切进去执行。
操作系统本质上在做什么
说得直白一点,操作系统就是一个被时钟中断不停打断的循环。硬件定时器按固定节奏发中断,CPU 进入内核,中断处理完以后再决定调度谁、切谁、唤醒谁。这个模型和 STM32 上定时器中断的感觉很接近,只是系统层面复杂得多。
三、sigaction 的用法
sigaction 用来设置或者检查信号的处理行为。
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
返回值:成功返回 0,失败返回 -1。signo 是信号编号,act 是输入参数,用来指定新的处理动作,oact 用来保存原来的处理动作。
结构体一般长这样:
struct sigaction {
(*sa_handler)();
(*sa_sigaction)(, *, *);
sa_mask;
sa_flags;
(*sa_restorer)();
};










