Zephyr线程优先级简介

Creative Commons
本作品采用知识共享署名

本文简要介绍Zephyr的优先级以及优先级如何影响调度。

在实际的线程运行中,不同的线程对CPU有不同的需求,CPU敏感的线程比IO敏感的线程会要求更多的CPU执行时间,高响应的任务线程比低响应任务的线程会要求更优先获得CPU。在创建线程时通过优先级告诉内核那些线程更需要CPU。内核调度时会根据线程的优先级不同为线程调度和分配CPU。
注意: 本文主要是说明优先级对调度的影响,并没有全面的说明Zephyr的调度方式,如果要了解Zephyr的调度方式可以参考文末链接或源代码

本文图片均来自Zephyr官方文档

Zephyr优先级的表示

Zephyr用整数表示线程优先级,优先级可以为正也可以为负。整数值越小对应线程的优先级就越高,获得CPU的机会就预告。
Zephyr并不限制优先级的数量,可以通过配置CONFIG_NUM_COOP_PRIORITIES和CONFIG_NUM_PREEMPT_PRIORITIES来设定优先级的范围,例如下面配置表示Zephyr的优先级可选范围是从-30~50

1
2
CONFIG_NUM_COOP_PRIORITIES=30
CONFIG_NUM_PREEMPT_PRIORITIES=50

Zephyr不限制同一优先级上的线程数量,例如在优先级20上允许有多个线程。

Zephyr线程分类及优先级影响

Zephyr将线程分为协作线程和抢占线程两类。协作线程的优先级大于抢占线程的优先级,官方给出了优先级的示意图
prio

协作线程

优先级为负的是协作线程,协作线程的优先级从-1开始,最大为-CONFIG_NUM_COOP_PRIORITIES配置项决定。协作线程一旦拿到CPU后会一直占用,和它相同优先级和比它优先级高的线程都无法抢占CPU,只有协作线程等待资源时才会让出CPU给其它线程,为了避免协作线程长时间占用CPU导致更高或相同优先级的线程长时间无法执行,在编写协作线程代码时应使用k_sleep或k_yield主动让出CPU。
官方提供了一张图说明了这个过程:
cop
当低优先级协作线程Thread1占用CPU时,即使高优先级协作线程Thread2就绪了也无法抢夺CPU,必须等到Thread1主动放弃CPU。
因此在设计协作线程时,需要关注该线程是否CPU密集型的任务长时间占用CPU,如果存在这种情况,应该根据整体的系统情况适时的使用k放出CPU,让同等或更高优先级的协作线程能够工作。

抢占式线程

优先级非负的是可抢占式线程,抢占式线程的优先级从0开始最大为CONFIG_NUM_PREEMPT_PRIORITIES。当高优先级任务(包括协作线程和更高优先级的抢占线程)就绪时,将会直接抢夺低优先级任务的CPU, 为了避免抢占式线程长时间占用CPU导致小于等于该优先级的线程无法执行,内核为抢占式线程配置时间片让高优先级有机会抢占和相同优先级的线程有机会执行,在编写抢占式线程代码时可使用k_sleep或k_yield主动让出CPU让低优先级任务有机会执行。

官方提供下面图例说明了高优先级如果抢占低优先级线程:
pree
优先级上thread1<thread2<thread3,当thread1的时间片用完后调度器检查有高优先级thread2已经就绪,就将CPU使用权交给thread2。同样的情况也出现到thread3上。

官方提供下面图例说明了同优先级的抢占式线程如何进行时间片轮转:
slice
thread1,2,3同为抢占式线程,它们的优先级一样,内核将以时间片为单位让这三个线程轮流使用CPU。如果有协作线程thread4就绪了将抢占CPU。

CPU让出差异

前面有提到协作线程可以通过k_sleep和k_yield主动让出CPU,这两种让出方式的区别是:
k_yield只是引发一次调度,让其它同优先级和高优先级的协作线程有机会被调度。而不会调度低优先级的线程。
k_sleep是让线程进入睡眠,让出指定时间的CPU,这样低优先级的协作线程和抢夺式线程就都机会被调度。
不只是协作线程有让出CPU的问题,在抢占是线程中,如果抢占式线程一直处于就绪太,那么低优先级的抢占式线程将无法被调度,还是需要高优先级线程通过k_sleep让出CPU,让低优先级线程有机会执行

参考

https://docs.zephyrproject.org/latest/reference/kernel/threads/index.html#thread-priorities
https://docs.zephyrproject.org/latest/reference/kernel/scheduling/index.html#