PX4 模块入口与运行模式

一、统一 CLI 入口

PX4 中所有 driversmodules(算法与中间件)都可通过 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
2
3
4
5
6
7
8
9
flowchart TD
A["<module>_main(argc, argv)"] --> B["ModuleBase<T>::main(argc, argv)"]
B --> C{"argv["1"] 是什么?"}
C -->|"-h/help/usage"| D["T::print_usage()"]
C -->|"start"| E["start_command_base(argc-1, argv+1)"]
C -->|"status"| F["status_command()<br/>-> print_status()"]
C -->|"stop"| G["stop_command()<br/>-> request_stop() + 等待退出"]
C -->|"其他"| H["T::custom_command(argc-1, argv+1)"]
E --> I["T::task_spawn(argc, argv)"]

start 分支将 start 字符串保留作为 argv[0] 占位,因为后续 px4_getopt()index=1 开始解析,argv[0] 被当作命令名/占位,不参与选项解析。

全局互斥锁

ModuleBase 启动/停止/查询依赖两个全局静态成员:

1
2
static px4::atomic<T *> _object;
static int _task_id;

start/stop/status 这些命令可能并行执行,全局互斥锁 px4_modules_mutex 保证对 _object/_task_id 的复合读写是原子的,避免并发踩踏。


三、独立线程模式

run_trampolineinstantiate()run()exit_and_cleanup()

run_trampoline 的定位

新线程的入口函数,生命周期分三段:

1
2
3
instantiate(argc, argv)  // 在新线程里创建对象
object->run() // 进入模块主循环
exit_and_cleanup() // delete 对象,清 task_id,通知 stop 等待者

为什么 run_trampoline 先丢弃 argv[0]

px4_task_spawn_cmd() 会把 task 名称塞到 argv[0](POSIX 明确行为),而 instantiate() 期望看到的 argv[0]"start"(作为 px4_getopt 的占位)。因此 run_trampoline 必须先执行:

1
2
argc -= 1;
argv += 1;

各函数职责

函数 职责
instantiate() 解析 CLI 参数 + 构造对象,失败返回 nullptr
run() 主循环,should_exit() 为真时尽快退出
exit_and_cleanup() 回收对象 + 置 task_id=-1stop_command() 等待此标志判断任务结束

独立线程模式的典型 run() 结构:

1
2
3
4
5
6
7
8
9
10
void MyModule::run() {
uORB::Subscription _topic_sub{ORB_ID(some_topic)};
while (!should_exit()) {
px4_poll(...); // 阻塞等待数据
some_topic_s msg{};
if (_topic_sub.update(&msg)) {
// 处理数据
}
}
}

四、WorkQueue 模式

mc_att_control 为例。

task_spawn() 不创建独立线程,而是设置 _task_id = task_id_is_work_queue,声明跑在 WorkQueue 里:

1
2
3
4
5
6
7
static int task_spawn(int argc, char *argv[]) {
MulticopterAttitudeControl *inst = new MulticopterAttitudeControl(vtol);
_object.store(inst);
_task_id = task_id_is_work_queue; // 不创建独立线程
inst->init(); // 注册 uORB 回调
return 0;
}

init() 中通过 vehicle_attitude_sub.registerCallback() 注册 uORB 回调,失败则启动失败:

1
2
3
4
5
6
7
bool init() {
if (!_att_sub.registerCallback()) {
return false;
}
ScheduleDelayed(10_ms); // 定时兜底
return true;
}

启动成功后,模块不进入 while(1) 循环,而是等 uORB 触发回调,由 WorkQueue 线程调用 Run()

工作流对比

特性 独立线程模式 WorkQueue 模式
线程创建 独立线程,px4_task_spawn_cmd() 复用 WorkQueue 线程,无新线程
执行触发 主循环 + px4_poll() 阻塞等待 uORB 回调 + ScheduleNow()
CPU 占用 阻塞时挂起,不占 CPU 事件驱动,更高效
适用场景 简单驱动、低频任务 高频控制算法(姿态、位置、速率环)

五、完整参数链路

module start -f -p 42 为例追踪参数流转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module start -f -p 42
-> <module>_main(argc=5, argv=["module","start","-f","-p","42"])
-> ModuleBase<T>::main() 命中 start 分支
-> start_command_base(argc-1=4, argv+1=["start","-f","-p","42"])
-> T::task_spawn(argc=4, argv=["start","-f","-p","42"])
-> px4_task_spawn_cmd("module", ..., run_trampoline, argv)

POSIX 在新线程入口构造 argv:
argv=["module","start","-f","-p","42"] argc=5

run_trampoline() 丢弃 argv[0]:
argc=4, argv=["start","-f","-p","42"]

T::instantiate(argc=4, argv=["start","-f","-p","42"])
px4_getopt 从 index=1 开始解析 "-f -p 42"

六、uORB 回调驱动执行流程

独立线程模式

1
2
3
4
5
6
7
8
9
10
11
// run() 内部
while (!should_exit()) {
px4_pollfd_struct_t fds[1];
fds[0].fd = topic_sub.getHandle();
fds[0].events = POLLIN;
px4_poll(fds, 1, 100); // 阻塞,最多等 100ms
if (fds[0].revents & POLLIN) {
topic_sub.copy(&data);
// 处理
}
}

WorkQueue 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// task_spawn 时不创建独立线程
// init() 注册回调
_att_sub.registerCallback();

// uORB 发布新数据 -> DeviceNode 触发回调 -> ScheduleNow()
// WorkQueue 线程调用 Run()
void Run() override {
if (should_exit()) {
_att_sub.unregisterCallback();
exit_and_cleanup();
return;
}
// 处理数据
vehicle_attitude_s att{};
if (_att_sub.update(&att)) {
// 控制算法
}
ScheduleDelayed(10_ms); // 定时兜底
}

WorkQueue 模式的关键约束:

  • Run() 里不允许 px4_poll,不允许阻塞 I/O
  • 不要打印高频日志(WorkQueue 线程被卡住会影响整个队列)
  • 回调触发 + ScheduleDelayed() 同时存在时,频率由兜底周期决定

上一篇:交叉编译顺序详解
下一篇:uORB 通信机制(上)