个人主页Cx330❄️个人专栏《C语言》《LeetCode刷题集》《数据结构-初阶》《C知识分享》《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔《Git深度解析》:版本管理实战全解心向往之行必能至Cx330的简介目录前言一、信号的本质与基础概念1.1 生活角度理解信号1.2 查看系统信号kill -l 命令二. 信号的产生5 种核心方式2.1 系统命令产生信号kill 命令2.2 终端按键产生信号键盘2.2.1 CtrlCSIGINT2 号信号2.2.2 Ctrl\SIGQUIT3 号信号2.1.3 CtrlZSIGTSTP20 号信号2.3 函数调用 产生信号编程触发2.3.1 kill 函数向指定进程发送信号2.3.2 raise 函数向当前进程发送信号2.3.3 abort 函数异常终止进程2.4 硬件异常产生信号CPU / 内存错误2.4.1 除零错误SIGFPE8 号信号2.4.2 非法内存访问SIGSEGV11 号信号2.4.3 关键说明2.5 软件条件产生信号定时 / 管道异常2.5.1 管道破裂信号SIGPIPE2.5.2 alarm 函数定时器信号SIGALRM三. 总结及补充知识重点板块3.1 总结信号产生的核心逻辑3.2 键盘怎么能向目标进程发送信号问题3.3 关于 Term VS Core 以及 core dump的补充四、核心转储Core Dump程序崩溃的 “黑匣子”1. 什么是 Core Dump2. Core Dump 生成条件3. 如何使用 Core 文件调试五、开发必避的 4 大坑点六、总结前言在 Linux 系统编程、服务端开发乃至进程调试的场景中信号Signal是无法绕开的核心机制。它是操作系统内核向进程传递的软中断承担着异步通知、异常处理和进程控制的关键角色。从你日常使用的CtrlC终止进程到服务端的优雅退出底层本质都是信号在工作。一、信号的本质与基础概念1.1 生活角度理解信号用 “快递收发” 的场景类比信号处理流程瞬间理清核心逻辑信号识别你知道快递来了要取进程能识别系统预设的信号如SIGINT信号产生快递员打电话通知信号由内核或其他进程发送信号未决你正在打游戏5 分钟后才去取信号产生后未立即处理处于 “未决” 状态信号递达游戏结束后取快递进程在合适时机执行信号处理动作处理方式默认动作打开快递使用自定义动作送给朋友忽略扔在一边。核心结论信号是异步通知进程无法预知信号何时到来但提前知道如何处理。1.2 查看系统信号kill -l 命令Linux 系统支持 64 种信号34 以下为常规信号34 以上为实时信号通过kill -l可查看所有信号的编号和名称kill -l # 输出示例核心信号 1) SIGHUP 2) SIGINT 3) SIGQUIT 9) SIGKILL 11) SIGSEGV 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 20) SIGTSTP核心信号说明开发高频用到信号编号信号名称产生场景默认动作2SIGINT按下CtrlC终止进程3SIGQUIT按下Ctrl\终止进程 Core Dump6SIGABRTabort()函数调用终止进程 Core Dump8SIGFPE算术异常如除零终止进程 Core Dump9SIGKILLkill -9 PID命令强制终止进程不可捕捉 / 忽略11SIGSEGV非法内存访问野指针终止进程 Core Dump14SIGALRMalarm()函数超时终止进程15SIGTERMkill PID命令默认信号终止进程可捕捉19SIGSTOPkill -STOP PID等暂停进程不可捕捉 / 忽略20SIGTSTP按下CtrlZ暂停进程二. 信号的产生5 种核心方式testsig.cc#include iostream #include string #include unistd.h #include signal.h // sig: 表示的是收到的信号编号 void handlersig(int sig) { std::cout 哈哈, 我正在处理一个信号, pid: getpid() sig number: sig std::endl; } int main() { for(int signo 0; signo 31; signo) signal(signo, handlersig); // signal(2, handlersig); // signal(2, SIG_DFL); // signal(2, SIG_IGN); // signal(3, handlersig); // signal(4, handlersig); // signal(11, handlersig); int a; while(true) { // scanf(%d, a); std::cout 我是一个进程, pid: getpid() std::endl; sleep(1); // int *p nullptr; // *p 100; // 野指针报错 // int a 10; // a / 0; // 除0错误 // raise(SIGINT); // 指定信号给当前进程 // abort(); // 是6号信号给当前进程 } }mykill.cc#include iostream #include string #include signal.h void Usage(std::string proc) { std::cerr Usage:\n\t proc signumber pid\n\n; } int main(int argc, char* argv[]) { if(argc ! 3) { Usage(argv[0]); return 1; } int signumber std::stoi(argv[1]); int pid std::stoi(argv[2]); std::cout send signumber to pid std::endl; int n kill(pid, signumber); (void)n; return 0; }前置储备2.1 系统命令产生信号kill 命令kill命令是发送信号的常用工具本质是调用kill系统函数语法kill -信号编号 进程PID kill -信号名称 进程PID实战案例用 kill 命令发送 SIGSEGV 信号// 程序死循环运行等待外部信号 #include iostream #include unistd.h int main() { std::cout 进程PID getpid() 等待信号用kill命令测试... std::endl; while (true) { sleep(1); } return 0; }操作步骤编译运行程序记录 PID如 213784打开新终端发送 SIGSEGV11 号信号段错误kill -11 213784 # 或 kill -SIGSEGV 213784终端输出Segmentation fault (core dumped)进程终止。2.2 终端按键产生信号键盘终端通过组合键产生预设信号用于控制前台进程核心组合键及对应信号2.2.1 CtrlCSIGINT2 号信号默认动作终止前台进程可通过signal函数自定义捕捉。#include iostream #include unistd.h #include signal.h // 自定义信号处理函数 void sigint_handler(int signum) { std::cout \n进程[ getpid() ]捕获到SIGINT信号编号 signum 未终止 std::endl; } int main() { std::cout 进程PID getpid() 等待信号按CtrlC测试... std::endl; // 注册SIGINT信号的处理函数 signal(SIGINT, sigint_handler); // 死循环等待信号 while (true) { sleep(1); std::cout 运行中... std::endl; } return 0; }编译运行按下CtrlC后进程不会终止而是执行自定义处理函数也就是打印出一段文字并继续运行。2.2.2 Ctrl\SIGQUIT3 号信号默认动作终止进程 生成 Core Dump 文件用于事后调试同样可自定义捕捉。#include iostream #include unistd.h #include signal.h void sigquit_handler(int signum) { std::cout \n进程[ getpid() ]捕获到SIGQUIT信号编号 signum std::endl; } int main() { std::cout 进程PID getpid() 等待信号按Ctrl\\测试... std::endl; signal(SIGQUIT, sigquit_handler); while (true) { sleep(1); } return 0; }关键说明Core Dump 文件默认关闭可通过ulimit -c 1024开启允许最大 1024KB 的 Core 文件调试时用gdb ./程序名 core.进程号分析。2.1.3 CtrlZSIGTSTP20 号信号默认动作暂停前台进程将其挂入后台可通过fg命令恢复前台运行也可以通过bg把这个后台进程运行起来。#include iostream #include unistd.h #include signal.h void sigtstp_handler(int signum) { std::cout \n进程[ getpid() ]捕获到SIGTSTP信号编号 signum std::endl; } int main() { std::cout 进程PID getpid() 等待信号按CtrlZ测试... std::endl; signal(SIGTSTP, sigtstp_handler); while (true) { sleep(1); std::cout 运行中... std::endl; } return 0; }后台操作示例# 运行程序后按CtrlZ进程暂停 [1] Stopped ./sigtstp_demo # 查看后台进程 jobs # 将后台进程恢复到前台 fg 1 # 将后台进程运行起来 # bg 12.3 函数调用 产生信号编程触发通过系统函数主动发送信号核心函数包括kill、raise、abort适用于编程场景下的信号触发。2.3.1 kill 函数向指定进程发送信号函数原型#include sys/types.h #include signal.h int kill(pid_t pid, int sig);pid目标进程 PID正数进程组 ID负数发送给组内所有进程0发送给当前进程组-1发送给所有有权限的进程sig信号编号0 表示检测进程是否存在不发送信号返回值成功返回 0失败返回 - 1。实战实现简易版 kill 命令我最开始给的那个mykill也是可以的#include iostream #include sys/types.h #include signal.h #include cstdlib // 用法./mykill -信号编号 进程PID int main(int argc, char* argv[]) { if (argc ! 3) { std::cerr 用法错误 argv[0] -signum pid std::endl; return 1; } // 解析信号编号去掉前缀- int sig std::stoi(argv[1] 1); // 解析目标进程PID pid_t pid std::stoi(argv[2]); // 发送信号 int ret kill(pid, sig); if (ret 0) { std::cout 向进程[ pid ]发送信号[ sig ]成功 std::endl; } else { perror(kill failed); return 1; } return 0; }2.3.2 raise 函数向当前进程发送信号函数原型#include signal.h int raise(int sig);作用自己给自己发送信号等价于kill(getpid(), sig)返回值成功返回 0失败返回非 0。实战案例#include iostream #include unistd.h #include signal.h void sig_handler(int signum) { std::cout 进程[ getpid() ]捕获到信号 signum std::endl; } int main() { // 注册SIGINT信号处理函数 signal(SIGINT, sig_handler); std::cout 每隔1秒给自己发送SIGINT信号... std::endl; while (true) { sleep(1); raise(SIGINT); // 主动发送信号 } return 0; }2.3.3 abort 函数异常终止进程函数原型#include stdlib.h void abort(void);作用向当前进程发送 SIGABRT6 号信号强制异常终止不可被忽略或自定义捕捉但是他能执行自定义的操作只不过最后依旧会终止无返回值必然终止进程。实战案例#include iostream #include unistd.h #include signal.h #include stdlib.h void sigabrt_handler(int signum) { std::cout 捕获到信号 signum 但仍会终止 std::endl; } int main() { // 尝试捕捉SIGABRT6号信号 signal(SIGABRT, sigabrt_handler); std::cout 3秒后调用abort()... std::endl; sleep(3); abort(); // 发送SIGABRT进程终止 std::cout 这里不会执行 std::endl; return 0; }运行结果3秒后调用abort()... 捕获到信号6但仍会终止 Aborted (core dumped)2.4 硬件异常产生信号CPU / 内存错误由硬件检测到的异常触发内核将其解释为对应信号发送给进程所有硬件异常信号基本上均触发 Core Dump后续有补充核心场景包括除零错误SIGFPE、非法内存访问SIGSEGV也就是8号和11号信号2.4.1 除零错误SIGFPE8 号信号#include iostream #include signal.h #include unistd.h void sigfpe_handler(int signum) { std::cout 捕获到SIGFPE信号编号 signum 除零错误 std::endl; // 注意除零后CPU状态未清理信号会持续触发需退出进程 exit(1); } int main() { signal(SIGFPE, sigfpe_handler); std::cout 模拟除零错误... std::endl; sleep(1); int a 10; a / 0; // 除零触发SIGFPE return 0; }2.4.2 非法内存访问SIGSEGV11 号信号#include iostream #include signal.h #include unistd.h void sigsegv_handler(int signum) { std::cout 捕获到SIGSEGV信号编号 signum 非法内存访问 std::endl; exit(1); } int main() { signal(SIGSEGV, sigsegv_handler); std::cout 模拟野指针访问... std::endl; sleep(1); int* p nullptr; *p 100; // 访问空指针触发SIGSEGV return 0; }2.4.3 关键说明硬件异常产生的信号本质是 CPU 或硬件检测到错误后通知内核再由内核转化为信号发送给进程除零错误CPU 运算单元检测到异常状态寄存器标记错误内核解释为 SIGFPE非法内存访问MMU内存管理单元检测到地址无效内核解释为 SIGSEGV若不退出进程异常状态会持续存在导致信号反复触发。2.5 软件条件产生信号定时 / 管道异常由软件内部状态触发的信号核心场景包括定时器超时alarm函数、管道破裂SIGPIPE等。图中就是以管道的例子来引入软件条件的2.5.1 管道破裂信号SIGPIPE当向无读端的管道写入数据时内核会向写进程发送SIGPIPE13 号信号默认动作是终止进程。#include unistd.h #include signal.h #include iostream void sigpipe_handler(int signum) { std::cout 捕获到SIGPIPE信号编号 signum 管道破裂 std::endl; exit(1); } int main() { int pipefd[2]; pipe(pipefd); // 创建管道 close(pipefd[0]); // 关闭读端 signal(SIGPIPE, sigpipe_handler); char buf[1024] hello; write(pipefd[1], buf, sizeof(buf)); // 向无读端的管道写数据触发SIGPIPE return 0; }2.5.2 alarm 函数定时器信号SIGALRM函数原型#include unistd.h unsigned int alarm(unsigned int seconds);作用设置定时器seconds秒后向当前进程发送 SIGALRM14 号信号返回值0无之前的定时器或之前定时器的剩余秒数特性一个进程同时只能有一个活跃的alarm定时器重复调用会覆盖之前的设置。#include iostream #include signal.h #include unistd.h int cnt 0; void handler(int sig) { std::cout 我正在处理一个进程, pid: getpid() sig number: sig cnt: cnt std::endl; alarm(2); // exit(1); // 这里必须要退出,不然使用信号捕捉函数就是死循环 } int main() { alarm(1); // 1秒后发送SIGALRM // int n alarm(0); // 取消闹钟,返回剩余时间 signal(SIGALRM, handler); while(true) { std::cout cnt: cnt std::endl; cnt; sleep(1); } return 0; }三. 总结及补充知识重点板块3.1 总结信号产生的核心逻辑本文详细拆解了 Linux 信号的 5 种产生方式核心要点总结信号是异步通知机制处理方式分为默认、自定义、忽略三类终端按键、kill 命令、系统函数、软件条件、硬件异常是信号产生的核心场景关键函数signal注册处理函数、kill发送信号、alarm定时信号、abort异常终止raise(给自己发送指定信号)不可捕捉 / 忽略的信号SIGKILL9、SIGSTOP19用于强制控制进程6号SIGABRT其实也算是比较特殊可以捕捉但是最后依旧会终止进程。3.2 键盘怎么能向目标进程发送信号问题3.3 关于 Term VS Core 以及 core dump的补充四、核心转储Core Dump程序崩溃的 “黑匣子”你在笔记中重点提到了Core Dump这是排查程序崩溃的关键手段。1. 什么是 Core Dump当进程收到某些信号如SIGSEGV段错误、SIGABRT断言失败且未被捕获处理时操作系统会将进程退出时的内存数据、寄存器状态、调用栈信息写入磁盘生成一个核心转储文件通常名为core或core.进程ID。Term仅终止进程不遗留任何数据。Core终止进程 生成核心转储文件保留现场用于调试。2. Core Dump 生成条件默认情况下系统为了节省磁盘空间禁止生成 Core 文件。需手动开启bash运行# 临时开启设置core文件大小为无限制 ulimit -c unlimited # 永久开启修改/etc/security/limits.conf添加以下两行 # * soft core unlimited # * hard core unlimited3. 如何使用 Core 文件调试生成 Core 文件后使用 GDB 快速定位崩溃位置bash运行gdb 你的可执行程序路径 core.进程ID # 进入GDB后输入 bt 查看调用栈直接定位崩溃代码行 (gdb) bt五、开发必避的 4 大坑点不可重入函数风险信号处理函数执行是异步的严禁调用printf、malloc、free等非可重入函数。如需打印建议使用write系统调用。信号丢失1~31 号非实时信号不支持排队多发几次可能只会收到一次。重要场景请使用实时信号或自行维护状态。SIGCHLD与僵尸进程子进程退出时会给父进程发SIGCHLD。父进程若不捕获处理调用wait或waitpid子进程会变成僵尸进程占用系统资源。signal函数的不稳定性signal在不同系统上行为可能不一致且处理后可能重置为默认行为。生产环境请统一使用sigaction函数。六、总结Linux 信号是轻量级的异步通信机制核心在于内核态与用户态的中断交互。基础层面理解信号类型、三态处理和生命周期。实战层面掌握sigaction、kill、alarm的用法实现业务逻辑。调试层面开启 Core Dump利用核心转储定位段错误、除零等崩溃问题。掌握信号机制是 Linux 后端开发者从 “会写代码” 到 “懂系统” 的重要跨越也是构建高可用、高健壮性服务的必备技能。