FreeRTOS 快速入门(三)之任务管理

目录

  • 一、任务创建与删除
    • 1、什么是任务
    • 2、创建任务
    • 3、任务的删除
  • 二、任务优先级和 Tick
    • 1、任务优先级
    • 2、Tick
    • 3、 修改优先级
  • 三、任务状态
    • 1、阻塞状态(Blocked)
    • 2、暂停状态(Suspended)
    • 3、就绪状态(Ready)
    • 4、状态转换
  • 四、Delay 函数
  • 五、空闲任务及其钩子函数
    • 1、介绍
    • 2、使用钩子函数的前提
  • 六、调度算法
  • 七、工具函数
    • 1、获取任务信息
    • 2、获取内核信息
    • 3、其他函数


一、任务创建与删除

1、什么是任务

在 FreeRTOS 中,任务就是一个函数,原型如下:

void ATaskFunction(void *pvParameters);

要注意的是,函数内部,尽量使用局部变量。因为每个任务都有自己的栈,每个任务运行这个函数时,任务 A 的局部变量放在任务 A 的栈里、任务 B 的局部变量放在任务 B 的栈里。不同任务的局部变量,有自己的副本。函数使用全局变量、静态变量的话,只有一个副本:多个任务使用的是同一个副本。

如下例:

void ATaskFunction(void *pvParameters)
{
	/* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */
	int32_t lVariableExample = 0;

	/* 任务函数通常实现为一个无限循环 */
	for( ;; )
	{
		/* 任务的代码 */
	}

	/* 如果程序从循环中退出,一定要使用 vTaskDelete 删除自己
	 * NULL 表示删除的是自己
	 */
	vTaskDelete( NULL );

	/* 程序不会执行到这里, 如果执行到这里就出错了 */
}

2、创建任务

创建任务时使用的函数如下:

/**
  * @brief  动态分配内存创建任务函数
  * @param  pvTaskCode:任务函数
  * @param  pcName:任务名称,单纯用于辅助调试
  * @param  usStackDepth:任务栈深度,单位为字(word)
  * @param  pvParameters:任务参数
  * @param  uxPriority:任务优先级
  * @param  pxCreatedTask:任务句柄,可通过该句柄进行删除/挂起任务等操作
  * @retval pdTRUE:创建成功,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:内存不足创建失败
  */
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,
					   const char * const pcName,
					   unsigned short usStackDepth,
					   void *pvParameters,
					   UBaseType_t uxPriority,
					   TaskHandle_t *pxCreatedTask);
 
/**
  * @brief  静态分配内存创建任务函数
  * @param  pvTaskCode:任务函数
  * @param  pcName:任务名称
  * @param  usStackDepth:任务栈深度,单位为字(word)
  * @param  pvParameters:任务参数
  * @param  uxPriority:任务优先级
  * @param  puxStackBuffer:任务栈空间数组
  * @param  pxTaskBuffer:任务控制块存储空间
  * @retval 创建成功的任务句柄
  */
TaskHandle_t xTaskCreateStatic(TaskFunction_t pvTaskCode,
							   const char * const pcName,
							   uint32_t ulStackDepth,
							   void *pvParameters,
							   UBaseType_t uxPriority,
							   StackType_t * const puxStackBuffer,
							   StaticTask_t * const pxTaskBuffer);

上述两个任务创建函数有如下几点不同,之后如无特殊需要将一律使用动态分配内存的方式创建任务或其他实例

  • xTaskCreateStatic 创建任务时需要用户指定任务栈空间数组和任务控制块的存储空间,而 xTaskCreate 创建任务其存储空间被动态分配,无需用户指定
  • xTaskCreateStatic 创建任务函数的返回值为成功创建的任务句柄,而 xTaskCreate 成功创建任务的句柄需要以参数形式提前定义并指定,同时其函数返回值仅表示任务创建成功/失败

参数说明:

参数描述
pvTaskCode函数指针,可以简单地认为任务就是一个 C 函数。
它稍微特殊一点:永远不退出,或者退出时要调用"vTaskDelete(NULL)"
pcName任务的名字,FreeRTOS 内部不使用它,仅仅起调试作用。
长度为:configMAX_TASK_NAME_LEN
usStackDepth每个任务都有自己的栈,这里指定栈大小。
单位是 word,比如传入 100,表示栈大小为 100 word,也就是 400 字节。
最大值为 uint16_t 的最大值。
怎么确定栈的大小,并不容易,很多时候是估计。
精确的办法是看反汇编码。
pvParameters调用 pvTaskCode 函数指针时用到:pvTaskCode(pvParameters)
uxPriority优先级范围:0~(configMAX_PRIORITIES – 1)
数值越小优先级越低,
如果传入过大的值,xTaskCreate 会把它调整为(configMAX_PRIORITIES – 1)
pxCreatedTask用来保存 xTaskCreate 的输出结果:task handle。
以后如果想操作这个任务,比如修改它的优先级,就需要这个 handle。
如果不想使用该 handle,可以传入 NULL。
返回值成功:pdPASS;
失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因只有内存不足)
注意:文档里都说失败时返回值是 pdFAIL,这不对。
pdFAIL 是 0,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 是-1。

3、任务的删除

删除任务时使用的函数如下:

void vTaskDelete(TaskHandle_t xTaskToDelete);

参数说明:

参数描述
pvTaskCode任务句柄,使用 xTaskCreate 创建任务时可以得到一个句柄。
也可传入 NULL,这表示删除自己。

例:

  • 自杀:vTaskDelete(NULL)
  • 被杀:别的任务执行 vTaskDelete(pvTaskCode) ,pvTaskCode 是自己的句柄
  • 杀人:执行 vTaskDelete(pvTaskCode) ,pvTaskCode 是别的任务的句柄

二、任务优先级和 Tick

1、任务优先级

高优先级的任务先运行。优先级的取值范围是:0~(configMAX_PRIORITIES – 1)数值越大优先级越高

FreeRTOS 的调度器可以使用 2 种方法来快速找出优先级最高的、可以运行的任务。使用不同的方法时,configMAX_PRIORITIES 的取值有所不同。

  1. 通用方法
    • 使用 C 函数实现,对所有的架构都是同样的代码。对 configMAX_PRIORITIES 的取值没有限制。但是 configMAX_PRIORITIES 的取值还是尽量小,因为取值越大越浪费内存,也浪费时间。
    • configUSE_PORT_OPTIMISED_TASK_SELECTION 被定义为 0、或者未定义时,使用此方法。
  2. 架构相关的优化的方法
    • 架构相关的汇编指令,可以从一个 32 位的数里快速地找出为 1 的最高位。使用这些指令,可以快速找出优先级最高的、可以运行的任务。
    • 使用这种方法时,configMAX_PRIORITIES 的取值不能超过 32。
    • configUSE_PORT_OPTIMISED_TASK_SELECTION 被定义为 1 时,使用此方法。

总而言之:

  • FreeRTOS 会确保最高优先级的、可运行的任务,马上就能执行
  • 对于相同优先级的、可运行的任务,轮流执行

2、Tick

FreeRTOS 中也有心跳,它使用定时器产生固定间隔的中断。这叫 Tick、滴答,比如每 10ms 发生一次时钟中断。

如下图:假设 t1、t2、t3 发生时钟中断,两次中断之间的时间被称为时间片(time slice、tick period)。时间片的长度由 configTICK_RATE_HZ 决定,假设 configTICK_RATE_HZ 为 100,那么时间片长度就是 10ms。

相同优先级的任务怎么切换呢?请看下图:

任务 2 从 t1 执行到 t2,在 t2 发生 tick 中断,进入 tick 中断处理函数:选择下一个要运行的任务。执行完中断处理函数后,切换到新的任务:任务 1。任务 1 从 t2 执行到 t3,从图中可以看出,任务运行的时间并不是严格从 t1,t2,t3 哪里开始。

有了 Tick 的概念后,我们就可以使用 Tick 来衡量时间了,比如:

vTaskDelay(2); // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms

// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms

注意,基于 Tick 实现的延时并不精确,比如 vTaskDelay(2) 的本意是延迟 2 个 Tick 周期,有可能经过 1 个 Tick 多一点就返回了。如下图:

使用 vTaskDelay 函数时,建议以 ms 为单位,使用 pdMS_TO_TICKS 把时间转换为 Tick。
这样的代码就与 configTICK_RATE_HZ 无关,即使配置项 configTICK_RATE_HZ 改变了,我们也不用去修改代码。

3、 修改优先级

我们使用 uxTaskPriorityGet 来获得任务的优先级:

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

使用参数 xTask 来指定任务,设置为 NULL 表示获取自己的优先级。

通过使用 vTaskPrioritySet 来设置任务的优先级:

void vTaskPrioritySet( TaskHandle_t xTask,
					   UBaseType_t uxNewPriority );
  • 使用参数 xTask 来指定任务,设置为 NULL 表示设置自己的优先级;
  • 参数 uxNewPriority 表示新的优先级,取值范围是 0~(configMAX_PRIORITIES – 1)。

三、任务状态

1、阻塞状态(Blocked)

在实际产品中,我们不会让一个任务一直运行,而是使用"事件驱动"的方法让它运行:

  • 任务要等待某个事件,事件发生后它才能运行
  • 在等待事件过程中,它不消耗 CPU 资源
  • 在等待事件的过程中,这个任务就处于阻塞状态(Blocked

在阻塞状态的任务,它可以等待两种类型的事件:

  • 时间相关的事件
    • 可以等待一段时间:我等 2 分钟
    • 也可以一直等待,直到某个绝对时间:我等到下午 3 点
  • 同步事件:这事件由别的任务,或者是中断程序产生
    • 例子 1:任务 A 等待任务 B 给它发送数据
    • 例子 2:任务 A 等待用户按下按键
    • 同步事件的来源有很多(这些概念在后面会细讲):
      • 队列(queue)
      • 二进制信号量(binary semaphores)
      • 计数信号量(counting semaphores)
      • 互斥量(mutexes)
      • 递归互斥量、递归锁(recursive mutexes)
      • 事件组(event groups)
      • 任务通知(task notifications)

在等待一个同步事件时,可以加上超时时间。比如等待队里数据,超时时间设为 10ms:

  • 10ms 之内有数据到来:成功返回
  • 10ms 到了,还是没有数据:超时返回

2、暂停状态(Suspended)

FreeRTOS 中的任务也可以进入暂停状态,唯一的方法是通过 vTaskSuspend 函数。函数原型如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

参数 xTaskToSuspend 表示要暂停的任务,如果为 NULL,表示暂停自己。

要退出暂停状态,只能由别人来操作:

  • 别的任务调用:vTaskResume
  • 中断程序调用:xTaskResumeFromISR

实际开发中,暂停状态用得不多。

3、就绪状态(Ready)

这个任务完全准备好了,随时可以运行:只是还轮不到它。这时,它就处于就绪态(Ready)。

4、状态转换

  • 创建任务–>就绪态:任务创建完成后进入就绪态,表明任务已准备就绪,随时可以运行,只等待调度器进行调度。
  • 就绪态→运行态:发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运态。
  • 运行态–>就绪态:有更高优先级任务创建或者恢复后,在滴答中断会发生任务调度,此刻最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原来的任务(此处可以看做是 CPU 使用权被更高优先级的任务抢占了)。
  • 运行态–>阻塞态:正在运行的任务发生阻塞(挂起、延时、读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
  • 阻塞态–>就绪态:阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
  • 就绪态–>挂起态:任务可以通过调用 vTaskSuspend() 函数可以将处于就绪态的任务挂起,被挂起的任务得不到CPU 的使用权,也不会参与调度,除非它从挂起态中解除。
  • 阻塞态–>挂起态:同样,任务可以通过调用 vTaskSuspend() 函数将处于阻塞态的任务挂起。
  • 运行态–>挂起态:同样,任务可以通过调用 vTaskSuspend() 函数将处于运行态的任务挂起。总之,不管当前任务处于何种状态,调用 vTaskSuspend() 后都会将任务挂起。
  • 挂起态–>就绪态:把 一 个 挂 起 状态 的 任 务 恢复的 唯 一 途 径 就 是调 用 vTaskResume()vTaskResumeFromISR() 函数,如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
/**
  * @brief  查询一个任务当前处于什么状态
  * @param  pxTask:要查询任务状态的任务句柄,NULL查询自己
  * @retval 任务状态的枚举类型
  */
eTaskState eTaskGetState(TaskHandle_t pxTask);
 
/*任务状态枚举类型返回值*/
typedef enum
{
	eRunning = 0,	/* 任务正在查询自身的状态,因此肯定是运行状态 */
	eReady,			/* 就绪状态 */
	eBlocked,		/* 阻塞状态 */
	eSuspended,		/* 挂起状态 */
	eDeleted,		/* 正在查询的任务已被删除,但其 TCB 尚未释放 */
	eInvalid		/* 无效状态 */
} eTaskState;

......

/**
  * @brief  挂起某个任务
  * @param  pxTaskToSuspend:被挂起的任务的句柄,通过传入NULL来挂起自身
  * @retval None
  */
void vTaskSuspend(TaskHandle_t pxTaskToSuspend);
 
/**
  * @brief  将某个任务从挂起状态恢复
  * @param  pxTaskToResume:正在恢复的任务的句柄
  * @retval None
  */
void vTaskResume(TaskHandle_t pxTaskToResume);
 
/**
  * @brief  vTaskResume的中断安全版本
  * @param  pxTaskToResume:正在恢复的任务的句柄
  * @retval 返回退出中断之前是否需要进行上下文切换(pdTRUE/pdFALSE)
  */
BaseType_t xTaskResumeFromISR(TaskHandle_t pxTaskToResume);

四、Delay 函数

这里有两个 Delay 函数:

  • vTaskDelay:至少等待指定个数的 Tick Interrupt 才能变为就绪状态
  • vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。

这 2 个函数原型如下:

void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少个
Tick */

/* pxPreviousWakeTime: 上一次被唤醒的时间
 * xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
 * 单位都是Tick Count
 */
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
							const TickType_t xTimeIncrement );

下面画图说明:

  • 使用 vTaskDelay(n) 时,进入、退出 vTaskDelay 的时间间隔至少是 n 个 Tick 中断
  • 使用 xTaskDelayUntil(&Pre, n) 时,前后两次退出 xTaskDelayUntil 的时间至少是 n 个 Tick 中断
    • 退出 xTaskDelayUntil 时任务就进入的就绪状态,一般都能得到执行机会
    • 所以可以使用 xTaskDelayUntil 来让任务周期性地运行

五、空闲任务及其钩子函数

1、介绍

一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。在使用 vTaskStartScheduler() 函数来创建、启动调度器时,这个函数内部会创建空闲任务:

  • 空闲任务优先级为 0:它不能阻碍用户任务运行
  • 空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞

空闲任务的优先级为 0,这以为着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。

要注意的是:如果使用 vTaskDelete() 来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。

我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环没执行一次,就会调用一次钩子函数。钩子函数的作用有这些:

  • 执行一些低优先级的、后台的、需要连续执行的函数
  • 测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率。
  • 让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式了。

空闲任务的钩子函数的限制:

  • 不能导致空闲任务进入阻塞状态、暂停状态
  • 如果你会使用 vTaskDelete() 来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植卡在钩子函数里的话,它就无法释放内存。

2、使用钩子函数的前提

在 task.c 文件中:

  1. 把这个宏定义为 1:configUSE_IDLE_HOOK
  2. 实现 vApplicationIdleHook 函数

六、调度算法

所谓调度算法,就是怎么确定哪个就绪态的任务可以切换为运行状态。

通过配置文件 FreeRTOSConfig.h 的两个配置项来配置调度算法:configUSE_PREEMPTIONconfigUSE_TIME_SLICING

还有第三个配置项:configUSE_TICKLESS_IDLE,它是一个高级选项,用于关闭 Tick 中断来实现省电。

调度算法的行为主要体现在两方面:高优先级的任务先运行、同优先级的就绪态任务如何被选中。调度算法要确保同优先级的就绪态任务,能"轮流"运行,策略是"轮转调度"(Round Robin Scheduling)。轮转调度并不保证任务的运行时间是公平分配的,我们还可以细化时间的分配方法。

从 3 个角度统一理解多种调度算法:

  • 可否抢占?高优先级的任务能否优先执行(配置项: configUSE_PREEMPTION)
    • 可以:被称作"可抢占调度",高优先级的就绪任务马上执行,下面再细化。
    • 不可以:不能抢就只能协商了,被称作"合作调度模式"
      • 当前任务执行时,更高优先级的任务就绪了也不能马上运行,只能等待当前任务主动让出 CPU 资源。
      • 其他同优先级的任务也只能等待:更高优先级的任务都不能抢占,平级的更应该老实点
  • 可抢占的前提下,同优先级的任务是否轮流执行(配置项:configUSE_TIME_SLICING)
    • 轮流执行:被称为"时间片轮转",同优先级的任务轮流执行,你执行一个时间片、我再执行一个时间片
    • 不轮流执行:当前任务会一直执行,直到主动放弃、或者被高优先级任务抢占
  • 在"可抢占"+"时间片轮转"的前提下,进一步细化:空闲任务是否让步于用户任务(配置项:configIDLE_SHOULD_YIELD)
    • 空闲任务低人一等,每执行一次循环,就看看是否主动让位给用户任务
    • 空闲任务跟用户任务一样,大家轮流执行,没有谁更特殊

列表如下:

配置项ABCDE
configUSE_PREEMPTION11110
configUSE_TIME_SLICING1100X
configIDLE_SHOULD_YIELD1010X
说明常用很少用很少用很少用几乎不用

注:

  • A:可抢占+时间片轮转+空闲任务让步
  • B:可抢占+时间片轮转+空闲任务不让步
  • C:可抢占+非时间片轮转+空闲任务让步
  • D:可抢占+非时间片轮转+空闲任务不让步
  • E:合作调度
/**
  * @brief  启动调度器
  * @retval None
  */
void vTaskStartScheduler(void);
 
/**
  * @brief  停止调度器
  * @retval None
  */
void vTaskEndScheduler(void);
 
/**
  * @brief  挂起调度器
  * @retval None
  */
void vTaskSuspendAll(void);
 
/**
  * @brief  恢复调度器
  * @retval 返回是否会导致发生挂起的上下文切换(pdTRUE/pdFALSE)
  */
BaseType_t xTaskResumeAll(void);

除了任务被时间片轮询切换或者高优先级抢占发生切换两种常见的调度方式外,还有其他的调度方式,比如任务自愿让出处理器给其他任务使用等函数,这些函数将在后续 “中断管理” 章节中被详细介绍,这里简单了解即可,如下所述

/**
  * @brief  让位于另一项同等优先级的任务
  * @retval None
  */
void taskYIELD(void);
 
/**
  * @brief  ISR 退出时是否执行上下文切换(汇编)
  * @param  xHigherPriorityTaskWoken:pdFASLE不请求上下文切换,反之请求上下文切换
  * @retval None
  */
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
 
/**
  * @brief  ISR 退出时是否执行上下文切换(C语言)
  * @param  xHigherPriorityTaskWoken:pdFASLE不请求上下文切换,反之请求上下文切换
  * @retval None
  */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

七、工具函数

任务相关的实用工具函数较多,官方网站上一共列出了 23 个 API 函数,这里仅简单介绍一些可能常用的 API 函数:

1、获取任务信息

/**
  * @brief  获取一个任务的信息,需启用参数configUSE_TRACE_FACILITY(默认启用)
  * @param  xTask:需要查询的任务句柄,NULL查询自己
  * @param  pxTaskStatus:用于存储任务状态信息的TaskStatus_t结构体指针
  * @param  xGetFreeStackSpace:是否返回栈空间高水位值
  * @param  eState:指定查询信息时任务的状态,设置为eInvalid将自动获取任务状态
  * @retval None
  */
void vTaskGetInfo(TaskHandle_t xTask,
				  TaskStatus_t *pxTaskStatus,
				  BaseType_t xGetFreeStackSpace,
				  eTaskState eState);
 
/**
  * @brief  获取当前任务句柄
  * @retval 返回当前任务句柄
  */
TaskHandle_t xTaskGetCurrentTaskHandle(void);
 
/**
  * @brief  获取任务句柄(运行时间较长,不宜大量使用)
  * @param  pcNameToQuery:要获取任务句柄的任务名称字符串
  * @retval 返回指定查询任务的句柄
  */
TaskHandle_t xTaskGetHandle(const char *pcNameToQuery);
 
/**
  * @brief  获取空闲任务句柄
  * @注意:需要设置 INCLUDE_xTaskGetIdleTaskHandle 为1,在CubeMX中不可调,需自行定义
  * @retval 返回空闲任务句柄
  */
TaskHandle_t xTaskGetIdleTaskHandle(void);
 
/**
  * @brief  获取一个任务的高水位值(任务栈空间最少可用剩余空间大小,单位为字(word))
  * @param  xTask:要获取高水位值任务的句柄,NULL查询自己
  * @retval 
  */
UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask);
 
/**
  * @brief  获取一个任务的任务名称字符串
  * @param  xTaskToQuery:要获取名称字符串的任务的句柄,NULL查询自己
  * @retval 返回一个任务的任务名称字符串
  */
char* pcTaskGetName(TaskHandle_t xTaskToQuery);

2、获取内核信息

/**
  * @brief  获取系统内所有任务状态,为每个任务返回一个TaskStatus_t结构体数组
  * @param  pxTaskStatusArray:数组的指针,数组每个成员都是TaskStatus_t类型,用于存储获取到的信息
  * @param  uxArraySize:设置数组pxTaskStatusArray的成员个数
  * @param  pulTotalRunTime:返回FreeRTOS运行后总的运行时间,NULL表示不返回该数据
  * @retval 返回实际获取的任务信息条数
  */
UBaseType_t uxTaskGetSystemState(TaskStatus_t * const pxTaskStatusArray,
								 const UBaseType_t uxArraySize,
								 unsigned long * const pulTotalRunTime);
 
/**
  * @brief  返回调度器状态
  * @retval 0:被挂起,1:未启动,2:正在运行
  */
BaseType_t xTaskGetSchedulerState(void);
 
/**
  * @brief  获取内核当前管理的任务总数
  * @retval 返回内核当前管理的任务总数
  */
UBaseType_t uxTaskGetNumberOfTasks(void);
 
/**
  * @brief  获取内核中所有任务的字符串列表信息
  * @param  pcWriteBuffer:字符数组指针,用于存储获取的字符串信息
  * @retval None
  */
void vTaskList(char *pcWriteBuffer);

3、其他函数

/**
  * @brief  获取一个任务的标签值
  * @param  xTask:要获取任务标签值的任务句柄,NULL表示获取自己的标签值
  * @retval 返回任务的标签值
  */
TaskHookFunction_t xTaskGetApplicationTaskTag(TaskHandle_t xTask); 
 
/**
  * @brief  获取一个任务的标签值的中断安全版本函数
  */
TaskHookFunction_t xTaskGetApplicationTaskTagFromISR(TaskHandle_t xTask);
 
/**
  * @brief  设置一个任务的标签值,标签值保存在任务控制块中
  * @param  xTask:要设置标签值的任务的句柄,NULL表示设置自己
  * @param  pxTagValue:要设置的标签值
  * @retval None
  */
void vTaskSetApplicationTaskTag(TaskHandle_t xTask, 
								TaskHookFunction_t pxTagValue);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/870838.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

议题揭晓 | 8 月 24 日,deepin 23 Release Party 暨武汉 LUG 等你来!

查看原文 Hi,Linuxer! 历经三年的精心打磨和无数次的迭代测试,deepin 23 已正式发布,不少伙伴已上手体验,我们也收到了诸多积极反馈。 为了庆祝这一里程碑,本次武汉LUG暨deepin 23 线下发布活动&#xf…

二叉树(四)

一、二叉树的性质 二、练习 1.某二叉树共有399个节点,其中有199个度为2的节点,则二叉树中的叶子节点数为( )。 A.不存在这样的树 B.200 C.198 D.199 答案:B 参考二叉树的性质第三条 2.在具有2…

单位严禁非授权设备接入网络,此方案高效防护,安心无忧!

MAC与IP地址绑定策略 MAC地址(媒体访问控制地址)与IP地址(互联网协议地址)作为计算机网络中的两大关键标识符,分别在数据链路层与网络层各司其职。将二者绑定,是网络安全领域的一项常用手段,旨…

零基础5分钟上手亚马逊云科技核心云开发知识 - 网络基础

简介: 欢迎来到小李哥全新亚马逊云科技AWS云计算知识学习系列,适用于任何无云计算或者亚马逊云科技技术背景的开发者,通过这篇文章大家零基础5分钟就能完全学会亚马逊云科技一个经典的服务开发架构方案。 我会每天介绍一个基于亚马逊云科技…

Spring发送邮件性能优化?如何集成发邮件?

Spring发送邮件安全性探讨!Spring发送邮件功能有哪些? 邮件发送的性能逐渐成为影响用户体验的重要因素之一。AokSend将探讨如何在Spring框架中进行Spring发送邮件的性能优化,确保系统能够高效、稳定地处理大量邮件请求。 Spring发送邮件&am…

Chat App 项目之解析(三)

Chat App 项目介绍与解析(一)-CSDN博客文章浏览阅读76次。Chat App 是一个实时聊天应用程序,旨在为用户提供一个简单、直观的聊天平台。该应用程序不仅支持普通用户的注册和登录,还提供了管理员登录功能,以便管理员可以…

《黑神话:悟空》媒体评分解禁 M站均分82

《黑神话:悟空》媒体评分现已解禁,截止发稿时,M站共有43家媒体评测,均分为82分。 部分媒体评测: God is a Geek 100: 毫无疑问,《黑神话:悟空》是今年最好的动作游戏之一&#xff…

计算机网络部分基础知识

网络协议的意义 单台主机内部的设备之间需要发送和接收消息,那么和相隔很远的两台主机之间发送消息有什么区别呢?两台主机通过网络发送消息,相当于两个网卡设备之间进行通信,最大的区别在于距离变长了。而距离变长带来的结果就是&…

<Linux> 进程控制

目录 一、进程创建 1. fork函数 2. fork函数返回值 3. 写时拷贝 4. fork常规用法 5. fork调用失败原因 6. 如何创建多个子进程? 二、进程终止 1. 进程退出场景 2. 进程退出码 3. errno 4. 进程异常退出 5. 进程常见退出方法 5.1 return退出 5.2 exit退出 5.3 _ex…

【FPGA数字信号处理】- 数字信号处理如何入门?

​数字信号处理(Digital Signal Processing,简称DSP)是一种利用计算机或专用数字硬件对信号进行处理的技术,在通信、音频、视频、雷达等领域发挥着越来越重要的作用,也是FPGA主要应用领域之一。 本文将详细介绍数字信…

Web3链上聚合器声呐已全球上线,开启区块链数据洞察新时代

在全球区块链技术高速发展的浪潮中,在创新发展理念的驱动下,区块链领域的工具类应用备受资本青睐。 2024年8月20日,由生纳(香港)国际集团倾力打造的一款链上应用工具——“声呐链上聚合器”,即“声呐链上数…

ESP RainMaker OTA 自动签名功能的安全启动

【如果您之前有关注乐鑫的博客和新闻,那么应该对 ESP RainMaker 及其各项功能有所了解。如果不曾关注,建议先查看相关信息,知晓本文背景。】 在物联网系统的建构中,安全性是一项核心要素。乐鑫科技对系统安全给予了极高的重视。ES…

OpenCV学堂 | 汇总 | 深度学习图像去模糊技术与模型

本文来源公众号“OpenCV学堂”,仅用于学术分享,侵权删,干货满满。 原文链接:汇总 | 深度学习图像去模糊技术与模型 引言 深度学习在图像去模糊领域展现出了强大的能力,通过构建复杂的神经网络模型,可以自…

Golang | Leetcode Golang题解之第337题打家劫舍III

题目: 题解: func rob(root *TreeNode) int {val : dfs(root)return max(val[0], val[1]) }func dfs(node *TreeNode) []int {if node nil {return []int{0, 0}}l, r : dfs(node.Left), dfs(node.Right)selected : node.Val l[1] r[1]notSelected : …

Kotlin Multiplatform 跨平台开发的优化策略与实践

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点 Kotlin Multiplatform 跨平台开发的优化策略与实践 在当今快速发展的软件开发领域,跨平台开发技术正变得越来越重要。Kotlin Multi…

Ubuntu中服务部署

Ubuntu中服务部署 一、root用户密码一、SSH远程连接二、JDK1.8安装1、解压上传的安装包2、配置jdk环境变量 三、minio安装1、官网下载安装包2、上传文件并授权3、书写启动脚本4、启动及说明5、启动异常 四、nacos安装1、下载上传安装包,并解压2、修改启动脚本3、配置…

[RCTF2015]EasySQL1

打开题目 点进去看看 注册个admin账户,登陆进去 一个个点开看,没发现flag 我们也可以由此得出结论,页面存在二次注入的漏洞,并可以大胆猜测修改密码的源代码 resoponse为invalid string的关键字是被过滤的关键字,Le…

2百多首胎教儿童音乐ACCESS\EXCEL数据包

还记录之前我搞到过一个《113个大自然声音助眠纯音乐白噪音数据包》吗?今天又遇到了一个胎教儿童音乐,辅助用于怀孕手册、胎教指南、儿童早教类产品是个很不错的数据包哦。 MP3文件对应记录数共258条,大小总容量为1GB,其中分类汇总…

基于JavaWeb的本科生交流培养管理平台的设计与实现--论文pf

TOC springboot529基于JavaWeb的本科生交流培养管理平台的设计与实现--论文pf 第1章 绪论 1.1选题动因 当前的网络技术,软件技术等都具备成熟的理论基础,市场上也出现各种技术开发的软件,这些软件都被用于各个领域,包括生活和…

物联网(IoT)详解

物联网(IoT)详解 1. IoT定义简介2. IoT工作原理3. IoT关键技术4. 物联网与互联网区别5. IoT使用场景6. 开源物联网平台7. 参考资料 1. IoT定义简介 首先第一个问题,什么是物联网(IoT)? 物联网(英文&#…