3. VxWorks调度器架构总览
VxWorks 的实时性并不来自某一个“神奇函数”,而是来自一套分工明确的调度器架构:任务状态机负责描述任务此刻能不能跑,优先级队列负责决定谁先跑,抢占逻辑负责在更高优先级任务就绪时立刻切换,轮转调度负责让同优先级任务公平分享 CPU,而调度器锁与中断锁则分别控制“内核还能不能换任务”和“中断还能不能打断当前执行”。理解这四层关系,是后续做确定性优化的前提。
一、基于优先级的抢占式调度
VxWorks 默认为每个任务分配一个优先级,数值越小优先级越高。以常见的 0~255 优先级范围为例,0 通常保留给系统关键任务,255 为最低优先级。调度器维护一组按优先级组织的就绪队列,当某个任务被 taskSpawn 创建、被信号量唤醒、被延时到期或从中断服务程序释放后,它进入就绪态;调度器只需比较当前运行任务与最高优先级就绪任务,就能决定是否切换。
抢占发生的本质是“更高优先级任务进入就绪态”。例如当前运行任务优先级为 120,一个优先级 80 的任务因等待的信号量被释放而就绪,内核在调度点立即保存 120 的上下文,切换到 80。这里的关键不是“时间片用完”,而是优先级关系被打破。因此抢占式调度对中断响应、信号量释放、消息到达等事件非常敏感,也决定了系统的响应上界主要由最高优先级任务的阻塞时间决定。
二、轮转调度:同优先级的时间片公平
当多个任务处于同一优先级时,仅靠优先级无法区分先后。VxWorks 提供轮转调度(Round-Robin),通过 kernelTimeSlice() 为同优先级就绪任务分配时间片。时间片耗尽后,当前任务被放到该优先级就绪队列尾部,同优先级的下一个任务获得 CPU。需要注意:轮转只在同优先级任务之间发生,不会让低优先级任务抢占高优先级任务;若系统中没有同优先级竞争者,轮转几乎不产生额外切换。
时间片设置过小会带来频繁上下文切换,增加调度开销;设置过大则同优先级任务的响应抖动变大。工程上常把时间片设为毫秒级,并结合任务实际执行时间评估。例如三个优先级均为 100 的任务 A、B、C,时间片设为 10 ms,若 A 每次计算约 4 ms,则 A 在时间片内完成后主动阻塞,B、C 依次运行,轮转开销很小;若 A 持续计算 30 ms,则会被强制切换,B、C 各获得约 10 ms,A 的完成时间被拉长,这就是公平性与确定性之间的权衡。
三、调度器锁与中断锁的区别
调度器锁 taskLock() 禁止调度器进行任务切换,但中断仍然可以被响应,中断服务程序仍可运行。它保护的是“当前任务不被换出”,适合保护一段不允许被其它任务打断、但允许中断处理的临界区。中断锁 intLock() 则关闭中断,CPU 不再响应外部中断,通常用于保护与中断服务程序共享的极短数据结构。两者层级不同:中断锁比调度器锁更强,代价也更大。
实际使用中应遵循“能短不长、能弱不强”的原则:能用信号量或互斥量解决的共享问题,不要用锁;必须禁止任务切换时用 taskLock();只有确实需要屏蔽中断的极短代码才用 intLock()。长时间持锁会直接破坏系统的确定性,因为高优先级任务无法及时获得 CPU,中断响应也被推迟。
四、调度点与内核可抢占性
调度点是指内核检查是否需要切换任务的时刻,常见触发条件包括:任务阻塞(等待信号量、消息、延时)、任务被唤醒、任务优先级被修改、时间片耗尽、中断服务程序退出、任务主动让出 CPU 等。并非每条指令后都会调度,因此“可抢占”并不等于“任意时刻都能切换”。内核可抢占性取决于当前是否处于不可抢占区间,例如持有调度器锁、中断锁或内核内部临界区。
下面这张图把任务状态、优先级队列、调度点与两类锁放在同一视图中,帮助建立整体架构印象。
五、把架构落到确定性优化上
从这张架构图可以提炼出三条优化主线。第一,优先级分配要反映真实紧迫度,避免高优先级任务被低优先级任务长时间阻塞;第二,临界区要尽量短,调度器锁和中断锁的持有时长直接决定最坏响应时间;第三,轮转时间片要与任务执行时间匹配,避免无意义的切换开销。实际调优时,可以先测量每个任务的最坏执行时间、阻塞时间和中断关闭时间,再判断调度点是否足够密集、锁是否过强。
需要强调的是,VxWorks 的调度器并不是“越抢占越好”。过度抢占会带来频繁上下文切换和缓存抖动,反而降低吞吐。合理的做法是:对硬实时任务使用高优先级和短临界区,对软实时或非实时任务使用低优先级和轮转,把调度器锁与中断锁限制在真正必要的范围内。这样,优先级抢占、轮转、锁和调度点才能协同工作,形成可预测的实时行为。