3. 进程管理:进程的创建与销毁、进程生命周期管理、进程监控与看门狗机制
进程管理,说白了就是Adaptive平台上的“生老病死”管理。你想想看,一个系统里跑着几十上百个进程,谁该活、谁该死、谁出了问题谁来管——这就是我们要聊的核心。
我个人习惯把进程管理分成三块:创建与销毁(怎么生、怎么死)、生命周期管理(活着的时候怎么管)、监控与看门狗(死了怎么发现、怎么救)。咱们一个一个来。
3.1 进程的创建与销毁
在Adaptive平台上,进程不是你想创建就能随便创建的。它有一套严格的规则。我刚开始接触Adaptive时,以为跟Linux下fork()一样简单,结果踩了个大坑——Adaptive的进程创建必须通过Execution Management(EM)来管理,不能自己乱来。
3.1.1 进程创建流程
进程创建的核心是ara::exec::ExecutionClient接口。EM通过这个接口向State Management(SM)报告进程状态,SM再决定下一步动作。流程大概是这样的:
- SM下发状态切换请求(比如从“启动”到“运行”)
- EM根据Manifest文件中的配置,启动对应的进程
- 进程启动后,通过ExecutionClient向EM报告“我活了”
- 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平台上,我们主要用软件看门狗。
软件看门狗的原理很简单:
- EM启动一个定时器,设定超时时间(比如5秒)
- 进程每次执行完一个关键任务后,调用
feedDog()重置定时器 - 如果定时器超时了,说明进程卡死了,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 实战经验总结
最后分享几个我这些年积累的经验:
- 进程启动顺序很重要。依赖关系一定要在Manifest里写清楚,别指望代码里sleep等待。我见过一个项目,因为启动顺序不对,两个进程互相等对方,结果死锁了。
- 看门狗超时时间要合理。太短容易误触发,太长又起不到保护作用。一般建议是正常处理时间的3-5倍。
- 日志要打全。进程创建、销毁、状态切换、看门狗喂狗,这些关键事件都要打日志。出了问题才好排查。
- 测试要覆盖异常场景。比如模拟进程崩溃、模拟看门狗超时、模拟资源耗尽。别等到上路了才发现监控机制本身有bug。
嗯,进程管理这块内容其实挺多的。今天咱们聊了创建销毁、生命周期、监控看门狗这三个核心点。下一章我会讲状态切换的具体实现,到时候咱们再细聊。