提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、任务调度
- 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的时间片了,导致立刻跳到另一个任务执行,使得数据不完整。