引言:从一次触摸到"应用无响应"
当你在 Android 手机上点击一个按钮,屏幕却没有反应,几秒后系统弹出"应用无响应"(ANR)对话框时,这背后究竟发生了什么?很多人把 ANR 简单归结为"主线程做了耗时操作",但真相远不止于此。
ANR 的判定虽然发生在 Android 用户态框架,但根因往往深埋在内核的调度、内存、IO 和进程间通信(IPC)路径中。本文将带你沿着一条完整的链路,从硬件中断到用户态超时,梳理 ANR 的内核底层逻辑。
读码说明 :本文基于 Linux stable
v7.2.2(Greg K-H 发布)[1](#1) 与 AOSPandroid-17.0.0_r1标签[2](#2)[3](#3)[4](#4) 进行机制分析。需要强调的是,Android 17 设备实际内核可能是下游 common kernel/LTS,不一定就是上游 v7.2.2;本文用 v7.2.2 解释"机制",不要把它当成每台 Android 17 设备的精确二进制版本。
1. 一句话结论
ANR 不是内核主动判定"应用死了",而是 Android 用户态框架用超时器发现"某个该被快速消费的事件/回调没有完成"。但超时背后的根因经常落在内核路径上:中断与 input 事件入队延迟、evdev 唤醒后调度不到、UI 线程处于 R/S/D/frozen 状态、binder 事务排队或目标 binder 线程不消费、锁与优先级反转、直接内存回收/IO 写回把任务拖进 D 态、cgroup freezer/低内存/热限频让"5 秒窗口"被系统性压缩。
完整链条如下:
text
硬件 IRQ/线程化 IRQ/kworker
-> input core: input_event()->input_handle_event()->input_pass_values()
-> evdev: evdev_events()->evdev_pass_values() 写 per-client ring,wake_up_interruptible_poll()
-> try_to_wake_up()/CFS/EAS/调度延迟/热限频/cgroup
-> system_server InputReader/EventHub epoll 读取 /dev/input/event*
-> InputDispatcher 计算 dispatch deadline,向应用 InputChannel/Looper 投递
-> 应用 UI 线程 nativePollOnce/epoll_wait 被唤醒,处理 input/draw/binder/handler
-> 若 UI 线程 R 不让出、S 在 futex/epoll/binder、D 在 IO/reclaim、或被 freezer 冻住
-> InputDispatcher/AMS/ActiveServices/BroadcastQueue 超时触发 appNotResponding
-> dump traces:SIGQUIT、/proc/stat、PSI、CPU usage、binder/lock/contention
-> 弹 ANR 对话框或 kill 进程;SIGKILL/exit 触发 binder death、窗口与输入通道清理
2. 用户态判定面:ANR 的"表"
Android 暴露给开发者的 ANR 类型,本质上是几个用户态 watchdog/deadline:
| 类型 | 典型阈值 | 触发点 | 内核含义 |
|---|---|---|---|
| Input dispatching timeout | 约 5 s | InputDispatcher 发现 focused window/ touched window 没消费事件 | 事件已从内核进入用户态,但应用 UI 线程没及时取走或没完成处理;也可能是 InputReader/Dispatcher 自己没被调度 |
| Broadcast timeout | 前台约 10 s,后台约 60 s | BroadcastQueue 等 onReceive() 完成 |
onReceive() 跑在主线程;主线程被 binder/IO/锁/GC 拖住即超时 |
| Service timeout | 前台约 20 s,后台约 200 s | ActiveServices 等 service start/exec 完成 |
service 回调仍在主线程或受主线程锁影响 |
| Content provider publish timeout | 约 10 s | 进程 attach 后 publish provider 超时 | 进程启动、binder、类加载、IO、锁竞争叠加 |
| Watchdog/system_server | 系统级 | system_server 关键 Handler/Monitor 不回报 | system_server 自己死锁或被内核资源拖死,可能引发整机重启/系统性 ANR |
这些阈值在近几个 Android 大版本中长期稳定;公开资料仍按 input 5 s、broadcast 10/60 s、service 20/200 s 讲解[5](#5)。而 Android 15 的 AnrLatencyTracker 已把 ANR 类型细分为 input、broadcast、service、provider、short FGS、job service 等,并明确记录 updateCpuStatsNow、currentPsiState、AMS/global/proc/anr record 锁竞争、dump 耗时等指标[6](#6)。Android 17 阅读时应以 android-17.0.0_r1 的 frameworks/base 与 frameworks/native 对应文件为准,但机制链条没有发生"从超时检测变成内核检测"的本质变化。
3. 内核第一公里:输入事件如何到 evdev
触摸/按键进入 Linux 的第一段是中断上下文或线程化中断/kworker。驱动调用 input core 上报事件;drivers/input/input.c 中 input_event() 在拿 dev->event_lock 并关中断后进入 input_handle_event(),再经 input_event_dispose() 把 value 暂存到 dev->vals,遇到 INPUT_FLUSH 或缓冲将满时调用 input_pass_values()。input_pass_values() 在 RCU 保护下遍历 dev->h_list,优先给 grabbed handle,再分发给打开的 handler;evdev handler 的 .events = evdev_events 会被调用[7](#7)。
evdev 的关键是"每客户端环形缓冲 + 等待队列"。drivers/input/evdev.c 中 evdev_events() 对每个 client 调 evdev_pass_values();后者用 client->buffer_lock 把 struct input_event 写入 ring,写满时插入 SYN_DROPPED,最后 wake_up_interruptible_poll(&client->wait, EPOLLIN|...) 唤醒读者[8](#8)。读者侧 evdev_read() 先 evdev_fetch_next_event() 取包;若无事件且非 O_NONBLOCK,就 wait_event_interruptible(client->wait, client->packet_head != client->tail || !evdev->exist || client->revoked) 睡眠[9](#9)。
这段对 ANR 的意义:
- 如果中断风暴、IRQ affinity 错误、线程化 IRQ 被高优先级 RT/热限频压制,事件在到达 evdev 前就晚。
- 如果 InputReader 不读,per-client ring 会溢出并出现
SYN_DROPPED;这是"消费端太慢"的内核痕迹。 wake_up_interruptible_poll()只把任务标记为可运行并触发try_to_wake_up();真正何时运行由调度器决定。唤醒到运行之间的延迟是 ANR 的隐形部分。
4. 调度层:被唤醒不等于正在运行
evdev_read/epoll_wait/binder/futex 的睡眠最终都会落到 schedule();唤醒走 try_to_wake_up()/wake_up_process(),把任务入队到 runqueue。kernel/sched/core.c 明确区分了睡眠路径 __schedule() 与唤醒路径 try_to_wake_up(),并处理跨 CPU 唤醒的 ttwu_queue_wakelist()/sched_ttwu_pending();schedule()、io_schedule()、preempt_schedule_irq() 是不同进入调度器的入口[10](#10)。CFS 侧 kernel/sched/fair.c 用 enqueue_task_fair()、update_curr()、place_entity()、pick_next_task_fair()、put_prev_task_fair() 维护 vruntime/Deadline 与挑选下一个任务[11](#11)。
ANR 分析里要把"主线程状态"翻译成调度语言:
R但长时间不响应 :可能在自旋、密集 GC/JIT、死循环,或同 CPU 被更高优先级任务/RT/中断挤占;看Load、CPU usage、sched:sched_switch、sched:sched_wakeup、runqueue latency、thermal。S:可中断睡眠,如epoll_wait、futex、binderTASK_INTERRUPTIBLE;要看 wchan、锁、binder 对端。D:不可中断睡眠,多在 IO、直接回收、块设备、文件系统、dm-crypt/dm-verity、某些驱动;这是 5 s input ANR 里最危险的内核态,因为信号也不能立刻把它拉出。- frozen:cgroup freezer 把后台/受限进程冻结;若误冻前台依赖链或 binder 对端,表现为"所有线程都不跑但 CPU 不高"。
Linux 7.2 的上游变化也说明这条链仍在演进:7.2 引入更 cache-aware 的任务放置、内存回收与 swap 性能改进、GPU fair 调度等[12](#12);这些改动会影响"同 LLC 任务放置""回收延迟""GPU/渲染并发"的边际行为,但不会改变 ANR 的基本结构。
5. binder:同步 IPC 把别人的卡顿变成你的 ANR
Android framework 大量路径是 binder 同步调用。内核入口是 /dev/binder 的 ioctl(BINDER_WRITE_READ):binder_ioctl_write_read() 先 binder_thread_write() 写命令,再 binder_thread_read() 读返回;读侧无工作且阻塞时进入 binder_wait_for_work(),它 prepare_to_wait(&thread->wait, ..., TASK_INTERRUPTIBLE|TASK_FREEZABLE),确认无工作后 schedule(),醒来再检查 signal_pending(current)[13](#13)。
事务发送侧 binder_transaction() 会解析 target node/thread,分配 struct binder_transaction,记录 t->priority = task_nice(current),并用 binder_alloc_new_buf(&target_proc->alloc, ...) 在目标进程 binder 映射区分配缓冲区;失败会走 failed reply 与 buffer release[14](#14)。目标侧被 wake_up_interruptible_sync(&target_thread->wait) 或 proc 级唤醒拉起后,在 binder_thread_read() 中把 BINDER_WORK_TRANSACTION 转成 BR_TRANSACTION 拷贝到用户态。
这把 ANR 的根因扩展成"跨进程依赖链":
- UI 线程同步 binder 到 system_server,而 system_server 持 AMS/global lock 等另一个 binder;锁链末端线程 D 在 IO,UI 线程即便代码"只调了一个系统 API"也会超时。
- 目标进程 binder 线程池耗尽、所有 binder 线程都在等同一个锁、目标进程被 freeze 或 oom_adj 被压低后调度不到,都会让 caller 卡在
binder_thread_read/等待 reply。 - binder 自己的锁是分层的:
proc->outer_lock保护 ref,node->lock保护 node 大多数字段,proc->inner_lock保护 thread/node list、todo、delivered_death、async_todo 与 transaction_stack[15](#15)。这些 spinlock 本身短,但高并发下会形成 system_server 热点;AnrLatencyTracker专门统计 AMS/global/proc/anr record 锁竞争,说明 framework 已把"报告 ANR 时也被锁拖慢"当成一等问题[6](#6)。 - binder buffer 分配与 mmap/shrinker 也受内存压力影响;
binder_alloc.c使用alloc->mutex、page install、mmap_read_lock/per-vma lock、shrinker freelist 管理远程进程映射页[16](#16)。内存紧张时,binder 事务可能不是"锁死",而是分配/拷贝/缺页路径变慢。
6. 锁、futex 与优先级反转
Java synchronized、ART monitor、ReentrantLock/LockSupport.park、native pthread_mutex/condition_variable 到内核后大多落在 futex/等待队列/调度睡眠上;真正带内核优先级继承的是 rtmutex/futex PI 路径。kernel/locking/rtmutex.c 的慢路径 task_blocks_on_rt_mutex()、rt_mutex_slowlock_block()、remove_waiter() 维护 waiter 树与 PI 链,rt_mutex_adjust_pi_chain()/__rt_mutex_adjust_prio 负责沿锁链调整优先级[17](#17)。
对 ANR 要谨慎区分:
- 普通 pthread/ART 锁不等于内核 rtmutex PI;低优先级线程持锁被抢占,高优先级 UI 线程等锁,仍会优先级反转。
- 即便有 PI,PI 只解决"持锁者优先级被压低"的一类问题,解决不了持锁者 D 在 IO、被 freezer 冻住、在 direct reclaim 里出不来。
- system_server 的 Java 锁(AMS/global lock、proc lock、WindowManager lock)与 native lock 叠加时,kernel 只能看到任务睡眠/唤醒/调度,不能自动理解"业务锁语义";所以 traces 里要同时看 Java monitor 与 native wchan。
7. 内存与 IO:把 UI 线程拖进 D 态的典型路径
内存压力下,分配路径可能进入直接回收:mm/vmscan.c 中 try_to_free_pages() 进入 do_try_to_free_pages(),失败/拥塞时 throttle_direct_reclaim()、reclaim_throttle()、too_many_isolated() 会让分配者等待;kswapd 则 balance_pgdat() 异步回收,wakeup_kswapd() 在水位不足时唤醒它[18](#18)。若回收仍失败,mm/oom_kill.c 的 out_of_memory() 选择 victim,oom_kill_process()/__oom_kill_process() 打印 oom 信息、标记 victim、发 SIGKILL,并 queue_oom_reaper() 让 oom_reaper 尽快回收 mm[19](#19)。
Android 通常在 kernel OOM 前由 lmkd 依据 PSI/oom_score_adj 杀后台;但当 lmkd 反应慢、PSI 触发阈值不合适、突发分配/缺页集中在 UI 路径上时,UI 线程仍可能先进入 direct reclaim 或 major fault 风暴。ANR log 里常见 Load、CPU usage、/proc/pressure/memory 输出;真实用户报告里也能看到 ANR 直接附带 PSI memory 段[20](#20)。PSI 由 kernel/sched/psi.c 维护:psi_task_change()/psi_task_switch() 在任务状态切换时更新 stall,psi_memstall_enter()/leave() 标记内存 stall 区间,/proc/pressure/io|cpu|memory 由 proc ops 暴露[21](#21)。
IO 侧同理:SQLite/fsync/读大图/解码/日志写 flash 会把任务放进 D;块层慢、UFS/eMMC 拥塞、F2FS GC、ext4 journal、dm-crypt/dm-verity、热限频都会把"5 秒"吃光。内核 hung task watchdog kernel/hung_task.c 专门检测 TASK_UNINTERRUPTIBLE 长时间不切换:khungtaskd 周期检查,阈值由 sysctl_hung_task_timeout_secs 控制,可打印/ panic[22](#22)。但它默认量级远大于 5 s,所以 hung task 是"更重的卡死证据",不是 input ANR 的第一触发器。
8. 超时到 dump/kill:ANR 的收尾也在内核留痕
当 InputDispatcher/AMS/ActiveServices/BroadcastQueue 判定超时后,framework 会进入 appNotResponding 路径:记录 reason、CPU usage、load、PSI,dump 第一进程/相关 native 进程/额外进程栈,统计锁竞争与 dump 耗时;Android 15 的 AnrLatencyTracker 已把这些指标结构化,包括 currentPsiStateTotalLatency、updateCpuStatsNowTotalLatency、global/pid/AMS/proc/anr record lock contention、early dump 状态等[6](#6)。
栈 dump 常通过向目标进程发 SIGQUIT 让 ART 写 traces;进程终止则走 signal/exit。kernel/signal.c 中 send_sig()/do_send_sig_info() 最终 __send_signal_locked(),complete_signal() 对 SIGKILL 这类不可捕捉信号会唤醒/标记线程组,zap_other_threads() 在 exec/致命路径给同组线程塞 SIGKILL 并 signal_wake_up()[23](#23)。进程退出后,binder death recipient、input channel 关闭、窗口移除、AMS 记录清理,才把这次 ANR 的"现场"结束。
9. 用内核证据反推 ANR:一张诊断矩阵
| 日志/现场 | 更可能的内核层 | 下一步看哪里 |
|---|---|---|
Input dispatching timed out ... Waited 5004ms ... wait queue head age |
UI 线程未消费;或 InputDispatcher/InputReader 调度延迟;或 input channel 对端慢 | traces 主线程状态;dumpsys input;sched_wakeup/sched_switch;evdev SYN_DROPPED;InputReader/Dispatcher CPU |
TOTAL 接近 100%,app 高 user |
R 态自旋/GC/JIT/死循环;调度只是放大器 | simpleperf/Perfetto、schedstat、runqueue latency、thermal、big.LITTLE/EAS 放置 |
CPU 很低,主线程 WAIT/MONITOR/park |
futex/锁/binder 同步等待;锁持有者被抢占或 D | Java monitor + native backtrace;pidstat -w -t;off-CPU;binder debugfs;lock owner 的 wchan |
高 iowait、PSI io/memory full 高、major faults 多 |
direct reclaim、kswapd 不及、块 IO/文件系统/dm 慢 | /proc/pressure/*、vmstat、mm_vmscan_* trace、block_rq_*、F2FS/ext4、zram/swap、lmkd 日志 |
主线程或 binder 对端 D、伴随 hung task |
不可中断 IO/驱动/回收;5 s ANR 只是更早的用户态报警 | /proc/<pid>/stack、wchan、hung task、mmc/ufs、dm-verity/dm-crypt、文件系统 |
| binder failed/-ENOMEM/async backlog/binder alloc 慢 | binder 线程池耗尽、目标进程死/冻、buffer 映射压力 | /sys/kernel/debug/binder/*、dumpsys binder、transaction log、目标进程状态与冻结状态 |
| system_server Watchdog 先炸 | AMS/WMS/PackageManager 锁或 native 死锁;整机性输入无响应 | dumpsys watchdog、system_server traces、LockGuard、native deadlocks、RT/IRQ 优先级 |
常用观测命令:
adb shell cat /proc/pressure/{cpu,io,memory}adb shell pidstat -w -t -p <pid> 1adb shell cat /proc/<pid>/task/<tid>/wchan与stackadb shell dumpsys input|activity|window|binder|meminfo|cpuinfo|watchdog- Perfetto/ftrace 开
sched, irq, binder_driver, binder_lock, input, vmscan, oom, psi, block, ext4/f2fs, am, wm, view, res - ftrace 重点事件:
sched_switch/sched_wakeup/sched_pi_setprio、irq_handler_entry/exit、binder_transaction/binder_transaction_received/binder_wait_for_work/binder_update_page_range、mm_vmscan_direct_reclaim_begin/end、mm_vmscan_kswapd_wake、oom_kill、block_rq_issue/complete、hungtask等
10. 修复与预防:按层打断链条
应用层 :主线程不做网络/DB/大 IO/重解码;用 StrictMode 抓主线程 IO;减少主线程持有的 Java 锁与跨线程锁环;同步 binder 调用改异步或加超时;大 bitmap/文件走工作线程并控制并发;避免在 onReceive/service 回调做长任务;启动路径延迟初始化 provider/ heavy component。
系统/内核层 :保证 input IRQ 与 InputReader/Dispatcher 有足够调度优先级和合理 CPU affinity;谨慎使用 RT/FIFO UI(Android 有 sys.use_fifo_ui 这类开关,但要防 RT 饥饿);调 cpuset/uclamp/EAS 让前台关键线程不被后台批量任务挤出 LLC/大核;binder 线程池与优先级策略匹配业务峰值;lmkd/PSI 阈值、minfree、swappiness、watermark、MGLRU/zram/swap 要按内存与闪存寿命调;IO 侧用合适调度器、readahead、writeback 阈值,限制主进程 fsync/日志洪峰;热限频与 GPU fair 调度下要留 UI 余量;调试期可缩短 kernel.hung_task_timeout_secs、开 hung task warnings、softlockup/hardlockup 检测,但生产要防误 panic。
11. 最终心智模型
把 ANR 想成一条"deadline 接力赛":内核只负责把事件、唤醒、锁、内存页、IO 完成、信号送到正确任务;Android framework 在每个接力点放了秒表。任何一棒慢------IRQ 晚、evdev ring 溢出、wake 后不上 CPU、InputDispatcher 排队、UI 线程 D 在 reclaim/IO、binder 对端不读、锁持有者优先级低、freezer 误冻、lmkd/OOM 抖动------都会让秒表先响。
分析 ANR 时不要用"主线程做了耗时操作"一句话结束,而要追问:
- 主线程当时在 R/S/D/frozen 的哪一种?
- 如果是 S/D,wchan 指向哪个内核对象?
- 如果是 R,谁抢走了 CPU?
- 如果 CPU 很闲,为什么调度器没把它放上去?
这样就把 ANR 从 framework 现象落回内核因果链。
-
LWN,Greg Kroah-Hartman 发布 Linux 7.2.2 公告,2026-08-28:https://lwn.net/Articles/1091120/ ↩︎
-
Pulse2,Google 发布 Android 17 并开放 AOSP 源码,2026-06-17:https://pulse2.com/google-releases-android-17-for-supported-pixel-devices/ ↩︎
-
Debian Package Tracker 显示 AOSP frameworks/base 上游已有
17.0.0_r1:https://tracker.debian.org/android-platform-frameworks-base ↩︎ -
BetaWiki 引用 AOSP tag
android-17.0.0_r1的Build.java链接:https://betawiki.net/wiki/Android_Eclair ↩︎ -
Sukai's Blog,基于 Android 10 源码整理 ANR 触发场景与阈值:https://sukaidev.gitee.io/2021/07/30/8c057ce7/ ↩︎
-
xrefandroid,Android 15
AnrLatencyTracker.java(ANR 类型、PSI/CPU/锁竞争/dump 耗时指标):https://xrefandroid.com/android-15.0.0_r1/xref/frameworks/base/core/java/com/android/internal/os/anr/AnrLatencyTracker.java ↩︎ ↩︎ ↩︎ -
Linux stable v7.2.2,
drivers/input/input.c(Greg K-H stable 镜像,与 Bootlin 同 tag 路径):https://raw.githubusercontent.com/gregkh/linux/v7.2.2/drivers/input/input.c ↩︎ -
Linux stable v7.2.2,
drivers/input/evdev.c:evdev_events/evdev_pass_values/wake_up_interruptible_poll:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/drivers/input/evdev.c ↩︎ ↩︎ -
同[8](#8),
evdev_read/evdev_fetch_next_event/wait_event_interruptible。 ↩︎ -
Linux stable v7.2.2,
kernel/sched/core.c:schedule/__schedule/try_to_wake_up/io_schedule/preempt_schedule_irq:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/kernel/sched/core.c ↩︎ -
Linux stable v7.2.2,
kernel/sched/fair.c:CFS 入队/出队/挑选/抢占路径:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/kernel/sched/fair.c ↩︎ -
KernelNewbies,Linux 7.2 特性摘要(cache-aware scheduler、内存回收、swap、GPU fair 调度等):https://kernelnewbies.org/Linux_7.2 ↩︎
-
Linux stable v7.2.2,
drivers/android/binder.c:binder_ioctl_write_read/binder_thread_read/binder_wait_for_work:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/drivers/android/binder.c ↩︎ ↩︎ ↩︎ -
同[13](#13),
binder_transaction/binder_alloc_new_buf/t->priority。 ↩︎ -
同[13](#13),文件头部 locking overview 与
binder_proc_lock/binder_inner_proc_lock/binder_node_lock。 ↩︎ -
Linux stable v7.2.2,
drivers/android/binder_alloc.c:binder 映射页、mutex、mmap lock、shrinker freelist:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/drivers/android/binder_alloc.c ↩︎ -
Linux stable v7.2.2,
kernel/locking/rtmutex.c:rtmutex 慢路径与 PI 链调整:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/kernel/locking/rtmutex.c ↩︎ -
Linux stable v7.2.2,
mm/vmscan.c:direct reclaim/kswapd/throttle:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/mm/vmscan.c ↩︎ -
Linux stable v7.2.2,
mm/oom_kill.c:OOM 选择、kill、reaper:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/mm/oom_kill.c ↩︎ -
Stack Overflow 实例:ANR log 中
Waited 5004ms并附/proc/pressure/memory:https://stackoverflow.com/questions/72642545/anr-input-dispatching-timed-out-server-is-not-responding ↩︎ -
Linux stable v7.2.2,
kernel/sched/psi.c:PSI 状态更新与/proc/pressure/*:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/kernel/sched/psi.c ↩︎ -
Linux stable v7.2.2,
kernel/hung_task.c:khungtaskd检测 D 态任务:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/kernel/hung_task.c ↩︎ -
Linux stable v7.2.2,
kernel/signal.c:send_sig/do_send_sig_info/complete_signal/zap_other_threads:https://raw.githubusercontent.com/gregkh/linux/v7.2.2/kernel/signal.c ↩︎