Linux 学习笔记(二十九):进程信号(上)—— 信号捕捉与异步魔法

一、 什么是进程信号?

信号(Signal)是 Linux 操作系统中用于进程间异步通知 的一种机制,属于软中断。

就像快递员给你送快递,你不需要一直在门口等着,快递到了会打电话通知你。进程收到信号后,有三种处理方式:

  1. 默认动作(SIG_DFL):系统预设的操作,如 Ctrl+C 就是默认终止。

  2. 忽略(SIG_IGN):收到后当作没看见。

  3. 自定义捕捉:执行程序员指定的自定义函数。

二、 信号的产生方式

  1. 终端按键 :如 Ctrl+C (SIGINT, 2号)、Ctrl+\ (SIGQUIT, 3号)、Ctrl+Z (SIGTSTP, 20号)。

  2. 系统调用:

    • kill(pid, sig):给指定进程发信号。

    • raise(sig):给自己发信号。

    • abort():给自己发 SIGABRT 信号,异常终止。

  3. 软件条件 :alarm(seconds)。调用后设置一个定时器,时间一到,内核给该进程发送 SIGALRM (14号) 信号。

  4. 硬件异常:除零错误 (SIGFPE, 8号)、非法内存访问/野指针 (SIGSEGV, 11号)。

三、 实战:自定义信号捕捉

使用 signal(int signum, handler) 函数可以改变信号的处理动作。

cpp 复制代码
void my_handler(int signo) {
    printf("捕捉到信号: %d\n", signo);
}

int main() {
    signal(SIGINT, my_handler); // 拦截 Ctrl+C
    while(1) {
        printf("我在死循环里等信号...\n");
        sleep(1);
    }
}

在这个实战中,进程对 Ctrl+C 产生了免疫。如果真的要退出,需要发送 9号信号 (SIGKILL),这是一种绝对无法被捕捉和忽略的终极杀招。

四、 pause() 的妙用

pause() 函数会让当前进程进入无 CPU 消耗的挂起状态(S状态),直到进程收到一个由信号捕捉函数处理并返回的信号为止。非常适合用来做基于信号驱动的异步程序。

总结

信号是操作系统的神经系统。理解信号的自定义捕捉,是编写健壮的服务端程序(如处理优雅退出)的必备基

自我实战操作

咱们建个新目录来玩信号。

bash 复制代码
cd ~/linux_study
mkdir signal_test
cd signal_test
实战 1:让进程"刀枪不入"w
bash 复制代码
vim sig_catch.c

(输入以下代码)

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

// 自定义信号处理函数
void my_handler(int signo) {
    printf("\n[捕捉到信号] 编号: %d, 我是不会退出的!\n", signo);
}

int main() {
    // 注册捕捉函数:收到 2号信号(SIGINT, 即Ctrl+C) 时,执行 my_handler
    // 如果写 signal(SIGINT, SIG_IGN); 就是忽略该信号
    signal(SIGINT, my_handler); 

    while(1) {
        printf("我在死循环里等信号...\n");
        sleep(1);
    }
    return 0;
}

编译并运行:

bash

bash 复制代码
gcc sig_catch.c -o sig_catch
./sig_catch

👉 现象 :你在终端里猛按 Ctrl+C,它不但不会死,还会打印出你自定义的提示语!

👉 怎么退出? :因为 Ctrl+C 被我们拦截了,你得按 Ctrl+\(发送3号信号 SIGQUIT),或者新开一个终端用 kill -9 进程PID 杀掉它。

实战 2:体验系统调用 kill 和 raise 以及 alarm
bash 复制代码
vim sig_alarm.c

(输入以下代码)

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

void handler_alarm(int signo) {
    printf("闹钟响了!收到信号: %d\n", signo);
    exit(0);
}

int main() {
    signal(SIGALRM, handler_alarm); // 捕捉闹钟信号
    alarm(3); // 3秒后触发 SIGALRM
    
    printf("我给自己设了个3秒的闹钟,自己给自己发信号...\n");
    
    while(1) {
        pause(); // 将进程挂起,直到收到信号
    }
    return 0;
}

编译运行:

bash 复制代码
gcc sig_alarm.c -o sig_alarm
./sig_alarm

👉 现象 :程序卡住 3 秒,然后打印"闹钟响了",接着退出。pause() 是个非常牛的函数,它会让进程进入睡眠状态,一点 CPU 都不占,直到被信号"叫醒"。

实战 3:模拟野指针崩溃信号
bash 复制代码
vim sig_segv.c

(输入以下代码)

cpp 复制代码
#include <stdio.h>
#include <signal.h>

void handler(int signo) {
    printf("抓到了!信号编号: %d (段错误)\n", signo);
}

int main() {
    signal(SIGSEGV, handler); // 捕捉段错误信号 (11号)
    
    int *p = NULL;
    *p = 100; // 故意触发野指针访问异常,OS会发 SIGSEGV
    
    return 0;
}

(编译运行,你会发现原本要直接崩溃的程序,打印出了"抓到了!",但是!它依然会崩溃。因为硬件异常哪怕你捕捉了,返回时还是会在那条非法指令处再次触发异常,形成死循环,OS最终还是会把它杀掉。这也是段错误很难"捕捉"着救回来的原因。)

相关推荐
lingran__1 小时前
Linux进程万字详解(四) | 进程地址空间深度剖析:虚拟内存、页表映射与缺页中断
linux·虚拟地址空间·虚拟内存·1024程序员节·页表·物理内存·缺页中断
wuminyu1 小时前
JVM规范学习总结-Class文件结构
java·linux·c语言·jvm·c++
m0_547486661 小时前
《Linux操作系统应用项目教程》全套PPT课件2026
linux·运维·操作系统
曾庆睿1 小时前
计算机图形学入门笔记_GAMES101_Lecture01-02
笔记·计算机图形学
西索斯coding1 小时前
qwen3.8-max 和 minimax-m3 改后端仓库实测复核:补丁通过率与调用成本别急着排冠军
linux·运维·ubuntu·ai
coolwest1 小时前
某游戏辅助完整破解复盘报告(仅用于本人学习记录)
笔记
紧跟先前的步伐1 小时前
Go AI Agent 实战 18|Tool Calling 的基本原理与完整执行流程
前端·数据库·人工智能·ai·golang·1024程序员节
能源探索家2 小时前
本周能源政策密集落子:电力现货规则修订、碳市场扩围至石化化工、储能升格新兴支柱产业
能源·1024程序员节·新型电力系统·十五五规划·能源信息化·能源资讯
女神下凡2 小时前
芯参谋(71):全桥电机驱动电路设计指南
arm开发·stm32·单片机·嵌入式硬件·硬件工程·设计规范·1024程序员节