Linux 进程信号:从生活类比到内核原理

Linux 进程信号:从生活类比到内核原理你一定在 Linux 下用过 Ctrl C 终止程序 用 kill 命令杀进程 也见过程序崩溃报 Segmentation fault 这些日常操作背后 都是进程信号 在起作用 信号是 Linux 最基础 最核心的进程间异步通知机制 堪称进程的 软中断 本文用生活类比 代码实战 内核原理 带你从 0 到 1 彻底搞懂 Linux 信号

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



你一定在 Linux 下用过 Ctrl+C 终止程序、用 kill 命令杀进程,也见过程序崩溃报 Segmentation fault ,这些日常操作背后,都是进程信号在起作用。

信号是 Linux 最基础、最核心的进程间异步通知机制,堪称进程的 "软中断"。本文用生活类比 + 代码实战 + 内核原理,带你从 0 到 1 彻底搞懂 Linux 信号。


先抛开复杂术语,用收快递完美类比信号机制:

  • 你 = 进程
  • 快递员 = 操作系统(OS)
  • 快递 = 信号
  • 取件通知 = 信号产生
  • 暂时没空取、先记着 = 信号未决(Pending)
  • 有空了再去取 = 信号递达(Delivery)
  • 拆快递用 / 送人 / 扔一边 = 三种处理方式

由此得出信号4 大核心特性

  1. 识别是内置的:进程天生 "认识" 信号,是内核写死的能力
  2. 处理方式提前定:信号没来,就已经知道该怎么处理
  3. 不是立即处理:进程可能在忙更高优先级的事,要等 "合适时机"
  4. 异步通知:进程不知道信号啥时候来,来了就响应

一句话总结:信号 = 内核发给进程的异步事件通知,是进程间最轻量的 "事件提醒"。


所有信号最终都由操作系统发送,来源分 5 类:

1. 终端按键产生(最常用)

  • Ctrl+C → 发送 SIGINT(2):终止前台进程
  • Ctrl+ → 发送 SIGQUIT(3):终止并生成 core dump
  • Ctrl+Z → 发送 SIGTSTP(20):挂起前台进程

注意:Ctrl+C 只作用于前台进程 ,后台进程(加 & 运行)收不到。

2. 系统调用 / 命令产生

  • kill -信号 进程PID:手动发信号(如 kill -9 PID 强杀)
  • kill():代码中给指定进程发信号
  • raise():自己给自己发信号
  • abort():给自己发 SIGABRT(6),强制异常退出

3. 软件条件触发

  • alarm(秒) → 时间到发 SIGALRM(14)
  • 管道读端关闭,写端继续写 → SIGPIPE(13)
  • 定时器超时、资源超限等

4. 硬件异常转化

硬件报错 → 内核解释成信号发给进程:

  • 除 0 运算 → SIGFPE(8)
  • 野指针 / 非法访问 → SIGSEGV(11)
  • MMU 异常、指令非法等

5. 子进程退出通知

子进程退出 / 停止 → 父进程收到 SIGCHLD(17),默认忽略


进程对任何信号,只有3 种合法处理动作

1. 默认动作(SIG_DFL)

  • 多数信号:终止进程
  • 部分信号:终止 + core dump(方便事后调试)
  • SIGCHLD:默认忽略

2. 忽略信号(SIG_IGN)

  • 收到信号直接丢掉,不做任何处理
  • 例外SIGKILL(9)SIGSTOP(19) 不能忽略、不能捕获、不能阻塞,是系统 "终极权限" 信号

3. 自定义捕捉(信号捕获)

signal()/sigaction() 注册回调函数,信号来了执行你写的逻辑。

极简示例(捕获 Ctrl+C):

 
     
    
       
#include 
            
           

#include #include

void handler(int sig) {

std::cout << "捕获到信号:" << sig << ",我不退出! 

"; }

int main() {

signal(SIGINT, handler); // 注册 2 号信号处理函数 while (1) { std::cout << "运行中... 

";

 sleep(1); } 

}

 

运行后按 Ctrl+C,进程不会退出 ,只会打印提示 ------ 这就是自定义捕捉的威力。


信号不是 "来了就立刻处理",完整生命周期分 3 步:

1. 信号产生

OS 检测到事件,给目标进程发信号。

2. 信号保存(内核层核心)

进程用 3 个结构 管理信号,都在 PCB(task_struct)里:

  1. Pending 未决信号集:已产生、但还没处理的信号(用位图记录)
  2. Block 阻塞信号集(信号屏蔽字):被 "屏蔽" 的信号,产生了也暂时不递达
  3. Handler 处理函数指针:每个信号对应处理方式(默认 / 忽略 / 自定义)

关键结论:

  • 阻塞 ≠ 忽略:阻塞只是 "暂缓处理",解除阻塞后照样递达
  • 常规信号(1--31)多次产生只记录 1 次,不排队
  • 实时信号(34+)支持排队,本章不讨论

3. 信号递达

内核态 切回用户态的 "合适时机",处理未阻塞的未决信号:

  • 系统调用返回
  • 中断 / 异常处理完
  • 时钟中断返回前

想手动控制阻塞 / 未决,用这 4 个信号集函数 + 2 个系统调用:

1. 信号集操作函数

#include 
           
             // 清空信号集 int sigemptyset(sigset_t *set); // 填满所有信号 int sigfillset(sigset_t *set); // 添加某个信号 int sigaddset(sigset_t *set, int signo); // 删除某个信号 int sigdelset(sigset_t *set, int signo); // 判断是否包含该信号 int sigismember(const sigset_t *set, int signo); 
           

2. 读取 / 修改阻塞信号集

// 操作 Block 表 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • SIG_BLOCK:屏蔽 set 中的信号
  • SIG_UNBLOCK:解除屏蔽
  • SIG_SETMASK:直接设置为 set

3. 读取未决信号集

// 获取当前 Pending 信号集 int sigpending(sigset_t *set);

自定义信号处理,是面试最高频考点,完整流程如下:

  1. 进程在用户态运行 main 函数
  2. 发生中断 / 系统调用 → 切内核态
  3. 内核处理完,返回用户态前:检查未决信号
  4. 发现信号待处理,且是自定义捕捉
  5. 切回用户态,执行你的 handler 函数
  6. handler 执行完,自动切回内核态
  7. 无新信号 → 恢复 main 上下文,继续运行

关键点:

  • 信号处理函数在用户态执行,保证内核安全
  • 处理信号时,内核会自动阻塞当前信号,防止重入混乱

1. volatile:解决编译器优化导致的 "数据看不见"

// 不加 volatile,O2 优化后 flag 会被放进寄存器,信号修改后主流程看不见 volatile int flag = 0; void handler(int sig) { flag = 1; }
  • 作用:保持内存可见性,禁止编译器优化,每次都从内存读最新值
  • 场景 :信号处理函数与主流程共享的变量,必须加 volatile

2. 可重入函数

  • 可重入:函数被中断后重入,执行结果不乱(只访问局部变量 / 参数)
  • 不可重入 :调用 malloc/free、标准 I/O、全局变量等,信号重入会崩溃
  • 信号安全 :handler 里只调用异步信号安全函数

父进程不用死循环 waitpid 轮询,靠信号自动回收:

 
          
    
            
void handler(int sig) { // 非阻塞循环回收所有退出子进程 while (waitpid(-1, NULL, WNOHANG) > 0); 

}

int main()

while (1) pause(); // 父进程安心做自己的事 

}

 
          
    
            

  • 子进程退出 → 发 SIGCHLD → 父进程回调回收
  • 无僵尸进程,无轮询消耗

    1. 信号是同步还是异步?异步。进程不知道信号何时到来,随机触发处理。
    2. SIGKILL 为什么不能捕获 / 忽略?内核保留的终极权限,防止进程 "卡死杀不死"。
    3. 阻塞和忽略的区别?阻塞:暂不处理,保留未决状态;忽略:直接丢弃。
    4. 信号什么时候被处理?从内核态切回用户态的 "合适时机"。
    5. 信号处理函数为什么要加 volatile?防止编译器优化,保证主流程能看到信号修改的值。

    Linux 信号本质就是:内核给进程发的异步软中断,用 "产生→保存→递达" 完成事件通知,支持默认 / 忽略 / 自定义 3 种处理。

    Ctrl+C 到进程异常、子进程回收、定时器,信号无处不在。理解它,你就真正看懂了 Linux 进程的 "事件驱动模型",写出更稳定、更优雅的系统程序。

    小讯
    上一篇 2026-04-09 07:25
    下一篇 2026-04-09 07:23

    相关推荐

    版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
    如需转载请保留出处:https://51itzy.com/kjqy/250724.html