Android 系统工程师(性能/功耗/稳定性)岗位问题深度解析:从内核源码到实战排查(完善版)

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 分析方法

  1. 内核段计时 :boot 参数加 initcall_debug(或运行时 echo 1 > /sys/module/kernel/parameters/initcall_debug 无效------必须在启动早期,一般改 bootconfig/cmdline),dmesg 会打印每个 initcall 的耗时;
  2. ftrace 方式initcall_start/initcall_finish trace 事件,可用 Perfetto 抓取;
  3. Android 段计时dmesginit: 打点时间戳、ro.boottime.init.* 属性、bootchart(adb shell 'touch /data/bootchart/enabled' 重启后取图)、Perfetto 的 android.boot 数据包;
  4. 分段定位 :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 分析方法

  1. Perfetto/systrace 是主力工具 ,关注四类事件:

    • sched_switch:线程何时跑在哪个核上、跑了多久;
    • sched_wakeup / sched_waking:唤醒到真正运行的调度延迟(runnable 时间过长 = 卡顿根因);
    • cpu_frequency / cpu_idle:频率升降是否与负载匹配;
    • binder_transaction:跨进程调用阻塞。
  2. 复现路径打点:在卡顿场景(如应用冷启动、滑动列表)抓 10~20 秒 trace,先看 UI 线程和 RenderThread 的 runnable/running 比例。

  3. 静态节点

    复制代码
    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_lockpm_wake_lock()__pm_stay_awake(wl->ws)(wakelock.c:244)→ 该 wakeup source 置 active → pm_wakeup_pending() 永远返回真 → 系统无法睡。

2.1.2 分析方法

  1. 确认睡没睡下去

    复制代码
    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_noirq

    success 不涨或 fail 猛涨 → 系统在反复尝试睡眠又失败。

  2. 找拦路的唤醒源

    复制代码
    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 大的
  3. Android 侧对账

    复制代码
    dumpsys power | grep -i "wake_lock\|PARTIAL"     # 谁持用户态锁
    dumpsys alarm | head -50                          # alarm 是否泛滥
    dumpsys batterystats > bs.txt                     # 唤醒次数/WakeLock 持有者/各应用耗电
  4. 硬件对账:功耗仪看电流波形------基线高(没睡下去)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 一般性解决方案

  1. 先找热源:是 CPU 拉满(某进程死循环/游戏)?基带弱网搜网?充电 IC 效率低?屏/相机模组?------热源不同责任团队完全不同(这就是岗位"责任界定"的核心);
  2. 软件缓解:调 trip point 阈值与 governor 参数、调整 IPA 功率分配权重、高温场景限制后台任务;
  3. 激进 vs 体验的权衡:降频太狠用户投诉卡顿,太松投诉发热------需要按场景(游戏/相机/充电)做差异化温控表;
  4. 硬件配合:散热结构(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 分析方法

  1. 取现场 :重启后 ls /sys/fs/pstore/,把 dmesg-ramoops-* 拷出来(这就是上次死亡的完整内核日志);

  2. 符号化调用栈 :用与设备完全匹配的 vmlinux:

    复制代码
    scripts/faddr2line vmlinux __raw_spin_lock+0x48/0x1a0   # 地址 → 文件:行号
    aarch64-linux-gnu-addr2line -e vmlinux -f <PC地址>
  3. 分类定性

    • 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)watchdogkernel/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 分析方法

  1. 死机后先看 pstore 里有没有 hung task / soft lockup 记录------有栈就有责任方
  2. hung task 栈的读法:栈顶(最内层)就是它正在等待的东西 ------__wait_on_bit/io_schedule = 等 I/O;mutex_lock = 等锁;rpc_wait = 等 binder/驱动回复;
  3. 若栈指向存储 I/O(mmc_/ufs_/blk_ 开头函数),转存储团队;指向某驱动锁,转该驱动 owner;
  4. configurable 加固:开发期把 kernel.hung_task_panic=1kernel.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/ramoopsfs/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 一般性解决方案

  1. 内存泄漏kmemleak(内核)、/proc/meminfo 趋势观察(Slab/AnonPages 只涨不降)、slabtop 找异常 slab、Android 用 dumpsys meminfo + libmemunreachable;
  2. lmkd 误杀/频繁杀 :查是否有进程内存暴涨(top 1 大户)、调 ro.lmk.* 策略、确认 zRAM 大小与健康度(/sys/block/zram0/mm_stat);
  3. dump 取不到 :确认 DT 里 ramoops 保留区、boot 后 /sys/fs/pstore 是否挂载;硬死机(总线挂死)只能上 ramdump。

第四部分 通用方法论与速查

8. 日志检索速查表

问题 dmesg 关键字 logcat 关键字 关键节点
开机慢 initcall.*returned.*usecsdeferred probe boot_progress /sys/module/kernel/parameters/initcall_debug
卡顿/调度 (主要用 Perfetto) Choreographer.*Skipped.*frames /proc/sched_debug
睡不下去 Wakeup pending, aborting suspendactive 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 panicOopsCall traceUnable to handle tombstone /sys/fs/pstore//data/tombstones/
死机卡住 blocked for more thansoft lockuprcu.*stall ANR inWatchdog /data/anr/traces.txt
内存耗尽 Out of memorypage allocation failure lowmemorykiller: Killing /proc/meminfo/sys/block/zram0/mm_stat

9. 通用分析方法论(岗位"责任界定"的套路)

  1. 先定性分层:问题发生在哪一层------硬件(电流/温度数据异常)→ 内核驱动(dmesg 栈/pstore)→ Android 框架(logcat/traces)→ 应用(tombstone/meminfo)。每一层都有明确的"取证工具",取到证据再分派,不凭猜。
  2. 先复现再分析:能稳定复现的问题,用 trace/日志打点对比正常与异常路径;不能复现的靠 pstore + bugreport 历史数据找规律(发生时段、场景、版本聚类)。
  3. 数据对账:性能问题必抓 Perfetto;功耗问题必对"电流波形 × wakelock × 唤醒源"三本账;稳定性问题必取"pstore + tombstone + traces"三件套。
  4. 源码定责:栈/日志定位到函数后,回到源码确认逻辑归属(本文各节的行号就是入口),再决定是驱动团队、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.cdrivers/base/power/wakeup.ckernel/power/wakelock.c、cpuidle wakeup_sources、suspend_stats、batterystats、功耗仪
发热/thermal drivers/thermal/(step_wise、IPA) thermal 节点、Perfetto power trace
死机/崩溃 kernel/panic.chung_task.cwatchdog.cmm/oom_kill.c pstore、ramdump、tombstone、faddr2line/addr2line
系统 dump 分析 fs/pstore/、上述全部 bugreport、crash、GDB

相关推荐
0+1111 小时前
Linux --进程信号
linux·运维·服务器
Kapaseker1 小时前
适配小米“中”折屏,非得买一台吗
android·kotlin
盟道科技1 小时前
电商小程序SKU数据模型设计:SPU、规格组合、库存与价格的工程实践
服务器·小程序·apache
恋猫de小郭1 小时前
Flutter 状态管理基准测评,一个很有趣的观点
android·前端·flutter
Lethehong1 小时前
服务器越来越多怎么统一监控?Beszel 接入 Agent、SMTP 告警与公网访问
运维·服务器
可乐鸡翅yeah_1 小时前
App WebView 加载 M3U8 流媒体踩坑,安卓 iOS 混合开发播放异常定位
android·ios·harmonyos·m3u8·m3u8在线播放
gs801401 小时前
Docker Desktop 报 Wsl/CommandTimedOut、wsl -l -v 卡死、0x80080005 的完整排查与解决
运维·docker·容器
其实防守也摸鱼1 小时前
CVE / NVD 漏洞数据库详解:从入门到实战
大数据·运维·人工智能·web安全·自动化
翼龙云_cloud2 小时前
阿里云国际代理商:2026零基础如何使用轻量应用服务器搭建独立站?
运维·服务器·阿里云·云计算