目录
[core(Core Dump机制)](#core(Core Dump机制))
1.信号入门
1.1预备知识
- 无论信号是否实际产生,进程在创建时就已经内置了识别和处理信号的能力
- 根据自定义的信号处理器(signal 设置的函数)或系统默认行为,进程在运行前就知道不同信号应该如何处理
- 默认动作 2.忽略 3.自定义动作
- 因为进程收到信号后,可能不会立即处理,而是等到合适的时机(如从内核态返回用户态时)才执行信号处理函数,所以信号处理是异步且延迟的(异步是事件触发与处理在时间上的完全解耦)
- 在信号产生到被处理的窗口期内,内核会为进程暂存已到达但未处理的信号 ,但需要注意:
- 普通信号(非实时信号)可能被合并(相同信号只保留一次)
- 实时信号通常会排队,保证不丢失
- Linux中进程可以分为前台进程和后台进程 两种。两者的关键区分方法在于谁来获取键盘输入,谁就是前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程
- Linux中,一次登陆只有一个终端,一般只配上一个bash,每一个登陆,只允许一个进程是前台进程,可以允许多个进程是后台进程
- linux中,信号就是数字,以宏体现。
- Linux中一共62个信号,1-31为普通信号,34-64为实时信号

signal
| 一、函数原型与基本概念 | 项目 | 详细说明 | |-----------------|-----------------------------------------------------------------| | 函数原型 | sighandler_t signal(int signum, sighandler_t handler) | | 头文件 | #include <signal.h> | | 返回值 | 成功:返回之前的信号处理函数指针 失败:返回SIG_ERR | | 参数1 signum | 信号编号(如SIGINT)或宏定义的数字(如2) | | 参数2 handler | 三种可能值: 1. SIG_DFL:恢复默认行为 2. SIG_IGN:忽略该信号 3. 函数指针:自定义信号处理函数 | 二、sighandler_t 类型定义解析 | 对比项 | void (*)(int) | sighandler_t | |--------------|--------------------------------------------|-------------------------------------------| | 本质 | 函数指针类型声明 | 函数指针类型别名 | | 定义方式 | 直接声明 | typedef void (*sighandler_t)(int); | | 可读性 | 较差,难以理解 | 好,意义明确 | | 使用场景 | 声明单个变量 | 函数参数/返回值类型 | | 示例 | void (*func_ptr)(int); | sighandler_t func_ptr; | | signal原型 | void (*signal(int, void (*)(int)))(int); | sighandler_t signal(int, sighandler_t); | 三、signal() 的行为特性 | 特性 | 说明 | 注意事项 | |---------|---------------|---------------------| | 一次性设置 | 只需调用一次,后续都有效 | 除非再次调用signal()覆盖 | | 继承性 | 子进程继承父进程的信号设置 | fork()后子进程有相同处理方式 | 四、三种处理方式对比 | 处理方式 | 宏定义 | 行为 | 示例 | |-----------|-----------|-----------------|-----------------------------| | 默认处理 | SIG_DFL | 执行系统预设的默认动作 | signal(SIGINT, SIG_DFL); | | 忽略信号 | SIG_IGN | 内核直接丢弃信号,不做任何处理 | signal(SIGPIPE, SIG_IGN); | | 自定义处理 | 函数指针 | 调用用户提供的处理函数 | signal(SIGINT, handler); | | |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|
| | |
Linux中可以使用 signal 系统调用捕捉信号并执行自定义动作
SIGKILL(9) 和 SIGSTOP(19)信号无法被捕捉
handler函数需要再次传入信号,主要是为了支持多信号共享同一个处理函数的编程模式
硬件中断
硬件中断驱动下的键盘输入处理流程:
1.触发中断 :当键盘按键被按下时,键盘控制器通过硬件引脚(如8042芯片的INT引脚)向CPU发送中断信号。
2. CPU响应 :CPU暂停当前任务,记录中断号(如IRQ1对应键盘中断),并通过中断向量表定位到操作系统注册的键盘中断处理程序。
3. 读取扫描码 :中断处理程序从键盘缓冲区中读取按键的扫描码(如按下"A"键的扫描码为0x1C)。
4. 转换与处理 :扫描码被转换为字符数据(如ASCII码'A')或控制指令(如识别Ctrl+C为终止信号),随后数据被送入终端驱动程序进行进一步处理(如行缓冲、特殊字符转换)。

硬件中断与信号
| 维度 | 硬件中断 | 软件信号 |
|---|---|---|
| 触发源 | 外设硬件 | 内核/进程/异常 |
| 响应实体 | CPU(内核态) | 进程(用户态) |
| 处理表 | IDT(中断描述符表) | 信号处理表(struct sighand) |
| 处理程序 | ISR(中断服务例程) | Handler(信号处理函数) |
| 屏蔽机制 | 中断屏蔽寄存器 | 信号掩码(sigset_t) |
| 优先级 | IRQ优先级 | 实时信号 > 普通信号 |
| 异步性 | 随时打断CPU执行 | 延迟到用户态安全点 |
打印错乱

终端的键盘输入通道绑定了前台进程,但是屏幕输出通道是所有进程共享的
当多进程同时打印输出时,显示器上的内容就会错乱,但这并不影响前台进程独享键盘输入
2.信号的产生
键盘组合键
| 组合键 | 信号 | 编号 | 默认行为 | 用途 |
|---|---|---|---|---|
| Ctrl + C | SIGINT |
2 | 终止进程 | 中断当前程序 |
| Ctrl + \ | SIGQUIT |
3 | 终止+core | 退出并生成调试文件 |
| Ctrl + Z | SIGTSTP |
20 | 暂停进程 | 挂起到后台 |
kill指令
| 分类 | 详情 |
|---|---|
| 功能 | 向进程发送信号(默认终止进程),用于管理进程生命周期或触发特定行为。 |
| 语法 | kill [选项] <PID>... 或 kill -l [信号名/编号] |
| 核心作用 | 终止失控进程、调试、资源释放、进程间通信(IPC)。 |
| 选项 | 说明 | 示例 |
|---|---|---|
-l |
列出所有支持的信号名称或编号(无参数时显示全部)。 | kill -l |
-<信号> |
指定发送的信号(可用名称或编号,默认 SIGTERM/15)。 |
kill -9 1234 或 kill -SIGKILL 1234 |
-p |
仅打印目标进程的 PID(不实际发送信号,用于调试)。 | kill -p 1234 |
系统调用(kill、raise、abort)
| 维度 | kill() | raise() | abort() |
|---|---|---|---|
| 功能 | 向任意进程/进程组发信号 | 向自己发信号 | 强制终止当前进程 |
| 头文件 | #include <signal.h> |
#include <signal.h> |
#include <stdlib.h> |
| 原型 | int kill(pid_t pid, int sig) |
int raise(int sig) |
void abort(void) |
| 目标 | 任何进程/进程组 | 仅调用进程自身 | 仅调用进程自身 |
| 返回值 | 成功0,失败-1 | 成功0,失败非0 | 永不返回 |
| 信号默认动作 | 取决于信号类型 | 取决于信号类型 | SIGABRT(6),终止+core |
| 是否可捕获 | 是(除SIGKILL等) | 是(除SIGKILL等) | 可捕获但最终仍终止 |
| 权限要求 | 需有权限向目标发信号 | 无(自己的进程) | 无(自己的进程) |
| 线程安全 | 是 | 是(向整个进程发) | 是 |
Linux提供了三种产生信号的系统调用:kill()允许向任意指定进程或进程组发送信号 ,是最通用的信号发送接口;raise()是向调用进程自身发送信号的便捷包装 ,在单线程环境中等价于kill(getpid(), sig),在多线程中默认作用于调用线程;abort()是引发进程强制终止的专用函数,它内部封装了对SIGABRT(6)信号的特殊处理逻辑,确保即使该信号被捕获或忽略,进程也最终会被终止,通常用于程序自检到不可恢复错误时的主动崩溃退出。
异常
异常信号是CPU执行指令时检测到异常情况后,由内核自动生成的信号。与外部事件(如Ctrl+C)不同,它们源于程序自身的错误行为。
| 信号 | 编号 | 触发原因 | 典型场景 |
|---|---|---|---|
| SIGFPE | 8 | 算术异常 | 除零、溢出 |
| SIGSEGV | 11 | 内存访问违规 | 空指针、越界 |
| SIGILL | 4 | 非法指令 | 损坏的可执行文件 |
| SIGBUS | 7 | 总线错误 | 未对齐访问 |
| SIGTRAP | 5 | 断点/跟踪陷阱 | 调试器使用 |
| SIGSYS | 31 | 无效系统调用 | 错误的syscall参数 |
异常触发的信号源于CPU执行指令时检测到的硬件异常 ,如除零操作触发SIGFPE(8)、野指针访问触发SIGSEGV(11),本质是CPU状态寄存器状态改变或MMU转换失败时 由操作系统作为硬件管理者捕获并转换为对应信号发送给故障进程,这些信号属于硬件层面的异常通知,其默认动作均为终止进程;若进程自定义捕获且不退出,会导致硬件异常持续存在、信号反复发送的"死循环",
| 对比维度 | 硬件异常(Hardware Exception) | C++异常(C++ Exception) |
|---|---|---|
| 来源 | CPU执行指令时触发的底层硬件错误(如算术错误、内存访问错误、非法指令等) | 程序逻辑中显式抛出的错误(如除零、数组越界、自定义业务错误等) |
| 触发方式 | 由CPU或MMU检测到异常状态后,由操作系统内核捕获并转换为信号(如SIGSEGV) |
通过throw语句显式抛出,由C++运行时系统处理 |
| 处理机制 | 操作系统通过信号机制(Signal)通知进程,默认行为是终止进程或生成核心转储 | 通过try-catch块捕获,可恢复执行或清理资源后继续运行 |
| 典型信号/类型 | SIGFPE(算术错误)、SIGSEGV(非法内存访问)、SIGILL(非法指令)等 |
std::exception及其派生类(如std::runtime_error、std::logic_error) |
| 默认行为 | 终止进程(Term),可能生成核心转储文件(Core Dump) |
抛出后若未捕获,调用std::terminate()终止程序 |
| 能否修复底层错误 | 否(如无法通过信号处理函数修复非法内存访问) | 是(如捕获后重新分配内存、调整逻辑参数等) |
| 目的 | 保障系统稳定性,防止硬件错误扩散 | 提高程序健壮性,处理可恢复的逻辑错误 |
| 抽象层级 | 系统级(System Level):直接与CPU、MMU、操作系统内核交互 | 语言级(Language Level):由C++运行时系统管理,与硬件无关 |
| 典型场景 | 除零操作、野指针访问、执行未定义指令、缺页中断(不可恢复部分) | 文件打开失败、网络连接超时、用户输入验证失败、业务规则冲突等 |
| 性能影响 | 高(涉及内核态切换、信号处理开销) | 中(依赖try-catch块的实现和异常传播路径) |
| 调试工具 | 核心转储文件(gdb、lldb)、系统日志(dmesg) |
调试器(gdb)、日志记录、堆栈跟踪 |
| 混用风险 | 在信号处理函数中抛出C++异常可能导致未定义行为(如std::terminate()被调用) |
在C++异常处理中模拟硬件错误可能掩盖真实问题(如用异常处理内存访问错误) |
硬件异常是CPU或MMU检测到底层错误(如非法内存访问、除零)后,由操作系统通过信号机制处理,旨在保障系统稳定性,通常不可修复且默认终止进程 ;而C++异常是程序逻辑中通过
throw/try-catch处理的可恢复错误(如文件打开失败、业务冲突),用于提升程序健壮性,二者分属系统级和语言级抽象,混用时需谨慎(如避免在信号处理中抛C++异常)
软件条件
软件条件信号是操作系统在特定软件状态条件满足时自动向进程发送的通知信号,如管道读端关闭时的SIGPIPE、定时器到期的SIGALRM、子进程状态变化的SIGCHLD等。这类信号的产生并非源于硬件错误(CPU无异常),也非用户或进程显式触发(如kill命令),而是内核作为系统管理者主动检测到既定条件成立(如资源超限、I/O状态变更)后,自动生成的异步事件通知,属于操作系统内置的软件级事件响应机制。
alarm
| 项目 | 详细说明 |
|---|---|
| 函数原型 | unsigned int alarm(unsigned int seconds) |
| 头文件 | #include <unistd.h> |
| 功能 | 设置一个定时器,在指定秒数后向调用进程发送 SIGALRM(14) 信号 |
| 返回值 | 返回之前设置的闹钟剩余秒数,若之前无闹钟则返回0 |
| 信号编号 | SIGALRM (14) |
| 默认行为 | 终止进程 |
| 精度 | 秒级(实际有±1秒误差) |
| 参数值 | 含义 | 效果 |
|---|---|---|
| seconds > 0 | 设置定时器秒数 | seconds秒后发送SIGALRM |
| seconds = 0 | 取消现有定时器 | 立即取消,不发送信号 |
| seconds > 0(重复调用) | 重置定时器 | 覆盖之前的定时器 |
| 返回值 | 含义 | 示例 |
|---|---|---|
| 0 | 之前没有设置闹钟 | alarm(5) 第一次调用返回0 |
| N > 0 | 之前闹钟剩余N秒 | alarm(5) → 3秒后 alarm(10) 返回2 |
| 无错误码 | 函数总是成功 | 不会设置errno |
| 使用场景 | 代码示例 | 说明 |
|---|---|---|
| 单次定时 | alarm(5); |
5秒后收到SIGALRM |
| 取消定时 | alarm(0); |
取消之前设置的定时器 |
| 重置定时 | alarm(10); sleep(3); alarm(5); |
3秒后重置为5秒后触发 |
| 获取剩余时间 | unsigned int remaining = alarm(0); alarm(remaining); |
先取消再恢复 |
core(Core Dump机制)
| 维度 | Core退出 | Term退出 |
|---|---|---|
| 代表信号 | SIGQUIT(3), SIGABRT(6), SIGSEGV(11), SIGILL(4), SIGFPE(8)等 | SIGTERM(15), SIGINT(2), SIGHUP(1)等 |
| 核心特征 | 产生core dump文件 + 进程终止 | 仅进程终止,不产生core文件 |
| 设计目的 | 调试/诊断 - 记录崩溃现场用于事后分析 | 管理控制 - 优雅或强制结束进程运行 |
| 触发类型 | 1. 程序异常(硬件/软件) 2. 调试请求(SIGQUIT) 3. 自终止(abort()) | 1. 用户请求(kill命令) 2. 系统管理(服务关闭) 3. 终端控制(Ctrl+C) |
在Linux中,使用waitpid()等待子进程时,其status参数是一个16位的整型变量,其第7位(从0开始计数)表示子进程是否生成了core dump文件,这是对已发生事件的记录标志(即本次退出是否实际产生了core文件),1表示生成,0表示未生成

在Linux系统中,默认会触发core dump机制生成核心转储文件的信号主要包括:SIGQUIT(3)、SIGILL(4)、SIGTRAP(5)、SIGABRT(6)、SIGBUS(7)、SIGFPE(8)、SIGSEGV(11)和SIGSYS(31) ,这些信号的默认动作均为"Core",当进程接收到这些信号且系统配置允许(ulimit -c非零)时,操作系统会在进程终止前将其完整内存状态转储到core文件中,供后续调试分析使用
云服务器默认关闭core dump机制(ulimit -c 显示为0) ,主要是出于生产环境稳定性和资源管理的考量:core文件可能体积巨大(与进程内存占用相当),频繁生成会迅速耗尽磁盘空间,导致系统服务异常;同时生成core时的I/O密集型操作会影响其他服务的性能。因此云服务商通常默认禁用,用户可根据调试需求按需开启并设置合理的限制
在Linux系统中,当通过ulimit -c设置core文件大小限制后,一旦进程因特定信号(如SIGSEGV、SIGABRT等)异常终止,操作系统将把进程的完整内存状态、寄存器值和堆栈信息转储到磁盘,生成名为core或core.<pid>的核心转储文件 。进行核心转储的主要目的是为事后调试提供完整的故障现场快照,使得开发者可通过gdb等工具分析core文件,精确回溯崩溃时的代码执行位置、变量状态和函数调用链,从而诊断难以复现的运行时错误
使用gdb进行事后调试时,需要同时加载可执行文件和对应的core文件,命令为gdb ./可执行文件 core.<pid>。gdb会解析core文件中保存的完整进程内存快照,自动定位到崩溃时的指令位置,并允许开发者使用bt查看完整的函数调用栈、info registers检查寄存器状态、info locals查看局部变量值等,从而实现对已终止进程的崩溃现场进行离线分析,这种无需重新运行程序的调试方式称为"事后调试"