嵌入式FreeRTOS学习五,FreeRTOS任务调度器

四、调度器

FreeRTOS 操作系统支持三种调度方式:抢占式调度,时间片调度和合作式调度。实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少。启动调度器的API函数vTaskStartScheduler()的源码如下:
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
StaticTask_t *pxIdleTaskTCBBuffer= NULL;
StackType_t *pxIdleTaskStackBuffer= NULL;
uint16_t usIdleTaskStackSize =tskIDLE_STACK_SIZE;
 
    /*如果使用静态内存分配任务堆栈和任务TCB,则需要为空闲任务预先定义好任务内存和任务TCB空间*/
    #if(configSUPPORT_STATIC_ALLOCATION == 1 )
    {
       vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &usIdleTaskStackSize);
    }
    #endif /*configSUPPORT_STATIC_ALLOCATION */
 
    /* 创建空闲任务,使用最低优先级*/
    xReturn =xTaskGenericCreate( prvIdleTask, "IDLE",usIdleTaskStackSize, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT), &xIdleTaskHandle,pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer, NULL );
 
    if( xReturn == pdPASS )
    {
        /* 先关闭中断,确保节拍定时器中断不会在调用xPortStartScheduler()时或之前发生.当第一个任务启动时,会重新启动中断*/
       portDISABLE_INTERRUPTS();
       
        /* 初始化静态变量 */
       xNextTaskUnblockTime = portMAX_DELAY;
       xSchedulerRunning = pdTRUE;
        xTickCount = ( TickType_t ) 0U;
 
        /* 如果宏configGENERATE_RUN_TIME_STATS被定义,表示使用运行时间统计功能,则下面这个宏必须被定义,用于初始化一个基础定时器/计数器.*/
       portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
 
        /* 设置系统节拍定时器,这与硬件特性相关,因此被放在了移植层.*/
        if(xPortStartScheduler() != pdFALSE )
        {
            /* 如果调度器正确运行,则不会执行到这里,函数也不会返回*/
        }
        else
        {
            /* 仅当任务调用API函数xTaskEndScheduler()后,会执行到这里.*/
        }
    }
    else
    {
        /* 执行到这里表示内核没有启动,可能因为堆栈空间不够 */
       configASSERT( xReturn );
    }
 
    /* 预防编译器警告*/
    ( void ) xIdleTaskHandle;
}

这个API函数首先创建一个空闲任务,空闲任务使用最低优先级(0级),空闲任务的任务句柄存放在静态变量xIdleTaskHandle中,可以调用API函数xTaskGetIdleTaskHandle()获得空闲任务句柄。

      如果任务创建成功,则关闭中断(调度器启动结束时会再次使能中断的),初始化一些静态变量,然后调用函数xPortStartScheduler()来启动系统节拍定时器并启动第一个任务。因为设置系统节拍定时器涉及到硬件特性,因此函数xPortStartScheduler()由移植层提供,不同的硬件架构,这个函数的代码也不相同。
 

--------------------------------------------------------------------------------------------------------------------------------

合作式调度

亦称为FreeRTOS的协程,实际上是线程并发出来的,每个线程并发出来的协程共用一个栈空间。合作式调度主要用在资源有限的设备上面,现在已经很少使用了。出于这个原因,后面的 FreeRTOS 版本中不会将合作式调度删除掉,但也不会再进行升级了。
抢占式调度
每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,比如vTaskDelay。
时间片调度

 每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数,比如函数vTaskDelay,才会执 行同优先级任务之间的任务切换。如果用户在 FreeRTOS.h 中禁止使用时间片调度,那么每个任务必须配置不同的优先级。

-------------------------------------------------------------------------------------------------------------------

4.1 调度器

简单的说,调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一个共同的特性:
调度器可以区分就绪态任务和挂起任务( 由于延迟,信号量等待,邮箱等待,事件组等待等原因而使得任务被挂起 )。
调度器可以选择就绪态中的一个任务,然后激活它( 通过执行这个任务 )。当前正在执行的任务是运行态的任务。 不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。
嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在不同的嵌入式 实时操作系统中区别不大,基本相同的硬件内核架构,任务切换也是相似的。调度算法就有些区别了。下面我们主要了解一下抢占式调度器和时间片调度器。

4.2 抢占式调度器

在实际的应用中,不同的任务需要不同的响应时间。例如,我们在一个应用中需要使用电机,键盘和LCD 显示。电机比键盘和 LCD 需要更快速的响应,如果我们使用合作式调度器或者时间片调度,那么电机将无法得到及时的响应,这时抢占式调度是必须的。
如果使用了抢占式调度,最高优先级的任务一旦就绪,总能得到 CPU 的控制权。比如,当一个运行着的任务被其它高优先级的任务抢占,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了 CPU 的控制权并运行。又比如,如果中断服务程序使一个高优先级的任务进入就绪态,中断完成时,被中断的低优先级任务被挂起,优先级高的那个任务开始运行。
使用抢占式调度器,使得最高优先级的任务什么时候可以得到 CPU 的控制权并运行是可知的,同时使得任务级响应 时间得以最优化。总的来说,学习抢占式调度要掌握的最关键一点是: 每个任务都被分配了不同的优先级,抢占式调度器会获得就绪列表中优先级最高的任务,并运行这个任务。

4.3 时间片调度器

在小型的嵌入式 RTOS 中,最常用的的时间片调度算法就是 Round-Robin 调度算法。这种调度算法可以用于抢占式或者合作式的多任务中。另外, 时间片调度适合用于不要求任务实时响应的情况 。实现 Round-Robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。
在RR调度策略下,一个任务会一直执行,直到:
自愿放弃控制权
被更高优先级的任务抢占
时间片用完
如下图所示,A在用完自己的时间片后,将CPU执行权让给任务B,于是A离开Read队列,而B进入Read队列。一旦线程的时间片用完,该线程就会被下一个READ的具有同等优先级的任务给抢占。
轮转调度算法在实际使用中,注意实现如下:
优点:
缩短任务的等待时间,提高系统的吞吐量,甚至在某个任务异常独占CPU,还可以执行其他任务。
缺点:
        对长作业非常不利,可能长时间得不到执行,未能依据作业的紧迫程度来划分执行的优先级,难以准确估计作业 (进程)的执行时间,从而影响调度性能。时间片长度该设为多少,这个问题需要好好调试,因为有时候时间片太短,任务还没来得及做事情,就被调度了;时间片太长,又会造成让其它任务等太久,所以实际编程中需要好好调试。倒不如直接不使用。
       还有的是FreeRTOS的时间片不能额外设置,例如系统定时器中断频率为1000Hz,则时间为1ms。μc/OS可以额外设置,例如系统定时器中断频率为1000Hz,定时周期为1ms,可通过OSSchedRoundRobinCfg()函数设置时间片大小。
---------------------------------------------------------------------------------------------------------------------------------
代码示例:
int main(void)  { 

   xReturn=xTaskCreate(app_task1,"app_task1",512,NULL,7,&app_task1_handle);
   xReturn=xTaskCreate(app_task2,"app_task2",512,NULL,7,&app_task2_handle);
   vTaskStartScheduler();
   while(1);
   } 

   void app_task1(void *pvParameters)
 {
  uint32_t i;
       while(1)
         {
            printf("#");
            i=0x1000000;
            while(i--);
         }
 }

 void app_task2(void *pvParameters)
 {
       uint32_t i;
       while(1)
      {
        printf("-");
        i=0x1000000;
        while(i--);
      }
 }

猜你喜欢

转载自blog.csdn.net/weixin_44651073/article/details/127226410