Android 系统工程师(性能/功耗/稳定性)岗位问题深度解析:从内核源码到实战排查
依据:Linux 6.18.7 内核源码(与 Android 17 的 GKI 基线
android17-6.18-lts同一条 LTS 线)+ AOSP 最新源码(Android 16/17)。岗位关键词:性能/系统启动/功耗、Thermal/耗电量/死机、系统崩溃/系统 dump。
本文结构:每个问题域给出【详细代码流程】→【分析方法】→【日志怎么看】→【一般性解决方案】。
0. 岗位在解决什么问题
| 岗位职责 | 典型问题 | 涉及内核子系统 | 涉及 Android 组件 |
|---|---|---|---|
| 性能 | 开机慢、系统启动慢、卡顿、CPU 调度问题 | init、调度器(EEVDF)、cpufreq/schedutil、I/O、内存回收 | init.rc、Zygote、SystemServer、Perfetto |
| 功耗 | 待机耗电大、底电流超标、漏电、发热 | suspend/resume、wakelock、cpuidle、thermal | PowerManager、Doze、Thermal HAL |
| 稳定性 | 死机、系统崩溃、系统 dump | panic/oops、hung task、watchdog、OOM、内存破坏检测 | debuggerd/tombstone、ANR、Watchdog、pstore |
三类问题相互关联:调度既影响性能也影响功耗;发热触发降频导致性能问题;内存泄漏最终演变成 OOM/死机。
第一部分 性能问题
1. 开机慢 / 系统启动慢
1.1.1 详细代码流程
内核启动主路径(init/main.c):
start_kernel() init/main.c ------ 架构初始化、mm_init、sched_init、time_init
└─ rest_init() init/main.c:711 ------ 创建 kernel_init 线程(pid 1)、kthreadd(pid 2)
└─ kernel_init() init/main.c:1474
└─ do_basic_setup() init/main.c:1374
│ └─ do_initcalls() init/main.c:1348 ------ 按 8 个级别执行全部 initcall
│ └─ do_initcall_level(level) init/main.c:1333
│ └─ do_one_initcall(fn) init/main.c:1273 ------ 真正调用驱动初始化函数
├─ console_on_rootfs() init/main.c:1550
└─ run_init_process("/init") ------ exec 用户态 1 号进程,进入 Android 世界
initcall 的 8 个级别(init/main.c:1316,与 include/linux/init.h 对应):
pure → core → postcore → arch → subsys → fs → device → late
do_one_initcall()(main.c:1273)是逐条执行的,源码可见其中埋了 ftrace 跟踪点:
c
int __init_or_module do_one_initcall(initcall_t fn)
{
...
do_trace_initcall_start(fn); // ftrace 事件: initcall_start
ret = fn(); // 真正执行驱动初始化(串行!)
do_trace_initcall_finish(fn, ret);
...
if (irqs_disabled()) { // 驱动初始化回来忘了开中断 → 告警并修复
strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
local_irq_enable();
}
WARN(msgbuf[0], "initcall %pS returned with %s\n", fn, msgbuf);
...
}
关键结论:所有驱动的 initcall 在同一级别内是串行执行的,任何一个驱动 probe 慢(等固件、I2C/存储超时、deferred probe 反复重试)都会直接拉长开机时间。
initcall_debug 是内核原生参数(main.c:819-820):
c
bool initcall_debug;
core_param(initcall_debug, initcall_debug, bool, 0644);
1.1.2 分析方法
- 内核段计时 :boot 参数加
initcall_debug(或运行时echo 1 > /sys/module/kernel/parameters/initcall_debug无效------必须在启动早期,一般改 bootconfig/cmdline),dmesg 会打印每个 initcall 的耗时; - ftrace 方式 :
initcall_start/initcall_finishtrace 事件,可用 Perfetto 抓取; - Android 段计时 :
dmesg里init:打点时间戳、ro.boottime.init.*属性、bootchart(adb shell 'touch /data/bootchart/enabled'重启后取图)、Perfetto 的android.boot数据包; - 分段定位 :bootloader 耗时 → kernel 耗时(dmesg 首行到
Freeing unused kernel memory)→ init 到 Zygote → SystemServer 到ams boot completed→ Launcher 显示。
1.1.3 日志怎么看
# initcall_debug 打开后的典型输出(找耗时大户):
[ 2.345678] calling xxx_driver_init+0x0/0x12c @ 1
[ 4.678901] initcall xxx_driver_init+0x0/0x12c returned 0 after 2333223 usecs
^^^^ 这一条就花了 2.3 秒,直接责任人
# 常用提取命令:
dmesg | grep "initcall.*returned" | sort -t' ' -k9 -n -r | head -20 # 按耗时排序
dmesg | grep -E "calling|initcall" > initcall.txt
# deferred probe 重试(驱动依赖没就绪反复推迟,常见于开机慢):
dmesg | grep -i "defer" # "platform xxx: deferred probe pending"
# Android 侧:
logcat -b events | grep boot_progress # 各启动阶段埋点
dumpsys activity lru | grep -i boot # AMS 启动完成时间
1.1.4 一般性解决方案
| 手段 | 说明 |
|---|---|
| 慢驱动异步化 | 驱动 probe 改 probe_type = PROBE_PREFER_ASYNCHRONOUS,不阻塞 initcall 主链 |
| 消除 deferred probe 乒乓 | 调整驱动依赖顺序(dt 里 phandle 引用、initcall 级别),让被依赖者先就绪 |
| 裁剪 | 移除量产机用不到的驱动/文件系统;裁剪 initcall 数量 |
| 存储优化 | 提升 kernel/ramdisk 读取速度(分区布局、只挂必要 verity) |
| Android 侧 | Zygote 预加载类/资源精简、SystemServer 服务并行启动(SystemServerInitThreadPool)、Launcher 延迟加载 |
2. CPU 调度问题(卡顿、响应慢、大小核放置错误)
1.2.1 详细代码流程
(1)唤醒与切换主循环 (kernel/sched/core.c):
try_to_wake_up(p) core.c:4144 ------ 中断/锁释放/IPC 唤醒任务
├─ select_task_rq(p) ------ 选核:考虑亲和性、核间负载、能效
├─ ttwu_queue → enqueue_task ------ 挂到目标 CPU 的 runqueue
└─ check_preempt_curr() ------ 若优先级更高,设置 TIF_NEED_RESCHED 抢占当前任务
__schedule() core.c ------ 调度点(主动 schedule()/抢占/返回用户态前)
├─ prev = rq->curr
├─ next = pick_next_task(rq) ------ 按调度类依次询问: stop → deadline → fair(EEVDF) → idle
└─ context_switch(rq, prev, next) ------ 切地址空间(switch_mm) + 切寄存器栈(switch_to)
(2)EEVDF 选任务 (kernel/sched/fair.c):6.6 起 CFS 被 EEVDF 取代。每个任务维护 vruntime(虚拟运行时间,fair.c:527 起的 min_vruntime/max_vruntime 体系),调度器在"已 eligible(虚拟已到期该跑了)"的任务里选虚拟截止时间最早的。延迟敏感任务(UI 线程)因 vruntime 落后会被优先选中------这就是"公平"与"低延迟"兼顾的机制。
(3)调频(schedutil governor) (kernel/sched/cpufreq_schedutil.c):
负载变化 → 调度器更新 util(PELT 指数平均负载)
└─ sugov_update_single_freq() cpufreq_schedutil.c:419
├─ sugov_update_single_common() ------ 汇总该 CPU 最新 util
├─ get_next_freq(util, max_cap) ------ util/capacity 线性映射到目标频率
├─ sugov_hold_freq() ------ rate_limit 防抖:距上次调频太近则保持
└─ cpufreq_driver_fast_switch() ------ 快速路径直接写寄存器切频(在中断/调度上下文)
Android 对调频的关键干预是 uclamp(utilization clamp),源码证据(cpufreq_schedutil.c:371-372):
c
/* if capped by uclamp_max, always update to be in compliance */
if (uclamp_rq_is_capped(cpu_rq(sg_cpu->cpu)))
Framework 通过 cgroup 的 cpu.uclamp.min/max 给前台任务设下限------即使 PELT 算出的瞬时负载还没涨上来,也强制把频率抬到下限,解决"负载突增升频慢半拍"的瞬时卡顿。
1.2.2 分析方法
-
Perfetto/systrace 是主力工具 ,关注四类事件:
sched_switch:线程何时跑在哪个核上、跑了多久;sched_wakeup/sched_waking:唤醒到真正运行的调度延迟(runnable 时间过长 = 卡顿根因);cpu_frequency/cpu_idle:频率升降是否与负载匹配;binder_transaction:跨进程调用阻塞。
-
复现路径打点:在卡顿场景(如应用冷启动、滑动列表)抓 10~20 秒 trace,先看 UI 线程和 RenderThread 的 runnable/running 比例。
-
静态节点 :
cat /proc/sched_debug # runqueue 状态、任务分布 cat /sys/devices/system/cpu/cpuX/cpufreq/scaling_governor # 应为 schedutil cat /proc/sys/kernel/sched_util_clamp_min # 全局 uclamp
1.2.3 日志怎么看
# Perfetto 里的典型问题模式:
UI thread: runnable 80ms → running 5ms # 长时间可运行但不被调度 = CPU 被占满/绑错核
RenderThread: running 但 cpu_frequency 很低 # 频率没拉起来 = 调频/uclamp 问题
surfaceflinger 等大锁 → binder 调用排队 # 系统服务锁竞争
# dmesg 侧辅助:
dmesg | grep -i "sched" # 调度器异常(如 CPU hotplug 异常)
cat /sys/kernel/debug/sched/debug | less # 详细调度统计
1.2.4 一般性解决方案
| 问题模式 | 解决方案 |
|---|---|
| 关键线程 runnable 过长 | 提高 uclamp.min / 调度优先级;把重后台任务塞进 background cgroup(限核限频) |
| 升频慢 | 调 schedutil 的 up_rate_limit_us、开 fast_switch;对游戏/相机场景用 perf HAL 锁频 |
| 大小核放错 | 校准 energy model(kernel/power/energy_model.c 的功耗表);关键线程 sched_setaffinity 绑大核 |
| 中断集中 | IRQ 亲和性分散到不同核(/proc/irq/N/smp_affinity),避免中断打爆小核 |
| 锁竞争 | perfetto 定位持锁方,拆锁/减少 critical section(SystemServer 的 AMS 锁是常客) |
第二部分 功耗问题
3. 待机耗电大 / 底电流超标 / 漏电
2.1.1 详细代码流程
灭屏后 Android 的 PowerManager 写 /sys/power/state,进入内核睡眠主流程(kernel/power/suspend.c):
echo mem > /sys/power/state
└─ pm_suspend()
└─ enter_state(state) suspend.c:569
├─ suspend_prepare() ------ 冻结用户进程(freezer)、冻结内核线程
│ └─ freeze_processes() ------ 给每个进程发 fake signal 使其进 refrigerator
├─ suspend_devices_and_enter() suspend.c:497
│ ├─ dpm_suspend_start() ------ 所有设备驱动 ->suspend() 回调
│ └─ suspend_enter() suspend.c:412 ★核心
│ ├─ dpm_suspend_late() suspend.c:420 ------ 设备 late 阶段
│ ├─ dpm_suspend_noirq() suspend.c:429 ------ 关中断前的最后回调
│ ├─ pm_sleep_disable_secondary_cpus() suspend.c:446 ------ 只留 CPU0
│ ├─ arch_suspend_disable_irqs() suspend.c:450
│ ├─ syscore_suspend() suspend.c:455 ------ 中断控制器/定时器等核心部件
│ ├─ *wakeup = pm_wakeup_pending() suspend.c:457 ★睡前最后检查
│ ├─ suspend_ops->enter(state) suspend.c:461 ------ SoC 平台钩子:CPU 进 WFI/掉电
│ └─ (被唤醒后)syscore_resume() → 开次核 → 设备逐级 resume
└─ suspend_finish() ------ 解冻进程,系统恢复运行
睡眠中止(abort suspend)机制 ------待机耗电问题的核心代码(drivers/base/power/wakeup.c:875):
c
bool pm_wakeup_pending(void)
{
...
split_counters(&cnt, &inpr);
ret = (cnt != saved_count || inpr > 0); // 有新的唤醒事件 或 有正在处理的唤醒
...
if (ret) {
pm_pr_dbg("Wakeup pending, aborting suspend\n");
pm_print_active_wakeup_sources(); // 打印是谁在拦着睡觉 ★
}
return ret || atomic_read(&pm_abort_suspend) > 0;
}
pm_print_active_wakeup_sources()(wakeup.c:841)遍历 wakeup_sources 链表,打印 active wakeup source: <名字>------这个名字就是底电流超标的第一嫌疑人。
用户态 wakelock 的落点(kernel/power/wakelock.c):App 持 PARTIAL_WAKE_LOCK → PowerManagerService 写 /sys/power/wake_lock → pm_wake_lock() → __pm_stay_awake(wl->ws)(wakelock.c:244)→ 该 wakeup source 置 active → pm_wakeup_pending() 永远返回真 → 系统无法睡。
2.1.2 分析方法
-
确认睡没睡下去 :
cat /sys/power/suspend_stats/success /sys/power/suspend_stats/fail cat /sys/kernel/debug/suspend_stats # 各阶段失败计数: failed_freeze / failed_prepare / # failed_suspend / failed_suspend_late / failed_suspend_noirqsuccess不涨或fail猛涨 → 系统在反复尝试睡眠又失败。 -
找拦路的唤醒源 :
cat /sys/kernel/debug/wakeup_sources # 字段: name | active_count | event_count | wakeup_count | expire_count | # active_since | total_time(ms) | max_time(ms) | last_change(ms) | prevent_suspend_time(ms) # 重点看 active_since != 0(此刻正持锁)和 prevent_suspend_time 大的 -
Android 侧对账 :
dumpsys power | grep -i "wake_lock\|PARTIAL" # 谁持用户态锁 dumpsys alarm | head -50 # alarm 是否泛滥 dumpsys batterystats > bs.txt # 唤醒次数/WakeLock 持有者/各应用耗电 -
硬件对账:功耗仪看电流波形------基线高(没睡下去)vs 周期性毛刺(被频繁唤醒)是两类不同问题。
2.1.3 日志怎么看
# dmesg 中一次完整的 suspend 尝试:
[ 123.456] PM: suspend entry (deep) ← 开始睡
[ 123.567] Freezing user space processes ... (elapsed 0.012 seconds) done.
[ 123.789] Freezing remaining freezable tasks ... done.
[ 124.001] PM: suspend of devices complete after 200.123 msecs
[ 124.100] PM: Wakeup pending, aborting suspend ← ★ 被拦下了
[ 124.101] active wakeup source: qcom_rx_wakelock ← ★ 罪魁祸首(此处示例为 Modem 锁)
[ 124.200] PM: suspend exit
# 如果是"睡下去又被秒唤醒":
[ 130.000] PM: suspend entry (deep)
[ 130.500] PM: suspend exit ← 只睡了 0.5s
# 看唤醒中断源:
cat /sys/kernel/debug/irq_wake_source (厂商节点,名字因平台而异)
dmesg | grep -i "wake" # 找唤醒中断号,/proc/interrupts 对号入座
2.1.4 一般性解决方案
| 问题 | 解决方案 |
|---|---|
| 驱动异常持锁 | 修驱动:wakeup source 用完后必须 __pm_relax();用带超时的 pm_wakeup_event(ws, ms) 代替永久持锁 |
| App 持 PARTIAL_WAKE_LOCK | 推动应用修复;系统侧 Doze/App Standby 限制;厂商可加 wakelock 白名单管控 |
| alarm/定时器频繁唤醒 | alarm 对齐批处理(setWindow/setExact 管控)、JobScheduler 合并任务 |
| 传感器/Modem 唤醒频繁 | 调上报频率;弱网搜网功耗找 Modem 团队 |
| 能睡但底电流高("漏电") | 查 cpuidle 是否进最深 state(/sys/devices/system/cpu/cpu0/cpuidle/state*/time)、regulator/clock 是否关断(/sys/kernel/debug/clk/clk_summary)、DDR 自刷新、GPIO 漏电------这类多由厂商 BSP 电源域配置错误导致 |
4. 发热 / Thermal
2.2.1 详细代码流程
内核热管理框架(drivers/thermal/)的分层:
温度传感器(thermal sensor) → thermal zone(thermal_core.c 周期更新)
└─ thermal_zone_device_update()
├─ tz->ops->get_temp() ------ 读当前温度
├─ 遍历 trip points(触发点: active/passive/hot/critical)
└─ 越限 → 调用该 zone 绑定的 governor(温控策略)
├─ step_wise(gov_step_wise.c)------ 阶梯式:
│ get_target_state() gov_step_wise.c:32
│ 温度升(THERMAL_TREND_RAISING) → 冷却档位 +1
│ 温度降(THERMAL_TREND_DROPPING) → 冷却档位 -1
│ → cdev->ops->set_cur_state() → 实际动作: CPU 降频/GPU 降频/风扇/限充电
└─ power_allocator(gov_power_allocator.c,IPA 智能功率分配)
divvy_up_power() gov_power_allocator.c:350
------ 按功耗预算在 big/LITTLE/GPU 各 power actor 间分功率,
适合大小核 SoC 的精细化温控
step_wise 的核心判断逻辑(gov_step_wise.c:53-66):
c
if (throttle) {
if (trend == THERMAL_TREND_RAISING)
return clamp(cur_state + 1, instance->lower, instance->upper); // 升温→加重冷却
if (trend == THERMAL_TREND_DROPPING)
return clamp(cur_state - 1, min(instance->lower + 1, instance->upper),
instance->upper); // 降温→逐步松开
}
Android 侧:内核 thermal zone 经 Thermal HAL(hardware/interfaces/thermal)上报 Framework,高温时触发降亮度、限充电电流、限制性能模式,critical 级别直接关机保护。
2.2.2 分析方法
# 看所有温区当前温度和触发点:
for z in /sys/class/thermal/thermal_zone*; do
echo "$z: $(cat $z/type) = $(cat $z/temp)"
done
cat /sys/class/thermal/thermal_zone0/trip_point_*_temp # 各触发点阈值
cat /sys/class/thermal/thermal_zone0/policy # 当前 governor: step_wise / power_allocator
# 冷却设备当前档位:
cat /sys/class/thermal/cooling_device*/cur_state
cat /sys/class/thermal/cooling_device*/max_state
# Perfetto 抓 thermal + cpu_frequency 联动,确认"降频是否由发热引起"
2.2.3 日志怎么看
# ftrace/perfetto 中的关键事件:
thermal_zone_trip: tz=cpu-thermal trip=1 type=passive ← 越过触发点
thermal_temperature: thermal_zone=cpu-thermal temp=85000 ← 温度采样(85°C)
thermal_cdev_update: cdev=cpufreq-cpu0 target=3 ← 降频档位变化
# dmesg 里的过热告警:
dmesg | grep -i thermal
# "thermal thermal_zoneX: critical temperature reached, shutting down" ← 过热关机
2.2.4 一般性解决方案
- 先找热源:是 CPU 拉满(某进程死循环/游戏)?基带弱网搜网?充电 IC 效率低?屏/相机模组?------热源不同责任团队完全不同(这就是岗位"责任界定"的核心);
- 软件缓解:调 trip point 阈值与 governor 参数、调整 IPA 功率分配权重、高温场景限制后台任务;
- 激进 vs 体验的权衡:降频太狠用户投诉卡顿,太松投诉发热------需要按场景(游戏/相机/充电)做差异化温控表;
- 硬件配合:散热结构(VC 均热板)、传感器布点位置不合理的要反馈硬件。
第三部分 稳定性问题
5. 系统崩溃(panic / oops)
3.1.1 详细代码流程
panic()(kernel/panic.c:621)→ vpanic()(panic.c:428)的完整动作序列:
c
void vpanic(const char *fmt, va_list args)
{
local_irq_disable(); // 关本地中断
preempt_disable_notrace();
if (panic_try_start()) { // ★ 只允许一个 CPU 执行 panic 流程
/* go ahead */ // 其他 CPU 走 panic_smp_self_stop() 自停
} else if (panic_on_other_cpu())
panic_smp_self_stop();
console_verbose(); // 控制台打印级别提到最高
bust_spinlocks(1);
pr_emerg("Kernel panic - not syncing: %s\n", buf); // ★ 日志标志性一行
dump_stack(); // 打印调用栈
kgdb_panic(buf); // 给调试器接管的机会
if (!_crash_kexec_post_notifiers)
__crash_kexec(NULL); // ★ kdump:跳到捕获内核导 vmcore
panic_other_cpus_shutdown(...); // smp_send_stop() 停掉其他核
atomic_notifier_call_chain(&panic_notifier_list, 0, buf); // ★ 通知链:pstore 在此落盘
sys_info(panic_print); // 打印内存/任务等系统状态
kmsg_dump_desc(KMSG_DUMP_PANIC, buf); // ★ 把 log_buf 导出给 pstore/kdump
...
// 最后按 panic_timeout(panic.c:72,默认 CONFIG_PANIC_TIMEOUT)重启或停住
}
日志里 Tainted: G... 的每个字母含义就定义在 panic.c:642 的 taint_flags[] 表里(如 P=私有模块、O=树外模块、W=发生过 WARN、L=soft lockup 过、D=die 过)------读崩溃日志先看 tainted,判断内核是否被第三方模块"污染"。
3.1.2 分析方法
-
取现场 :重启后
ls /sys/fs/pstore/,把dmesg-ramoops-*拷出来(这就是上次死亡的完整内核日志); -
符号化调用栈 :用与设备完全匹配的 vmlinux:
scripts/faddr2line vmlinux __raw_spin_lock+0x48/0x1a0 # 地址 → 文件:行号 aarch64-linux-gnu-addr2line -e vmlinux -f <PC地址> -
分类定性 :
Unable to handle kernel NULL pointer dereference→ 空指针,看 PC 所在函数;KFENCE: use-after-free/KASAN→ 内存破坏,报告里直接给出分配/释放栈;Bad page state/BUG: KFENCE→ 页/堆破坏;Internal error: Oops+PC is at xxx→ 通用异常,faddr2line 解析。
3.1.3 日志怎么看
# 一份典型 panic 日志的读法(从 pstore 取出):
[ 888.1] Unable to handle kernel NULL pointer dereference at virtual address 00000010
[ 888.2] pc : my_driver_work+0x48/0x120 [my_driver] ← ★ 出错指令位置(模块名=责任方)
[ 888.3] lr : process_one_work+0x1a0/0x3c0
[ 888.4] Call trace: ← ★ 调用栈,自下往上读
[ 888.5] my_driver_work+0x48/0x120 [my_driver]
[ 888.6] process_one_work+0x1a0/0x3c0
[ 888.7] worker_thread+0x2c0/0x4a0
[ 888.8] Kernel panic - not syncing: Fatal exception ← panic 确认
[ 888.9] Tainted: G O 6.18.7 ← O=加载了树外模块
# 检索关键字(按出现频率排序):
dmesg | grep -E "Kernel panic|BUG:|Oops|Unable to handle|Internal error|Call trace|KFENCE|KASAN"
3.1.4 一般性解决方案
| 崩溃类型 | 排查/解决路线 |
|---|---|
| 空指针/野指针 | faddr2line 定位到行 → 修驱动判空/生命周期管理 |
| 内存破坏(UAF/越界/double-free) | 开 KFENCE(GKI 默认)/KASAN 复现取报告;Android 用户态用 MTE+Scudo;修复后回归压测 |
| 锁问题(死锁/递归上锁) | 开 CONFIG_PROVE_LOCKING(lockdep)复现,按 lockdep 报告调整锁顺序 |
| 栈溢出 | thread_info 越界检测,减少栈上大数组/深递归 |
| 第三方模块 | 看 Tainted 的 O/E 位,让模块提供方用匹配内核版本重编 |
6. 死机(无响应)------ hung task 与 watchdog
3.2.1 详细代码流程
(1)hung task 检测 (kernel/hung_task.c):内核线程 khungtaskd 周期性醒来:
khungtaskd 主循环
└─ check_hung_uninterruptible_tasks(timeout) hung_task.c:298
└─ for_each_process 遍历所有任务
└─ check_hung_task(t, timeout) hung_task.c:221
├─ task_is_hung() ------ 处于 TASK_UNINTERRUPTIBLE(D 状态)
│ 且超过 hung_task_timeout_secs(默认120s)没被调度
├─ pr_err("INFO: task %s:%d blocked for more than %ld seconds.") hung_task.c:247
├─ sched_show_task(t) ------ 打印该任务的完整内核栈 ★
├─ debug_show_blocker() ------ 尝试打印它在等谁(锁持有者)
└─ sysctl_hung_task_panic=1 时 → 触发 panic 保留现场(hung_task.c:234)
(2)watchdog (kernel/watchdog.c):每 CPU 的 hrtimer 定期喂狗,如果某 CPU 长时间不调度(soft lockup,定时器线程得不到运行)或关中断不响应(hard lockup,NMI 都喂不了狗),打印 BUG: soft lockup - CPU#N stuck for 22s! 并可配置 panic。
(3)Android 用户态对映 :SystemServer 的 Watchdog 线程定期向关键服务(AMS/PMS 等)发心跳,超时则 dump 所有线程栈并杀掉 system_server(手机软重启);App 主线程卡顿由 AMS 报 ANR 并把各线程栈写入 /data/anr/traces.txt。
3.2.2 分析方法
- 死机后先看 pstore 里有没有 hung task / soft lockup 记录------有栈就有责任方;
- hung task 栈的读法:栈顶(最内层)就是它正在等待的东西 ------
__wait_on_bit/io_schedule= 等 I/O;mutex_lock= 等锁;rpc_wait= 等 binder/驱动回复; - 若栈指向存储 I/O(
mmc_/ufs_/blk_开头函数),转存储团队;指向某驱动锁,转该驱动 owner; - configurable 加固:开发期把
kernel.hung_task_panic=1、kernel.softlockup_panic=1打开,让"卡住"变"崩溃",换取完整现场。
3.2.3 日志怎么看
# hung task 标志性输出:
[ 3600.1] INFO: task system_server:4521 blocked for more than 122 seconds.
[ 3600.2] Tainted: G W O 6.18.7
[ 3600.3] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 3600.4] task:system_server state:D stack:0 pid:4521 ...
[ 3600.5] Call trace:
[ 3600.6] __switch_to+0x...
[ 3600.7] __schedule+0x...
[ 3600.8] schedule+0x...
[ 3600.9] io_schedule+0x... ← ★ 在等 I/O → 存储方向
[ 3610.0] wait_on_page_bit+0x...
# soft lockup:
[ 500.0] watchdog: BUG: soft lockup - CPU#2 stuck for 23s! [kworker/2:1:1234]
[ 500.1] Call trace: ... ← 该 CPU 在死循环/长时间关抢占
# Android 侧:
logcat | grep -E "ANR in|Watchdog|Blocked" # ANR 与系统 watchdog
cat /data/anr/traces.txt # ANR 时全进程线程栈
3.2.3 一般性解决方案
| 现象 | 解决方案 |
|---|---|
| D 状态等 I/O | 查存储健康(坏块/超时重试)、驱动队列深度;必要时加超时兜底 |
| 等锁(mutex/rwsem) | 找持锁者(debug_show_blocker 输出、lockdep),修锁顺序或缩小临界区 |
| soft lockup 死循环 | 栈定位循环体,加 cond_resched() 或修退出条件 |
| 反复 hung 同一进程 | 该进程子系统 owner 介入;临时方案可配置 hung_task_panic 换取现场 |
| ANR | traces.txt 找主线程等待点:binder 对端慢、锁、I/O、广播队列堆积 |
7. 内存耗尽类崩溃(OOM)与系统 dump 体系
3.3.1 OOM 代码流程
分配失败走到最后一步(mm/page_alloc.c 的 __alloc_pages_slowpath 反复回收无果)→ out_of_memory()(mm/oom_kill.c):
out_of_memory()
├─ dump_header(oc) oom_kill.c:459 ------ 打印全系统内存水位、各进程 RSS 表
├─ oom_badness(p, totalpages) oom_kill.c:202 ------ 给每个进程打分:
│ 占用内存越多分越高,再叠加 oom_score_adj 调整(Android 用它保护前台进程)
└─ oom_kill_process(oc, message) oom_kill.c:1023 ------ 向"最差"进程发 SIGKILL
Android 一般不会让内核 OOM 先动手:用户态 lmkd 通过 PSI 压力提前按 oom_score_adj 杀缓存进程(system/memory/lmkd);内核 OOM 是最后兜底。日志区分:lmkd 杀的是 lowmemorykiller: Killing 'xxx' (uid), adj 900;内核杀的是 Out of memory: Killed process。
3.3.2 系统 dump 体系(代码与使用)
pstore/ramoops (fs/pstore/ram.c)------最重要的死机现场机制:
panic_notifier 通知链 → ramoops_pstore_write() ram.c:313
└─ persistent_ram_write(cprz, buf, size) ram.c:322 ------ 把 log_buf 写进保留内存区
(该区域 reboot 不清零,由 DT 里 reserved-memory 预留)
重启后:
/sys/fs/pstore/dmesg-ramoops-0 ← 上次死亡的 dmesg(fs/pstore/inode.c 提供文件接口)
关键参数(ram.c:31-33):record_size(单条记录大小)、mem_address/mem_size(保留区位置),由设备树 ramoops 节点配置。
| 层级 | 机制 | 取出方式 | 适用 |
|---|---|---|---|
| 内核日志 | pstore/ramoops | /sys/fs/pstore/ |
panic/oops/hung/lockup 后的 dmesg |
| 完整内存 | kdump/vmcore | crash 工具分析 | 实验室深度分析 |
| SoC 级 | 高通 ramdump / MTK aee | QPST/离线解析 | 连 pstore 都没写出来的硬死机 |
| native 崩溃 | debuggerd → tombstone | /data/tombstones/tombstone_XX |
用户态 SIGSEGV/SIGABRT |
| 全量汇总 | bugreport(dumpstate) | adb bugreport |
所有问题的标准入口 |
3.3.3 日志怎么看
# 内核 OOM:
[ 700.1] kswapd0: page allocation failure: order:0, mode:0x...
[ 700.2] Mem-Info: ...(全系统水位)
[ 700.3] Out of memory: Killed process 1234 (com.xxx) total-vm:... anon-rss:...
[ 700.4] oom_reaper: reaped process 1234
# lmkd(logcat):
lowmemorykiller: Killing 'com.xxx.app' (uid 10123), adj 955, state cached
to free 123456kB on behalf of 'kswapd0' because ...
(设备内存水位/swap/thrashing 详情)
# tombstone 关键字段:
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
backtrace: #00 pc 000000000001234 /vendor/lib64/libxxx.so (func+0x48) ← 责任 so 与函数
3.3.4 一般性解决方案
- 内存泄漏 :
kmemleak(内核)、/proc/meminfo趋势观察(Slab/AnonPages 只涨不降)、slabtop找异常 slab、Android 用dumpsys meminfo+ libmemunreachable; - lmkd 误杀/频繁杀 :查是否有进程内存暴涨(top 1 大户)、调
ro.lmk.*策略、确认 zRAM 大小与健康度(/sys/block/zram0/mm_stat); - dump 取不到 :确认 DT 里 ramoops 保留区、boot 后
/sys/fs/pstore是否挂载;硬死机(总线挂死)只能上 ramdump。
第四部分 通用方法论与速查
8. 日志检索速查表
| 问题 | dmesg 关键字 | logcat 关键字 | 关键节点 |
|---|---|---|---|
| 开机慢 | initcall.*returned.*usecs、deferred probe |
boot_progress |
/sys/module/kernel/parameters/initcall_debug |
| 卡顿/调度 | (主要用 Perfetto) | Choreographer.*Skipped.*frames |
/proc/sched_debug |
| 睡不下去 | Wakeup pending, aborting suspend、active wakeup source |
wake_lock |
/sys/kernel/debug/wakeup_sources、/sys/kernel/debug/suspend_stats |
| 频繁唤醒 | PM: suspend entry/exit 时间差 |
AlarmManager |
/proc/interrupts |
| 发热降频 | thermal |
ThermalManager |
/sys/class/thermal/ |
| 内核崩溃 | Kernel panic、Oops、Call trace、Unable to handle |
tombstone |
/sys/fs/pstore/、/data/tombstones/ |
| 死机卡住 | blocked for more than、soft lockup、rcu.*stall |
ANR in、Watchdog |
/data/anr/traces.txt |
| 内存耗尽 | Out of memory、page allocation failure |
lowmemorykiller: Killing |
/proc/meminfo、/sys/block/zram0/mm_stat |
9. 通用分析方法论(岗位"责任界定"的套路)
- 先定性分层:问题发生在哪一层------硬件(电流/温度数据异常)→ 内核驱动(dmesg 栈/pstore)→ Android 框架(logcat/traces)→ 应用(tombstone/meminfo)。每一层都有明确的"取证工具",取到证据再分派,不凭猜。
- 先复现再分析:能稳定复现的问题,用 trace/日志打点对比正常与异常路径;不能复现的靠 pstore + bugreport 历史数据找规律(发生时段、场景、版本聚类)。
- 数据对账:性能问题必抓 Perfetto;功耗问题必对"电流波形 × wakelock × 唤醒源"三本账;稳定性问题必取"pstore + tombstone + traces"三件套。
- 源码定责:栈/日志定位到函数后,回到源码确认逻辑归属(本文各节的行号就是入口),再决定是驱动团队、BSP 团队、框架团队还是应用的责任。
10. 最新 Android(16/17,GKI 6.12/6.18)相关演进
| 新特性 | 版本 | 对岗位问题的影响 |
|---|---|---|
DAMON 主动回收(CONFIG_DAMON=y 进 GKI defconfig) |
Android 17 / 6.18 | 内存从"等压力再杀"转向"持续监测、提前回收冷页",配合 pmgd 守护进程与 cgroup v2 Memory Limiter,减少内存压力型卡顿和 lmkd 误杀 |
| MGLRU | Android 13+ 内核 | 回收更精准,改善低内存流畅度与后台保活 |
| Rust binder 等 Rust 内核模块 | 6.12+ / Android 16+ | 语言层面消除 UAF/越界类内存破坏 → 直接降低 panic 率;Android 17 配 Rust 1.91.1 |
| 16KB 页 | Android 15+ 可选 / 16 强制对齐 | 降低缺页与 TLB miss 提升性能;带来兼容性与内存占用新问题 |
| MTE 推广 | Android 13+ 逐步铺开 | 堆破坏类崩溃在开发期暴露,线上稳定性提升 |
| zygote_next | Android 17 | 新应用启动路径,启动性能的新分析对象 |
11. 岗位能力映射速查
| 岗位要求 | 需要懂的内核源码 | 需要会的工具 |
|---|---|---|
| 开机/启动慢 | init/main.c(initcall 级别与串行执行)、驱动 probe |
initcall_debug、bootchart、Perfetto |
| CPU 调度问题 | kernel/sched/(EEVDF、uclamp、schedutil) |
Perfetto sched trace、uclamp 调整 |
| 待机耗电/底电流 | kernel/power/suspend.c、drivers/base/power/wakeup.c、kernel/power/wakelock.c、cpuidle |
wakeup_sources、suspend_stats、batterystats、功耗仪 |
| 发热/thermal | drivers/thermal/(step_wise、IPA) |
thermal 节点、Perfetto power trace |
| 死机/崩溃 | kernel/panic.c、hung_task.c、watchdog.c、mm/oom_kill.c |
pstore、ramdump、tombstone、faddr2line/addr2line |
| 系统 dump 分析 | fs/pstore/、上述全部 |
bugreport、crash、GDB |