【FreeRTOS】【应用篇】任务管理相关函数

news2024/11/17 10:50:59

文章目录

  • 前言
  • 一、函数解析
    • 1. 任务挂起 vTaskSuspend()
      • ① 使用场景
      • ② 设计思路
      • ③ 代码
    • 2. 任务恢复 vTaskResume()
      • ① 作用
      • ② 设计思路
      • ③ 代码
    • 3. 挂起任务调度器 vTaskSuspendAll()
      • ① 作用
      • ② 代码
    • 4. 恢复任务调度器 xTaskResumeAll()
      • ① 设计思路
      • ② 代码
    • 5. 任务删除函数 vTaskDelete()
      • ① 设计关键点
      • ② 任务删除函数 vTaskDelete() 代码
      • ③ 空闲任务中删除预删除函数 prvCheckTasksWaitingTermination()
    • 6. 相对延时 vTaskDelay() 和绝对延时 vTaskDelayUntil()
      • ① 区别
      • ② 相对延时 vTaskDelay()
        • 关于相对延时周期的探讨
      • ③ 绝对延时 vTaskDelayUntil()
        • a. 设计思路
        • b. 代码
        • d. 绝对延时的使用方式
  • 二、系统实时性与任务的关系
    • 1. 任务执行时间的两个方面
    • 2. 在系统设计中考虑任务执行时间
    • 3. 任务 Ta 对事件 A 的响应示例
    • 4. 同时存在的其他任务 Tb 和 Tc 的影响
    • 5. 任务 Tc 的影响
  • 后记

前言

本篇文章主要对 FreeRTOS 中任务管理相关的函数进行了详解,最最重要的是解析了 绝对延时 vTaskDelayUntil()” 为什么能够确保任务执行周期准确???

一部分代码和图片参考野火 FreeRTOS 教程。

一、函数解析

1. 任务挂起 vTaskSuspend()

① 使用场景

  • 当一个任务暂时不使用时,我们可以将其挂起
  • 挂起的意思就是这个任务不参与任务调度器的调度,也就是任务调度器不知道有这个任务的存在
  • 挂起相比于直接把任务删除的优点是:保留了任务运行时的环境,当任务恢复的时候可以迅速转换到挂起时的状态

② 设计思路

任务的挂起主要涉及调度器方面,要让调度器不调度这个被挂起的函数:

  • 把要挂起的任务从就绪列表/延时列表中删除
  • 把要挂起的任务从事件等待列表中删除
  • 更新下一个任务阻塞延时到期的时间
  • 依情况进行任务切换

③ 代码

/*-----------------------------------------------------------*/

#if (INCLUDE_vTaskSuspend == 1)

void vTaskSuspend(TaskHandle_t xTaskToSuspend)
{
    TCB_t *pxTCB;

    taskENTER_CRITICAL();
    {
        /* 如果在此处传递null,则它正在被挂起的正在运行的任务。*/
        pxTCB = prvGetTCBFromHandle(xTaskToSuspend);

        traceTASK_SUSPEND(pxTCB);

        /* 从就绪/阻塞列表中删除任务并放入挂起列表中。*/
        if (uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0) {
            taskRESET_READY_PRIORITY(pxTCB->uxPriority);
        } else {
            mtCOVERAGE_TEST_MARKER();
        }

        /* 如果任务在等待事件,也从等待事件列表中移除 */
        if (listLIST_ITEM_CONTAINER(&(pxTCB->xEventListItem)) != NULL) {
            (void)uxListRemove(&(pxTCB->xEventListItem));
        } else {
            mtCOVERAGE_TEST_MARKER();
        }
        /* 将任务状态添加到挂起列表中 */
        vListInsertEnd(&xSuspendedTaskList, &(pxTCB->xStateListItem));
    }
    taskEXIT_CRITICAL();

    if (xSchedulerRunning != pdFALSE) {
        /* 重置下一个任务的解除阻塞时间。
        重新计算一下还要多长时间执行下一个任务。
        如果下个任务的解锁,刚好是被挂起的那个任务,
        那么变量NextTaskUnblockTime 就不对了,
        所以要重新从延时列表中获取一下。*/

        taskENTER_CRITICAL();
        {
            prvResetNextTaskUnblockTime();
        }
        taskEXIT_CRITICAL();
    } else {
        mtCOVERAGE_TEST_MARKER();
    }

    if (pxTCB == pxCurrentTCB) {
        if (xSchedulerRunning != pdFALSE) {
            /* 当前的任务已经被挂起。*/
            configASSERT(uxSchedulerSuspended == 0);

            /* 调度器在运行时,如果这个挂起的任务是当前任务,立即切换任务。*/
            portYIELD_WITHIN_API();
        } else {
            /* 调度器未运行(xSchedulerRunning == pdFALSE ),
            但 pxCurrentTCB 指向的任务刚刚被暂停,
            所以必须调整 pxCurrentTCB 以指向其他任务。
            首先调用函数listCURRENT_LIST_LENGTH()
            判断一下系统中所有的任务是不是都被挂起了,
            也就是查看列表xSuspendedTaskList
            的长度是不是等于uxCurrentNumberOfTasks,
            事实上并不会发生这种情况,
            因为空闲任务是不允许被挂起和阻塞的,
            必须保证系统中无论如何都有一个任务可以运行*/

            if (listCURRENT_LIST_LENGTH(&xSuspendedTaskList) == uxCurrentNumberOfTasks) {
                /* 没有其他任务准备就绪,因此将pxCurrentTCB设置回NULL,
                以便在创建下一个任务时pxCurrentTCB将被设置为指向它,
                实际上并不会执行到这里 */

                pxCurrentTCB = NULL;
            } else {
                /* 有其他任务,则切换到其他任务 */
                vTaskSwitchContext();
            }
        }
    } else {
        mtCOVERAGE_TEST_MARKER();
    }
}

#endif /* INCLUDE_vTaskSuspend */

/*-----------------------------------------------------------*/

这个函数的功能是挂起一个任务。具体步骤如下:

  1. 获取要挂起的任务的TCB(任务控制块)。
  2. 从就绪/延时任务列表中移除任务,并将其放入挂起任务列表中。
  3. 如果任务还等待事件,则将其从事件列表中移除。
  4. 如果调度器正在运行,则重置下一个期望的解除阻塞时间。
  5. 如果被挂起的是当前正在运行的任务,则根据调度器的运行情况进行判断:
    • 如果调度器正在运行,则断言调度器未被暂停,然后调用portYIELD_WITHIN_API()函数进行任务切换。
    • 如果调度器未运行,则判断是否还有其他任务处于就绪状态,并根据情况进行任务切换。
  6. 如果要挂起的任务不是当前任务,那么什么都不做。

2. 任务恢复 vTaskResume()

① 作用

恢复被挂起的任务,使其从挂起的状态立刻回到就绪的状态。

② 设计思路

  • 判断要恢复的任务是否已经被挂起或者是当前任务(因为被挂起的任务得不到执行所以不可能是当前任务)
  • 如果要恢复的任务确实被挂起,就从挂起列表中移到就绪列表中
  • 记得更新当前最高优先级并尝试任务切换

③ 代码

#if (INCLUDE_vTaskSuspend == 1)

void vTaskResume(TaskHandle_t xTaskToResume)
{
    /* 根据xTaskToResume获取对应的任务控制块 */
    TCB_t *const pxTCB = (TCB_t *)xTaskToResume;

    /* 检查要恢复的任务是否被挂起,
    如果没被挂起,恢复调用任务没有意义 */
    configASSERT(xTaskToResume);

    /* 该参数不能为 NULL,
    同时也无法恢复当前正在执行的任务,
    因为当前正在运行的任务不需要恢复,
    只能恢复处于挂起态的任务 */
    if ((pxTCB != NULL) && (pxTCB != pxCurrentTCB)) {
        /* 进入临界区 */
        taskENTER_CRITICAL();
        {
            if (prvTaskIsTaskSuspended(pxTCB) != pdFALSE) {
                traceTASK_RESUME(pxTCB);

                /* 由于我们处于临界区,
                即使任务被挂起,我们也可以访问任务的状态列表。
                将要恢复的任务从挂起列表中删除 */
                (void)uxListRemove(&(pxTCB->xStateListItem));

                /* 将要恢复的任务添加到就绪列表中去 */
                prvAddTaskToReadyList(pxTCB);

                /* 如果刚刚恢复的任务优先级比当前任务优先级更高
                则需要进行任务的切换 */
                if (pxTCB->uxPriority >= pxCurrentTCB->uxPriority) {
                    /* 因为恢复的任务在当前情况下的优先级最高
                    调用taskYIELD_IF_USING_PREEMPTION()进行一次任务切换*/
                    taskYIELD_IF_USING_PREEMPTION();
                } else {
                    mtCOVERAGE_TEST_MARKER();
                }
            } else {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        taskEXIT_CRITICAL(); /* 退出临界区 */
    } else {
        mtCOVERAGE_TEST_MARKER();
    }
}

#endif /* INCLUDE_vTaskSuspend */

/*-----------------------------------------------------------*/

这个函数的功能是恢复一个挂起的任务。具体步骤如下:

  1. 检查要恢复的任务是否有效。如果为空或者是当前任务,则无法恢复,会触发断言错误。
  2. 如果要恢复的任务不为空且不是当前任务,则进行以下操作:
    • 进入临界区保护关中断。
    • 检查任务是否已经被挂起。
    • 如果任务已被挂起,则将其从挂起任务列表中移除,并将其添加到就绪任务列表中。
    • 如果被恢复的任务的优先级不低于当前任务的优先级,则进行任务切换(调用taskYIELD_IF_USING_PREEMPTION()函数)。
    • 退出临界区恢复中断状态。

3. 挂起任务调度器 vTaskSuspendAll()

① 作用

函数名的意思是挂起所有任务,实际上就是挂起任务调度器,使所有任务都不参与调度。

② 代码

注意,调用多少次挂起任务调度器,相应的恢复的时候就要调用恢复任务调度器函数对应的次数。

用 uxSchedulerSuspended 记录调用的次数。

void vTaskSuspendAll( void )
{
	++uxSchedulerSuspended;
}

4. 恢复任务调度器 xTaskResumeAll()

① 设计思路

  • 当恢复任务调度器的调用次数和调用悬起任务调度器的调用次数一致时,才开始恢复任务调度器
  • 将所有挂起的任务移到就绪列表中
  • 更新全局最高优先级
  • 更新下一个阻塞结束时间点
  • 更新任务调度器悬起期间的系统时基计数,确保即使准确
  • 注意任务切换

② 代码

/*----------------------------------------------------------*/

BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;

	/* If uxSchedulerSuspended is zero then this function does not match a
	previous call to vTaskSuspendAll(). */
	configASSERT( uxSchedulerSuspended );

	/* It is possible that an ISR caused a task to be removed from an event
	list while the scheduler was suspended.  If this was the case then the
	removed task will have been added to the xPendingReadyList.  Once the
	scheduler has been resumed it is safe to move all the pending ready
	tasks from this list into their appropriate ready list. */
	taskENTER_CRITICAL();
	{
		--uxSchedulerSuspended;

		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
		{
			if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
			{
				/* Move any readied tasks from the pending list into the
				appropriate ready list. */
				while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
				{
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
					( void ) uxListRemove( &( pxTCB->xEventListItem ) );
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					prvAddTaskToReadyList( pxTCB );

					/* If the moved task has a priority higher than the current
					task then a yield must be performed. */
					if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
					{
						xYieldPending = pdTRUE;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( pxTCB != NULL )
				{
					/* A task was unblocked while the scheduler was suspended,
					which may have prevented the next unblock time from being
					re-calculated, in which case re-calculate it now.  Mainly
					important for low power tickless implementations, where
					this can prevent an unnecessary exit from low power
					state. */
					prvResetNextTaskUnblockTime();
				}

				/* If any ticks occurred while the scheduler was suspended then
				they should be processed now.  This ensures the tick count does
				not	slip, and that any delayed tasks are resumed at the correct
				time. */
				{
					UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */

					if( uxPendedCounts > ( UBaseType_t ) 0U )
					{
						do
						{
							if( xTaskIncrementTick() != pdFALSE )
							{
								xYieldPending = pdTRUE;
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
							--uxPendedCounts;
						} while( uxPendedCounts > ( UBaseType_t ) 0U );

						uxPendedTicks = 0;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}

				if( xYieldPending != pdFALSE )
				{
					#if( configUSE_PREEMPTION != 0 )
					{
						xAlreadyYielded = pdTRUE;
					}
					#endif
					taskYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	taskEXIT_CRITICAL();

	return xAlreadyYielded;
}

这个函数的功能是恢复所有被挂起的任务,并在需要时进行任务切换。具体步骤如下:

  1. 检查调度器是否已被暂停。如果没有暂停,则触发断言错误。
  2. 减少调度器暂停计数器(uxSchedulerSuspended)。
  3. 如果调度器暂停计数器降为0,则进行以下操作:
    • 检查当前任务数是否大于0。如果是,则执行以下任务:
      • 将xPendingReadyList列表中的所有挂起的任务移动到适当的就绪任务列表中。
      • 如果被移动的任务的优先级不低于当前任务的优先级,则将xYieldPending标志设置为true。
      • 如果存在被解除阻塞的任务,则重新计算下一个预期的解除阻塞时间。
      • 处理在调度器暂停期间发生的滴答计数,确保滴答计数不会丢失,并确保延时任务在正确的时间恢复。
      • 如果xYieldPending标志为true,则进行任务切换(调用taskYIELD_IF_USING_PREEMPTION()函数)。
  4. 返回xAlreadyYielded标志,标志着是否已经进行了任务切换。

5. 任务删除函数 vTaskDelete()

① 设计关键点

关键是要区分是否是自删除任务和非自删除任务,也就是是否在一个任务中执行删除自己的操作。

这个函数的设计思路和关键点可以简洁地总结如下:

  • 从就绪任务列表和事件等待列表中移除任务。
  • 更新任务列表。
  • 自删除任务的操作:插入终止任务等待列表,标记任务切换,准备在空闲任务中删除此任务。
  • 非自删除任务的操作:减少当前任务数,删除任务的TCB,重置下一个任务解除阻塞时间。
  • 如果需要,进行任务切换。

② 任务删除函数 vTaskDelete() 代码

#if ( INCLUDE_vTaskDelete == 1 )

	void vTaskDelete( TaskHandle_t xTaskToDelete )
	{
	TCB_t *pxTCB;

		taskENTER_CRITICAL();
		{
			/* If null is passed in here then it is the calling task that is
			being deleted. */
			pxTCB = prvGetTCBFromHandle( xTaskToDelete );

			/* Remove task from the ready list. */
			if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
			{
				taskRESET_READY_PRIORITY( pxTCB->uxPriority );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* Is the task waiting on an event also? */
			if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			{
				( void ) uxListRemove( &( pxTCB->xEventListItem ) );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* Increment the uxTaskNumber also so kernel aware debuggers can
			detect that the task lists need re-generating.  This is done before
			portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
			not return. */
			uxTaskNumber++;

			if( pxTCB == pxCurrentTCB )
			{
				/* A task is deleting itself.  This cannot complete within the
				task itself, as a context switch to another task is required.
				Place the task in the termination list.  The idle task will
				check the termination list and free up any memory allocated by
				the scheduler for the TCB and stack of the deleted task. */
				vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

				/* Increment the ucTasksDeleted variable so the idle task knows
				there is a task that has been deleted and that it should therefore
				check the xTasksWaitingTermination list. */
				++uxDeletedTasksWaitingCleanUp;

				/* The pre-delete hook is primarily for the Windows simulator,
				in which Windows specific clean up operations are performed,
				after which it is not possible to yield away from this task -
				hence xYieldPending is used to latch that a context switch is
				required. */
				portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
			}
			else
			{
				--uxCurrentNumberOfTasks;
				prvDeleteTCB( pxTCB );

				/* Reset the next expected unblock time in case it referred to
				the task that has just been deleted. */
				prvResetNextTaskUnblockTime();
			}

			traceTASK_DELETE( pxTCB );
		}
		taskEXIT_CRITICAL();

		/* Force a reschedule if it is the currently running task that has just
		been deleted. */
		if( xSchedulerRunning != pdFALSE )
		{
			if( pxTCB == pxCurrentTCB )
			{
				configASSERT( uxSchedulerSuspended == 0 );
				portYIELD_WITHIN_API();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}

#endif /* INCLUDE_vTaskDelete */
/*-----------------------------------------------------------*/

这个函数的功能是删除一个任务,并在需要时进行任务切换。具体步骤如下:

  1. 进入临界区保护关中断。
  2. 从任务句柄(xTaskToDelete)获取要删除的任务的TCB(任务控制块)指针(pxTCB)。
  3. 从就绪任务列表中移除该任务。如果移除成功(返回值为0),则调用taskRESET_READY_PRIORITY宏重置与任务优先级相关的位图。
  4. 检查任务是否同时在等待事件。如果是,则从事件等待列表中移除该任务。
  5. 增加uxTaskNumber,以便内核感知调试器可以检测到任务列表需要重新生成。
  6. 如果要删除的任务是当前任务(自删除),则进行以下操作:
    • 将任务插入终止任务等待列表xTasksWaitingTermination中。
    • 增加uxDeletedTasksWaitingCleanUp,以便空闲任务知道有一个已删除的任务需要进行内存清理。
    • 调用portPRE_TASK_DELETE_HOOK宏执行预删除操作,并通过xYieldPending标志指示需要进行任务切换。
  7. 如果要删除的任务不是当前任务,则进行以下操作:
    • 减少当前任务数(uxCurrentNumberOfTasks)。
    • 调用prvDeleteTCB函数删除TCB。
    • 重置下一个预期解除阻塞时间(prvResetNextTaskUnblockTime)。
  8. 跟踪任务删除事件。
  9. 退出临界区恢复中断状态。
  10. 如果调度器正在运行,且要删除的任务是当前任务,则进行任务切换。
  11. 函数结束。

③ 空闲任务中删除预删除函数 prvCheckTasksWaitingTermination()

当任务自删除的时候,只是对任务进行删除标记,直到执行空闲任务的时候才对其进行删除和内存清理。

这个函数的设计思路如下:

  • 检查任务列表是否为空。
  • 判断待删除任务列表是否为空,以决定是否需要执行删除操作。
  • 从就绪任务列表中删除任务。
  • 更新当前任务数和待删除任务计数。
static void prvCheckTasksWaitingTermination( void )
{

	/** THIS FUNCTION IS CALLED FROM THE RTOS IDLE TASK **/

	#if ( INCLUDE_vTaskDelete == 1 )
	{
		BaseType_t xListIsEmpty;

		/* ucTasksDeleted is used to prevent vTaskSuspendAll() being called
		too often in the idle task. */
		while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
		{
			vTaskSuspendAll();
			{
				xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination );
			}
			( void ) xTaskResumeAll();

			if( xListIsEmpty == pdFALSE )
			{
				TCB_t *pxTCB;

				taskENTER_CRITICAL();
				{
					pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					--uxCurrentNumberOfTasks;
					--uxDeletedTasksWaitingCleanUp;
				}
				taskEXIT_CRITICAL();

				prvDeleteTCB( pxTCB );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}
	#endif /* INCLUDE_vTaskDelete */

这个函数的功能是检查是否有任务等待终止,并在需要时删除这些任务。函数会在RTOS的空闲任务中被调用。具体步骤如下:

  1. 检查是否启用了任务删除功能。
  2. 如果要清理的待删除任务数量大于0,则执行以下操作:
    • 调用vTaskSuspendAll函数暂时挂起调度器。
    • 检查xTasksWaitingTermination列表是否为空。
    • 通过xTaskResumeAll函数恢复调度器,并获取xListIsEmpty标志用于后续判断。
    • 如果xListIsEmpty标志为false,则进行以下操作:
      • 获取xTasksWaitingTermination列表头部任务的TCB指针(pxTCB)。
      • 从就绪任务列表中移除该任务。
      • 减少当前任务数。
      • 减少待删除任务计数。
    • 如果xListIsEmpty标志为true,则什么都不做。
  3. 函数结束。

6. 相对延时 vTaskDelay() 和绝对延时 vTaskDelayUntil()

① 区别

  • 相对延时函数不适合于需要周期性执行的任务,只适用于简单暂停一下任务,相对延时函数的延时是 vTaskDelay() 结束后再开始延时,而且如果有其它高优先级的任务延缓了 vTaskDelay() 的调用,那么会影响下一次执行这个任务的时间
  • 而绝对延时函数常用于较精确的周期运行任务,不会受其他高优先级任务的影响

② 相对延时 vTaskDelay()

这个函数之前我们已经详细介绍过:
【学习日记】【FreeRTOS】空闲任务与阻塞延时

关于相对延时周期的探讨

我们想要绿色以 150ms 的周期执行:

  • 设置两个任务,绿色优先级较高
    在这里插入图片描述
    此时绿色不会被红色打断,用相对延时可以得到准确的任务周期:
    在这里插入图片描述
  • 如果绿色优先级较低:
    在这里插入图片描述
    此时绿色会被红色打断,相对延时得到的周期不准确:
    在这里插入图片描述

③ 绝对延时 vTaskDelayUntil()

a. 设计思路

  • 绝对延时中最重要的是引入了一个变量 PreviousWakeTime,这个变量可以记录上次延时后唤醒的时间,以这个变量为基础可以计算出下一次唤醒的时间,然后再由下一次唤醒的时间和当前时间计算出需要的延时时长
  • 从上面相对延时周期不准确可以看出,一旦有任务主体被其他任务抢占,周期就从本任务主体 + 延时时长变成了本任务主体 + 抢占任务主体 + 延时时长,导致周期不准确
  • 所以绝对延时是在确定任务周期后,根据本任务主体的耗时是否有变化来决定具体的延时时长的,这就能保证任务执行周期的相对准确

b. 代码

代码主要分为几部分:

  • 下一次唤醒时间的计算
  • 根据节拍计数器和唤醒时间等有无溢出,判断是否需要进行延时(也就是确保设定的周期大于任务主体的耗时)
    以下的情况都符合要求,也就是设定的周期大于任务主体的耗时(图片来自野火教程):
    在这里插入图片描述
    在这里插入图片描述
  • 更新上一次唤醒时间
  • 任务切换
#if ( INCLUDE_vTaskDelayUntil == 1 )

	void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
	{
	TickType_t xTimeToWake;
	BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

		configASSERT( pxPreviousWakeTime );
		configASSERT( ( xTimeIncrement > 0U ) );
		configASSERT( uxSchedulerSuspended == 0 );

		vTaskSuspendAll();
		{
			/* Minor optimisation.  The tick count cannot change in this
			block. */
			const TickType_t xConstTickCount = xTickCount;

			/* Generate the tick time at which the task wants to wake. */
			xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

			if( xConstTickCount < *pxPreviousWakeTime )
			{
				/* The tick count has overflowed since this function was
				lasted called.  In this case the only time we should ever
				actually delay is if the wake time has also	overflowed,
				and the wake time is greater than the tick time.  When this
				is the case it is as if neither time had overflowed. */
				if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				/* The tick time has not overflowed.  In this case we will
				delay if either the wake time has overflowed, and/or the
				tick time is less than the wake time. */
				if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
				{
					xShouldDelay = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}

			/* Update the wake time ready for the next call. */
			*pxPreviousWakeTime = xTimeToWake;

			if( xShouldDelay != pdFALSE )
			{
				traceTASK_DELAY_UNTIL( xTimeToWake );

				/* prvAddCurrentTaskToDelayedList() needs the block time, not
				the time to wake, so subtract the current tick count. */
				prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		xAlreadyYielded = xTaskResumeAll();

		/* Force a reschedule if xTaskResumeAll has not already done so, we may
		have put ourselves to sleep. */
		if( xAlreadyYielded == pdFALSE )
		{
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

#endif /* INCLUDE_vTaskDelayUntil */
/*-----------------------------------------------------------*/

c. 绝对延时为什么能确保任务执行周期相对准确呢

因为绝对延时能根据任务主体的耗时来调整具体的延时时间:任务耗时长(比如被其他任务抢占耽误了一会),延时就短;任务耗时短,延时就长。最终达到执行周期相对准确的目的。

具体的图解:
在这里插入图片描述
可以看到,当把绝对延时函数放置与 while 中的任务主体代码第一行时,只有第一个任务之前的延时过长,后面就以准确的周期执行任务了。

如果想使第一个任务之前的延时也准确,只需要把绝对延时函数放在任务主体的最后一行。

d. 绝对延时的使用方式

void vTaskA(void* pvParameters)
{
    /* 用于保存上次时间。调用后系统自动更新 */
    static portTickType PreviousWakeTime;
    /* 设置延时时间,将时间转为节拍数 */
    const portTickType TimeIncrement = pdMS_TO_TICKS(1000);

    /* 获取当前系统时间 */
    PreviousWakeTime = xTaskGetTickCount();

    while (1)
    {
        /* 调用绝对延时函数,任务时间间隔为 1000 个 tick */
        vTaskDelayUntil(&PreviousWakeTime, TimeIncrement);

        // ...
        // 这里为任务主体代码
        // ...
    }
}

二、系统实时性与任务的关系

1. 任务执行时间的两个方面

  • 任务从开始到结束的时间
  • 任务的周期

2. 在系统设计中考虑任务执行时间

  • 系统设计需要考虑任务的实时响应指标和运行时间
  • 任务的实时响应指标是指要求任务在一定时间内完成对特定事件的响应
  • 任务的运行时间是指任务执行所需的时间

3. 任务 Ta 对事件 A 的响应示例

  • 任务 Ta 要求在 10ms 内实时响应事件 A
  • 任务 Ta 的最大运行时间是 1ms
  • 因此,任务 Ta 的周期为 10ms,运行时间为 1ms
  • 任务 Ta 在 10ms 内完成对事件 A 的响应即可

4. 同时存在的其他任务 Tb 和 Tc 的影响

  • 存在以 50ms 为周期的任务 Tb,每次运行最大时间长度为 100us
  • 即使将任务 Tb 的优先级提高,对系统的实时性指标没有影响
  • 因为即使在任务 Ta 的运行过程中,任务 Tb 抢占了资源,等 Tb 执行完毕,消耗的时间仍然在事件 A 规定的响应时间内(10ms)
  • 可以确保任务 Ta 安全完成对事件 A 的响应

5. 任务 Tc 的影响

  • 存在任务 Tc,运行时间为 20ms
  • 如果将任务 Tc 的优先级设置高于任务 Ta
  • 在任务 Ta 运行时,被突然打断执行任务 Tc
  • 等 Tc 执行完毕,任务 Ta 已经错过对事件 A(10ms)的响应
  • 这是不允许的,因此需要考虑任务的时间,并将处理时间更短的任务设置更高的优先级

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

牡丹宣言:七种皮肤类型|教你如何区分和保姆级护肤大法

经常听到有人说&#xff0c;我的皮肤T区油&#xff0c;脸颊干&#xff0c;应该是混合型皮肤吧 正常的皮肤根据皮脂腺分泌油脂量的多少可分为&#xff1a;中性&#xff0c;干性&#xff0c;油性&#xff0c;混合性。 接下来小编就帮大家细化整理了七种不同的皮肤类型&#xff0c…

Nginx详解 第一部分:编译安装Nginx+Nginx模块

Part 1 一 、HTTP 和 Nginx1.1 套接字Socket1.2 URL1.2.1 定义1.2.1 URL和URN的区别1.2.3 URL组成 1.3 请求访问完整过程详解 二、I/O模型 处理高并发的时候用2.1 I/O模型简介2.2 多路复用I/O型2.3 异步I/O模型2.4 事件模型 select poll epoll 三、NGINX概述3.1 简介3.2 NGINX和…

【Java并发】聊聊对象内存布局和syn锁升级过程

对象存储解析&#xff1a;一个空Object对象到底占据多少内存&#xff1f; 对象内存布局 Mark Word占用8字节&#xff0c;类型指针占用8个字节&#xff0c;对象头占用16个字节。 好了&#xff0c;我们来看一下一个Object对占用多少空间&#xff0c; 因为java默认是开启压缩…

帆软只是一个BI厂商?答案是“No”!

大数据产业创新服务媒体 ——聚焦数据 改变商业 2023年&#xff0c;8月17-19日&#xff0c;帆软智数大会落子花城广州&#xff0c;邀请了1200海内外CIO和数字化专家&#xff0c;共同探讨数字化转型新机遇。 值得关注的是&#xff0c;这也是帆软首次以BI和零代码双赛道第一的身…

django开发流程

设计model django采用ORM映射&#xff0c;可以在代码中描述数据库的布局 只需要导入from django.db import models 并使类继承models.Model&#xff0c;models中的一个类对应数据库中的一个表&#xff0c;类的变量对应表字段。 创建数据库 $ python manage.py makemigration…

Ubuntu【系统环境下】【编译安装OpenCV】【C++调用系统opencv库】

Ubuntu【系统环境下】【编译安装OpenCV】【C调用系统opencv库】 前言&#xff1a; 本人需要用C写代码&#xff0c;调用OpenCV库&#xff0c;且要求OpenCV版本号大于4.1.0 由于使用的是18.04的版本&#xff0c;所以apt安装OpenCV的版本始终是3.2.0&#xff0c;非常拉胯&#…

python爬虫的js逆向入门到进阶教程文章分享汇总~持续更新

目录 一、内容介绍二 、专栏内容-持续更新1、JS逆向入门2、Js逆向进阶3、爬虫基础知识4、工具与安装5、漫星内容分享 三、星球使用四、b站up主视频推荐 一、内容介绍 二 、专栏内容-持续更新 1、JS逆向入门 2023-08-25》11.常见加密>xx音乐RSA加密 https://articles.zsxq.c…

电视乱收费致200元电视也无人买,广电总局出手了,用户拍手欢迎

各个互联网企业盯着电视用户&#xff0c;将用户当韭菜&#xff0c;收费太狠&#xff0c;导致国内市场的电视销量暴跌&#xff0c;消费者怨声载道&#xff0c;日前国家广播电视台联合其他部门开展专项整治&#xff0c;狠杀这类乱收费的乱象&#xff0c;或许将有望挽回用户。 近几…

报错处理:MySQL数据库连接超时

具体报错&#xff1a; ERROR 2002 (HY000): Cant connect to local MySQL server through socket /var/run/mysqld/mysqld.sock (2) 报错环境&#xff1a;该报错一般发生在Linux服务器上运行MySQL数据库时&#xff0c;尝试连接MySQL时出现连接超时的情况。 排错思路&#xff1a…

VIOOVI分享:什么是动作分析?动作分析的方法有哪些?

动作分析是由吉尔布雷斯夫妇始创的&#xff0c;是根据操作者实施的动作顺序观察动作&#xff0c;用特定的标记记录以手和眼睛为中心的人体各部位的动作内容&#xff0c;掌握实际情况&#xff0c;并将上述记录制成图表的一套分析方法&#xff0c;在此基础上判断动作质量&#xf…

Docker容器学习:Dockerfile制作Web应用系统nginx镜像

目录 编写Dockerfile 1.文件内容需求&#xff1a; 2.编写Dockerfile&#xff1a; 3.开始构建镜像 4.现在我们运行一个容器&#xff0c;查看我们的网页是否可访问 推送镜像到私有仓库 1.把要上传的镜像打上合适的标签 2.登录harbor仓库 3.上传镜像 编写Dockerfile 1.文…

[机缘参悟-102] :IT人 - 管理的本质?管理人与从事技术的本质区别?人性、冰山模型、需求层次模型

感悟&#xff1a; 管理的本质是&#xff1a;学习各种管理理论、方法、技能&#xff0c;克服自身的人性缺点、预防他人人性的恶点、利用他人的人性特点拿到结果&#xff0c;从而完成组织、管理者的上司、管理者自身、管理者下属的目标。管理中的问题&#xff0c;80%以上都人性问…

基于实例的学习方法

基于实例的学习方法 动机基本概念基于实例的学习基于实例的概念表示 1. 最近邻最近邻的例子理论结果最近邻&#xff08;1- NN&#xff09;:解释问题 K-近邻(KNN)KNN讨论1 &#xff1a;距离度量KNN 讨论2&#xff1a;属性KNN:属性归一化KNN:属性加权 KNN讨论3:连续取值目标函数K…

数据结构(Java实现)LinkedList与链表(上)

链表 逻辑结构 无头单向非循环链表&#xff1a;结构简单&#xff0c;一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构&#xff0c;如哈希桶、图的邻接表等等。 无头双向链表&#xff1a;在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。 链表的…

Stylet框架

Stylet框架 编辑时间:2023/8/25 1.Stylet简介 Stylet是一个小巧但功能强大的MVVM框架&#xff0c;灵感来自Caliburn.Micro。其目的是进一步降低复杂性和魔力&#xff08;译者注&#xff1a;Caliburn.Micro有很多让人抓狂的约定&#xff0c;看起来像魔法&#xff0c;这对新手…

项目进度管理(4-2)关键链法和关键路径法的区别和联系

1 关键链法和关键路径法的主要区别 1.1 关键链法和关键路径法的关注焦点不同 关键路径法&#xff08;CPM&#xff09;&#xff1a;关注项目中最长的路径&#xff0c;也就是所需时间最长的路径&#xff0c;这被称为关键路径。关键路径决定了项目的最早完成时间。关键链法&…

Jetbrains IDE新UI设置前进/后退导航键

背景 2023年6月&#xff0c;Jetbrains在新发布的IDE&#xff08;Idea、PyCharm等&#xff09;中开放了新UI选项&#xff0c;我们勾选后重启IDE&#xff0c;便可以使用这一魔性的UI界面了。 但是前进/后退这对常用的导航键却找不到了&#xff0c;以前的设置方式&#xff08;Vi…

【2022年电赛】有人开摆,有人跑路,有人5秒不识数

前言&#xff1a;该作品是2022年四川省电子设计竞赛一等奖作品&#xff0c;其能稳定完成全部四个问题&#xff0c;但存在停车距离的精度问题。该文章将会介绍该作品的整体设计思路&#xff0c;关键控制算法等技术相关问题&#xff0c;也会给出工程的下载链接。同时本人参加过20…

考研408 | 【操作系统】 内存管理

内存的基础 内存和内存的作用&#xff1a; 几个常用的数量单位&#xff1a; 指令的工作原理&#xff1a; 问题&#xff1a;如何将指令中的逻辑地址转换为物理地址&#xff1f; 解决办法&#xff1a;装入的三种方式 1.绝对装入 2.可重定位装入 3.动态重定位 从写程序到程…