前面几个小节的讲解都是没有实现优先级的,这一小节的讲解将要实现优先级。所谓的优先级就是每次在进行任务切换或调度的时候选择优先级最高的任务进行。任务可以被调度的前提是它有被挂载在就绪列表中(还在延时中的任务应该就不会放在就绪列表了,应该会被刚在阻塞列表,这个后面会讲到),就绪列表可以说就是一个链表的数组,就绪列表的每一个元素都是一个链表的根节点,数组的索引越小,表示挂载到这个链表上的任务的优先级最低,所以空闲任务挂载到索引为0的链表中。现在的关键是如何找到优先级最高的任务,简单一点就是找到就绪列表中索引最大的链表且链表里面还挂载的有任务(但是这里就会出现一种情况,找到的就绪列表中索引最大的链表且链表里面还挂载的有任务的链表中有多个任务,也就是此时有多个任务处于当前的最高优先级,那么此时就会使用时间片,这个后面会讲到)。
F
r
e
e
R
T
O
S
FreeRTOS
FreeRTOS中选择最高优先级的任务的时候一共有两种方法,一种是通用的方法(宏
c
o
n
f
i
g
U
S
E
_
P
O
R
T
_
O
P
T
I
M
I
S
E
D
_
T
A
S
K
_
S
E
L
E
C
T
I
O
N
configUSE\_PORT\_OPTIMISED\_TASK\_SELECTION
configUSE_PORT_OPTIMISED_TASK_SELECTION定义为0),一种是针对特定的处理架构而优化的方法(宏
c
o
n
f
i
g
U
S
E
_
P
O
R
T
_
O
P
T
I
M
I
S
E
D
_
T
A
S
K
_
S
E
L
E
C
T
I
O
N
configUSE\_PORT\_OPTIMISED\_TASK\_SELECTION
configUSE_PORT_OPTIMISED_TASK_SELECTION定义为1,但是在这种方法之下支持的最高优先级的等级个数不能超过32(
0
−
>
31
0->31
0−>31),但是在实际应用中似乎很少有任务优先等级数超过32的)。
P
a
r
t
1
Part\quad 1
Part1在源文件
p
o
r
t
m
a
c
r
o
.
h
portmacro.h
portmacro.h中定义,
P
a
r
t
2
Part\quad 2
Part2在源文件
t
a
s
k
s
.
c
tasks.c
tasks.c中定义(
P
a
r
t
2
Part\quad 2
Part2相对于源码有部分修改,详见代码中
#
i
f
0
\#if\quad 0
#if0那一块)。通用方法主要有
t
a
s
k
R
E
C
O
R
D
_
R
E
A
D
Y
_
P
R
I
O
R
I
T
Y
taskRECORD\_READY\_PRIORITY
taskRECORD_READY_PRIORITY和
t
a
s
k
S
E
L
E
C
T
_
H
I
G
H
E
S
T
_
P
R
I
O
R
I
T
Y
_
T
A
S
K
taskSELECT\_HIGHEST\_PRIORITY\_TASK
taskSELECT_HIGHEST_PRIORITY_TASK这两个宏接口,
u
x
T
o
p
R
e
a
d
y
P
r
i
o
r
i
t
y
uxTopReadyPriority
uxTopReadyPriority是一个在
t
a
s
k
s
.
c
tasks.c
tasks.c中定义的全局变量,它表示当前的就绪列表中所有的就绪任务中优先级数最高的任务的优先级数,宏接口
t
a
s
k
R
E
C
O
R
D
_
R
E
A
D
Y
_
P
R
I
O
R
I
T
Y
taskRECORD\_READY\_PRIORITY
taskRECORD_READY_PRIORITY用来更新这个值。宏接口
t
a
s
k
S
E
L
E
C
T
_
H
I
G
H
E
S
T
_
P
R
I
O
R
I
T
Y
_
T
A
S
K
taskSELECT\_HIGHEST\_PRIORITY\_TASK
taskSELECT_HIGHEST_PRIORITY_TASK用来寻找当前就绪列表中就绪的任务中优先级最高的任务中的一个,将
p
x
C
u
r
r
e
n
t
T
C
B
pxCurrentTCB
pxCurrentTCB指向这个任务的任务控制块并更新
u
x
T
o
p
R
e
a
d
y
P
r
i
o
r
i
t
y
uxTopReadyPriority
uxTopReadyPriority。
/*********************************************************************************************/
/*------------------------------------Part 1-------------------------------------------------*/
/*********************************************************************************************/
/* Port specific optimisations. */
#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#endif
#if configUSE_PORT_OPTIMISED_TASK_SELECTION == 1
/* Check the configuration. */
#if ( configMAX_PRIORITIES > 32 )
#error configUSE_PORT_OPTIMISED_TASK_SELECTION can only be set to 1 when configMAX_PRIORITIES is less than or equal to 32. It is very rare that a system requires more than 10 to 15 difference priorities as tasks that share a priority will time slice.
#endif
/* Store/clear the ready priorities in a bit map. */
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )
/*-----------------------------------------------------------*/
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
#endif /* taskRECORD_READY_PRIORITY */
/*-----------------------------------------------------------*/
/*********************************************************************************************/
/*------------------------------------Part 2-------------------------------------------------*/
/*********************************************************************************************/
#if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 )
/* If configUSE_PORT_OPTIMISED_TASK_SELECTION is 0 then task selection is
* performed in a generic way that is not optimised to any particular
* microcontroller architecture. */
/* uxTopReadyPriority holds the priority of the highest priority ready
* state task. */
#define taskRECORD_READY_PRIORITY( uxPriority ) \
{ \
if( ( uxPriority ) > uxTopReadyPriority ) \
{ \
uxTopReadyPriority = ( uxPriority ); \
} \
} /* taskRECORD_READY_PRIORITY */
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority = uxTopReadyPriority; \
\
/* Find the highest priority queue that contains ready tasks. */ \
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
{ \
configASSERT( uxTopPriority ); \
--uxTopPriority; \
} \
\
/* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \
* the same priority get an equal share of the processor time. */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
uxTopReadyPriority = uxTopPriority; \
} /* taskSELECT_HIGHEST_PRIORITY_TASK */
/*-----------------------------------------------------------*/
/* Define away taskRESET_READY_PRIORITY() and portRESET_READY_PRIORITY() as
* they are only required when a port optimised method of task selection is
* being used. */
#define taskRESET_READY_PRIORITY( uxPriority )
#define portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )
#else /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
/* If configUSE_PORT_OPTIMISED_TASK_SELECTION is 1 then task selection is
* performed in a way that is tailored to the particular microcontroller
* architecture being used. */
/* A port optimised version is provided. Call the port defined macros. */
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( ( uxPriority ), uxTopReadyPriority )
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
\
/* Find the highest priority list that contains ready tasks. */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
/*-----------------------------------------------------------*/
/* A port optimised version is provided, call it only if the TCB being reset
* is being referenced from a ready list. If it is referenced from a delayed
* or suspended list then it won't be in a ready list. */
#if 0
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
#else
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \ \
}
#endif
对于针对特定处理器架构的优化方法(这里以
C
o
r
t
e
x
−
M
3
Cortex-M3
Cortex−M3为例),限制优先级数最多为32(对应数组索引
0
−
>
31
0->31
0−>31),因此这里32位变量
u
x
T
o
p
R
e
a
d
y
P
r
i
o
r
i
t
y
uxTopReadyPriority
uxTopReadyPriority的每一个比特位用来表示对应的优先级上是否有就绪的任务,如果对应的优先级上有1个或多个任务就绪那么对应的比特位的值为1,如果对应的优先级上没有任务就绪那么对应的比特位的值为0。比如当
u
x
T
o
p
R
e
a
d
y
P
r
i
o
r
i
t
y
uxTopReadyPriority
uxTopReadyPriority的值为
0
x
01000000
0x01000000
0x01000000的时候,那表明有优先级为24的任务已经就绪了。这里这样做的目的是充分利用了
C
o
r
t
e
x
−
M
3
Cortex-M3
Cortex−M3架构的
_
_
c
l
z
\_\_clz
__clz指令且可以快速计算当前就绪列表中优先级最高的任务的优先级数,该指令可以用来计算变量(以二进制算)的前导0的个数,比如
_
_
c
l
z
\_\_clz
__clz指令以值
0
x
01000000
0x01000000
0x01000000为参数的时候的返回值是7,假如现在
u
x
T
o
p
R
e
a
d
y
P
r
i
o
r
i
t
y
uxTopReadyPriority
uxTopReadyPriority的值为
0
x
01000000
0x01000000
0x01000000,那么可以快速计算出目前当前就绪列表中优先级最高的任务的优先级数为
31
−
7
=
24
31-7=24
31−7=24。优化方法主要有
t
a
s
k
R
E
C
O
R
D
_
R
E
A
D
Y
_
P
R
I
O
R
I
T
Y
taskRECORD\_READY\_PRIORITY
taskRECORD_READY_PRIORITY,
t
a
s
k
S
E
L
E
C
T
_
H
I
G
H
E
S
T
_
P
R
I
O
R
I
T
Y
_
T
A
S
K
taskSELECT\_HIGHEST\_PRIORITY\_TASK
taskSELECT_HIGHEST_PRIORITY_TASK和
t
a
s
k
R
E
S
E
T
_
R
E
A
D
Y
_
P
R
I
O
R
I
T
Y
taskRESET\_READY\_PRIORITY
taskRESET_READY_PRIORITY(相对于源代码中的定义有改动,只是简单的将对应的优先级比特位置0,而不是先判断该比特位对应的优先级下是否还有就绪的任务存在,只有当该比特位对应的优先级下没有有就绪的任务存在的时候才将对应的优先级比特位置0。因为目前的代码还没有实现延时列表,任务的延时还是通过任务空控制块里面的
x
T
i
c
k
s
T
o
D
e
l
a
y
xTicksToDelay
xTicksToDelay元素来实现的,而不是在任务有延时需要的时候将任务从就绪任务删除而添加到延时列表)这三个宏接口,
u
x
T
o
p
R
e
a
d
y
P
r
i
o
r
i
t
y
uxTopReadyPriority
uxTopReadyPriority是一个在
t
a
s
k
s
.
c
tasks.c
tasks.c中定义的全局变量,它的32个比特位中的每一个比特位表示对应的优先级级数下是否有就绪的任务,当对应的优先级数上有任务就绪时宏接口
t
a
s
k
R
E
C
O
R
D
_
R
E
A
D
Y
_
P
R
I
O
R
I
T
Y
taskRECORD\_READY\_PRIORITY
taskRECORD_READY_PRIORITY用来将对应的比特位置1。当对应的优先级数上没有任务就绪时宏接口
t
a
s
k
R
E
S
E
T
_
R
E
A
D
Y
_
P
R
I
O
R
I
T
Y
taskRESET\_READY\_PRIORITY
taskRESET_READY_PRIORITY用来将对应的比特位清0。宏接口
t
a
s
k
S
E
L
E
C
T
_
H
I
G
H
E
S
T
_
P
R
I
O
R
I
T
Y
_
T
A
S
K
taskSELECT\_HIGHEST\_PRIORITY\_TASK
taskSELECT_HIGHEST_PRIORITY_TASK用来寻找当前就绪列表中就绪的任务中优先级最高的任务中的一个,将
p
x
C
u
r
r
e
n
t
T
C
B
pxCurrentTCB
pxCurrentTCB指向这个任务的任务控制块。
我们这里实现的优先级基于优化的方法,为了实现优先级还需要对前面几个小节的接口进行一定的修改。首先任务控制块里面需要添加元素
u
x
P
r
i
o
r
i
t
y
uxPriority
uxPriority来表示任务的优先级级数。相应的前面小节中创建任务的接口
x
T
a
s
k
C
r
e
a
t
e
S
t
a
t
i
c
xTaskCreateStatic
xTaskCreateStatic也需要做修改,需要添加优先级的参数,这个添加的参数主要是因为接口
p
r
v
I
n
i
t
i
a
l
i
s
e
N
e
w
T
a
s
k
prvInitialiseNewTask
prvInitialiseNewTask(这个接口新加了对任务控制块里面的
u
x
P
r
i
o
r
i
t
y
uxPriority
uxPriority元素的初始化)也新加了优先级的参数,还有就是因为之前的小节没有实现优先级因此只是简单的调用链表的接口
v
L
i
s
t
I
n
s
e
r
t
E
n
d
vListInsertEnd
vListInsertEnd简单的将任务插入到就绪列表,但是现在的话会在接口
x
T
a
s
k
C
r
e
a
t
e
S
t
a
t
i
c
xTaskCreateStatic
xTaskCreateStatic里面调用接口
p
r
v
A
d
d
N
e
w
T
a
s
k
T
o
R
e
a
d
y
L
i
s
t
prvAddNewTaskToReadyList
prvAddNewTaskToReadyList来将任务插入到就绪列表。
接口
p
r
v
A
d
d
N
e
w
T
a
s
k
T
o
R
e
a
d
y
L
i
s
t
prvAddNewTaskToReadyList
prvAddNewTaskToReadyList(在
F
r
e
e
R
T
O
S
FreeRTOS
FreeRTOS源码中的
t
a
s
k
s
.
c
tasks.c
tasks.c文件中定义,但是这里相对源码做了大量的精简)。该接口在具体操作之前会利用关中断来进行临界段的操作,在所有的操作结束之后会利用关中断来退出临界段。该接口首先检查当前需要添加的任务是否是第一个初始化的任务,如果是那就将指针
p
x
C
u
r
r
e
n
t
T
C
B
pxCurrentTCB
pxCurrentTCB指向它,然后调用接口
p
r
v
I
n
i
t
i
a
l
i
s
e
T
a
s
k
L
i
s
t
s
prvInitialiseTaskLists
prvInitialiseTaskLists(在
F
r
e
e
R
T
O
S
FreeRTOS
FreeRTOS源码中的
t
a
s
k
s
.
c
tasks.c
tasks.c文件中定义,但是这里相对源码做了大量的精简)初始化就绪列表(因为目前我们只实现了就绪列表,因此这里只初始化了就绪列表,在后续实现了延时列表之后该接口还会初始化延时列表在
F
r
e
e
R
T
O
S
FreeRTOS
FreeRTOS源码中该接口会初始化素有任务相关的列表),最后调用宏接口
p
r
v
A
d
d
T
a
s
k
T
o
R
e
a
d
y
L
i
s
t
prvAddTaskToReadyList
prvAddTaskToReadyList将任务插入到就绪列表的对应位置上。如果当前的任务不是第一个初始化的任务,接口
p
r
v
A
d
d
N
e
w
T
a
s
k
T
o
R
e
a
d
y
L
i
s
t
prvAddNewTaskToReadyList
prvAddNewTaskToReadyList首先检查当前的这个需要添加的任务的优先级是否大于当前
p
x
C
u
r
r
e
n
t
T
C
B
pxCurrentTCB
pxCurrentTCB指向的任务的优先级,如果大于则将
p
x
C
u
r
r
e
n
t
T
C
B
pxCurrentTCB
pxCurrentTCB指向当前的这个需要添加的任务,否则不做任何操作,最后调用宏接口
p
r
v
A
d
d
T
a
s
k
T
o
R
e
a
d
y
L
i
s
t
prvAddTaskToReadyList
prvAddTaskToReadyList将任务插入到就绪列表的对应位置上。
/*
* Called after a new task has been created and initialised to place the task
* under the control of the scheduler.
*/
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{
/* Ensure interrupts don't access the task lists while the lists are being
* updated. */
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++;
if( pxCurrentTCB == NULL )
{
/* There are no other tasks, or all the other tasks are in
* the suspended state - make this the current task. */
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
/* This is the first task to be created so do the preliminary
* initialisation required. We will not recover if this call
* fails, but we will report the failure. */
prvInitialiseTaskLists();
}
}
else
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
}
prvAddTaskToReadyList( pxNewTCB );
}
taskEXIT_CRITICAL();
}
宏接口 p r v A d d T a s k T o R e a d y L i s t prvAddTaskToReadyList prvAddTaskToReadyList(在 F r e e R T O S FreeRTOS FreeRTOS源码中的 t a s k s . c tasks.c tasks.c文件中定义,但是这里相对源码做了精简)将任务插入到就绪列表的对应位置上。它首先根据当前任务的优先级确定当前的任务应该插入到就绪列表的哪一个索引对应的链表里面,确定好之后直接调用链表的接口 v L i s t I n s e r t E n d vListInsertEnd vListInsertEnd简单的将任务插入到链表的尾部。
/*
* Place the task represented by pxTCB into the appropriate ready list for
* the task. It is inserted at the end of the list.
*/
#define prvAddTaskToReadyList( pxTCB ) \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );
有了接口
p
r
v
A
d
d
N
e
w
T
a
s
k
T
o
R
e
a
d
y
L
i
s
t
prvAddNewTaskToReadyList
prvAddNewTaskToReadyList里面的相应操作,前面的小节中在接口
v
T
a
s
k
S
t
a
r
t
S
c
h
e
d
u
l
e
r
vTaskStartScheduler
vTaskStartScheduler里面直接将指针
p
x
C
u
r
r
e
n
t
T
C
B
pxCurrentTCB
pxCurrentTCB指向特定任务的操作也可以删除了。在
F
r
e
e
R
T
O
S
FreeRTOS
FreeRTOS源码中,任务进入延时之后应该被从就绪列表中删除并插入延时列表,还会将
u
x
T
o
p
R
e
a
d
y
P
r
i
o
r
i
t
y
uxTopReadyPriority
uxTopReadyPriority变量中和它的优先级对应的位清零(清零之前还会检查对应的优先级之下是否只有它一个任务,如果是对应的位就会被清0,如果不是就不会被清0,但是这里的实现只是简单的直接清0,而没有检查,因为这里目前还没有实现延时列表),但是这里只是简单在接口
v
T
a
s
k
D
e
l
a
y
vTaskDelay
vTaskDelay中将
u
x
T
o
p
R
e
a
d
y
P
r
i
o
r
i
t
y
uxTopReadyPriority
uxTopReadyPriority变量中和相应任务的优先级对应的位清零,因为这里没有实现延时里列表且只是简单的利用任务控制块里面的
x
T
i
c
k
s
T
o
D
e
l
a
y
xTicksToDelay
xTicksToDelay元素来进行延时,但是在
F
r
e
e
R
T
O
S
FreeRTOS
FreeRTOS源码中的任务控制块中是没有
x
T
i
c
k
s
T
o
D
e
l
a
y
xTicksToDelay
xTicksToDelay这个元素的。相应的对应的当任务的延时时间到达的时候也会在接口
x
T
a
s
k
I
n
c
r
e
m
e
n
t
T
i
c
k
xTaskIncrementTick
xTaskIncrementTick中将
u
x
T
o
p
R
e
a
d
y
P
r
i
o
r
i
t
y
uxTopReadyPriority
uxTopReadyPriority变量中和对应任务的优先级对应的位重新置1。最大的变化是还是接口
v
T
a
s
k
S
w
i
t
c
h
C
o
n
t
e
x
t
vTaskSwitchContext
vTaskSwitchContext这里不再手动的进行任务的切换而是利用宏接口
t
a
s
k
S
E
L
E
C
T
_
H
I
G
H
E
S
T
_
P
R
I
O
R
I
T
Y
_
T
A
S
K
taskSELECT\_HIGHEST\_PRIORITY\_TASK
taskSELECT_HIGHEST_PRIORITY_TASK定位到优先级最高的任务然后切换过去。
好了为了实现优先级到此为止所有的修改都已经结束了,具体的工程代码在这里。这里的实验现象和前一小节的实验现象一样。