提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、任务调度
- 1.开启任务调度器函数
- 2.启动第一个任务
- 二、任务切换的原理
- 三、时间片调度
- 1.基本概念
- 2.看一个实际例程
- ①我们设置滴答定时器中断时间为50ms,即一个时间片是50ms,那么任务一有一个delay10ms的延时(这里不能用系统非阻塞延时),算上延时和打印语句,总共可能会执行四次左右,然后一个时间片消耗完,转向任务二也执行4次,二者循环往复
- ②为什么要加入临界区保护呢?
- ③现象不解释了,就是次数不断增大。
前言
不多逼逼,直接上内容:
这些内容基本上都是讲解源码,比较深,比较枯燥。
一、任务调度
1.开启任务调度器函数
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. */portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */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 /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */{/* The Idle task is being created using dynamically allocated RAM. */xReturn = xTaskCreate( prvIdleTask,configIDLE_TASK_NAME,configMINIMAL_STACK_SIZE,( void * ) NULL,portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */&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.* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html* for additional information. */_impure_ptr = &( pxCurrentTCB->xNewLib_reent );}#endif /* configUSE_NEWLIB_REENTRANT */xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE;xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;/* 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();traceTASK_SWITCHED_IN();/* 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;/* OpenOCD makes use of uxTopUsedPriority for thread debugging. Prevent uxTopUsedPriority* from getting optimized out as it is no longer used by the kernel. */( void ) uxTopUsedPriority;
}
比较复杂而且难,我也看不懂。后面再说。
2.启动第一个任务
只能说这部分确实是枯燥难懂,我目前也不想花时间了解,等以后有时间再来深入探究吧。
二、任务切换的原理
任务切换的本质:就是CPU寄存器的切换。
假设当由任务A切换到任务B时,主要分为两步:
第一步:需暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈,这个过程叫做保存现场;
第二步:将任务B的各个寄存器值(被存于任务堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场;对任务A保存现场,对任务B恢复现场,这个整体的过程称之为:上下文切换
也很复杂,我也不想去深入了解,哎。就先留个图。
三、时间片调度
1.基本概念
首先,时间片调度机制是针对同等优先级的任务的。
其次,一个时间片大小:在FreeRTOS中,一个时间片就等于SysTick 中断周期,也就是1ms。
重点是这几句话:
2.看一个实际例程
注意,两个任务的优先级要相同。
代码如下(示例):
/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{uint32_t task1_num = 0;while(1){taskENTER_CRITICAL(); /* 进入临界区 */printf("task1运行次数:%d\r\n",++task1_num);taskEXIT_CRITICAL(); /* 退出临界区 */delay_ms(10);}
}/* 任务二,列表项的插入和删除实验 */
void task2( void * pvParameters )
{uint32_t task2_num = 0;while(1){taskENTER_CRITICAL(); /* 进入临界区 */printf("task2运行次数:%d\r\n",++task2_num);taskEXIT_CRITICAL(); /* 退出临界区 */delay_ms(10);}
}
①我们设置滴答定时器中断时间为50ms,即一个时间片是50ms,那么任务一有一个delay10ms的延时(这里不能用系统非阻塞延时),算上延时和打印语句,总共可能会执行四次左右,然后一个时间片消耗完,转向任务二也执行4次,二者循环往复
②为什么要加入临界区保护呢?
答:因为 printf(“task1运行次数:%d\r\n”,++task1_num);这个语句的执行也会花一点时间,那么如果不加临界段保护,当每执行到第五次时,可能还没有把文本打印完,就到了50ms的时间片了,导致立刻跳到另一个任务执行,使得数据不完整。