第2章 内核调度
2.1 线程调度概念
zephyr内核是基于优先级抢占,时间片分配的实时操作系统。每当调度程序切换线程或当ISR打断当前线程运行时,内核首先保存当前线程的CPU寄存器值。当线程恢复运行时,这些寄存器值将被恢复。在操作系统术语里,称为切换上下文。
2.1.1 当前线程
CPU当前正在运行的线程,称之为当前线程(current thread)。
2.1.2 线程优先级
线程的优先级由一个有符号整型表示,值越小代表优先级越高。内核调度器总是选择优先级最高的就绪线程作为当前线程。当多个线程具有相同的优先级时,调度器选择等待时间最久的线程。
1) 优先级小于0的线程
为协作式线程(cooprative thread), 这样的线程一旦成为了当前线程,将一直运行下去且不可被抢占,除非它主动释放CPU控制权让其它更高或相同优先级的线程得以运行。
2) 优先级大于或等于0的线程
为抢占式线程(preemptible thread),这样的线程一旦成为了当前线程,允许被优先级更高的线程所抢占,当然它也可以通过锁定内核调度防止被抢占。同样,它也可以主动释放CPU控制权让相同优先级的其它线程得以运行。除此之外还允许采用时间片分配的方式,当在时间片用完时,调度器选择让相同优先级的其它线程得以运行。
线程的初始优先级值可以在线程启动后动态地增加或减小。因此,通过改变线程的优先级,抢占式线程与协作式线程可互相转变。
2.1.3 线程状态
准备运行的线程状态称之为就绪态,被阻塞的线程状态称之为非就绪态。
以下情况都可以使得线程成为非就绪态的线程:
1) 线程还未启动,例如线程创建时为延时启动
2) 线程正在等待某个内核服务对象,例如等待信号量
3) 线程正在等待超时服务, 例如睡眠一段时间
4) 线程被挂起、结束或终止
2.2 锁住当前线程
void k_sched_lock(void)
void k_sched_unlock(void)
如果抢占式线程希望在执行某个特殊的操作时不被抢占,它可以调用 k_sched_lock(),让调度器将其临时当做协作式线程,从而避免被抢占。一旦完成特殊操作,该线程应调用 k_sched_unlock(),以恢复其可抢占特性。
在锁住情形下,如果线程主动释放CPU控制权,还是可以被切换到其它线程的。当恢复运行后,其锁住状态依旧有效。
2.3 线程忙等待
void k_busy_wait(uint32_t usec_to_wait) // 单位微秒
线程可以调用k_busy_wait()延迟空运转一段时间, 但并不会放弃CPU的控制权。通常使用忙等待,而不是线程休眠的情形:当所需的延迟太短,无法及时让调度程序从当前线程切换到另一个线程,然后再切换回来。
2.4 线程主动释放CPU控制权
1) 调用k_yield()函数
void k_yield(void)
此种方式将切换到其它更高或相同优先级的就绪态线程,如果不存在,将立即返回。但使用时有个问题:如果当前线程被切换到更高优先级的协作式线程后,就算高优先级的协作式线程调用k_yield(),也切换不回该线程。
2) 调用k_sleep()函数
void k_sleep(int32_t duration) // 单位毫秒
此种方式让当前线程进入持续一段时间的睡眠非就绪状态。在该时间内允许其它任意优先级的线程得以运行。但这种情形下,低优先级的线程仍有可能得不到调度。
3) 调用内核服务
此种方式通过调用内核相关的服务而暂时释放CPU控制权,例如等待信号量、队列消息、邮箱等。这方面的内容将在后面章节学习过程中逐一描述。
2.5 改变其它线程状态
1) 调用k_wakeup()函数
void k_wakeup(k_tid_t thread)
用于唤醒一个正常睡眠的线程。
2) 调用k_thread_cancel()函数
int k_thread_cancel(k_tid_t thread)
用于取消一个正在延时启动的线程。如果返回0表示取消成功,否则表示该线程已启动。
3) 调用k_thread_abort()函数
void k_thread_abort(k_tid_t thread)
用于终止一个线程,被终止的线程永远不会再得到运行。
4) 调用k_thread_suspend()函数
void k_thread_suspend(k_tid_t thread)
用于挂起一个线程,被挂起的线程暂时不会得到运行。
5) 调用k_thread_resume()函数
void k_thread_resume(k_tid_t thread)
用于恢复一个被挂起的线程。
2.6 获取当前线程标识
k_tid_t k_current_get(void)
常用于操作系统调试打印,线程监控等。
2.7 获取和更改线程优先级
int k_thread_priority_get(k_tid_t thread)
void k_thread_priority_set(k_tid_t thread, int prio)
2.8 设置抢占式线程时间片
void k_sched_time_slice_set(int32_t slice, int prio)
用于设定调度程序允许当前线程的运行时间片大小。优先级高于prio的任何线程不适用时间片限制。要禁用时间片段,将slice和prio设置为零即可。