为什么程序会崩溃:从段错误、非法访问到 OOM
程序一崩,很多人第一反应是“代码炸了”。这话没错,但只说了一半。更准确地说,程序崩溃通常是操作系统在替你做边界检查:你越过了不该碰的地址,或者机器已经没有足够内存继续撑下去,于是内核把这件事掐断了。
如果把程序放进虚拟内存这张地图里看,很多“莫名其妙的崩溃”其实都很有逻辑。
用户态程序
-> 访问一个虚拟地址
-> MMU/页表检查
-> 地址合法且有权限:继续
-> 地址不存在或权限不对:触发 page fault
-> 用户态:SIGSEGV
-> 内存压力过大:OOM killer 介入Linux 在 RISC-V 上的异常入口里,读/写/取指 page fault 会先进入架构相关的故障处理路径;在 arch/riscv/mm/fault.c 里,内核会先判断是不是用户态、是不是权限错误,再决定是补页、报错,还是直接给进程一个致命信号。
段错误到底是什么
段错误不是玄学。它的本质是:进程访问了一个不该访问的虚拟地址。
最常见的几种情况很朴素:
- 空指针解引用
- 访问已经释放的内存
- 数组越界,把别的对象踩坏了
- 试图往只读页里写
- 栈太深,栈空间被耗尽
这类错误之所以会变成 SIGSEGV,是因为页表告诉 CPU:这块地址要么根本没映射,要么权限不对。Documentation/mm/page_tables.rst 里说得很直接,page fault 既可能来自 lazy allocation、Copy-on-Write,也可能来自代码 bug 或恶意构造的地址。如果是在用户态,内核就发 Segmentation Fault。
你可以把它理解成门禁系统:
地址存在且有权限 -> 放行
地址不存在 -> 你踩空了
地址存在但没权限 -> 你试图进不该进的房间前者通常继续执行,后两种就很容易看到崩溃。
为什么“非法访问”有时不是立刻崩
这里容易误解。程序并不是一碰到内存就死,它先触发的是 page fault。很多 page fault 是正常的,比如第一次访问某段懒分配内存,或者第一次写入 Copy-on-Write 页。
真正决定生死的,是 fault 能不能被内核修复。
如果地址属于进程的合法虚拟空间,只是这页还没在内存里,内核可能去把数据补回来。比如文件映射会从磁盘把页读进来,匿名页可能在需要时才分配。
如果地址根本不属于这个进程,或者权限不对,arch/riscv/mm/fault.c 里最终会走到 bad_area*() 这类路径,用户态看到的就是 SIGSEGV。
所以你在日志里常见的“Segmentation fault”,往往不是程序自己喊疼,而是内核在说:这次访问我不认。
OOM 和段错误不是一回事
这两个名字经常被混在一起,但它们解决的是完全不同的问题。
段错误是“你访问错了地址”。 OOM 是“系统已经没法再分配内存了”。
Linux 不会无限制地给你画饼。内存紧张时,内核会先回收缓存、写回脏页、换出不活跃页面;实在腾不出来,就会进入 OOM 流程。Documentation/admin-guide/sysctl/vm.rst 里把这件事写得很清楚:oom_kill_allocating_task、oom_dump_tasks、panic_on_oom 都是在描述内存耗尽时内核怎么选人、怎么记录、要不要直接崩整机。
这时候被杀掉的,通常不是“最坏”的程序,而是“最适合被杀”的程序。内核会根据内存占用、oom_score_adj、任务行为等因素挑一个牺牲品。目的不是惩罚,而是让系统活下来。
两种崩溃,体感却很像
用户看到的现象有时很接近:
| 现象 | 更像什么 | | --- | --- | | 运行到一半突然退出,终端提示 Segmentation fault | 非法访问 / 权限错误 | | 进程直接消失,dmesg 里有 OOM 记录 | 内存耗尽,被内核杀掉 | | 整机开始卡,最后某个程序被杀 | 持续内存压力,进入回收和 OOM |
这也是为什么排障时别只盯着“崩了”。先看它是怎么崩的,比盯着崩溃本身更有用。
char *p = NULL;
*p = 'x'; /* 大概率直接 SIGSEGV */
char *q = malloc(1UL << 30);
if (!q) {
/* 这里不是崩溃,是分配失败 */
}第一种是地址错了,第二种是资源不够。症状都可能是“程序没了”,根因完全不同。
最后一句
程序崩溃并不神秘。段错误说明你越过了虚拟内存边界,非法访问说明页表和权限不允许,OOM 说明系统已经没法再优雅地分配内存了。
把这三件事分开看,很多“代码怎么一运行就挂”的问题就会清楚得多。你下次如果看到崩溃日志,先找的是 SIGSEGV 还是 OOM killer,这一步通常就已经把排查方向缩小了一半。
京公网安备11011202100605号