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最终还是会把它杀掉。这也是段错误很难"捕捉"着救回来的原因。)

相关推荐
乌暮10 分钟前
Spring AOP 与 Spring 事务:用大白话彻底理解原理与实践
java·数据库·spring boot·后端·sql·学习·spring
分布式存储与RustFS1 小时前
Terraform 状态文件老在本地丢、并发还撞锁:把 state 锁进 RustFS
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
csdn_aspnet1 小时前
macOS 10 、Docker 玩轉 MySQL
运维·mysql·macos·docker·容器·mac
爱和冰阔落3 小时前
【Linux网络】TCP 半包粘包怎么解决?从长度报头、JSON 拆包到网络计算器完整实战
linux·运维·网络·tcp/ip
Go Work4 小时前
docker 占满根分区时先分清谁在吃盘
经验分享·笔记
3095566665 小时前
股票接口返回 404 与空数组的排查笔记:证书状态与级别参数
java·笔记·python·ios·iphone·股票数据api·股票数据接口
蒸蒸yyyyzwd5 小时前
从邓明学习的后端笔记
笔记·学习
say_fall5 小时前
【Linux网络编程】UDP套接字编程:系统编程组件、基础回显服务、InetAddr与地址转换、翻译服务与线程池聊天室
linux·服务器·网络·udp·vr
岑梓铭5 小时前
《考研408数据结构》第八章(排序)复习笔记
数据结构·笔记
机器学习之心5 小时前
基于变量相关图与残差学习的 GCN 多变量回归方法——混凝土 28 天抗压强度预测
学习·回归·gcn