(定时器用于计时和触发事件,任务则由调度器进行调度和执行:每当时钟节拍到达时,系统会触发一个称为 tick 中断的事件。当 tick 中断发生时,操作系统会在中断服务例程中执行一定的处理,其中包括更新任务的运行时间、检查任务的状态和优先级等。然后,调度器会在中断返回后根据任务的优先级和调度策略,从就绪列表中选择下一个要执行的任务。FreeRTOS 不直接提供像 OSEK 调度表(Schedule Table)那样的概念和功能。OSEK 调度表是一种基于时间的调度机制,按照预定义的时间顺序在到期点执行不同的任务,在调度表中,是可以做到在每个EP到期点执行各种优先级任务,只要在下个EP到期点抵达之前执行完就可以)
RTOS的任务触发底层逻辑
从整体来看,首先FreeRTOS的任务执行是基于全抢占调度的,也就是说任务按照就绪列表中优先级高低执行的,如果一个任务优先级很高,没有其他同级别任务,并且任务也没有阻塞,挂起或延时,那这个高优先级任务就会一直执行,其他的任务就会完全失效。所以如果设计一个合理的系统,就必须合理安排他的优先级,时间片以及判断高中底优先级任务的执行时间,在合理的时间里,释放CPU的资源,让低优先级任务进入执行。比如高优先级任务执行后,在这个任务的结尾用vTaskDelay 来暂时释放 CPU 资源。之后中等优先级会抢占资源执行任务,之后在这个任务的结尾用vTaskDelay 来暂时释放 CPU 资源。低优先级任务抢占执行。vTaskDelay的时间要设计的合理,否则任务就进入空闲任务或低优先级任务没有执行完就被高优先级任务抢占。
如何触发各个优先级任务以及RTOS调度任务逻辑
在 FreeRTOS 中 vTaskStartScheduler 用于启动 FreeRTOS 调度器,并且执行第一个任务,在 main 函数中 xTaskCreate 创建任务,并将创建的任务添加到双向链表中,在双向链表中,当一个高优先级任务执行完释放了CPU的资源后,就切换到链表中的下一个任务。
(猜想:从RTOS的双向链表可以猜测OSEK的调度表机制,也就是为什么调度表可以周期往复的执行,以及调度表为什么每到一个EP到期点就可以执行下一个任务。当最后一个任务执行完,返回到第一个任务,这就是OSEK中任务会依次周期触发逻辑)
在 main 函数中 xTaskCreate 创建任务,并分配任务的优先级。创建的任务放在就绪任务列表中,就绪任务列表根据任务的优先级进行排序(其实就是双向链表套了个任务优先级排序插入的逻辑以及数据结构增加了些要判断的内容)。
FreeRTOS的任务调度是基于优先级和时间片轮转的机制:
- 基于优先级的任务调度:创建的任务根据优先级的高低插入到就绪列表中,之后调度器根据任务的优先级来执行任务。如果遇到相同优先级任务,调度器就采用先到先服务(First-Come, First-Served)的策略,即按照任务在就绪列表中的顺序进行选择。
- 基于时间片轮转的任务调度:为了解决在遇到相同优先级多个任务的情况,采用时间片轮转调度通过每个任务轮流地享有相同的 CPU 时间(享有 CPU 的时间叫时间片), 解决具有相同优先级的多个任务之间的公平性问题。每个任务会按照设定的时间片长度轮流地享有相同的 CPU 时间。确保每个任务都能获得一定的执行时间,而不会长时间独占处理器资源。
FreeRTOS的任务调度顺序是基于就绪列表的高低优先级,那高优先级任务执行完要如何切到低优先级任务里,或者说岂不是系统基本主要都在执行高优先级任务,低优先级任务只有在特殊情况下才会被触发:
任务在系统中的状态:
首先高优先级任务确实要更频繁的去执行,所以在一个实时系统中,主要的几个任务要被配置成很高的优先级,基本处于一直run的状态。与此同时,时间片轮转调度就派上了用场,同级别的高优先级太多,就要用到时间片来划分执行时间。要注意时间片轮转调度的任务切换,时间片用完的情况下会从就绪列表中获取优先级最高的任务去执行。
其次,高优先级任务想切换到低优先级任务,那么高优先级任务一定是由run的状态,切到了挂起Suspend或者阻塞Block状态。或者主动切换任务taskYIELD或者用延时函数 vTaskDelay 来暂时释放 CPU 资源,vTaskDelay 可以使较低优先级的任务有机会得到执行。但请注意,使用延时函数可能导致较高优先级任务的执行时间不确定,因为它们会暂时停止执行。vTaskDelay函数可以使低优先级任务在执行一段时间后暂时让出CPU,等待指定的延迟时间后再次被调度执行。但是,一旦高优先级任务变为可运行状态并具备运行条件,它可以随时抢占低优先级任务,并开始执行。
RTOS的任务触发场景描述和分析
场景描述:智能车控制系统
- 高优先级任务:
- 紧急安全任务:例如紧急制动、避障等任务。这些任务需要立即执行以确保车辆和乘客的安全。
- 实时任务:例如车轮控制、电机控制等任务,需要及时响应和执行,确保车辆的平稳运行和精确控制。
- 中优先级任务:
- 传感器读取任务:例如读取距离传感器、摄像头等任务。这些任务提供车辆周围环境的信息,需要及时获取,但相对于实时控制任务来说,可以略有延迟。
- 数据处理任务:例如图像处理、数据滤波等任务。这些任务对传感器数据进行处理和分析,为决策任务提供支持。
- 低优先级任务:
- 决策任务:例如路径规划、目标识别等任务。这些任务基于传感器数据和环境信息做出决策,但相对于实时控制任务来说,可以有一定的延迟。
- 用户界面任务:例如与用户的交互界面、显示信息等任务。这些任务对于整个系统的实时响应性要求相对较低。
系统分析:
- 高优先级任务:
- 当智能车系统启动时,实时控制任务会立即开始执行,控制车辆的转向和速度。紧急制动任务则处于阻塞状态,等待紧急情况的触发。
- 这些任务应具有最高的执行优先级,能够立即抢占其他任务执行。
- 电机控制任务通常具有较高的优先级,因为它直接影响到物理设备(电机)的动作。该任务负责监测和控制电机的状态,确保电机按照预期速度或位置进行运转。该任务需要及时响应,并且通常需要以较高的采样率进行控制,以保证电机的稳定性和精确性。
- 中优先级任务:
- 传感器读取任务和数据处理任务以较低的频率定时执行。在 FreeRTOS 中,任务切换的频率非常高,通常以微秒级或毫秒级为单位。这意味着任务的执行时间很短,任务切换速度很快。当高优先级任务(如电机控制任务)一直运行时,RTOS 调度器会以非常短的时间间隔切换到中优先级任务(如摄像头数据采集和数据处理任务),并执行它们的代码。然后,调度器会再次迅速切换回高优先级任务,恢复电机控制任务的执行。
- camera数据采集放到这里也可以放在低优先级,如果系统对相机采集的图像数据有较高的实时性要求,例如需要快速检测和响应变化的场景或对象,那么将相机图像采集任务设置为中优先级可能更合适。相机图像采集任务如果需要大量的计算资源来处理和分析图像数据,例如进行目标检测、图像识别等算法。如果系统的计算资源有限,将相机图像采集任务设置为低优先级可以确保其他任务(如电机控制和决策任务)能够获得足够的计算资源和响应时间。如果决策任务依赖于相机图像采集任务的输出数据进行分析和决策,那么将相机图像采集任务设置为中优先级可能更合适。
- 在智能车控制中PID算法应该算在这个层面。根据电机的反馈信号进行实时的控制决策。PID算法根据设定的目标值和当前反馈值计算出控制输出,以调整电机的速度或位置。该任务与电机控制任务密切相关,因此通常被划分为中优先级任务。PID算法的执行周期通常较快,但相对于电机控制任务可能有一定的延迟。
- 低优先级任务:
- 决策任务可能涉及识别和避免障碍物的策略。该任务可能需要使用传感器数据来检测障碍物,并根据障碍物的位置、大小和运动状态等信息,决定如何安全地避开障碍物。
- 决策任务通常需要对相机采集的数据进行处理和分析,这可能涉及复杂的计算和算法。如果将决策任务设置为高优先级,它将占用大量的计算资源和时间片,可能导致电机控制任务得不到足够的执行时间,影响系统的实时性。因此,将决策任务放到低优先级可以确保电机控制任务得到及时的执行,保证了系统对电机的实时控制。
- 决策任务的执行可能是基于一系列的检测和分析结果,可能需要多次采样和处理。如果决策任务设置为高优先级并立即反馈给电机,可能会导致频繁的控制信号变化,从而造成系统的震荡和不稳定。将决策任务放到低优先级,可以允许一定的延迟和平滑控制信号的变化,提高系统的稳定性和抗干扰能力。
闭环分析:
- 高优先级任务,车辆电机的主动控制。
- 中优先级任务,camera的数据采集和PID算法电机控制。
- 低优先级任务,对于camera采集的数据进行数据处理和分析,之后将分析结果反馈给高优先级任务电机控制。
滴答定时器如何触发任务的执行和任务的切换
OS基于滴答定时器通过中断触发任务的执行:
在启动调度器的过程中,会配置 SysTick 定时器的加载值 (Load Value) 和使能定时器,在 vPortSetupTimerInterrupt 中将这些配置定义到硬件中。(FreeRTOS中没有专门的软件定时器用于设置时间片并触发任务)
当 SysTick 定时器的计数器减为零时,会触发 SysTick 中断。SysTick 定时器的计数器会从加载值递减,当减到零时会触发中断。
中断的触发是通过硬件来执行的,根据不同的芯片配置启动文件,在启动文件中,定义好中断向量表,根据上面的配置,就会定时触发定时器中断,
当 SysTick 中断触发时,处理器会跳转到预定义的 SysTick_Handler 函数执行相应的处理,进入到 xTaskIncrementTick 函数,它是在任务调度器中的关键函数之一,负责管理系统时间和任务调度。
xTaskIncrementTick 函数的作用:
- 增加系统时基计数:xTaskIncrementTick() 函数会递增 FreeRTOS 内核的系统时基计数器。在内核中存在一个系统时基计数器(System Tick Counter)和一个滴答定时器(Tick Timer)。滴答时间是滴答定时器的触发间隔,通常以固定的时间间隔产生中断。当滴答定时器的计数达到滴答时间时,会触发一个中断。每当滴答定时器中断发生时,在中断处理函数中会递增系统时基计数器的值。递增的步长通常是1,表示一个滴答时间间隔。
- 执行任务调度:增加系统时基计数器后,函数会检查是否有更高优先级的任务需要被调度。如果有,它会选择一个优先级最高的任务来执行,并且切换到该任务的上下文环境。
- 处理延时和阻塞操作:函数会检查所有正在延时或阻塞的任务,并根据任务的延时时间或等待条件来决定是否需要唤醒这些任务。
通过滴答时钟触发滴答中断,对调度器的进一步思考
调度器并不是一个函数执行很多功能,而是很多函数,很多功能组成的调度器。调度器的核心函数有 vTaskStartScheduler() 启动调度器。vTaskEndScheduler() 终止调度器。vTaskSuspend() 挂起任务。vTaskResume() 恢复任务。vTaskDelay() 延迟任务。vTaskDelete() 删除任务。taskYIELD()任务切换。xTaskCreate() 创建任务等等。
对任务的切换和任务执行的思考
对于高优先级任务,那种需要一直执行的,需要在TASK里加入while(1)循环执行。所以在设计的智能车的电机控制任务中要加入while(1)循环执行,除非等到了某些事件触发了挂起或阻塞任务或 vTaskDelay 才交出CPU的控制权给低优先级,vTaskDelay 之后高优先级任务重回控制权,循环在while里。
对于任务的切换,当高优先级任务在while(1)的循环中时,在while的循环内部加入一个 vTaskDelay,暂时释放CPU,所谓的释放CPU确实就是先将调度器挂起,将高优先级任务放到延时列表中,之后在恢复调度器的执行。在调度器恢复执行后,再从就绪列表中找到下一个优先级的任务去执行,调用的 portYIELD 去切换任务。
vTaskDelay输入的参数是需要延时的系统节拍数,它使用的是 FreeRTOS 内部的时钟节拍来进行延时,不直接使用系统时钟频率 SystemCoreClock。假如我想延时500ms,就直接输入vTaskDelay(500);
xTaskIncrementTick 在被触发的时候,会判断vTaskDelay定义的延时节拍,如果时间到了后,会将在延时列表中的任务进行唤醒。延时列表是按照延时时间排列,第一个任务就是最先需要被唤醒的任务。之后高优先级任务重回掌控权。
基于上面的智能车控制系统的闭环分析,高优先级任务,车辆电机的主动控制。中优先级任务,camera的数据采集和PID算法电机控制。低优先级任务,对于camera采集的数据进行数据处理和分析。我要如何分配这个系统中这三个任务的执行时间。对于车辆电机的主动控制是最高的优先级,在整个系统的运行中,我可以每次执行电机控制后用vTaskDelay释放一会时间,切换到中优先级任务camera的数据采集和PID算法电机控制,预估这两个中优先级任务要执行多久,之后用vTaskDelay释放一会时间,切换到低优先级任务对于camera采集的数据进行数据处理和分析。中优先级任务vTaskDelay的时间是低优先级任务判断的时间,高优先级任务vTaskDelay的时间是中优先级和低优先级任务的时间。
时间片轮转调度机制对系统的影响
FreeRTOS 中的时间片轮转调度机制主要用于解决相同优先级任务之间的公平调度,确保每个任务轮流地享有相同的 CPU 时间。在函数 taskSELECT_HIGHEST_PRIORITY_TASK 中执行 listGET_OWNER_OF_NEXT_ENTRY 时间片轮转调度任务的功能。
时间片的大小是由 configTICK_RATE_HZ 的值来控制,如果将 configTICK_RATE_HZ 设置为 1000,表示每秒钟有 1000 个时钟节拍,则默认情况下时间片的大小就是 1 毫秒,所以时间轮转调度是每个时间片切一个任务,也就是说每毫秒执行一个任务,如果有3个相同优先级的任务在3毫秒内将会挨个执行一遍,但是每个时间片基本都不会执行完任务,每个时间片过后,先将任务挂起,将寄存器或数据保存到堆栈上,之后切到下一个任务,等到又拿到时间片后,恢复堆栈上的寄存器和数据,继续执行上一次没执行完的内容。比如任务在while(1)循环里。
时间片轮转调度机制其实最好的应用法是对于某些任务,需要等待某个事件,或者需要等待什么资源的时候,为了不白白空等,就先去执行其他的任务,等到其他任务执行完了,我这个任务也等到了想要的资源。比如camera的数据采集和数据处理与分析,完全可以在同一个优先级,也就是数据该采集采集,等camera数据采集完了我就执行数据分析,之后camera依旧在不同的时间片进行数据采集,采集完的数据在不同的时间片进行分析。两不耽误。