第四章 进程管理稳定性:进程生命周期、进程优先级与调度策略、OOM Killer机制、进程组与cgroup

各位同学,今天我们聊聊进程管理。说实话,Android系统稳定性出问题,十有八九跟进程管理脱不了干系。我这些年排查的稳定性问题里,进程相关的至少占了一半。你想想看,一个系统里几百个进程在跑,谁先谁后,谁生谁死,这要是没个规矩,不乱套才怪。

4.1 进程生命周期——从生到死的完整旅程

进程的生命周期,说白了就是五个状态:创建、就绪、运行、阻塞、终止。嗯,这里要注意,Android里还有个特殊的"已停止"状态,这个我们后面再说。

核心要点:进程状态转换的稳定性风险,往往出现在状态切换的边界条件上。

我在项目中遇到过一个问题:某个后台服务进程频繁地在"运行"和"阻塞"之间切换,每次切换都要做上下文保存和恢复。你猜怎么着?这个进程一天之内切换了上百万次,直接把CPU时间片都耗在了切换上,系统响应变得奇慢无比。

进程创建时,fork()系统调用会复制父进程的地址空间。我个人习惯在创建进程后立即检查返回值,因为fork失败的情况虽然少见,但一旦发生,后果很严重。

// 进程创建的标准写法
pid_t pid = fork();
if (pid == -1) {
    // 创建失败,需要处理
    perror("fork failed");
    exit(EXIT_FAILURE);
} else if (pid == 0) {
    // 子进程
    // 记得关闭不必要的文件描述符
    close_unused_fds();
    // 执行实际工作
    do_work();
} else {
    // 父进程
    // 等待子进程结束,避免僵尸进程
    waitpid(pid, &status, 0);
}

避坑指南:我曾经见过一个案例,父进程fork后没有调用wait,子进程结束后变成了僵尸进程。系统里积累了上千个僵尸进程,最终导致进程表溢出,新进程无法创建。记住:每个fork都要对应一个wait。

4.2 进程优先级与调度策略——谁先谁后,谁多谁少

Android的进程优先级,从高到低大致是:前台进程、可见进程、服务进程、后台进程、空进程。但实际调度时,Linux内核用的是完全公平调度器(CFS),它不看优先级数字,而是看"虚拟运行时间"。

为什么会这样?因为优先级数字是静态的,但进程的行为是动态的。CFS的思路很简单:让每个进程都能公平地获得CPU时间。你想想看,如果一个高优先级进程一直在运行,低优先级进程永远得不到CPU,那系统就卡死了。

进程类型 优先级范围 典型场景 稳定性风险
前台进程 -20 ~ -5 当前交互的App 优先级反转
可见进程 -4 ~ 0 可见但非前台 资源饥饿
服务进程 1 ~ 5 后台Service 被误杀
后台进程 6 ~ 10 不可见Activity 频繁创建销毁
空进程 11 ~ 19 无组件的进程 容易被回收

我记得有一次,一个视频播放App在播放时突然卡顿。排查后发现,是另一个后台进程在疯狂做垃圾回收,占用了大量CPU。解决方案很简单:给播放线程设置实时优先级,确保它不会被其他线程抢占。

// 设置线程优先级
struct sched_param param;
param.sched_priority = 99; // 实时优先级
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
    // 需要CAP_SYS_NICE权限
    perror("sched_setscheduler failed");
}

个人经验:设置实时优先级要谨慎。我曾经在一个项目中给所有音频处理线程都设了实时优先级,结果导致系统UI线程得不到CPU,界面直接卡死。记住:实时优先级是双刃剑,用好了是神器,用不好是灾难。

4.3 OOM Killer机制——谁该为内存不足买单

OOM Killer,说白了就是系统内存不够时,选一个进程杀掉。但选谁呢?Linux内核有一套复杂的评分机制,综合考虑进程的内存占用、运行时间、优先级等因素。

Android对OOM Killer做了定制,引入了lmk(Low Memory Killer)。它比标准OOM Killer更智能,会根据内存压力等级,选择不同优先级的进程来杀掉。

// 查看OOM评分
// 每个进程的oom_score可以在/proc/[pid]/oom_score中查看
adb shell cat /proc/1234/oom_score

// 调整OOM评分,值越大越容易被杀
adb shell echo -1000 > /proc/1234/oom_score_adj

嗯,这里要注意,oom_score_adj的取值范围是-1000到1000。负值表示不容易被杀,正值表示容易被杀。系统核心服务通常设为-1000,确保它们不会被OOM Killer误杀。

我曾经踩过的坑:有一次,系统总是莫名其妙地重启。排查了三天,最后发现是某个系统服务被OOM Killer杀了。为什么?因为这个服务在启动时申请了大量内存,oom_score_adj设得不够低。从那以后,我养成了一个习惯:所有关键服务启动后,第一件事就是检查并调整oom_score_adj。

4.4 进程组与cgroup——资源隔离的艺术

进程组,就是把多个进程放在一个组里统一管理。cgroup(Control Groups)则是Linux内核提供的资源隔离机制。Android从4.3开始引入cgroup,用来限制App的资源使用。

cgroup可以限制的资源包括:CPU使用率、内存使用量、I/O带宽、网络带宽等。每个App启动时,系统会为它创建一个cgroup,设置好资源上限。

// cgroup的目录结构示例
/sys/fs/cgroup/
├── cpu/
│   ├── app1/    # App1的CPU限制
│   ├── app2/    # App2的CPU限制
│   └── ...
├── memory/
│   ├── app1/    # App1的内存限制
│   ├── app2/    # App2的内存限制
│   └── ...
└── blkio/
    ├── app1/    # App1的I/O限制
    ├── app2/    # App2的I/O限制
    └── ...

我个人习惯在排查性能问题时,先检查cgroup配置。因为很多时候,App卡顿不是因为代码写得不好,而是cgroup限制了它的资源。

核心要点:cgroup的稳定性价值在于:当一个App出现内存泄漏或CPU异常时,cgroup可以把它隔离起来,不让它影响其他App。这就是"故障隔离"的思想。

我记得有一次,一个游戏App因为内存泄漏,占用了2GB内存。如果没有cgroup限制,它会把整个系统的内存都吃光,导致所有App都被OOM Killer杀掉。但因为cgroup限制了它的内存上限,系统只是杀掉了这个游戏App,其他App正常运行。

最后说一句:进程管理稳定性的核心,就是"隔离"和"优先级"这两个词。隔离保证了一个进程的故障不会扩散,优先级保证了关键进程能获得足够的资源。理解了这两点,你就掌握了进程管理稳定性的精髓。