FreeRTOS重点(持续更新)

1、任务的三要素:任务主题函数、任务栈、任务控制块。通过静态任务创建函数vTaskCreateStatic()函数将其联合在一起。

 

2、使用动态内存的时候,不用跟使用静态内存那样要预先定义好一个全局的静态的任务控制块空间。先定义好一个全局的静态栈空间,动态内存就是按需分配内存,随取随用。且任务控制块是在任务创建的时候分配内存空间创建,任务创建函数会返回一个任务控制块指针,用于指向任务控制块,所以要预先为任务栈定义一个任务控制块指针,也就是任务句柄。

3、任务句柄:任务句柄是一个指针,用于指向一个任务,当任务创建好了之后,它就具有了一个任务句柄,以后我们想操作这个任务的时候都需要通过这个任务句柄。

4、使用静态内存时,使用xTaskCreateStatic()来创建一个任务,而使用动态内存时,则使用xTaskCreate()这个函数来创建一个任务。

5、在FreeRtos中,数值越大的优先级越高,0代表最低优先级。

6、什么是临界段?

临界段就是一段在执行的时候不能被中断的代码段。

7、每个FreeRTOS任务都需要有自己的栈空间,当任务切出时,它的执行环境(寄存器值、堆栈内容)会保存在该任务的栈中,这样当任务再次运行时,就能从堆栈中正确的恢复上次的运行环境。

8、什么是时间片?

所谓时间片就是同一优先级下可以有多个任务,每个任务轮流地享有相同的CPU时间,享有CPU的时间称为时间片。

9、FreeRTOS内核中两种寻找最高优先级任务的方法。

(1)通用法:在就绪链表中查找uxTopPriority,再通过uxTopPriority获得对应的任务控制块。

(2)特殊法(STM32使用):利用计算前导零指令CLZ,直接在uReadyPriority这个32位的变量中直接得出uxTopPriority。

注:前导零指令CLZ:_clz指令返回操作数二进制编码中第一个1前0的个数。如果操作数为0,则指令返回32;如果操作数二进制编码第31位为1,指令返回0。

10、FreeRTOS中相同优先级的任务采用时间片轮转方式进行调度,也就是常说的分时调度器。但时间片轮转调度尽在当前系统之无更高优先级就绪任务存在的情况下才有效。

11、任务状态迁移图

 图 16-1(1):创建任务→就绪态(Ready):任务创建完成后进入就绪态,表明任务已 准备就绪,随时可以运行,只等待调度器进行调度。
图 16-1(2):就绪态→运行态(Running):发生任务切换时,就绪列表中最高优先级 的任务被执行,从而进入运行态。
图 16-1(3):运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度, 此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态, 依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原来的任务(此处可以看做 是 CPU 使用权被更高优先级的任务抢占了)。
图 16-1(4):运行态→阻塞态(Blocked):正在运行的任务发生阻塞(挂起、延时、 读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
图 16-1(5):阻塞态→就绪态:阻塞的任务被恢复后(任务恢复、延时时间超时、读 信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成 就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换, 将该任务将再次转换任务状态,由就绪态变成运行态。
图 16-1(6) (7) (8):就绪态、阻塞态、运行态→挂起态(Suspended):任务可以通 过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到 CPU 的使用权,也不会参与调度,除非它从挂起态中解除。
图 16-1(9):挂起态→就绪态:把 一 个 挂 起 状态 的 任 务 恢复的 唯 一 途 径 就 是 调 用 vTaskResume() 或 vTaskResumeFromISR() API 函数,如果此时被恢复任务的优先级高 于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态 变成运行态。

12、如何区分挂起态阻塞态

当任务有较长的时间不允许运行的时候,我们可以挂起任务,这样子调度器就不会管这个任务的任何信息,直到调用回复任务的API函数;而当任务处于阻塞态的时候,系统还需要判断阻塞态的任务是否超时,是否可以消除阻塞。

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

**************************************************重点*******************************************************

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

13、FreeRTOS中常用的任务函数

(1)vTaskSuspend()任务挂起函数:任务可以通过调用 vTaskSuspend()函数都可以将处于任何状态的任务挂起,被挂起的任务得不到 CPU 的使用权,也不会参与调度,它相对于调度器而言是不可见的,除非它从挂起态中解除。

(2)vTaskSuspendAll():将所有的任务都挂起,说白了挂起所有任务就 是挂起任务调度器。调度器被挂起后则不能进行上下文切换,但是中断还是使能的。调度器恢复可以调 xTaskResumeAll() 函数,调用了多少次的vTaskSuspendAll() 就要调用多少次 xTaskResumeAll()进行恢复。

(3)vTaskResume()任务恢复函数:任务恢复就是让挂起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂起时的状态继续运行。如果被恢复任务在所有就绪态任务中,处于最高优先级列表的第一位,那么系统将进行任务上下文的切换。vTaskResume()函数用于恢复挂起的任务。无论任务在挂起时候调用过多少次这个 vTaskSuspend()函数,也只需调用一次 vTaskResume ()函数即可将任务恢复运行,当然,无论调用多少次的 vTaskResume()函数,也只在任务是挂起态的时候才进行恢复。

(4)TaskResumeAll():当调用了 vTaskSuspendAll()函数将调度器挂起,想要恢复调度器的时候我们就需要调用 xTaskResumeAll()函数。xTaskResumeAll 函数的使用方法很简单,但是要注意,调用了多少次 vTaskSuspendAll()函数就必须同样调用多少次 xTaskResumeAll()函数.

(5)xTaskResumeFromISR():xTaskResumeFromISR()与 vTaskResume()一样都是用于恢复被挂起的任务,不一样的是xTaskResumeFromISR()专门用在中断服务程序中。无论通过调用一次或多次vTaskSuspend()函数而被挂起的任务,也只需调用一次xTaskResumeFromISR()函数即可解 挂。要想使用该函数必须在FreeRTOSConfig.h中把INCLUDE_vTaskSuspend和 INCLUDE_vTaskResumeFromISR 都定义为 1 才有效。任务还没有处于挂起态的时候,调用 xTaskResumeFromISR()函数是没有任何意义的。

(6)vTaskDelete()任务删除函数:vTaskDelete()用于删除一个任务。当一个任务删除另外一个任务时,形参为要删除任务创建时返回的任务句柄,如果是删除自身,则形参为 NULL。 要想使用该函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelete 定义为 1,删除的任务将从所有就绪,阻塞,挂起和事件列表中删除。

(7)vTaskDelay()任务延时函数(它是相对延时,其他任务或中断会影响其定时时间):vTaskDelay()在我们任务中用得非常之多,每个任务都必须是死循环,并且是必须要有阻塞的情况,否则低优先级的任务就无法被运行了。要想使用 FreeRTOS 中的vTaskDelay() 函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定义为 1 来使能。vTaskDelay()用于阻塞延时,调用该函数后,任务将进入阻塞状态,进入阻塞态的任务将让出 CPU 资源。延时的时长由形参 xTicksToDelay 决定,单位为系统节拍周期,比如系统的时钟节拍周期为1ms,那么调用vTaskDelay(1)的延时时间则为1ms。vTaskDelay()延时是相对性的延时,它指定的延时时间是从调用 vTaskDelay()结束后开始计算的,经过指定的时间后延时结束。

(8)vTaskDelayUntil()绝对延时):这个绝对延时常用于较精确的周期运行任务,比如我有一个任务,希望它以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的,而不是相对的,适用于周期性执行的任务。

从图 16-2、图 16-3 与图 16-4 可以看出无论是溢出还是没有溢出,都要求在下次唤醒任务之前,当前任务主体代码必须被执行完。也就是说任务执行的时间必须小于任务周期时间 xTimeIncrement,总不能存在任务周期为 10ms 的任务,其主体代码执行时间为 20ms,这样子根本执行不完任务主体代码。计算的唤醒时间合法后,就将当前任务加入延时列表,同样延时列表也有两个。每次产生系统节拍中断,都会检查这两个延时列表,查看延时的任务是否到期,如果时间到,则将任务从延时列表中删除,重新加入就绪列表,任务从阻塞态变成就绪态,如果此时的任务优先级是最高的,则会触发一次上下文切换。

14、空闲任务可以通过钩子完成一些功能函数,如指示灯等。同时空闲任务是唯一一个不允许出现阻塞情况的任务。

15、队列又称消息队列,是一种常用于任务间通信的数据结构。消息队列是一种异步的通信方式。

16、当发送紧急消息时,发送的位置是消息队列的对头而不是队尾。而发送消息时freertos会将消息拷贝到消息队列队尾。

17、消息队列可应用于发送不定长消息的场合,包括任务与任务间的消息交换,队列是FreeRTOS 主要的任务间通讯方式,可以在任务与任务间、中断和任务间传送信息,发送到队列的消息是通过拷贝方式实现的,这意味着队列存储的数据是原数据,而不是原数据的引用。

18、消息队列常用函数

(1)xQueueCreate()消息队列创建函数:用于创建一个新的队列并返回可用于访问这个队列的队列句柄。队列句 柄其实就是一个指向队列数据结构类型的指针。

(2)xQueueCreateStatic()消息队列静态创建函数:用于创建一个新的队列并返回可用于访问这个队列的队列句柄。 队列句柄其实就是一个指向队列数据结构类型的指针。

(3)vQueueDelete()消息队列删除函数:队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都 会被系统回收清空,而且不能再次使用这个消息队列了,但是需要注意的是,如果某个消 息队列没有被创建,那也是无法被删除的。xQueue 是 vQueueDelete()函数的形参,是消息队列句柄,表示的是要删除哪个想队列。

19、向消息队列发送消息函数

任务或者中断服务程序都可以给消息队列发送消息。消息队列发送函数有好几个,都是使用宏定义进行展开的,有些只能在任务调用, 有些只能在中断中调用。

(1)xQueueSend()与 xQueueSendToBack():xQueueSend()是一个宏,宏展开是调用函数 xQueueGenericSend()。该 宏 是 为 了 向 后 兼 容 没 有 包 含 xQueueSendToFront() 和 xQueueSendToBack() 这两个宏的FreeRTOS 版本。xQueueSend() 等同于 xQueueSendToBack()。xQueueSend()用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,而不是以 引用的形式。该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护功能的 xQueueSendFromISR()来代替。、

(2)xQueueSendFromISR()与 xQueueSendToBackFromISR():xQueueSendFromISR()是一个宏,宏展开是调用函数 xQueueGenericSendFromISR()。该 宏是 xQueueSend()的中断保护版本,用于在中断服务程序中向队列尾部发送一个队列消息, 等价于 xQueueSendToBackFromISR()。

(3):xQueueSendToFront():xQueueSendToFron() 是 一 个 宏 , 宏 展 开 也 是 调 用 函 数 xQueueGenericSend() 。 xQueueSendToFront()用于向队列队首发送一个消息。该函数绝不能在中断服务程序里面被调用,而是必须使用带有中断保护功能的 xQueueSendToFrontFromISR ()来代替。

(4)xQueueSendToFrontFromISR():xQueueSendToFrontFromISR() 是 一 个 宏 , 宏 展 开 是 调 用 函 数 xQueueGenericSendFromISR()。该宏是 xQueueSendToFront()的中断保护版本,用于在中断 服务程序中向消息队列队首发送一个消息。

(5)xQueueGenericSend()通用消息队列发送函数(任务):上面看到的那些在任务中发送消息的函数都是 xQueueGenericSend()展开的宏定义,真 正起作用的就是 xQueueGenericSend()函数,根据指定的参数不一样,发送消息的结果就不 一样。

(6)xQueueGenericSendFromISR()消息队列发送函数(中断):既然有任务中发送消息的函数,当然也需要有在中断中发送消息函数,其实这个函数 跟 xQueueGenericSend() 函数很像,只不过是 执行的上下文环境是不一样的, xQueueGenericSendFromISR()函数只能用于中断中执行,是不带阻塞机制的。

20、消息队列使用注意事项

(1)使用 xQueueSend()、xQueueSendFromISR()、xQueueReceive()等这些函数之前应先 创建需消息队列,并根据队列句柄进行操作。

(2)队列读取采用的是先进先出(FIFO)模式,会先读取先存储在队列中的数据。当 然也 FreeRTOS 也支持后进先出(LIFO)模式,那么读取的时候就会读取到后进 队列的数据。

(3)在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数 据区域大小不小于消息大小,否则,很可能引发地址非法的错误。

(4)无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将 消息的地址作为消息进行发送、接收。

(5)队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同 一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读 出倒是用的比较少。

21、信号量:信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资 源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。抽象的来讲,信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它 当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态。 通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。

22、二值信号量和互斥信号量的区别

互斥量有优先级继承机制,二值信号量则没有这个机制。这使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于 临界资源的访问。

23、在 FreeRTOS 中我们用信号量用于同步,任务与任务的同步,中断与任务的同步,可以大大提高效率。可以将二值信号量看作只有一个消息的队列,因此这个队列只能为空或满(因此称为二 值),我们在运用的时候只需要知道队列中是否有消息即可,而无需关注消息是什么。

24、互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用于简单互锁,也就是保护临界资源。互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不 同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。任意时刻互斥量的状态只有两种,开锁或闭锁。互斥量就是用来降低优先级翻转的产生的危害。

25、用作互斥时,信号量创建后可用信号量个数应该是满的,任务在需要使用临界资源时,(临界资源是指任何时刻只能被一个任务访问的资源),先获取互斥信号量,使其变空, 这样其他任务需要使用临界资源时就会因为无法获取信号量而进入阻塞,从而保证了临界 资源的安全。

26、计数信号量可以用于资源管理,允许多个任务获取信号量访问共享资源,但会限制任 务的最大数目。访问的任务数达到可支持的最大数目时,会阻塞其他试图获取该信号量的 任务,直到有任务释放了信号量。

27、如果想要用于实现同步(任务之间或者任务与中断之间),二值信号量或许是更好的 选择,虽然互斥量也可以用于任务与任务、任务与中断的同步,但是互斥量更多的是用于 保护资源的互锁。

28、互斥量的优先级继承机制

优先级继承算法是指,暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该 资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。这个优先级继承机制确保高优先级任务进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”危 害降低到最小。

29、互斥量的作用

多任务环境下往往存在多个任务竞争同一临界资源的应用场景,互斥量可被用于对临界资源的保护从而实现独占式访问。另外,互斥量可以降低信号量存在的优先级翻转问题带来的影响。另外需要注意的是互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,在中断的上下文环境毫无意义。

30、不是递归的互斥量由函数 xSemaphoreCreateMutex() 或 xSemaphoreCreateMutexStatic()创建(我们只讲解动态创 建),且只能被同一个任务获取一次,如果同一个任务想再次获取则会失败。递归信号量 则相反,它可以被同一个任务获取很多次,获取多少次就需要释放多少次。递归信号量与 互斥量一样,都实现了优先级继承机制,可以降低优先级反转的危害。

31、如果互斥量有效,调用获取互斥量函数后结构体成员变量 uxMessageWaiting 会减 1, 然后将队列结构体成员指针 pxMutexHolder 指向任务控制块,表示这个互斥量被哪个任务持有,只有 这个任务才拥有互斥量的所有权 ,并且该任务的控制块结构体成员 uxMutexesHeld 会加 1,表示任务已经获取到互斥量。

32、FreeRTOS提供的事件具有如下特点:

(1)事件只与任务相关联,事件相互独立,一个 32 位的事件集合(EventBits_t 类型的变量,实际可用与表示事件的只有 24位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发 生),一共 24 种事件类型。

(2)事件仅用于同步,不提供数据传输功能。

(3)事件无排队性,即多次向任务设置同一事件(若任务还未来得及读走),等效于只设置一次。

(4)允许多个任务对同一事件进行读写操作。

(5)支持事件等待超时机制。

33、在 FreeRTOS 事件中,每个事件获取的时候,用户可以选择感兴趣的事件,并且选择 读取事件信息标记,它有三个属性,分别是逻辑与,逻辑或以及是否清除标记。

34、FreeRTOS提供的软件定时器的两种模式

(1)单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。

(2)周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除。

35、系统节拍配置默认是 1000。那么系统的时钟节拍周期就为 1ms(1s跳动 1000 下,每一下就为 1ms)。软件定时器的所定时数值必须是这个节拍周期的整数倍,例如节拍周期是 10ms,那么上层软件定时器定时数值只能是 10ms,20ms,100ms 等,而不能取值为 15ms。

36、FreeRTOS 中采用两个定时器列表维护软件定时器,pxCurrentTimerList 与 pxOverflowTimerList 是列表指针,在初始化的时候分别指向 xActiveTimerList1 与 xActiveTimerList2。

37、在嵌入式实时操作系统中,对内存的分配时间要求更为苛刻,分配内存的时间必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。而寻找这样一个空闲内存块所耗费 的时间是不确定的,因此对于实时系统来说,这就是不可接受的,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不 可确定。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值