PX4-4-任务调度

PX4所有的功能都封装在独立的模块中,uORB是任务间数据交互和同步的工具,而管理和调度每个任务,PX4也提供了一套很好的机制,这一篇我们分享PX4的任务调度机制。

我们以PX4 1.11.3版本为例

PX4的任务调度方式有两种:

  • 任务 (Tasks): 模块在它自己的任务中运行, 具有自己的堆栈和进程优先级(这是更常见的方法)。
  • 工作队列 (Work queues):模块在共享任务上运行, 这意味着它没有自己的堆栈。 多个任务在同一堆栈上运行, 每个工作队列只有一个优先级。

工作队列的优点是 RAM 占用更少,减少任务切换,缺点是队列任务不能休眠,也不能轮训消息。对于运行时间比较长的任务应该使用Tasks或者在一个独立的工作队列中

工作队列(work Queue)用于执行周期性任务, 如传感器驱动程序。

Task

使用工作队列功能需要使用任务队列的任务需要继承ModuleBase,这个类提供了队列需要的方法,其继承关系如图所示:

类的继承关系

classDiagram
	ModuleBase~xxx~ <|-- Task模块
	atomic <.. ModuleBase~xxx~
	pthread <.. ModuleBase~xxx~
	class Task模块{
		+task_spawn(int argc, char *argv[])$ int
		-Run() void
	}
	class ModuleBase~xxx~{
		atomic~T *~ _object
		int _task_id
		+main(int argc, char *argv[])$ int
		+run_trampoline(int argc, char *argv[])$ int
		+start_command_base(int argc, char *argv[])$ int
		+stop_command(int argc, char *argv[])$ int
		+status_command(int argc, char *argv[])$ int
		+run(int argc, char *argv[])* void
		-lock_module()$ void
		-unlock_module()$ void
	}

时序图

sequenceDiagram
	Task模块 ->> Task模块 :xxx_main()
	Task模块 ->> ModuleBase :main()
	note right of ModuleBase :运行指令:start stop...
	ModuleBase ->> ModuleBase :start_command_base()
	ModuleBase ->> Task模块 :task_spawn()
	Task模块 ->> ModuleBase :px4_task_spawn_cmd
	ModuleBase ->> ModuleBase :run_trampoline()
	loop 主任务函数
		ModuleBase ->> Task模块 :Run()
	end
	

由上图我们可以知道:

  • 任务模块通过xxx_main()函数启动,这个函数是系统执行改任务的入口函数
  • ModuleBase类封装了任务管理所需要的基础功能,如:start、stop、status、help、info等
  • ModuleBase通过px4_task_spawn_cmd函数启动任务,这个函数封装了pthread的接口
  • Task模块通过Run函数运行任务,这里需要一个while循环,用于周期性执行

工作队列

使用工作队列功能需要使用任务队列的任务需要继承ScheduledWorkItem,这个类提供了队列需要的方法,其继承关系如图所示:

类的继承关系

classDiagram
	ScheduledWorkItem <|-- 队列任务模块
	WorkItem <|-- ScheduledWorkItem
	IntrusiveSortedListNode~xxx *~ <|-- WorkItem
	IntrusiveQueueNode~xxx *~ <|-- WorkItem
	WorkQueue <.. WorkItem
	IntrusiveSortedListNode~xxx *~ <|-- WorkQueue
	class ScheduledWorkItem{
		<<>>
		-hrt_call	_call
		+ScheduleDelayed(uint32_t delay_us) void
		+ScheduleOnInterval(uint32_t interval_us, uint32_t delay_us = 0) void
		+ScheduleClear()
		-Run()* void
		-schedule_trampoline()$ void
	}
	class WorkItem{
		<<abstract>>
		WorkQueue	*_wq;
		+ScheduleNow() void
		#Run()* void
		#ScheduleClear() void
	}
	
	

时序图

sequenceDiagram
	队列任务模块 ->> 队列任务模块 :start()
	队列任务模块 ->> ScheduledWorkItem :ScheduleOnInterval()
	ScheduledWorkItem ->> hrt :hrt_call_every()
	hrt ->> hrt :hrt_call_internal
	loop 高精度定时器中断
		hrt ->> hrt :hrt_tim_isr
	end
	hrt ->> ScheduledWorkItem :hrt_call_invoke
	ScheduledWorkItem ->> WorkItem :schedule_trampoline
	WorkItem ->> WorkQueue :Add()
	WorkQueue ->> WorkQueueManager :加入任务队列管理器
	loop WorkQueueRunner
		WorkQueueManager ->> WorkQueueManager :WorkQueueRunner
	end
	WorkQueueManager ->> WorkQueue :Run()
	WorkQueue ->> 队列任务模块 :Run()

PX4的任务队列调用稍微复杂一些,由上述时序图,工作队列的调度设计一个高精度定时中断hrt和一个队列管理线程WorkQueueManager,它的设计目的是提供一个高精度的周期性任务调度方法。

在hrt定时器中对插入周期任务的队列检测是否到达执行时间,如果需要运行则将需要执行的周期任务添加进WorkQueue中,在高优先级线程WorkQueueRunner检测是否有插入需要运行的任务,在该线程中执行该任务。

这样设计的优点:

  • 使用hrt的高精度定时中断可以确保周期调度的精度,这个中断运行周期为1MHz,理论最小调度分辨率为1us,远高于使用rtos的调度分辨率,它取决于rtos的时间片轮长度,一般情况是1ms
  • 多个工作队列共享一个WorkQueueRunner线程,因此队列任务是共享堆栈的,可以减小RAM的使用
  • 多个工作队列在一个线程中是顺序执行的,可以减少任务切换的次数
  • 工作队列任务时间过长可能会造成线程中其他队列任务延时,因此队列任务不能运行执行时间过长的任务

我的微信公众号,文章同步更新,欢迎关注。

微信公众号