调度是实时操作系统中十分重要的概念之一,正是因为实时操作系统中有个调度者,多线程才变得可能。线程调度策略直接影响到应用系统的实时性。
调度是内核的主要职责之一,它决定将哪一个线程投入运行、何时投入运行及运行多久,协调线程对系统资源的合理使用。对系统资源非常匮乏的嵌入式系统来说,线程调度策略尤为重要,它直接影响到系统的实时性能。
调度是一种指挥方式,有多种调度策略。调度策略不同,线程被投入运行的时刻也不同。常用的调度策略主要有优先级抢占调度和时间片轮询(Round Robin,RR)调度。下面介绍这两种调度策略的基本含义。除以上两种调度策略之外,还有一种被称为显示调度的策略,就是用命令直接让其运行,在实时操作系统中很少用到。
优先级抢占式调度总是让就绪列表中优先级最高的线程先运行,对于优先级相同的线程,则根据先进先出的原则。
所谓优先级是指计算机操作系统在处理多个线程(或中断)时,决定各个线程(或中断)接收系统资源的优先等级的参数。操作系统会根据各个线程(或中断)优先级的高低,决定处理各个线程(或中断)的先后次序。在ARM Cortex-M处理器中,中断(异常)的优先级一般在MCU设计阶段确定,优先级编号越小表示中断(异常)的优先级越高,而且高优先级可以抢占低优先级的中断(异常)。在Mbed OS中,线程共有53种优先级,数值分别为-1~1、8~56、0x7FFFFFFF,优先级数值越大表示线程的优先级越高。但线程的优先级数值不宜过大,否则将会影响线程管理列表所占的资源和管理的时效性。
基于优先级先进先出的调度策略在运行时可以分为以下三种情况。
第一种情况,设线程B的优先级高于线程A,当线程A正在运行时,线程B准备就绪(发生的情景:一是线程A创建了线程B;二是线程B的延时到期;三是用户显式地调度线程B;四是线程B已获得等待的事件、信号量或互斥量等),而调度系统在下一个时间嘀嗒中断发生的时候,会抢夺线程A的CPU使用权,切换其状态为就绪态,并将线程A放入就绪列表中,同时分配CPU使用权给线程B,线程B开始运行。
第二种情况,当线程A被阻塞后主动放弃CPU使用权,此时调度系统从就绪列表中寻找优先级最高的线程,将CPU的使用权分配给它。
第三种情况,当存在同一优先级的多个线程都处于就绪态时,较早进入就绪态的线程优先获得系统分配的一段固定时间片供其运行。
当发生以下任意一种情况时,当前线程会停止运行,并进入CPU调度。
第一种情况,由于调用了阻塞功能函数(如等待事件、信号量或互斥量等),激活态(运行态)线程主动放弃CPU使用权,同时被放到等待列表和阻塞列表中。
第二种情况,产生了一个比激活态(运行态)线程所能屏蔽的中断优先级更高的中断。
第三种情况,更高优先级的线程已经处于就绪态。
在协调同一优先级下的多个就绪线程时,一般实时操作系统可能会加入时间片轮询的调度机制,以此协调多个同优先级线程共享CPU的问题。
时间片轮询调度策略,就是对于优先级相同的线程使用时间片轮询方式,即给各个相同优先级的线程分配固定的时间片来分享CPU时间。实际上,当采用时间片轮询调度时,不同优先级的线程是按照先进先出策略进行调度的;相同优先级的线程才会采用时间片轮询的调度方式。
不同的操作系统采取的线程调度策略有所区别,如μC/OS总是运行处于就绪态且优先级最高的线程;FreeRTOS支持三种调度方式,即优先级抢占式调度、时间片调度和合作式调度,实际应用主要是使用优先级抢占式调度和时间片调度,很少使用合作式调度;MQXLite采用优先级抢占式调度、时间片轮询调度和显式调度。
在Mbed OS中,采用优先级抢占和时间片轮询的综合调度策略。该调度策略总是将CPU的使用权分配给当前就绪的、优先级最高的且较先进入就绪态的线程,同一优先级的线程可以采用时间片轮询调度策略(时间片轮询调度策略是可选的),它是作为优先级抢占式调度策略的补充,可以协调同一优先级的多个就绪线程共享CPU的问题,改善多个同优先级就绪线程的调度问题。
在Mbed OS中,每个轮询线程有最长时间限制(时间片)。在此时间片内,该线程可以被激活。在Mbed OS内核中定义默认的时间片OS_ROBIN_TIMEOUT 为5,也就意味着时间片轮询调度的时间间隔为5个时间嘀嗒,若每个时间嘀嗒为1ms,那么时间间隔就是5ms。同时,在线程执行的时间片期间并不禁止抢占,这就意味着CPU使用权可能被其他优先级高的线程抢占。
在Mbed OS中,如果设置所有线程的时间片大小为0,就不会进行时间片轮询调度。若未出现优先级抢占或者线程阻塞的情况,正在运行的线程则不会主动放弃对CPU的使用权。反之,当线程运行到规定时间间隔之后,会产生一次调度判断,若此时有同优先级的线程处于就绪态,则让出CPU使用权,否则继续运行。
在Mbed OS中,调度策略是通过系统服务调用SVC(Supervisor Call)中断、可挂起系统调用PendSV(Pendable Supervisor)中断和SysTick定时器中断来实现的。
Mbed OS中的固有线程有自启动线程、空闲线程和定时器线程,其中定时器线程的优先级(40)最高,空闲线程的优先级(1)最低,自启动线程的优先级(24)介于它们之间。
在内核启动之前,需要创建一个自启动线程,以便内核启动后执行它,并由它来创建其他用户线程。当自启动线程被创建时,其状态为就绪态,会自动被放入就绪列表中。在Mbed OS中,自启动线程的优先级为24,由于在启动过程中,最后是由自启动线程来创建其他用户线程的,因此它的优先级必须要高于或等于其他用户线程的优先级,这样才能保证其他用户线程被正常创建并运行。若自启动线程的优先级低于它所创建的用户线程的优先级,则一旦创建一个线程后,自启动线程会被抢占,无法继续创建其他线程。
为了确保在内核无用户线程可运行的时候,CPU能继续保持运行状态,就必须安排一个空闲线程,该线程不完成任何实际工作,其状态为就绪态,始终在就绪列表中。在Mbed OS中,空闲线程是在内核启动的过程中被创建的,其优先级为11,是所有线程中最低的。
在Mbed OS的内核启动过程中,不仅创建了自启动线程和空闲线程,还创建了定时器线程,其优先级较高,为40。当定时器线程被创建后,其状态为就绪态,存放在就绪列表中。此时,就绪列表中有定时器线程(优先级为40)、自启动线程(优先级为24)和空闲线程(优先级为1),其中定时器线程优先级最高。因此,当内核启动从SVC中断返回时,就会转到定时器线程中执行,它是第一个获得运行的线程。当定时器线程运行后,先创建了一个消息队列,然后从消息队列中获取消息。由于此时消息队列刚创建,无消息可取,因此定时器线程就会进入阻塞态。之后,Mbed OS内核会进行线程调度,从就绪列表中选择此时优先级最高的线程(此时为自启动线程),将其状态设置为激活态,准备运行。