西电嵌入式操作系统内核原理与分析大作业(实现最近截止时间优先调度算法)

image

实现方法

最近截至时间优先算法

最近截止时间优先算法(Earliest Deadline First, EDF) :一种基于任务的实时调度算法,会根据任务的 截止时间 来排序任务,截止时间最早的任务拥有最高优先级,优先执行。

  • 最长运行时间:任务执行一次的最长时间

  • 最近截止时间:任务截止的时刻

  • 抢占式:每当有任务到达时,调度器会重新排序任务队列,确保截止时间最早的任务得到执行。

实现思路

前提:每次调度时的截止时间随机(1~1000的随机数),任务的最大运行时间固定,由于截止时间随机且是抢占式调度,所以运行时间不相同,满足要求。

说明:简易起见,代码中没有单独再创建按照deadline升序排列的任务就绪队列,而是借用优先级为1的就绪队列作为deadline升序队列,并复用相关的List操作函数。

  1. 在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;
    
  2. 静态创建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;
    
    		...
    }
    
  3. 添加辅助宏函数函数prvAddTaskToReadyListByDeadline:插入pxTCB到就绪队列,按照deadLine升序排列。
    原先调用prvAddTaskToReadyList的地方(xTaskIncrementTickprvAddNewTaskToReadyList)都替换为prvAddTaskToReadyListByDeadline

    /* 将任务按照Deadline添加到就绪列表 */                                    
    #define prvAddTaskToReadyListByDeadline( pxTCB ) \
    	taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );\
    	vListInsert( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
    
  4. 任务切换时机:时钟中断处理程序

    • 每次时钟中断到来,当前任务的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();
    		}
    }
    
  5. 选择需要切换到的任务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 );
	}
}

image-20250126172825896

  • 黄线以前:为初始截止时间决定的调度顺序。

    截止时间决定的调度顺序为TCB2->TCB1->TCB3,最大执行时间决定了运行时间分别为50,100,200。

  • 黄线以后:为随机截止时间决定的调度顺序。

    可见随着时间的推移,由于只会执行截止时间最小的任务,所以当前正在运行的TCB的截止时间会越来越小,因此随机生成的截止时间小于当前TCB截止时间的概率越来越小,因此每个TCB的运行时间会越来越长。

六个任务执行结果:TCB1到6的执行时间都为100,初始截止时间分别为1,2,3,4,5,6。

image-20250126181158512

源代码

网盘地址

posted @ 2025-01-26 18:45  Miaops  阅读(20)  评论(0)    收藏  举报