【FreeRTOS】任务管理与调度

文章目录


调度:

  1. 相同优先级的任务轮流运行
  2. 最高优先级的任务先运行

可以得出结论如下:

  • a 高优先级的任务在运行,未执行完,更低优先级的任务无法运行
  • b 一旦高优先级任务就绪,它会马上运行(假设厨房着火了,会马上去灭火)
  • c 如果最高优先级的任务有多个,他们轮流运行

他们都是使用链表进行管理

打开CubeMX,最高优先级56

56个List,

RadeyList[55] --->优先级为55的,处于就绪态的任务

RadeyList[54] --->优先级为54的,处于就绪态的任务

......

RadeyList[N] --->优先级为N的,处于就绪态的任务

......

RadeyList[0]

我们一开始创建了好几个任务,优先级默认都是osPriorityNormal=24

c 复制代码
  defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); // 默认任务

  /* 创建任务:光 */ 
  // 创建一个静态分配内存的任务
  xLightTaskHandle = xTaskCreateStatic(
            Led_Test,           //LED测试函数,PC13以500ms间隔亮灭一次
            "LightTask",        //光任务
            128,                //栈大小,这里提供了栈的大小(长度)
            NULL,               //无传入的参数
            osPriorityNormal,   //优先级默认
            g_pucStackOfLightTask,  // 静态分配的栈,一个buffer,这里只提供了首地址,长度就是栈的大小,最开始栈的类型不对,栈的类型uint32_t
            &g_TCBofLightTask       // 取址TCB
  );
  


  /* 创建任务:色 */ 
  xColorTaskHandle = xTaskCreateStatic(
            ColorLED_Test,           //LED测试函数,PC13以500ms间隔亮灭一次
            "ColorTask",        //光任务
            128,                //栈大小,这里提供了栈的大小(长度)
            NULL,               //无传入的参数
            osPriorityNormal,   //优先级默认
            g_pucStackOfColorTask,  // 静态分配的栈,一个buffer,这里只提供了首地址,长度就是栈的大小
            &g_TCBofColorTask       // 取址TCB
  );

执行完上面的程序之后,会创建三个任务, 放在 RadeyList[24] 这个链表里面~其他链表都是空的

这里是怎么调度的呢?

启动调度器后

执行

c 复制代码
osStatus_t osKernelStart (void) {
  osStatus_t stat;

  if (IS_IRQ()) {
    stat = osErrorISR;
  }
  else {
    if (KernelState == osKernelReady) {
      KernelState = osKernelRunning;
      vTaskStartScheduler();
      stat = osOK;
    } else {
      stat = osError;
    }
  }
  return (stat);
}

执行完之后,这里就创建了一个空闲的任务

c 复制代码
void vTaskStartScheduler( void )
{
BaseType_t xReturn;

	/* Add the idle task at the lowest priority. */
	#if( configSUPPORT_STATIC_ALLOCATION == 1 )
	{
		StaticTask_t *pxIdleTaskTCBBuffer = NULL;
		StackType_t *pxIdleTaskStackBuffer = NULL;
		uint32_t ulIdleTaskStackSize;

		/* The Idle task is created using user provided RAM - obtain the
		address of the RAM then create the idle task. */
		vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
		xIdleTaskHandle = xTaskCreateStatic(	prvIdleTask,
												configIDLE_TASK_NAME,
												ulIdleTaskStackSize,
												( void * ) NULL, /*lint !e961.  The cast is not redundant for all compilers. */
												( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
												pxIdleTaskStackBuffer,
												pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */

		if( xIdleTaskHandle != NULL )
		{
			xReturn = pdPASS;
		}
		else
		{
			xReturn = pdFAIL;
		}
	}
	#else
	{
		/* The Idle task is being created using dynamically allocated RAM. */
		xReturn = xTaskCreate(	prvIdleTask,
								configIDLE_TASK_NAME,
								configMINIMAL_STACK_SIZE,
								( void * ) NULL,
								( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
								&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
	}
	#endif /* configSUPPORT_STATIC_ALLOCATION */

	#if ( configUSE_TIMERS == 1 )
	{
		if( xReturn == pdPASS )
		{
			xReturn = xTimerCreateTimerTask();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_TIMERS */

	if( xReturn == pdPASS )
	{
		/* freertos_tasks_c_additions_init() should only be called if the user
		definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
		the only macro called by the function. */
		#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
		{
			freertos_tasks_c_additions_init();
		}
		#endif

		/* Interrupts are turned off here, to ensure a tick does not occur
		before or during the call to xPortStartScheduler().  The stacks of
		the created tasks contain a status word with interrupts switched on
		so interrupts will automatically get re-enabled when the first task
		starts to run. */
		portDISABLE_INTERRUPTS();

		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
			/* Switch Newlib's _impure_ptr variable to point to the _reent
			structure specific to the task that will run first. */
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */

		xNextTaskUnblockTime = portMAX_DELAY;
		xSchedulerRunning = pdTRUE;
		xTickCount = ( TickType_t ) 0U;

		/* If configGENERATE_RUN_TIME_STATS is defined then the following
		macro must be defined to configure the timer/counter used to generate
		the run time counter time base.   NOTE:  If configGENERATE_RUN_TIME_STATS
		is set to 0 and the following line fails to build then ensure you do not
		have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your
		FreeRTOSConfig.h file. */
		portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

		/* Setting up the timer tick is hardware specific and thus in the
		portable interface. */
		if( xPortStartScheduler() != pdFALSE )
		{
			/* Should not reach here as if the scheduler is running the
			function will not return. */
		}
		else
		{
			/* Should only reach here if a task calls xTaskEndScheduler(). */
		}
	}
	else
	{
		/* This line will only be reached if the kernel could not be started,
		because there was not enough FreeRTOS heap to create the idle task
		or the timer task. */
		configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
	}

	/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
	meaning xIdleTaskHandle is not used anywhere else. */
	( void ) xIdleTaskHandle;
}

函数名:prvIdleTask 空闲任务

现在的链表

这句话是把这个新的任务添加进ReadyList,添加进就绪列表,根据优先级找到链表

还有一个全局变量当前TCB

最开始只有StartDefaultTask这一个任务,那么全局指针pxCurrentTCB就指向这个任务

当我们创建led的任务时,指针指向Led_Test

当创建ColorLED_Test时候,指针就指向了ColorLED_Test

启动调度器的时候,创建了一个空闲任务,但是优先级是0,优先级比较低,pxCurrentTCB保留,还是指向ColorLED_Test

当我们真正启动调度器的时候,就从ColorLED_Test任务开始,先运行最后创建的任务

  • =这就是06_create_task_use_params里,后面创建的task3最先开始运行的原因!!!=

在系统里,会初始化一个Tick,Tick中断里面有计数累加,作为时钟基准,并且会调度

cubemx定义了一个时钟,频率是1000,每1ms产生一次中断

Tick中断:

  1. 计数累加,作为时钟基准
  2. 并且会发起调度

调度 :从上到下遍历ReadyList,找到24的时候,非空,然后指向下一个任务

  • 遍历ReadyList,找到第一个非空的链表,把pxCurrentTCB指向下一个任务,然后启动

运行1ms之后,计数值又累计,发起下一次调度,再次遍历这些链表,从上面找到下面,找到第24个不空,把pxCurrentTCB指向下一个任务,然后启动

下一次调度

上面就是轮流运行这些相同优先级的就绪态任务的,运行依赖于tick中断,依赖于调度机制

在默认任务里,读取遥控器的键值,如果是播放按键,会创建一个播放音乐的任务,优先级是25,如图所示:

这个任务马上处于就绪态,并且是最高优先级,所以会马上运行这个任务

当执行到vTaskDelay的时候,现在是Blocked状态,已经不是Ready状态了,现在就会从25优先级的链表中删除,移动到某一个delay的链表里,有DelayedTaskList1和DelayedTaskList2(这里是防止定时器溢出,才有两个delay链表)

假设存放到DelayedTaskList1里了,下一次Tick中断的时候,现在又要发起一次调度,重新遍历......

取出下一个任务来运行(上次运行的是任务1,下一个任务就是任务2)

下一次中断,在调度如下图所示:

在Tick中断里发起一个比较复杂的调度,同时也判断一下delay链表你的时间有没有到,如果时间到了,就把它放入就绪链表

  1. 计数值 ++
  2. 判断DelayedTaskList里的任务是否可恢复,
  3. 发起调度

假设5号任务是2个Tick,5号任务的Tick已经到了,就需要把这个5号任务从delay链表里移动到第25个优先级的链表中去

然后发起一个调度,再遍历所有链表,发现25非空,就执行5号任务~

在5号任务里又使用了delay,又把这个任务放到DelayedTaskList里

然后遍历整个链表,发现第24项非空,又执行下一个任务(任务1)

假设在任务1里又调用了这个函数(按下了播放键,暂停的功能)

这个任务被暂停了,是不可能通过Tick中断唤醒的,只能Resume(从链表中移除,重新叨叨ReadyList里)

上面全是链表的操作


总结

  • 在Tick中断里计数值++
  • 判断DelaydeTaskList里的任务是否可以被恢复,如果时间到了,就移动到就绪态链表中
  • 发起调度,在找到非空链表中,找到下一个任务运行~

相关推荐
低调包含1 小时前
RT-Thread和freeRTOS启动流程
c语言·arm开发·rtos
一棵树8282 小时前
windows电脑如何运行python的定时任务
windows
飞凌嵌入式3 小时前
预告 | 飞凌嵌入式即将亮相2024慕尼黑上海电子展
嵌入式硬件·飞凌嵌入式
阳爱铭3 小时前
GitHub:现代软件开发的协作平台
驱动开发·后端·中间件·面试·架构·github·学习方法
前端阿森纳4 小时前
解决npm与yarn痛点:幽灵依赖与依赖分身
前端·架构·npm
码农超哥同学4 小时前
Python面试题:如何在 Python 中反转一个字符串?
开发语言·windows·python·面试·编程
小小怪下士的编程小屋4 小时前
stm32中断
c语言·stm32·单片机·嵌入式硬件
Mero技术博客4 小时前
Fragment+Viewpage2+FragmentStateAdapter实现滑动式标签布局
windows
东方佑5 小时前
使用tkinter 制作工作流ui
前端·windows·笔记·python·ui·分类
明明跟你说过7 小时前
无服务器【Serverless】架构的深度剖析:组件介绍、优缺点与适用场景
微服务·云原生·容器·架构·serverless·运维开发·无服务器