PX4 模块入口与运行模式
一、统一 CLI 入口
PX4 中所有 drivers 与 modules(算法与中间件)都可通过 NSH Shell 或启动脚本 rcS 以命令行方式启动、停止与查询状态。每个模块的可执行入口本质是一个函数:
1 | <module>_main(int argc, char *argv[]) |
继承 ModuleBase<T> 的模块通过 T::main(argc, argv) 获得统一的 start/stop/status/help 分发能力,与模块是 drivers 还是算法模块无关。
相关知识:uORB 通信机制(上) 理解模块间数据流的基础
二、ModuleBase::main() 的分发逻辑
1 | flowchart TD |
start 分支将 start 字符串保留作为 argv[0] 占位,因为后续 px4_getopt() 从 index=1 开始解析,argv[0] 被当作命令名/占位,不参与选项解析。
全局互斥锁
ModuleBase 启动/停止/查询依赖两个全局静态成员:
1 | static px4::atomic<T *> _object; |
start/stop/status 这些命令可能并行执行,全局互斥锁 px4_modules_mutex 保证对 _object/_task_id 的复合读写是原子的,避免并发踩踏。
三、独立线程模式
run_trampoline → instantiate() → run() → exit_and_cleanup()
run_trampoline 的定位
新线程的入口函数,生命周期分三段:
1 | instantiate(argc, argv) // 在新线程里创建对象 |
为什么 run_trampoline 先丢弃 argv[0]
px4_task_spawn_cmd() 会把 task 名称塞到 argv[0](POSIX 明确行为),而 instantiate() 期望看到的 argv[0] 是 "start"(作为 px4_getopt 的占位)。因此 run_trampoline 必须先执行:
1 | argc -= 1; |
各函数职责
| 函数 | 职责 |
|---|---|
instantiate() |
解析 CLI 参数 + 构造对象,失败返回 nullptr |
run() |
主循环,should_exit() 为真时尽快退出 |
exit_and_cleanup() |
回收对象 + 置 task_id=-1,stop_command() 等待此标志判断任务结束 |
独立线程模式的典型 run() 结构:
1 | void MyModule::run() { |
四、WorkQueue 模式
以 mc_att_control 为例。
task_spawn() 不创建独立线程,而是设置 _task_id = task_id_is_work_queue,声明跑在 WorkQueue 里:
1 | static int task_spawn(int argc, char *argv[]) { |
init() 中通过 vehicle_attitude_sub.registerCallback() 注册 uORB 回调,失败则启动失败:
1 | bool init() { |
启动成功后,模块不进入 while(1) 循环,而是等 uORB 触发回调,由 WorkQueue 线程调用 Run()。
工作流对比
| 特性 | 独立线程模式 | WorkQueue 模式 |
|---|---|---|
| 线程创建 | 独立线程,px4_task_spawn_cmd() |
复用 WorkQueue 线程,无新线程 |
| 执行触发 | 主循环 + px4_poll() 阻塞等待 |
uORB 回调 + ScheduleNow() |
| CPU 占用 | 阻塞时挂起,不占 CPU | 事件驱动,更高效 |
| 适用场景 | 简单驱动、低频任务 | 高频控制算法(姿态、位置、速率环) |
五、完整参数链路
以 module start -f -p 42 为例追踪参数流转:
1 | module start -f -p 42 |
六、uORB 回调驱动执行流程
独立线程模式
1 | // run() 内部 |
WorkQueue 模式
1 | // task_spawn 时不创建独立线程 |
WorkQueue 模式的关键约束:
Run()里不允许px4_poll,不允许阻塞 I/O- 不要打印高频日志(WorkQueue 线程被卡住会影响整个队列)
- 回调触发 +
ScheduleDelayed()同时存在时,频率由兜底周期决定
上一篇:交叉编译顺序详解
下一篇:uORB 通信机制(上)