3. 进程管理:进程的创建与销毁、进程生命周期管理、进程监控与看门狗机制

进程管理,说白了就是Adaptive平台上的“生老病死”管理。你想想看,一个系统里跑着几十上百个进程,谁该活、谁该死、谁出了问题谁来管——这就是我们要聊的核心。

我个人习惯把进程管理分成三块:创建与销毁(怎么生、怎么死)、生命周期管理(活着的时候怎么管)、监控与看门狗(死了怎么发现、怎么救)。咱们一个一个来。

3.1 进程的创建与销毁

在Adaptive平台上,进程不是你想创建就能随便创建的。它有一套严格的规则。我刚开始接触Adaptive时,以为跟Linux下fork()一样简单,结果踩了个大坑——Adaptive的进程创建必须通过Execution Management(EM)来管理,不能自己乱来。

3.1.1 进程创建流程

进程创建的核心是ara::exec::ExecutionClient接口。EM通过这个接口向State Management(SM)报告进程状态,SM再决定下一步动作。流程大概是这样的:

  1. SM下发状态切换请求(比如从“启动”到“运行”)
  2. EM根据Manifest文件中的配置,启动对应的进程
  3. 进程启动后,通过ExecutionClient向EM报告“我活了”
  4. EM更新内部状态表,通知SM“进程已就绪”

关键点:进程的启动参数、依赖关系、重启策略,全部写在Manifest文件里。不是代码里硬编码的。

3.1.2 进程销毁

销毁比创建更讲究。你不能直接kill -9,那样会出大事。Adaptive平台要求优雅终止

  • EM先发SIGTERM信号,给进程5秒时间清理资源
  • 进程收到信号后,释放内存、关闭文件、断开网络连接
  • 如果5秒后还没死,EM才发SIGKILL强制干掉

注意:我曾经在一个项目中,有个进程的析构函数里做了数据库写操作,结果SIGTERM来了之后还没写完就被杀了,数据直接损坏。后来我们加了一个标志位,收到终止信号后先完成当前事务再退出。

3.2 进程生命周期管理

进程不是只有“活着”和“死了”两种状态。Adaptive定义了六种状态,比Linux的进程状态要丰富得多。

状态 说明 典型场景
Running 正常运行中 正常业务处理
Idle 空闲待命 等待事件触发
Suspended 挂起 低功耗模式
Terminating 正在终止 收到SIGTERM后
Terminated 已终止 进程已退出
Crashed 异常崩溃 段错误、看门狗超时

嗯,这里要注意:状态切换不是随意的。比如从Running直接跳到Terminated是不允许的,必须先经过Terminating。这是Adaptive平台的安全设计,防止进程突然暴毙导致资源泄漏。

3.2.1 状态机管理

每个进程内部其实维护了一个状态机。我建议你在代码里显式地实现这个状态机,而不是靠一堆if-else硬撑。举个例子:

// 伪代码,展示状态机实现思路
enum class ProcessState {
    Running,
    Idle,
    Suspended,
    Terminating,
    Terminated,
    Crashed
};

class ProcessStateMachine {
public:
    bool transition(ProcessState newState) {
        // 检查状态转换是否合法
        if (!isValidTransition(currentState, newState)) {
            logError("非法状态转换: " + stateToString(currentState) + " -> " + stateToString(newState));
            return false;
        }
        // 执行退出动作
        onExit(currentState);
        // 更新状态
        currentState = newState;
        // 执行进入动作
        onEnter(newState);
        return true;
    }
private:
    ProcessState currentState = ProcessState::Running;
};

小技巧:我在项目中习惯把状态转换表做成一个静态数组,运行时查表判断合法性。这样比写一堆switch-case要清晰得多,而且容易做单元测试。

3.3 进程监控与看门狗机制

这部分是我觉得最实用的。你想想看,系统跑在路上,一个进程挂了怎么办?不能指望司机去重启吧?所以需要自动监控和恢复

3.3.1 健康监控

Adaptive平台要求每个进程定期向EM发送“心跳”。这个心跳不是简单的“我还活着”,而是包含:

  • CPU使用率
  • 内存占用
  • 响应时间
  • 自定义健康指标(比如“我处理的请求数”)

EM收到心跳后,会跟预设的阈值做比较。如果某个指标超标,EM会触发告警,甚至直接重启进程。

我曾经踩过的坑:有个同事把心跳间隔设成了100ms,结果进程光发心跳就占了30%的CPU。后来我们改成1秒一次,CPU占用降到1%以下。别让监控本身成为负担。

3.3.2 看门狗机制

看门狗分两种:硬件看门狗软件看门狗。在Adaptive平台上,我们主要用软件看门狗。

软件看门狗的原理很简单:

  1. EM启动一个定时器,设定超时时间(比如5秒)
  2. 进程每次执行完一个关键任务后,调用feedDog()重置定时器
  3. 如果定时器超时了,说明进程卡死了,EM直接重启它

代码实现大概是这样的:

class Watchdog {
public:
    Watchdog(int timeoutMs) : timeout(timeoutMs) {
        // 启动监控线程
        monitorThread = std::thread([this]() {
            while (running) {
                auto now = std::chrono::steady_clock::now();
                auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastFeed).count();
                if (elapsed > timeout) {
                    // 看门狗超时,触发重启
                    triggerRecovery();
                }
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            }
        });
    }

    void feed() {
        lastFeed = std::chrono::steady_clock::now();
    }

private:
    std::chrono::time_point<std::chrono::steady_clock> lastFeed;
    int timeout;
    std::thread monitorThread;
    bool running = true;
};

注意:看门狗的喂狗位置很关键。你不能在循环里无脑喂狗,那样看门狗就失去意义了。正确的做法是:在关键任务完成点喂狗。比如处理完一个CAN消息、完成一次传感器数据融合。

3.3.3 恢复策略

进程挂了之后怎么恢复?Adaptive平台支持三种策略:

  • 立即重启:进程崩溃后马上重新拉起,适合无状态服务
  • 延迟重启:等几秒再重启,避免频繁崩溃导致系统震荡
  • 降级运行:不重启,但降低该进程的优先级,或者切换到备用算法

我个人建议:第一次崩溃立即重启,第二次崩溃延迟重启,第三次崩溃直接降级。这个策略我在项目里用了好几年,效果不错。

3.4 实战经验总结

最后分享几个我这些年积累的经验:

  1. 进程启动顺序很重要。依赖关系一定要在Manifest里写清楚,别指望代码里sleep等待。我见过一个项目,因为启动顺序不对,两个进程互相等对方,结果死锁了。
  2. 看门狗超时时间要合理。太短容易误触发,太长又起不到保护作用。一般建议是正常处理时间的3-5倍。
  3. 日志要打全。进程创建、销毁、状态切换、看门狗喂狗,这些关键事件都要打日志。出了问题才好排查。
  4. 测试要覆盖异常场景。比如模拟进程崩溃、模拟看门狗超时、模拟资源耗尽。别等到上路了才发现监控机制本身有bug。

嗯,进程管理这块内容其实挺多的。今天咱们聊了创建销毁、生命周期、监控看门狗这三个核心点。下一章我会讲状态切换的具体实现,到时候咱们再细聊。