---
title: rtos-freertos-03-任务
EntryName: rtos-freertos-03-task
date: 2020-06-20 09:15:07
categories:
tags:
- freertos
---
**章节概述:**

介绍任务的基本概念,以及如何使用任务。

概述

在RTOS中,任务/进程是一个操作系统的基本概念,作为资源分配与调度的最小单位。

换句话说:使用RTOS的实时应用程序可认为是一系列独立任务的集合。

每个任务在自己的环境中运行,不依赖于系统中的其它任务或者RTOS调度器。在任何时刻,只有一个任务得到运行,RTOS调度器决定运行哪个任务。调度器会不断的启动、停止每一个任务,宏观看上去就像整个应用程序都在执行。

作为任务,不需要对调度器的活动有所了解,在任务切入切出时保存上下文环境(寄存器值、堆栈内容)是调度器主要的职责。

为了实现任务之间的调度切换,每个任务都需要有自己的堆栈。当任务切出时,它的执行环境会被保存在该任务的堆栈中,这样当再次运行时,就能从堆栈中正确的恢复上次的运行环境。

任务的特点

  • 简单
  • 没有使用限制
  • 支持完全抢占
  • 支持优先级
  • 每个任务都有自己的堆栈,消耗RAM较多
  • 如果使用抢占,必须小心的考虑可重入问题

任务优先级

每个任务都要被指定一个优先级,从0~configMAX_PRIORITIES,configMAX_PRIORITIES定义在FreeRTOSConfig.h中。

低优先级数值代表低优先级。空闲任务(idle task)的优先级为0(tskIDLE_PRIORITY)。

FreeRTOS调度器确保处于最高优先级的就绪或运行态任务获取处理器,换句话说,处于运行状态的任务,只有其中的最高优先级任务才会运行。(此时运行的任务总是当前优先级最高的)

任务创建删除

任务的组成

void vATaskFunction( void *pvParameters )
{
while(1)
{
/*-- 任务代码放在这里. --*/
} /* 任务不可以调用return进行退出。在较新的FreeRTOS移植包中,如果
试图从一个任务中返回,将会调用configASSERT()(如果定义的话)。
如果一个任务确实要退出函数,那么这个任务应调用vTaskDelete(NULL)
函数,以便处理一些清理工作。*/
vTaskDelete(NULL);
}

任务函数返回为void,参数只有一个void类型指针。void类型指针可以向任务传递任意类型信息。

任务函数决不应该返回,因此通常任务函数都是一个死循环。

任务由xTaskCreate()函数创建,由vTaskDelete()函数删除。

所有的任务函数都应该是这样。

创建任务

BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, /* 任务函数入口*/
const char * const pcName, /* 任务名称*/
unsigned short usStackDepth, /* 栈深度*/
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * pvCreatedTask
);

描述:创建新的任务并加入任务就绪列表。

参数解析:

  • pvTaskCode:任务的入口地址
  • pcName:任务的名字,超度不超过configMAX_TASK_NAME_LEN
  • usStackDepth:任务堆栈大小。以字(word)为单位(在32为架构处理器,byte * 4 = byte * CPU地址线 / 8
  • pvParameters:任务参数
  • uxPriority:任务优先级
  • pvCreatedTask:任务的句柄

句柄将在API 调用中对该创建出来的任务进行引用,比如改变任务优先级,或者删除任务。

如果应用程序中不会用到这个任务的句柄,则pxCreatedTask 可以被设为NULL。

返回值:

成功返回pdPASS,失败返回错误码(例如errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY)。

如果使用FreeRTOS-MPU(在官方下载包中,为Cortex-M3内核写了两个移植方案,一个是普通的FreeRTOS移植层,还有一个是FreeRTOS-MPU移植层。后者包含完整的内存保护),那么推荐使用函数xTaskCreateRestricted()来代替xTaskCreate()。在使用FreeRTOS-MPU的情况下,使用xTaskCreate()函数可以创建运行在特权模式或用户模式(见下面对函数参数uxPriority的描述)的任务。当运行在特权模式下,任务可以访问整个内存映射;当处于用户模式下,任务仅能访问自己的堆栈。

————————————————

版权声明:本文为CSDN博主「zhzht19861011」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zhzht19861011/article/details/50371956

删除任务

void vTaskDelete( TaskHandle_t xTaskToDelete )

描述:从RTOS内核管理器中删除一个任务。任务删除后将会从就绪、阻塞、暂停和事件列表中移除。

在文件FreeRTOSConfig.h中,必须定义宏INCLUDE_vTaskDelete 为1,本函数才有效。

参数解析:

  • xTaskToDelete:被删除任务的句柄。为NULL表示删除当前任务。

例子

一个最普通的例子,带有参数。

/* 创建任务. */
TaskHandle_t demoTaskHandler; void demoTask( void * pvParameters )
{
int type;
type = *((int *)pvParameters);
while(1)
{
printf("demo Task with arg %d\r\n", type); vTaskDelay(pdMS_TO_TICKS(1000));
} vTaskDelete(NULL);
} /* 创建任务函数 */
void tasksCreate(void)
{
int parm = 0x5c;
xTaskCreate(demoTask,
"demo",
128,
&parm,
1,
&demoTaskHandler ); /* 使用句柄删除任务. */
if( demoTaskHandler !=NULL )
{
vTaskDelete( demoTaskHandler );
}
}

任务控制

FreeRTOS任务控制API函数主要实现任务延时、任务挂起、解除任务挂起、任务优先级获取和设置等功能。

延迟

FreeRTOS提供了两个系统延时函数:相对延时函数vTaskDelay()和绝对延时函数vTaskDelayUntil()。

  • 相对延时是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束;
  • 绝对延时是指每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务。

首先,我们需要明确,任务以固定的频率执行。

对于相对延时,如果任务不是最高优先级,则任务执行周期更不可测,但问题不大,我们本来也不会使用它作为精确延时;

对于绝对延时函数,如果任务不是最高优先级,则仍然能周期性的将任务解除阻塞,但是解除阻塞的任务不一定能获得CPU权限,因此任务主体代码也不会总是精确周期性执行。

如果要想精确周期性执行某个任务,可以使用系统节拍回调函数vApplicationTickHook(),它在系统节拍中断服务函数中被调用,如果需要使用这个回调函数,那么其中的代码必须简洁。

相对延时

 void vTaskDelay( const TickType_t xTicksToDelay )

描述:使任务会进入阻塞状态。

在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelay 必须设置成1,此函数才能有效。

参数解析:

xTicksToDelay:持续时间,单位是系统节拍时钟周期。

常量portTICK_RATE_MS 用来辅助计算真实时间,此值是系统节拍时钟中断的周期,单位是毫秒。

例子:

void vTaskA( voidvoid * pvParameters )
{
/*
阻塞500ms.
注:宏pdMS_TO_TICKS用于将毫秒转成节拍数,FreeRTOS V8.1.0及以上版本才有这个宏,
如果使用低版本,可以使用 500 / portTICK_RATE_MS
*/
const portTickType xDelay = pdMS_TO_TICKS(500);
// const portTickType xDelay = 500 / portTICK_RATE_MS; for( ;; )
{
// ...
// 这里为任务主体代码
// ... /* 调用系统延时函数,阻塞500ms */
vTaskDelay( xDelay );
}
}

说明:

假设有2个任务A、B,但A的优先级最高,还配置了中断。

任务A每次延时都是从调用延时函数vTaskDelay()开始算起的,延时是相对于这一时刻开始的,所以叫做相对延时函数。

如果执行任务A的过程中(调用vTaskDelay之前)发生中断,那么中断回来以后,执行任务A执行的周期就会变长(因为存在中断的开销),从而影响任务下一次执行的时间。

因为调用vTaskDelay()到任务解除阻塞的时间不总是固定的并且该任务下一次调用vTaskDelay()函数的时间也不总是固定的(两次执行同一任务的时间间隔本身就不固定,中断或高优先级任务抢占也可能会改变每一次执行时间)

所以使用相对延时函数vTaskDelay(),不能准确地周期性的执行任务。

vTaskDelay()指定的延时时间是从调用vTaskDelay()后开始计算的相对时间

绝对延时

周期性任务可以使用绝对延迟以确保自身恒定的频率执行。

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement )

描述:任务延时一个指定的时间,每当时间到达,则解除任务阻塞。

在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelayUntil 必须设置成1,此函数才有效。

参数解析:

  • pxPreviousWakeTime:指针,指向一个变量,该变量保存任务最后一次解除阻塞的时间。

一般来说,调用函数之前,该变量必须初始化为当前时间。

之后这个变量会在vTaskDelayUntil()函数内自动更新。

  • xTimeIncrement:周期循环时间。当时间等于(*pxPreviousWakeTime + xTimeIncrement)时,任务解除阻塞。

注意:

  • 当调用vTaskSuspendAll()函数挂起RTOS调度器时,不可以使用此函数。
  • 为了确保任务固定时间运行,应该先调用vTaskDelayUntil再执行任务的主体内容。
  • 如果指定的唤醒时间已经达到,vTaskDelayUntil()立刻返回(不会有阻塞)。
  • (可选的)使用vTaskDelayUntil()周期性执行的任务,无论任何原因(比如,任务临时进入挂起状态)停止了周期性执行,使得任务少运行了一个或多个执行周期,那么需要重新计算所需要的唤醒时间。这可以通过传递给函数的指针参数pxPreviousWake指向的值与当前系统时钟计数值比较来检测。

例子:

void vTaskB( voidvoid * pvParameters )
{
static portTickType xLastWakeTime;
const portTickType xFrequency = pdMS_TO_TICKS(500); // 使用当前时间初始化变量xLastWakeTime ,注意这和vTaskDelay()函数不同
xLastWakeTime = xTaskGetTickCount(); for( ;; )
{
/* 调用系统延时函数,周期性阻塞500ms */
vTaskDelayUntil( &xLastWakeTime,xFrequency ); // ...
// 这里为任务主体代码,周期性执行.注意这和vTaskDelay()函数也不同
// ... }
}

说明:

假设有2个任务A、B,但A的优先级最高,还配置了中断。

当任务B获取CPU使用权后,先调用系统延时函数vTaskDelayUntil()使任务进入阻塞状态。任务B进入阻塞后,其它任务得以执行。FreeRTOS内核会周期性的检查任务A的阻塞是否达到,如果阻塞时间达到,则将任务A设置为就绪状态。由于任务B的优先级最高,会抢占CPU,接下来执行任务主体代码。任务主体代码执行完毕后,会继续调用系统延时函数vTaskDelayUntil()使任务进入阻塞状态,周而复始。

从调用函数vTaskDelayUntil()开始,每隔固定周期,任务B的主体代码就会被执行一次,即使任务B在执行过程中发生中断,也不会影响这个周期性,只是会缩短其它任务的执行时间!

所以vTaskDelayUntil被称为绝对延时函数,可用于周期性的执行任务的主体代码。

任务优先级

获取任务优先级

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

描述: 获取指定任务的优先级。

在文件FreeRTOSConfig.h中,宏INCLUDE_uxTaskPriorityGet必须设置成1,此函数才有效。

参数解析:

xTask:指定的任务句柄。NULL表示获取当前任务的优先级。

返回值:返回指定任务的优先级。

例子:

void vAFunction( void )
{
xTaskHandle xHandle;
// 创建任务,保存任务句柄
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用句柄获取创建的任务的优先级
if( uxTaskPriorityGet( xHandle ) !=tskIDLE_PRIORITY )
{
// 任务可以改变自己的优先级
}
// ...
// 当前任务优先级比创建的任务优先级高?
if( uxTaskPriorityGet( xHandle ) <uxTaskPriorityGet( NULL ) )
{
// 当前优先级较高
}
}

设置任务优先级

  void vTaskPrioritySet( TaskHandle_txTask,
UBaseType_tuxNewPriority );

描述:设置指定任务的优先级。如果设置的优先级高于当前运行的任务,在函数返回前会进行一次上下文切换。

在文件FreeRTOSConfig.h中,宏 INCLUDE_vTaskPrioritySet 必须设置成1,此函数才有效。

参数解析:

  • xTask:要设置优先级任务的句柄,为NULL表示设置当前运行的任务。
  • uxNewPriority:要设置的新优先级。

例如:

void vAFunction( void )
{
xTaskHandlexHandle;
// 创建任务,保存任务句柄。
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用句柄来提高创建任务的优先级
vTaskPrioritySet( xHandle,tskIDLE_PRIORITY + 1 );
// ...
// 使用NULL参数来提高当前任务的优先级,设置成和创建的任务相同。
vTaskPrioritySet( NULL, tskIDLE_PRIORITY +1 );
}

任务挂起

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

描述:挂起指定任务。被挂起的任务绝不会得到处理器时间,不管该任务具有什么优先级。

调用vTaskSuspend函数是不会累计的:即使多次调用vTaskSuspend ()函数将一个任务挂起,也只需调用一次vTaskResume ()函数就能使挂起的任务解除挂起状态。

在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskSuspend必须设置成1,此函数才有效。(下同vTaskResume)

参数解析:

xTaskToSuspend:要挂起的任务句柄。为NULL表示挂起当前任务。

例如:

void vAFunction( void )
{
xTaskHandlexHandle;
// 创建任务,保存任务句柄.
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用句柄挂起创建的任务.
vTaskSuspend( xHandle );
// ...
// 任务不再运行,除非其它任务调用了vTaskResume(xHandle )
//...
// 挂起本任务.
vTaskSuspend( NULL );
// 除非另一个任务使用handle调用了vTaskResume,否则永远不会执行到这里
}

恢复挂起的任务

void vTaskResume( TaskHandle_tx TaskToResume );

// 用于恢复一个挂起的任务,用在ISR中。
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume );

描述:恢复挂起的任务。

在文件FreeRTOSConfig.h中,宏 INCLUDE_vTaskSuspend 必须置1,此函数才有效。(同上vTaskSuspend)

使用xTaskResumeFromISR之前, INCLUDE_xTaskResumeFromISR 必须设置成1,此函数才有效。

FromISR返回值:(由ISR确定是否需要上下文切换)

  • 返回pdTRU:恢复任务后需要上下文切换返回
  • 返回pdFALSE:什么都不用做。

参数解析:

xTaskToResume:要恢复运行的任务句柄。

例如:

xTaskHandlexHandle;               //注意这是一个全局变量

void vAFunction( void )
{
xTaskHandle xHandle;
// 创建任务,保存任务句柄
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用句柄挂起创建的任务
vTaskSuspend( xHandle );
// ...
//任务不再运行,除非其它任务调用了vTaskResume( xHandle )
//...
// 恢复挂起的任务.
vTaskResume( xHandle );
// 任务再一次得到处理器时间
// 任务优先级与之前相同
} void vTaskCode( void *pvParameters )
{
for( ;; )
{
// ... 在这里执行一些其它功能 // 挂起自己
vTaskSuspend( NULL ); //直到ISR恢复它之前,任务会一直挂起
}
} void vAnExampleISR( void )
{
portBASE_TYPExYieldRequired; // 恢复被挂起的任务
xYieldRequired = xTaskResumeFromISR(xHandle ); if( xYieldRequired == pdTRUE )
{
// 我们应该进行一次上下文切换
// 注: 如何做取决于你具体使用,可查看说明文档和例程
portYIELD_FROM_ISR();
}
}

注意:

xTaskResumeFromISR()不可用于任务和中断间的同步,如果中断恰巧在任务被挂起之前到达,这就会导致一次中断丢失(任务还没有挂起,调用xTaskResumeFromISR()函数是没有意义的,只能等下一次中断)。这种情况下,可以使用信号量作为同步机制。

空闲任务

包括:空闲任务(idle task)和 空闲任务回调函数(Idle hook/callback function)。

在FreeRTOS中,回调函数 也称为 钩子函数。为了方便理解,以回调函数作为称呼。

概述

空闲任务是启动RTOS调度器时由内核自动创建的任务,这样可以确保至少有一个任务在运行。空闲任务具有最低任务优先级,这样如果有其它更高优先级的任务进入就绪态就可以立刻让出CPU。

删除任务后,空闲任务会释放RTOS分配给被删除任务的内存。因此,在应用中使用vTaskDelete()函数后确保空闲任务能获得处理器时间就很重要了。

除此之外,空闲任务没有其它有效功能,所以可以被合理的剥夺处理器时间,并且它的优先级也是最低的。

空闲任务回调函数

如果你想将在空闲的时候,运行某些任务,可以有两种做法:

  • 创建一个具有空闲优先级的任务:简单灵活但会带来更多RAM开销。

    需要配置: configUSE_PREEMPTION configIDLE_SHOULD_YIELD为 1 。

  • 实现一个空闲任务回调函数:因为FreeRTOS必须至少有一个任务处于就绪或运行状态,因此回调函数不可以调用可能引起空闲任务阻塞的API函数(比如vTaskDelay()或者带有超时事件的队列或信号量函数)。

第一种做法比较简单,我们介绍第二种方法,实现一个空闲任务回调函数。

在FreeRTOS文档中,有这样的一段话:

It is possible to add application specific functionality directly into the idle task through the use of an idle hook (or idle callback) function—a function that is called automatically by the idle task once per iteration of the idle task loop.

简单来讲:FreeRTOS允许我们配置一个自定义的函数,使其在每一个空闲任务周期自动调用一次。(因此,称其为回调函数)

熟悉STM32-CubeMx配置的学者,想想hal库的各种回调函数,肯定能够理解这里的章节。

因为原理都是一样的:处理完成以后调用回调函数。

当其他任务调用了带阻塞性质的 vTaskDelay() API 函数,会产生大量的空闲时间——在这期间空闲任务会得到执行,因为其他的应用任务均处于阻塞态,最终,空闲任务回调函数也会执行。

通常空闲任务回调函数被用于:

  • 执行低优先级,后台或需要不停处理的功能代码。
  • 测试出系统处理裕量(空闲任务只会在所有其它任务都不运行时才有机会执行,所以测量出空闲任务占用的处理时间就可以清楚的知道系统有多少富余的处理时间)。
  • 将处理器配置到低功耗模式——提供一种自动省电方法,使得在没有任何应用功能需要处理的时候,系统自动进入省电模式。
  • 延后一些紧急命令的硬件操作。

例如,在某个高优先级的任务中需要发送串口数据。我们知道,控制某些硬件是需要额外的开销的,为了不妨碍其他任务的运行,先将数据写到缓冲区;再在空闲任务中将缓冲区的数据发出去。

使用步骤

1、在FreeRTOSConfig.h头文件中设置configUSE_IDLE_HOOK为1;

#define configUSE_IDLE_HOOK 1

2、定义一个函数,名字和参数原型如下所示:

void vApplicationIdleHook( void );

3、实现vApplicationIdleHook,不使用阻塞、挂起等api。

空闲任务回调函数的实现限制

空闲任务回调函数必须遵从以下规则:

  1. 绝不能阻塞或挂起。空闲任务只会在其它任务都不运行时才会被执行(除非有应用任务共享空闲任务优先级)。以任何方式阻塞空闲任务都可能导致没有任务能够进入运行态!

  2. 如果应用程序用到了 vTaskDelete() API 函数,则空闲回调函数必须能够尽快返回。因为在任务被删除后,空闲任务负责回收内核资源。如果空闲任务一直运行在回调函数中,则无法进行回收工作。

任务标签

在文件FreeRTOSConfig.h中,宏configUSE_APPLICATION_TASK_TAG必须设置为1,此组函数才有效。

一般用于应用程序,RTOS内核不会使用。

设置任务标签值

void vTaskSetApplicationTaskTag(TaskHandle_t xTask,
TaskHookFunction_t pxTagValue );

描述:可以给每个任务分配一个标签值。

一般用于应用程序,RTOS内核不会使用。

在文件FreeRTOSConfig.h中,宏 configUSE_APPLICATION_TASK_TAG 必须设置为1,此函数才有效。

参数解析:

  • xTask:任务句柄。NULL表示当前任务。
  • pxTagValue:要分配给任务的标签值。这是一个TaskHookFunction_t类型的函数指针,但也可以给任务标签分配任意的值。

注:TaskHookFunction_t原型定义:typedef BaseType_t (*TaskHookFunction_t)(void * )

获取任务标签值

TaskHookFunction_t xTaskGetApplicationTaskTag( TaskHandle_t xTask );

描述:返回分配给任务的标签值。

参数解析:

xTask:任务句柄。NULL表示当前任务。

返回值: 返回指定任务的标签值。

执行任务的应用回调函数

BaseType_t xTaskCallApplicationTaskHook(
TaskHandle_txTask,
void*pvParameter );

描述:因为可以为每个任务分配一个标签值,当这个值是一个TaskHookFunction_t类型函数指针时,相当于应用程序向任务注册了一个回调函数,而API函数xTaskCallApplicationTaskHook用来调用这个回调函数。

一般这个函数配合RTOS跟踪回调宏使用

参数解析:

  • xTask:任务句柄。NULL表示当前任务。
  • pvParameter:作为参数传递给应用回调函数

例子:

/* 在这个例子中,给任务设置一个整形标签值。例子中使用了RTOS跟踪回调宏。*/
void vATask( void *pvParameters )
{
/* 为自己的标签分配一个整形值 */
vTaskSetApplicationTaskTag( NULL, ( void * ) 1 ); for( ;; )
{
/* 任务主体代码 */
}
}
/*****************************************************************************/ /*在这个任务中,给任务设置一个函数标签值。首先定义一个回调函数,这个函数必须声明为TaskHookFunction_t类型。 */
static BaseType_t prvExampleTaskHook( void * pvParameter )
{
/* 这里为用户定义代码 –可能是记录数据、更新任务状态值等。*/ return 0;
} /* 将回调函数设置为任务的标签值。 */
void vAnotherTask( void *pvParameters )
{
/* 注册回调函数*/
vTaskSetApplicationTaskTag( NULL, prvExampleTaskHook ); for( ;; )
{
/* 任务主体代码 */
}
}

FreeRTOSConfig.h添加下列代码。

/* 配合RTOS跟踪回调宏使用,每当任务切换时,会调用xTaskCallApplicationTaskHook 函数。 */
#define traceTASK_SWITCHED_OUT() xTaskCallApplicationTaskHook(pxCurrentTCB, 0)

线程本地存储指针

线程本地存储允许应用程序在任务的控制块中存储一些值,每个任务都有自己独立的储存空间。

比如,许多库函数都包含一个叫做errno的全局变量。某些库函数使用errno返回库函数错误信息,应用程序检查这个全局变量来确定发生了那些错误。在单线程程序中,将errno定义成全局变量是可以的,但是在多线程应用中,每个线程(任务)必须具有自己独有的errno值,否则,一个任务可能会读取到另一个任务的errno值。

FreeRTOS提供了一个灵活的机制,使得应用程序可以使用线程本地存储指针来读写线程本地存储。在文件FreeRTOSConfig.h中,宏configNUM_THREAD_LOCAL_STORAGE_POINTERS指定每个任务线程本地存储指针数组的大小。API函数vTaskSetThreadLocalStoragePointer()用于向指针数组中写入值,API函数pvTaskGetThreadLocalStoragePointer()用于从指针数组中读取值。

设置线程本地存储指针

void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet,
BaseType_t xIndex,
void* pvValue )

描述:从线程本地存储指针数组中设置值。

参数解析:

  • xTaskToSet:任务句柄。NULL表示当前任务。
  • xIndex:写入到线程本地存储数组的索引号,线程本地存储数组的大小由宏configNUM_THREAD_LOCAL_STORAGE_POINTERS设定,该宏在文件FreeRTOSConfig.h中。
  • pvValue:写入到指定索引地址的数据值

读取线程本地存储指针

void* pvTaskGetThreadLocalStoragePointer(
TaskHandle_txTaskToQuery,
BaseType_txIndex );

描述:从线程本地存储指针数组中读取值。

参数解析:

  • xTaskToQuery:任务句柄。NULL表示当前任务。
  • xIndex:写入到线程本地存储数组的索引号,线程本笃存储数组的大小由宏configNUM_THREAD_LOCAL_STORAGE_POINTERS设定,该宏在文件FreeRTOSConfig.h中。

返回值:返回一个指针,这个指针存储在线程本地存储指针数组中,数组索引由参数xIndex指定。

例子:

  • 存储一个整形数
uint32_tulVariable;

/* 向当前任务的线程本地存储数组下标为1的位置写入一个指向32位常量值的指针。*/
vTaskSetThreadLocalStoragePointer(NULL, 1, ( void * ) 0x12345678 ); /*向当前任务的线程本地存储数组下标为0的位置写入一个指向32整形值的指针*/
ulVariable= ERROR_CODE;
vTaskSetThreadLocalStoragePointer(NULL, 0, ( void * ) ulVariable ); /*从当前任务的线程本地存储数组下标为5的位置读取出一个指针并赋值给32位整形变量。*/
ulVariable= ( uint32_t ) pvTaskGetThreadLocalStoragePointer( NULL, 5 );
  • 存储结构体
typedefstruct
{
uint32_t ulValue1;
uint32_t ulValue2;
}xExampleStruct; xExampleStruct*pxStruct; /*为结构体分配内存*/
pxStruct= pvPortMalloc( sizeof( xExampleStruct ) ); /*为结构体成员赋值*/
pxStruct->ulValue1= 0;
pxStruct->ulValue2= 1; /*向当前任务的线程本地存储数组下标为0的位置写入一个指向结构体变量的指针*/
vTaskSetThreadLocalStoragePointer(NULL, 0, ( void * ) pxStruct ); /*从当前任务的线程本地存储数组下标为0的位置读取出一个结构体指针*/
pxStruct= ( xExampleStruct * ) pvTaskGetThreadLocalStoragePointer( NULL, 0 );

超时状态

任务因为等待某事件而进入阻塞状态,通常情况下任务会设置一个等待超时周期。如果在等待事件超时,任务会退出阻塞状态。

想象一个这样的应用,某任务等待一个事件而进入阻塞状态,但是事件迟迟不发生,超时后任务退出阻塞状态继续执行任务。假如任务等待的事件仍然没有发生,则任务又会阻塞在该事件下。只要任务等待的事件一直不发生,这个任务进入阻塞然后超时退出阻塞,再进入阻塞的循环就会一直存在。

是不是可以设定一个总超时时间,只要总阻塞时间大于这个总超时时间,则可以结束这个任务或进行相应记录?freeRTOS提供了两个API函数来完成这个功能,这就是vTaskSetTimeOutState()和xTaskCheckForTimeOut()。

vTaskSetTimeOutState()函数用于设置初始条件,之后调用xTaskCheckForTimeOut()函数检查任务总阻塞时间是否超过总超时时间,如果没有超过,则调整剩余的超时时间计数器。

设置超时状态

void vTaskSetTimeOutState( TimeOut_t *const pxTimeOut );

描述:设置初始条件

参数解析:

  • pxTimeOut:用来保存确定超时是否发生的必要信息的结构体。

超时检测

BaseType_t xTaskCheckForTimeOut(TimeOut_t * const pxTimeOut,
TickType_t* const pxTicksToWait );

描述:检查任务总阻塞时间是否超过总超时时间

参数解析:

  • pxTimeOut:指向一个结构体的指针。该结构体保存确定超时是否发生的必要信息。需要先使用vTaskSetTimeOutState初始化该结构体。
  • pxTicksToWait:指向的变量保存总超时时间。

返回值:

  • pdTRUE:总超时发生
  • pdFALSE:总超时未发生

例子:

/* 函数用于从RX缓冲区中接收uxWantedBytes字节数据,RX缓冲区由UART中断填充。如果RX缓冲区没有足够的数据,则任务进入阻塞状态,直到RX缓冲区有足够数据或者发生超时。如果超时后仍然没有足够的数据,则任务会再次进入阻塞状态,xTaskCheckForTimeOut()函数用于重新计算总超时时间以确保总阻塞状态时间不超过MAX_TIME_TO_WAIT。如果总阻塞状态时间大于了总超时时间,则不管RX缓冲区是否有充足数据,都将这些数据读出来。
*/
size_t xUART_Receive( uint8_t *pucBuffer, size_t uxWantedBytes )
{
size_t uxReceived = 0;
TickType_t xTicksToWait = MAX_TIME_TO_WAIT;
TimeOut_t xTimeOut; /* 初始化结构体变量xTimeOut。*/
vTaskSetTimeOutState( &xTimeOut ); /* 无限循环,直到缓冲区包含足够的数据或者阻塞超时发生。*/
while( UART_bytes_in_rx_buffer(pxUARTInstance ) < uxWantedBytes )
{
/* RX缓冲区没有足够多的数据,表示任务已经进入过一次阻塞状态。调用API函数xTaskCheckForTimeOut检查总阻塞时间是否超过总超时时间,如果没有,则调整剩余的总超时时间。*/
if( xTaskCheckForTimeOut( &xTimeOut,&xTicksToWait ) != pdFALSE )
{
/* 如果总阻塞时间大于总超时时间,则退出这个循环 */
break;
} /* 在等待了xTicksToWait个系统节拍周期后,向接收中断发出通知,需要更多数据。
*/
ulTaskNotifyTake( pdTRUE, xTicksToWait );
} /*从RX缓冲区读取uxWantedBytes个字节并放到pucBuffer缓冲区。*/
uxReceived = UART_read_from_receive_buffer(pxUARTInstance, pucBuffer, uxWantedBytes ); return uxReceived;
}

附录:任务和协程(Co-routines)

应用程序可以使用任务也可以使用协程,或者两者混合使用,但是任务和协程使用不同的API函数,因此在任务和协程之间不能使用同一个队列或信号量传递数据。

通常情况下,协程仅用在资源非常少的微处理器中,特别是RAM非常稀缺的情况下。

附录:FreeRTOS MPU模块

MPU是Cortex-M的选配件,拿STM32F1来说,STM32F10X_XL系列的芯片才具有这个MPU存储保护单元,而其他STM32F1芯片没有。

LPC17xx包括存储器保护单元(MPU)。这允许整个内存映射(包括Flash、RAM和外围设备)细分为若干区域,以及要分别分配给每个区域的访问权限。

区域是地址范围由起始地址和大小组成。

FreeRTOS MPU是一个FreeRTOS Cortex-M3端口,包括集成MPU支持。它允许额外的功能,并包括一个稍微扩展的API,但在其他方面与标准Cortex-M3端口兼容。

使用FreeRTOS MPU将始终:

保护内核免受任务的无效执行。

保护内核使用的数据不受任务的无效访问。

保护Cortex-M3核心资源的配置,如SysTick定时器。

确保所有任务堆栈溢出一发生就被检测到。

另外,在应用程序级别,可以确保任务独立于自己的任务并使内存空间和外围设备受到保护,不受意外修改。

FreeRTOS MPU通过隐藏MPU底层计算层面而提供一个简单的针对MPU的接口,然而给一个不允许数据随意访问的环境进行应用编程是一个很大的挑战

————————————————

版权声明:本文为CSDN博主「weixin_39344546」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_39344546/article/details/104209929

附录: 回调函数

在FreeRTOS中,回调函数(callback function) 也称为 钩子函数(hook function)。为了方便理解,以回调函数作为称呼。

HOOK:钩子、钩住;在操作系统中可以视为处理机制的一部分。

FreeRTOS中支持一些回调函数,只需要在FreeRTOSConfig.h中进行配置即可。

可以在FreeRTOS源码中,找到对应的宏。

  • configUSE_IDLE_HOOK:是否使用空闲任务回调函数。
  • configUSE_TICK_HOOK:是否使用TICK滴答回调函数。

使用 configUSE_TICK_HOOK 时需要 定义xTaskIncrementTick函数

提示:xTaskIncrementTick函数是在PendSV_Handler中断函数中被调用的。因此,vApplicationTickHook()函数执行的时间必须很短才行。

  • configCHECK_FOR_STACK_OVERFLOW:是否定义栈溢出回调函数

这个配置比较重要,特别对于复杂的系统设计,代码量比较大那种工程,使用该功能,可以帮你分析是否有内存越界的情况。

  • configUSE_MALLOC_FAILED_HOOK:是否定义内存分配失败回调函数

创建任务、信号量、队列等都需要耗费系统堆栈,如果我们对系统总共分配堆栈不够多,在创建多个任务或队列时容易分配失败,这个时候就起到一个提示作用。

  • configUSE_DAEMON_TASK_STARTUP_HOOK:是否定义守护进程回调函数

还需要配置configUSE_TIMERS为1。void vApplicationDaemonTaskStartupHook( void );

Freertos学习:03-任务的更多相关文章

  1. 【FreeRTOS学习03】小白都能懂的Task Management 任务管理基本概念介绍

    在FreeRTOS中,线程的术语又可以被称之为任务,或许这样更加合适,本文将介绍任务的创建/删除,任务参数的使用,以及任务优先级: 1 软实时和硬实时 硬实时系统的任务运行正确性与响应时限是紧密相关的 ...

  2. JavaScript学习03 JS函数

    JavaScript学习03 JS函数 函数就是包裹在花括号中的代码块,前面使用了关键词function: function functionName() { 这里是要执行的代码 } 函数参数 函数的 ...

  3. Java虚拟机JVM学习03 连接过程:验证、准备、解析

    Java虚拟机JVM学习03 连接过程:验证.准备.解析 类被加载后,就进入连接阶段. 连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去. 连接阶段三个步骤:验证.准备和解析. 类 ...

  4. Java学习03

    Java学习03 1.java面试一些问题 一.什么是变量 变量是指在程序执行期间可变的数据.类中的变量是用来表示累的属性的,在编程过程中,可以对变量的值进行修改.变量通常是可变的,即值是变化的 二. ...

  5. ThinkPhp学习03

    原文:ThinkPhp学习03 一.ThinkPHP 3 的输出      (重点) a.通过 echo 等PHP原生的输出方式在页面中输出 b.通过display方法输出   想分配变量可以使用as ...

  6. JVM学习03:性能监控工具

    JVM学习03:性能监控工具 写在前面:本系列分享主要参考资料是  周志明老师的<深入理解Java虚拟机>第二版. 性能监控工具知识要点Xmind梳理 案例分析 案例分析1-JPS 案例分 ...

  7. node.js 学习03

    node.js学习03 解决浏览器接收服务端信息之后乱码的问题: 服务器通过设置http响应报文头,告诉浏览器使用相应的编码 来解析网页. res.setHeader('Content','text/ ...

  8. Redis学习03——存储字符串(String)

    --------------------- 作者:愤怒的小明 来源:CSDN 原文:https://blog.csdn.net/qiwenmingshiwo/article/details/78118 ...

  9. 【FreeRTOS学习05】深度解剖FreeRTOSConfig.h实现对系统的自定义剪裁

    ROM/RAM太小,因此要对系统进行剪裁: 相关文章 [FreeRTOS实战汇总]小白博主的RTOS学习实战快速进阶之路(持续更新) 文章目录 相关文章 1 系统的剪裁 2 FreeRTOSConfi ...

  10. Mybatis学习03

    title: Mybatis学习03 date: 2020-01-19 13:03:20 tags: Mybatis学习的第二天,内容有日志和分页. <!--more--> 1.日志 1. ...

随机推荐

  1. vue-公共组件的注册

    注册公共组件,在每个需要的页面直接输入文件名(<g-table/>)即可引用该组件 步骤: 1.新建components/global文件夹,以及components/global/g-t ...

  2. Go-Zero技能提升:深度探究goctl的妙用,轻松应对微服务开发挑战!(三)

    前言 有位同学在群里说:"Go-Zero官方文档太简洁了,对小白有点不友好.好奇你们是怎么学习的?项目是怎么封装的?有什么提高开发效率的技巧吗?". 来来来,这期内容给你安排上,先 ...

  3. vue3.0 用vue ui 新建项目

    安装步骤: 1.打开安装界面 打开cmd vue ui 2.选择要安装的位置 3.设置详情 4.设置预设 5.设置功能 选择Babel / Router / Linter/Formatter / 使用 ...

  4. 使用自定义lua解析管理器调用lua脚本中的table

    [5] 使用自定义lua解析管理器调用table 访问数组类型的table CallLuaEntrance测试脚本中内容: //------------------------------------ ...

  5. 用 C 语言开发一门编程语言 — 字符串与文件加载

    目录 文章目录 目录 前文列表 字符串 读取字符串 注释 文件加载函数 命令行参数 打印函数 报错函数 源代码 前文列表 <用 C 语言开发一门编程语言 - 交互式解析器> <用 C ...

  6. grafana模板参考

    空的,把面板都删除了 { "__inputs": [ { "name": "DS_PROMETHEUS", "label" ...

  7. P1683 入门

    传送锚点:https://www.luogu.com.cn/problem/P1683 题目描述 不是任何人都可以进入桃花岛的,黄药师最讨厌像郭靖一样呆头呆脑的人.所以,他在桃花岛的唯一入口处修了一条 ...

  8. MyBatis日志模块源码分析

    MyBatis源码的logging包下是日志模块的相关实现,Mybatis日志模块通过适配器模式和代理模式优雅的实现了SQL日志的输出功能. 一. 适配器模式实现了MyBatis对第三方日志框架的适配 ...

  9. 不使用循环语句用if和else实现循环

    如果不使用循环语句,可以使用递归函数来实现循环的效果.递归函数是指在函数内部调用自身的函数.下面是一个使用递归函数来实现循环的示例: (初学者记得写include,这里是个普通函数,所以我没写) de ...

  10. js 留言板(带删除功能)

    本文所用的知识点:创建节点和添加节点 创建节点:document.createElement('li') 添加节点  node(父亲节点).appendChild(child)    child:子节 ...