一、引言
很多人把"App 闪退"统称为崩溃,但从操作系统视角看,用户看到的"闪退"其实有两种完全不同的成因:
| 类别 | 英文表述 | 触发者 | 典型场景 | App 内能否捕获 |
|---|---|---|---|---|
| 崩溃 | Crash / Exception | App 自己的代码,在进程内部触发了不可恢复的错误 | 野指针、数组越界、未捕获异常、除零、调用 abort() |
能,进程还活着,有机会执行处理逻辑 |
| (系统)终止 | Termination | 进程外的系统组件(内核、RunningBoard、Watchdog、Jetsam)直接下发"死刑判决" | 主线程卡死超时(0x8badf00d)、内存爆了被杀(FOOM)、持锁后台(0xdead10cc)、过热(0xc00010ff) | 不能直接捕获,只能事后推断 |
区分二者的分水岭是一个信号:SIGKILL。
- SIGKILL(9 号信号)是内核级"立即终止",不可以被捕获、阻塞或忽略 (这是 POSIX 硬性规定,
SIGKILL和SIGSTOP是两个唯二的例外)。进程在收到它的瞬间死亡,没有任何用户态代码可以执行。 - 真正的 Crash,进程在死亡前会先陷入内核异常处理流程,App 注册的处理器(handler)可以插在这个流程里,把现场记录下来,再让进程"按原计划死亡"。
Apple DTS 工程师的原话总结得很到位:Crash 是"running code from within its process"触发的;Termination 是"an external observer outside the app process"终止的。所有 Crash 采集 SDK 的能力边界,本质上都由这条分界线决定。
POSIX 硬性规定SIGKILL无法捕获,iOS和安卓都是这样吗?
二、POSIX 是什么?
POSIX (Portable Operating System Interface,可移植操作系统接口)是一套由 IEEE 制定的操作系统接口标准,不是某个具体的操作系统,也不是某个具体的库。
它规定了很多东西,比如:
- 系统调用/库函数的行为(
open、read、write、fork、kill等) - 进程、信号、线程、文件、终端、管道等机制应该怎么表现
- 各种常量的含义,比如信号编号
为什么要它? 因为 Unix 有很多分支(Linux、macOS、各种 BSD、Solaris......),如果每个系统各搞一套,程序就没法移植。POSIX 定了统一的"契约",只要程序只用 POSIX 规定的接口,理论上就能在这些系统上编译运行。
所以文中说"这是 POSIX 硬性规定",意思是:这不是 Linux 自己随便定的,而是标准要求所有符合 POSIX 的系统都必须这样 ------SIGKILL 和 SIGSTOP 不能被捕获、阻塞或忽略。
三、 安卓和 iOS 都是 POSIX 吗?
要分开看:
-
Android :内核是 Linux,Linux 基本符合 POSIX(严格说是"大部分符合",并非完全认证)。所以 Android 在内核和底层系统调用层面是 POSIX 风格的,信号、进程这些机制都遵循 POSIX 语义。但 Android 的上层应用框架(Java/Kotlin + ART 虚拟机)是另一套东西,普通 App 开发者一般接触不到 POSIX 信号。
-
iOS :内核是 Darwin(基于 Mach + BSD),BSD 部分是 POSIX 兼容的 ,macOS 还正式通过了 POSIX 认证。所以 iOS 底层同样是 POSIX 风格,
SIGKILL、SIGSTOP的语义也遵循 POSIX。但 iOS 对进程和信号的管理比普通 Unix 严格得多(App 沙箱、系统统一管理生命周期),普通 App 也很少直接玩信号。
一句话:两者底层都是 POSIX 风格的系统,但上层应用模型都不是"裸 POSIX",普通开发者通常不直接操作信号。
四、 用户态、内核态,一共有几个态?怎么协作?
"态"(privilege level / ring)指的是 CPU 的特权级别 。常见说法是两个态:
| 态 | 别称 | 能干什么 |
|---|---|---|
| 用户态 | user mode | 只能访问受限内存,不能直接操作硬件、不能执行特权指令 |
| 内核态 | kernel mode / supervisor mode | 可访问全部内存、硬件、执行特权指令 |
(x86 硬件上其实有 4 个 ring:ring 0~3。但通用操作系统一般只用两个:ring 0 = 内核态,ring 3 = 用户态。所以"操作系统有几个这样的态"通常答两个。)
如何协作?
- 用户程序在用户态运行,干普通活。
- 当它需要内核服务(读文件、发信号、创建进程)时,通过系统调用 (syscall)主动"陷入"内核,CPU 切换到内核态。
- 内核在内核态完成操作,把结果返回,CPU 再切回用户态继续跑用户代码。
- 另外,中断和异常也会让 CPU 从用户态切到内核态(比如时钟中断、缺页异常),处理完再回来。
关键点:用户态代码无法直接执行特权操作,必须"请求"内核代劳。这正是文中那句话的意思------
进程在收到 SIGKILL 的瞬间死亡,没有任何用户态代码可以执行。
因为终止进程这件事是内核在内核态直接完成的 :内核把该进程从调度队列里摘掉、回收资源,根本不会把控制权交还给用户态去运行什么"信号处理函数"。普通信号(如 SIGINT)可以注册 handler,内核会安排用户态代码去执行 handler;但 SIGKILL/SIGSTOP 被 POSIX 规定为不可捕获、不可阻塞、不可忽略,所以内核直接处理,用户态没有任何插手的余地。
小结
- POSIX = 操作系统接口标准,规定信号等机制的行为。
- Android/iOS 底层都是 POSIX 风格(Linux / Darwin-BSD),但上层应用模型不是裸 POSIX。
- 两个态:用户态、内核态;用户态通过系统调用/中断进入内核态请求服务,内核处理完再返回。
- SIGKILL 之所以"瞬间死亡、用户态无法干预",是因为它由内核在内核态直接执行,且 POSIX 强制它不可捕获/阻塞/忽略。