西电嵌入式操作系统内核原理与分析大作业(实现最近截止时间优先调度算法)
实现方法
最近截至时间优先算法
最近截止时间优先算法(Earliest Deadline First, EDF) :一种基于任务的实时调度算法,会根据任务的 截止时间 来排序任务,截止时间最早的任务拥有最高优先级,优先执行。
-
最长运行时间:任务执行一次的最长时间
-
最近截止时间:任务截止的时刻
-
抢占式:每当有任务到达时,调度器会重新排序任务队列,确保截止时间最早的任务得到执行。
实现思路
前提:每次调度时的截止时间随机(1~1000的随机数),任务的最大运行时间固定,由于截止时间随机且是抢占式调度,所以运行时间不相同,满足要求。
说明:简易起见,代码中没有单独再创建按照deadline升序排列的任务就绪队列,而是借用优先级为1的就绪队列作为deadline升序队列,并复用相关的List操作函数。
-
在TCB结构体添加
maxExecutionTime
(最大运行时间),deadline
(截止时间),TickType_t remainingTime
(剩余运行时间)// FreeRTOS.h typedef struct tskTaskControlBlock { .... TickType_t maxExecutionTime; /* 最大运行时间 */ TickType_t deadline; /* 截止时间 */ TickType_t remainingTime; /* 剩余运行时间 */ } tskTCB; typedef tskTCB TCB_t;
-
静态创建TCB时,传入最大运行时间和初始截止时间。
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, /* 任务入口 */ const char * const pcName, /* 任务名称,字符串形式 */ const uint32_t ulStackDepth,/* 任务栈大小,单位为字 */ void * const pvParameters, /* 任务形参 */ UBaseType_t uxPriority,/* 任务优先级,数值越大,优先级越高 */ StackType_t * const puxStackBuffer, /* 任务栈起始地址 */ TCB_t * const pxTaskBuffer, /* 任务控制块指针 */ UBaseType_t maxExecutionTime,/* 最大执行时间 */ UBaseType_t deadline /* 截止时间 */) { ... /* 创建新的任务 */ prvInitialiseNewTask( pxTaskCode, /* 任务入口 */ pcName, /* 任务名称,字符串形式 */ ulStackDepth, /* 任务栈大小,单位为字 */ pvParameters, /* 任务形参 */ uxPriority, /* 任务优先级,数值越大,优先级越高 */ &xReturn, /* 任务句柄 */ pxNewTCB); /* 任务栈起始地址 */ // 添加部分如下 pxNewTCB->maxExecutionTime = maxExecutionTime; pxNewTCB->deadline = deadline; pxNewTCB->remainingTime = maxExecutionTime; pxNewTCB->xStateListItem.xItemValue = deadline; ... }
-
添加辅助宏函数函数
prvAddTaskToReadyListByDeadline
:插入pxTCB到就绪队列,按照deadLine升序排列。
原先调用prvAddTaskToReadyList
的地方(xTaskIncrementTick
和prvAddNewTaskToReadyList
)都替换为prvAddTaskToReadyListByDeadline
。/* 将任务按照Deadline添加到就绪列表 */ #define prvAddTaskToReadyListByDeadline( pxTCB ) \ taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );\ vListInsert( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
-
任务切换时机:时钟中断处理程序
-
每次时钟中断到来,当前任务的
remainingTime
减一。 -
如果当前任务达到最大运行时间(
pxCurrentTCB->remainingTime <= 0
)或有deadline更近的任务到来(nextTCB != pxCurrentTCB
),则进行调度portYIELD()
。 -
调度前进行如下工作:恢复进程的剩余时间;随机生成截止时间;将任务从就绪列表中移除,再重新添加回去,即让重新生成截止时间的任务插入到正确的位置。
void xTaskIncrementTick( void ) { ... /* 任务切换 */ pxCurrentTCB->remainingTime--; TCB_t *nextTCB = listGET_OWNER_OF_HEAD_ENTRY(&( pxReadyTasksLists[ 1 ] )); if(pxCurrentTCB->remainingTime <= 0 || nextTCB != pxCurrentTCB) { /* 恢复执行进程的剩余时间 */ pxCurrentTCB->remainingTime = pxCurrentTCB->maxExecutionTime; /* 随机生成截止时间 */ pxCurrentTCB->deadline = (TickType_t)(rand() % 1000 + 1); pxCurrentTCB->xStateListItem.xItemValue = pxCurrentTCB->deadline; /* 将任务从就绪列表中移除,再重新添加回去 */ uxListRemove( &( pxCurrentTCB->xStateListItem )); prvAddTaskToReadyListByDeadline(pxCurrentTCB); portYIELD(); } }
-
-
选择需要切换到的任务:
vTaskSwitchContext
。-
原先该函数的作用是,选取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB。
-
如上文所述,由于我们采用优先级为1的就绪队列作为deadline队列,并且在创建和调度时,插入的TCB都按照deadline升序排列,所以该函数实际上获取到的就是deadline最小的TCB。
void vTaskSwitchContext( void ) { /* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ taskSELECT_HIGHEST_PRIORITY_TASK(); }
-
测试结果
相关参数:
- 最大运行时间:当任务单次执行时间达到最大时间时,发生调度
- 截止时间:截止时间小的优先被调度
- 不考虑超过截止时间和当前时间戳的大小关系,即使截止时间小于当前时间戳,依然执行。
测试程序:为方便说明,先采用三个任务
int main(void)
{
/* 硬件初始化 */
/* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
/* 初始化与任务相关的列表,如就绪列表 */
prvInitialiseTaskLists();
/* 创建任务 */
Task1Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */
(char *)"Task1", /* 任务名称,字符串形式 */
(uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(UBaseType_t) 1, /* 任务优先级,数值越大,优先级越高 */
(StackType_t *)Task1Stack, /* 任务栈起始地址 */
(TCB_t *)&Task1TCB, /* 任务控制块 */
100, /* 最大执行时间 */
200); /* 初始截止时间 */
Task2Handle = xTaskCreateStatic( (TaskFunction_t)Task2_Entry, /* 任务入口 */
(char *)"Task2", /* 任务名称,字符串形式 */
(uint32_t)TASK2_STACK_SIZE , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(UBaseType_t) 1, /* 任务优先级,数值越大,优先级越高 */
(StackType_t *)Task2Stack, /* 任务栈起始地址 */
(TCB_t *)&Task2TCB, /* 任务控制块 */
50, /* 最大执行时间 */
200); /* 初始截止时间 */
Task3Handle = xTaskCreateStatic( (TaskFunction_t)Task3_Entry,/* 任务入口 */
(char *)"Task3", /* 任务名称,字符串形式 */
(uint32_t)TASK3_STACK_SIZE , /* 任务栈大小,单位为字 */
(void *) NULL, /* 任务形参 */
(UBaseType_t) 1, /* 任务优先级,数值越大,优先级越高 */
(StackType_t *)Task3Stack, /* 任务栈起始地址 */
(TCB_t *)&Task3TCB, /* 任务控制块 */
200, /* 最大执行时间 */
200); /* 初始截止时间 */
/* 在启动调度器前,关闭中断 */
portDISABLE_INTERRUPTS();
/* 启动调度器,开始多任务调度,启动成功则不返回 */
vTaskStartScheduler();
for(;;)
{
/* 系统启动成功不会到达这里 */
}
}
/* 软件延时 */
void delay (uint32_t count)
{
for(; count!=0; count--);
}
/* 任务1函数,任务2,任务三同*/
void Task1_Entry( void *p_arg )
{
for( ;; )
{
flag1 = 1;
delay(1000);
//vTaskDelay( 2 );
flag1 = 0;
delay(1000);
//vTaskDelay( 2 );
}
}
-
黄线以前:为初始截止时间决定的调度顺序。
截止时间决定的调度顺序为TCB2->TCB1->TCB3,最大执行时间决定了运行时间分别为50,100,200。
-
黄线以后:为随机截止时间决定的调度顺序。
可见随着时间的推移,由于只会执行截止时间最小的任务,所以当前正在运行的TCB的截止时间会越来越小,因此随机生成的截止时间小于当前TCB截止时间的概率越来越小,因此每个TCB的运行时间会越来越长。
六个任务执行结果:TCB1到6的执行时间都为100,初始截止时间分别为1,2,3,4,5,6。