UC/OS III操作系统学习笔记

UCOSIII是一个可裁剪、可剥夺的多任务实时操作系统,适用于单核CPU。本文详细介绍了UCOSIII的任务管理、中断和时间管理、信号量和互斥信号量、消息传递、事件标志组以及存储管理。它支持时间片轮转调度、中断延迟发布、优先级反转解决的互斥信号量,并提供了多种同步和通信机制,如信号量、事件标志组和消息队列。此外,UCOSIII还具备内建的统计任务和定时任务功能,以及灵活的内存分区和调度策略。
摘要由CSDN通过智能技术生成

UCOSIII 是一个可裁剪、可固化、可剥夺的多任务系统, 没有任务数目的限制。
在这里插入图片描述
在这里插入图片描述

1.UCOSIII任务

1.1任务管理

1.1.1 启动和初始化
在使用 UCOSIII 的时候我们要按照一定的顺序初始化并打开 UCOSIII,我们可以按照下面的顺序:
1、 最先肯定是要调用 OSInit()初始化 UCOSIII。
2、 创建任务,一般我们在 main()函数中只创建一个start_task 任务,其他任务都在 start_task任务中创建,在调用 OSTaskCreate()函数创建任务的时候一定要调用OS_CRITICAL_ENTER()函数进入临界区,任务创建完以后调用 OS_CRITICAL_EXIT()函数退出临界区。
3、 最后调用 OSStart()函数开启 UCOSIII。
在这里插入图片描述
我们在调用 OSStart()开启 UCOSIII 之前一定要至少创建一个任务,其实我们在调用OSInit()函数初始化UCOSIII 的时候已经创建了一个空闲任务。

1.1.2 任务状态
UCOSIII 支持的是单核 CPU,不支持多核 CPU,这样在某一时刻只有一个任务会获得 CPU 使用权进入运行态,其他的任务就会进入其他状态, UCOSIII 中的任务有多个状态。
在这里插入图片描述
1.1.3 任务控制块
数据结构:任务控制块 OS_TCB,用来保存任务的消息,使用 OSTaskCreate()函数来创建任务的时候就会给任务分配一个任务控制块(一个结构体)。
在这里插入图片描述
在这里插入图片描述
1.1.4 任务堆栈
任务堆栈用来在切换任务和调用其它函数的时候保存现场,因此每个任务都应该有自己的堆栈。
1、 定义一个 CPU_STK 变量,在 UCOSIII 中用 CPU_STK 数据类型来定义任务堆栈,CPU_STK 在 cpu.h 中有定义,其实CPU_STK 就是 CPU_INT32U,可以看出一个 CPU_STK 变量为 4 字节,因此任务的实际堆栈大小应该为我们定义的 4 倍。

#define TASK_STK_SIZE 64 //任务堆栈大小
CPU_STK TASK_STK[LED1_STK_SIZE]; //任务堆栈

2、我们使用 OSTaskCreat()函数创建任务的时候就可以把创建的堆栈传递给任务, 将堆栈的基地址传递给 OSTaskCreate()函数的参数p_stk_base,将堆栈深度传递给参数 stk_limit,堆栈深度通常为堆栈大小的十分之一,主要用来检测堆栈是否为空,将堆栈大小传递给参数 stk_size。
(上述创建任务代码中查看)

1.1.5 任务就绪表
UCOSIII 中将已经就绪的任务放到任务就绪表里,任务就绪表有两部分:优先级位映射表OSPrioTbl[]和就绪任务列表 OSRdyList[]。

  • 优先级位映射表
    在这里插入图片描述
    有关于优先级的操作有 3 个函数: OS_PrioGetHighest()、OS_PrioInsert()和OS_PrioRemove() 分别为获取就绪表中最高优先级任务、 将某个任务在就绪表中相对应的位置 1 和将某个任务在就绪表中相对应的位清零。

  • 就绪任务列表
    就绪任务列表 OSRdyList[]是用来记录每一个优先级下所有就绪的任务, OSRdyList[]在 os.h 中有定义,数组元素的类型为 OS_RDY_LIST, OS_RDY_LIST 为一个结构体。

struct os_rdy_list {
OS_TCB *HeadPtr; //用于创建链表,指向链表头
OS_TCB *TailPtr; //用于创建链表,指向链表尾
OS_OBJ_QTY NbrEntries; //此优先级下的任务数量
};

UCOSIII 支持时间片轮转调度,因此在一个优先级下会有多个任务, 那么我们就要对这些任务做一个管理, 这里使用 OSRdyList[]数组管理这些任务。 OSRdyList[]数组中的每个元素对应一个优先级,比如 OSRdyList[0]就用来管理优先级 0 下的所有任务。 OSRdyList[0]为OS_RDY_LIST 类型, 从上面 OS_RDY_LIST 结构体可以看到成员变量: HeadPtr 和 TailPtr 分别指向 OS_TCB, 我们知道 OS_TCB 是可以用来构造链表的,因此同一个优先级下的所有任务是通过链表来管理的, HeadPtr 和 TailPtr 分别指向这个链表的头和尾, NbrEntries 用来记录此优先级下的任务数量。
在这里插入图片描述
在这里插入图片描述
有些优先级只能有一个任务,比如 UCOSIII 自带的 5 个系统任务: 空闲任务 OS_IdleTask()、时钟节拍任务 OS_TickTask()、统计任务 OS_StatTask、定时任务 OS_TmrTask()和中断服务管理任务OS_IntQTask()。

1.1.6 任务调度和切换

  • 可剥夺型调度
    任务调度和切换就是让就绪表中优先级最高的任务获得 CPU 的使用权, UCOSIII 是可剥夺型, 抢占式的,可以抢了低优先级任务的 CPU 使用权, 任务的调度是由一个叫做任务调度器的东西来完成的,任务调度器有两种:一种是任务级调度器,一种是中断级调度器。

在 OSSched()中真正执行任务切换的是宏 OS_TASK_SW()(在 os_cpu.h 中定义),宏OS_TASK_SW()就是函数 OSCtxSW(), OSCtxSW()是 os_cpu_a.asm 中用汇编写的一段代码,OSCtxSW()要做的就是将当前任务的 CPU 寄存器的值保存在任务堆栈中,也就是保存现场,保存完当前任务的现场后将新任务的 OS_TCB 中保存的任务堆栈指针的值加载到 CPU 的堆栈指针寄存器中,最后还要从新任务的堆栈中恢复 CPU 寄存器的值。

在中断级调度器中真正完成任务切换的就是中断级任务切换函数 OSIntCtxSW(),与任务级切换函数 OSCtxSW()不同的是,由于进入中断的时候现场已经保存过了,所以 OSIntCtxSW()不需要像 OSCtxSW()一样先保存当前任务现场,只需要做 OSCtxSW()的后半部分工作,也就是从将要执行的任务堆栈中恢复 CPU 寄存器的值。

  • 时间片轮转调度
    在 UCOSIII 中允许一个任务运行一段时间(时间片)后让出 CPU 的使用权,让拥有同优先级的下一个任务运行,这种任务调度方法就是时间片轮转调度。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2 任务创建和删除、挂起和恢复

1.2.1 OSTaskCreate()函数
UCOSIII 是多任务系统,创建任务就是将任务控制块、任务堆栈、任务代码等联系在一起,并且初始化任务控制块的相应字段。 在 UCOSIII 中我们通过函数OSTaskCreate()来创建任务, OSTaskCreate()函数原型如下(在 os_task.c 中有定义)。 调用OSTaskCreat()创建一个任务以后,刚创建的任务就会进入就绪态,注意!不能在中断服务程序中调用 OSTaskCreat()函数来创建任务。

void OSTaskCreate( OS_TCB        * p_tcb,
					CPU_CHAR      * p_name,
					os_task_ptr     p_task,
					void          * p_arg,
					OS_PRIO         prio,
					CPU_STK       * p_stk_base,
					CPU_STK_SIZE    stk_limit,
					CPU_STK_SIZE     stk_size,
					OS_MSG_QTY      q_size,
					OS_TICK         time_quanta,
					void           * p_ext,
					OS_OPT           opt,
					OS_ERR         * p_err)

在这里插入图片描述
1.2.2 OSTaskDel()函数
OSTaskDel()函数用来删除任务,当一个任务不需要运行的话,我们就可以将其删除掉,删除任务不是说删除任务代码,而是 UCOSIII 不再管理这个任务, 在有些应用中我们只需要某个任务只运行一次,运行完成后就将其删除掉,比如外设初始化任务。
在这里插入图片描述
1.2.3 OSTaskSuspend()函数
有时候有些任务因为某些原因需要暂停运行,但是以后还要运行,因此我们就不能删除掉任务,这里我们可以使用OSTaskSuspend()函数挂起这个任务,以后再恢复运行。
在这里插入图片描述
1.2.4OSTaskResume()函数
OSTaskResume()函数用来恢复被 OSTaskSuspend()函数挂起的任务, OSTaskResume()函数是唯一能恢复被挂起任务的函数。如果被挂起的任务还在等待别的内核对象,比如事件标志组、信号量、互斥信号量、消息队列等,即使使用OSTaskResume()函数恢复了被挂起的任务,该任务也不一定能立即运行,该任务还是要等相应的内核对象,只有等到内核对象后才可以继续运行。
在这里插入图片描述
1.2.5 时间片轮转调度
OSSchedRoundRobinCfg()函数
在这里插入图片描述
在这里插入图片描述

1.3 系统内部任务

1.3.1空闲任务
空闲任务:OS_IdleTask(),在 os_core.c 文件中定义。任务OS_IdleTask()是必须创建的,不过不需要手动创建,在调用OS_Init()初始化 UCOS 的时候就会被创建。打开OS_Init() 函数,可以看到 ,在OS_Init() 中调用了函数OS_IdleTaskInit() ,打开函数OS_IdleTaskInit()。

1.3.2时钟节拍任务
在 OS_Init()中调用了一个函数 OS_TickTaskInit(),可以看到在函数 OS_TickTaskInit()的最后调用 OSTaskCreate()来创建了一个任务,任务函数为 OS_TickTask(),所以说时钟节拍任务是 UCOSIII 必须创建的,同样,不需要我们手工创建。时钟节拍任务的任务优先级为 OSCfg_TickTaskPrio,时钟节拍任务的优先级尽可能的高一点,ALIENTEk 默认设置时钟节拍任务的任务优先级为 1**。时钟节拍任务的作用是跟踪正在延时的任务,以及在指定时间内等待某个内核对象的任务。**

1.3.3统计任务
在 UCOSIII 中统计任务可用来统计 CPU 的使用率、各个任务的 CPU 使用率和各任务的堆栈使用情况,默认情况下统计任务是不会创建的,如果要使能统计任务的话需要将宏OS_CFG_STAT_TASK_EN 置 1,宏OS_CFG_STAT_TASK_EN 在 os_cfg.h 文件中有定义。 当我
们将宏 OS_CFG_STAT_TASK_EN 置 1 以后, OSinit()函数中有关统计任务的代码就可以编译了 。 OS_StatTaskInit() 函数用来创建统计任务 ,统计任务的优先级通过宏OS_CFG_STAT_TASK_PRIO 设 置 , ALIENTEK 将统计任务的优先级设置为OS_CFG_PRIO_MAX-2,也就是倒数第二。如果要使用统计任务的话就需要在 main()函数创建的第一个也是唯一一个应用任务中调用OSStatTaskCPUUsageInit()函数。

1.3.4 定时任务
UCOSIII 提供软件定时器功能,定时任务是可选的,将宏OS_CFG_TMR_EN 设置为 1 就会使能定时任务,在 OSInit()中将会调用函数 OS_TmrInit()来创建定时任务。定时任务的优先级通过宏 OS_CFG_TMR_TASK_PRIO 定义, ALIENTEK 默认将定时器任务优先级设置为 2。

定时器工作模式
定时器其实就是一个递减计数器, 当计数器递减到 0 的时候就会触发一个动作,这个动作就是回调函数,当定时器计时完成时就会自动的调用这个回调函数。因此我们可以使用这个回调函数来完成一些设计。比如,定时 10 秒后打开某个外设等,在回调函数中应避免任何可以阻塞或者删除定时任务的函数。 如果要使用定时器的话需要将宏OS_CFG_TMR_DEL_EN 定义为 1。 定时器的分辨率由我们定义的系统节拍频率 OS_CFG_TICK_RATE_HZ 决定, 比如我们定义为 200,系统时钟周期就是 5ms,定时器的最小分辨率肯定就是 5ms。 但是定时器的实际分辨 率 是 通 过 宏OS_CFG_TMR_TASK_RATE_HZ 定义的 ,这个值绝对不能大于OS_CFG_TICK_RATE_HZ。比如我们定义OS_CFG_TMR_TASK_RATE_HZ 为 100,则定时器的时间分辨率为 10ms。 有关 UCOSIII 定时器的函数都在 os_tmr.c 文件中。

创建定时器
使用OSTmrCreate()函数来创建一个定时器,也用来确定定时器的运行模式。
在这里插入图片描述
单次定时器
使用 OSTmrCreate()函数创建定时器时把参数 opt 设置为OS_OPT_TMR_ONE_SHOT, 就是创建的单次定时器。 创建一个单次定时器以后,我们一旦调用 OSTmrStart()函数定时就会从创建时定义的 dly 开始倒计数,直到减为 0 调用回调函数。
在这里插入图片描述
周期定时器(无初始化延迟)
使用 OSTmrCreate()函数创建定时器时把参数 opt 设置为OS_OPT_TMR_PERIODIC,就是创建的周期定时器。 当时器倒计数完成后,定时器就会调用回调函数,并且重置计数开
始下一轮的定时,就这样一直循环下去。 如果使用OSTmrCreate()函数创建定时器的时候,参数 dly 为 0 的话,那么定时器在每个周期开始时计数器的初值就为 period。
在这里插入图片描述
周期定时器(有初始化延迟)
在创建定时器的时候也可以创建带有初始化延时的,初始化延时就是 OSTmrCreate()函数中的参数 dly 就是初始化延迟,定时器的第一个周期就是 dly。当第一个周期完成后就是用参数 period 作为周期值,调用 OSTmrStart()函数开启有初始化延时的定时器。
在这里插入图片描述
1.3.5 中断服务管理任务
当把 os_cfg.h 文件中的宏OS_CFG_ISR_POST_DEFERRED_EN 置 1 就会使能中断服务管理任务, UCOSIII 会创建一个名为 OS_IntQTask()的任务,该任务负责“延迟”在 ISR 中调用的系统 post 服务函数的行为。中断服务管理任务的任务优先级永远是最高的为 0!
在 UCOS 中可以通过关闭中断和任务调度器上锁两种方式来管理临界段代码,如果采用后一种,即调度器上锁的方式来管理临界段代码的话,那么在中断服务函数中调用的“post”类函数就不允许操作诸如任务就绪表、等待表等系统内部数据结构。
当 ISR(中断服务函数)调用 UCOSIII 提供的“post”函数时,要发送的数据和发送的目的地都会存入一个特别的缓冲队列中,当所有嵌套的 ISR 都执行完成以后 UCOSIII 会做任务切换,运行中断服务管理任务,该任务会把缓存队列中存放的信息重发给相应的任务。这样做的好处就是可以减少中断关闭的时间,否则,在 ISR 中还需要把任务从等待列表中删除,并把任务放入就绪表,以及做一些其他的耗时操作。

1.3.6 钩子函数
注意:在空闲任务的钩子函数中不能调用任何可以是空闲进入等待态的代码,原因很简单,CPU 总是在不停的运行, 需要一直工作,不能让 CPU 停下来,哪怕是执行一些对应用没有任何用的代码,比如简单的将一个变量加一。在 UCOS 中为了让 CPU 一直工作,在所有应用任务都进入等待态的时候 CPU 会执行空闲任务,我们可以从空闲任务的任务函数 OS_IdleTask()看出,在 OS_IdleTask()中没有任何可以让空闲任务进入等待态的代码。如果在 OS_IdleTask()中有可以让空闲任务进入等待态的代码的话有可能会在同一时刻所有任务(应用任务和空闲任务)同时进入等待态,此时 CPU 就会无所事事了, 所以在空闲任务的钩子函数 OSIdleTaskHook()中不能出现可以让空闲任务进入等待态的代码。

2.UCOSIII中断和时间管理

2.1 中断管理

中断处理过程
在 STM32 中是支持中断的,中断是一个硬件机制,主要用来向 CPU 通知一个异步事件发生了, 这时 CPU 就会将当前 CPU 寄存器值入栈,然后转而执行中断服务程序,在 CPU 执行中断服务程序的时候有可能有更高优先级的任务就绪,那么当退出中断服务程序的时候, CPU 就会直接执行这个高优先级的任务。
UCOSIII 是支持中断嵌套的, 既高优先级的中断可以打断低优先级的中断,在 UCOSIII 中使用 OSIntNestingCtr 来记录中断嵌套次数,最大支持 250 级的中断嵌套,每进入一次中断服务函数 OSIntNestingCtr 就会加 1,当退出中断服务函数的时候 OSIntNestingCtr 就会减 1。
在这里插入图片描述
直接发布和延迟发布
UCOSIII 对从中断发布消息或者信号的处理有两种模式:直接发布和延迟发布两种方式。 我们可以通过宏OS_CFG_ISR_POST_DEFERRED_EN 来选择使用直接发布还是延迟发布。宏 OS_CFG_ISR_POST_DEFERRED_EN 在os_cfg.h 文件中有定义, 当定义为 0 时使用直接发布模式,定义为 1 的时候使用延迟发布模式。不管使用那种方式,我们的应用程序不需要做出任何的修改,编译器会根据不同的设置编译相应的代码。
在这里插入图片描述
在这里插入图片描述
直接发布模式下, UCOSIII 通过关闭中断来保护临界段代码。延迟发布模式下, UCOSIII通过锁定任务调度来保护临界段代码。
在延迟发布模式下, UCOSIII 在访问中断队列时,仍然需要关闭中断,但这个时间是非常短的。

OSTimeTick()函数
系统时钟节拍,UCOSIII 通过时钟节拍来对任务进行整个节拍的延迟,并为等待事件的任务提供超时判断。 时钟节拍中断必须调用 OSTimeTick()函数,我们使用 Systick 来为系统提供时钟,因此在Systick 的中断服务程序中就必须调OSTimeTick()。

  • 滴答定时器Systick
    滴答定时器是一个 24 位的倒计数定时器,当计到 0 时,将从 RELOAD 寄存器中自动重装载定时器初值,只要不把它在SysTick 控制以及状态寄存器中的使能位清零,就将永久不息。SysTick 的最大使命,就是定期地产生异常请求作为系统的时基。OS 都需要这种“滴答”来推动任务和时间的管理。
//初始化延迟函数
//当使用 ucos 的时候,此函数会初始化 ucos 的时钟节拍
//SYSTICK 的时钟固定为 HCLK 时钟的 1/8
//SYSCLK:系统时钟
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_UCOS //如果需要支持 OS.
u32 reload;
#endif
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8; //为系统时钟的 1/8
#if SYSTEM_SUPPORT_UCOS //如果需要支持 OS.
reload=SYSCLK/8; //每秒钟的计数次数 单位为 K
reload*=1000000/delay_tickspersec; //根据 OS_TICKS_PER_SEC 设定溢出时间
//reload 为 24 位寄存器,最大值:16777216,在
//168M 下,约合 0.7989s 左右
fac_ms=1000/delay_tickspersec; //代表 ucos 可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启 SYSTICK 中断
SysTick->LOAD=reload; //每 1/OS_TICKS_PER_SEC 秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启 SYSTICK
#else
fac_ms=(u16)fac_us*1000;//非 ucos 下,代表每个 ms 需要的 systick 时钟数
#endif //SYSTEM_SUPPORT_UCOS
}
  • 如果SYSTEM_SUPPORT_UCOS 被定义了就说明使用了 UCOS,那么我们就需要配置 SysTick。 首先要根据 UCOSII中的定义的 OS_TICKS_PER_SEC 来计算出 SysTick 的装载值 reload,开启SysTick 中断,将 reload值写进SysTick 的 LOAD 寄存器中,最后开启SysTick。
    在这里插入图片描述
    (1) 时钟节拍中断服务程序中首先会调用钩子函数OSTimeTickHook(),这个函数中用户可以放置一些代码。
    (2) 如果使用了延迟发布模式,则 UCOSIII 读取当前的时间戳信息,并在中断队列中放入发布函数调用请求和相关参数,延迟向时钟节拍任务发信号的操作。然后,中断队列处理任务
    根据中断队列向时钟节拍任务发信号。
    (3) 向时钟节拍任务(OS_TickTask())发送一个信号量。
    (4) 如果 UCOSIII 使用了时间片轮转调度机制,判断当前任务分配的运行时间片是否已经用完。
    (5) 如果使用定时器的话就向定时器任务(OS_TmrTask())发送信号量。

临界段代码保护
有一些代码我们需要保证其完成运行,不能被打断,这些不能被打断的代码就是临界段代码,也叫临界区。我们在进入临界段代码的时候使用宏 OS_CRITICAL_ENTER(),退出临界区
的时候使用宏 OS_CRITICAL_EXIT()或者OS_CRITICAL_EXIT_NO_SCHED()。

当宏 OS_CFG_ISR_POST_DEFERRED_EN 定义为 0 的时候,进入临界区的时候 UCOSIII会使用关中断的方式,退出临界区以后重新打开中断。当 OS_CFG_ISR_POST_DEFERRED_EN
定义为 1 的时候进入临界区前是给调度器上锁,并在退出临界区的时候给调度器解锁。

在这里插入图片描述
在这里插入图片描述

2.2 时间管理

OSTimeDly()函数
当我们需要对一个任务进行延时操作的时候就可以使用这个函数,函数原型如下。

void OSTimeDly (OS_TICK dly,OS_OPT opt,OS_ERR *p_err)

dly: 指定延时的时间长度,这里单位为时间节拍数。
opt: 指定延迟使用的选项,有四种选项。
OS_OPT_TIME_DLY 相对模式
OS_OPT_TIME_TIMEOUT 和 OS_OPT_TIME_DLY 一样
OS_OPT_TIME_MATCH 绝对模式
OS_OPT_TIME_PERIODIC 周期模式
p_err: 指向调用该函数后返回的错误码

“相对模式”在系统负荷较重时有可能延时会少一个节拍,甚至偶尔差多个节拍,在周期模式下,任务仍然可能会被推迟执行,但它总会和预期的“匹配值”同步。因此,推荐使用“周
期模式”来实现长时间运行的周期性延时。
“绝对模式”可以用来在上电后指定的时间执行具体的动作,比如可以规定,上电 N 秒后关闭某个外设。

OSTimeDlyHMSM()函数
我 们 也 可 调 用 OSTimeDlyHMSM() 函 数 来 更 加 直 观 的 来 对 某 个 任 务 延 时 ,OSTimeDlyHMSM()函数原型如下:

void OSTimeDlyHMSM (CPU_INT16U hours, //需要延时的小时数
CPU_INT16U minutes, //需要延时的分钟数
CPU_INT16U seconds, //需要延时的秒数
CPU_INT32U milli, //需要延时的毫秒数
OS_OPT opt, //选项
OS_ERR *p_err)

hours
minutes
seconds
milli: 前面这四个参数用来设置需要延时的时间,使用的是:小时、分钟、秒和毫秒这种格式,这个就比较直观了,这个延时最小单位和我们设置的时钟节拍频率有关,比如我们设置时钟节拍频率 OS_CFG_TICK_RATE_HZ 为 200 的话,那么最小延时单位就是 5ms。
opt: 相比 OSTimeDly()函数多了两个选项OS_OPT_TIME_HMSM_STRICT 和OS_OPT_TIME_HMSM_NON_STRICT,其他四个选项都一样的使用 OS_OPT_TIME_HMSM_NON_STRICT 选项的话将会检查延时参数,hours 的范围应该是 0~ 99, minutes 的范围应该是 0~ 59, seconds 的范围为 0~ 59,milli的范围为 0~999。
使用 OS_OPT_TIME_HMSM_NON_STRICT 选项的话, hours 的范围为 0~ 999,minutes 的范围为 0~ 9999, seconds 的范围为 0~ 65535, mili 的范围为0~4294967259。
p_err: 调用此函数后返回的错误码

3.UCOSIII信号量和互斥信号量

3.1信号量

信号量像是一种上锁机制,代码必须获得对应的钥匙才能继续执行,一旦获得了钥匙,也就意味着该任务具有进入被锁部分代码的权限。一旦执行至被锁代码段,则任务一直等待,直到对应被锁部分代码的钥匙被再次释放才能继续执行。
信号量分为两种:二进制信号量与计数型信号量,二进制信号量只能取 0 和 1 两个值,计数型信号量不止可以取 2 个值,在共享资源中只有任何可以使用信号量,中断服务程序则不能使用。
在这里插入图片描述
信号量主要用于访问共享资源和进行任务同步
在这里插入图片描述
信号量现在更多的被用来实现任务的同步以及任务和 ISR 间的同步
在这里插入图片描述

  • 一个小旗子代表信号量,小旗子旁边的数值 N 为信号量计数值, 表示发布信号量的次数累积值, ISR 可以多次发布信号量,发布的次数会记录为 N。一般情况下, N 的初始值是 0,表示事件还没有发生过。在初始化时,也可以将 N 的初值设为大于零的某个值,来表示初始情况下有多少信号量可用。
    等待信号量的任务旁边的小沙漏表示等待任务可以设定超时时间。 超时的意思是该任务只会等待一定时间的信号量,如果在这段时间内没有等到信号量,UCOSIII 就会将任务置于就绪表中,并返回错误码。

优先级反转
优先级反转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果。
在这里插入图片描述

3.2 互斥信号量

为了避免优先级反转这个问题, UCOSIII 支持一种特殊的二进制信号量:互斥信号量,用它可以解决优先级反转问题。
在这里插入图片描述
在这里插入图片描述
任务内嵌信号量
在这里插入图片描述

  • 等待任务内嵌信号量使用函数 OSTaskSemPend(),OStaskSemPend()允许一个任务等待由其他任务或者 ISR 直接发送的信号,使用过程基本和独立的信号量相同。
  • OSTaskSemPost()可以通过一个任务的内置信号量向某个任务发送一个信号量。

4.UCOSIII消息传递

一个任务要和另外一个或者几个任务进行“交流”, 这个“交流”就是消息的传递,也称之为任务间通信, 在 UCOSIII 中消息可以通过消息队列作为中介发布给任务,也可以直接发布给任务。

4.1 消息队列

消息一般包含:指向数据的指针,表明数据长度的变量和记录消息发布时刻的时间戳,指针指向的可以是一块数据区或者甚至是一个函数, 消息的内容必须一直保持可见性,因为发布数据采用的是引用传递是指针传递而不是值传递,也就说,发布的数据本身不产生数据拷贝。消息队列是由用户创建的内核对象,数量不限制。
在这里插入图片描述
消息队列函数
在这里插入图片描述
常用的关于消息队列的函数其实只有三个,创建消息队列函数 OSQCreate(),向消息队列发送消息函数 OSQPost()和等待消息队列函数 OSQPend()。

  • OSQCreate()函数用来创建一个消息队列,消息队列使得任务或者中断服务程序可以向一个或者多个任务发送消息。
  • 当一个任务想要从消息队列中接收一个消息的话就需要使用函数OSQPend()。当任务调用这个函数的时候,如果消息队列中有至少一个消息时,这些消息就会返回给函数调用者。
  • 可以通过函数 OSQPost()向消息队列发送消息, 如果消息队列是满的,则函数 OSQPost()就会立刻返回,并且返回一个特定的错误代码。

任务内建消息队列
在这里插入图片描述

  • 函数 OSTaskQPend()用来请求消息, 该函数让任务直接接收从其他任务或者 ISR 中发送来的消息,不需要经过中间的消息队列。
  • 函数 OSTaskQPost()可以通过一个任务的内建消息队列向这个任务发送一条消息, 同外置的消息队列一样,一条消息就是一个指针。
    在这里插入图片描述

5.UCOSIII事件标志组

事件标志组用来解决一个任务和多个事件之间的同步
有时候一个任务可能需要和多个事件同步,这个时候就需要使用事件标志组。 事件标志组与任务之间有两种同步机制:“或”同步和“与”同步, 当任何一个事件发生,任务都被同步的同步机制是“或”同步;需要所有的事件都发生任务才会被同步的同步机制是“与”同步。
在这里插入图片描述

  • OSFlagPend()允许将事件标志组里事件标志的“与或”组合状态设置成任务的等待条件。任务等待的条件可以是标志组里任意一个标志置位或清零,也可以是所有事件标志都置位或清零。如果任务等待的事件标志组不满足设置的条件,那么该任务被置位挂起状态,直到等待的事件标志组满足条件、指定的超时时间到、事件标志被删除或另一个任务终止了该任务的挂起状态。
  • 调用函数 OSFlagPost()可以对事件标志组进行置位或清零。一般情况下,需要进行置位或者清零的标志由一个掩码确定(参数 flags)。 OSFlagPost()修改完事件标志后,将检查并使那些等待条件已经满足的任务进入就绪态。该函数可以对已经置位或清零的标志进行重复置位和清零操作。

同时等待多个内核对象
UCOSIII 中只支持同时等待多个信号量和消息队列, 不支持同时等待多个事件标志组和互斥信号量。

6.UCOSIII存储管理

UCOIII 将存储空间分成区和块,一个存储区有数个固定大小的库组成,在实际使用中我们可以根据应用程序对内存需求的不同建立多个存储区,每个存储区中有不同大小、不同数量的存储块,应用程序可以根据所需内存不同从不同的存储区中申请内存使用,使用完以后在释放到相应的存储区中。

存储区控制块OS_MEM
在这里插入图片描述
在这里插入图片描述
UCOSIII 自带的内存管理函数的局限性,每次申请内存的时候用户要先估计所申请的内存是否会超过存储区中存储块的大小。比如我们创建了一个有 10 个存储块,每个存储块大小为 100 字节的存储区 buffer。这时我们应用程序需要申请一个 10 字节的内存,那么就可以使用函数 OSMemGet()从存储区 buffer 中申请一个存储块。但是每个存储块有 100 个字节,但是应用程序只使用其中的 10 个字节,剩余的 90 个字节就浪费掉了,为了减少浪费我们可以创建一个每个存储块为 10 字节的存储区,这样就不会有内费了。
但是,假设我们在程序的其他地方需要申请一个 150 字节的内存,但是存储区 buffer 的每个存储块只有 100 字节,显然存储区 buffer 不能满足程序的需求。有读者就会问可不可以在存储区中连续申请两个 100 字节的存储块,这样就有 200 字节的内存供应用程序使用了?想法是好想法,但是通过阅读函数 OSMemGet()发现并没有提供这样的功能, OSMemGet()函数在申请内存的时候每次只取指定存储区的一个存储块!如果想申请 150 字节的内存就必须再新建一个每个存储块至少有 150 字节的存储区。

7.UCOSIII知识点

  1. 最有用的功能应该是时间片轮转法( roundrobin), 这个是 uC/OS-II 中不支持的,但是现在已经是uC/OS-III 的一个功能。
  2. uC/OS-III 被设计用于 32 位处理器, 但是它也能在 16 位或 8 位处理器中很好地工作。
  3. 一共有 2 种类型的实时系统:软实时系统和硬实时系统。硬实时系统中,运算超时是不允许发生的,运算超时会导致严重后果。但是在软实时系统中 , 超时不会导致严重后果。
  4. 前后台系统:包含一个无限循环的模块实现需要的操作(后台)。中断处理程序实现异步事件(前台)。前台也叫做中断级,后台也叫作任务级。
  5. 临界操作应该在任务级中被执行,不可避免地必须在中断处理程序中执行也要确保是在很短的时间内完成。 因为这会导致 ISR 占用更长的时间。 通常的, ISR 中使能相关的信息而在后台程序中执行相应的操作。
  6. ucos-iii中的任务(也叫做线程) 是一段简单的程序, 运行时完全地占用 CPU 。在单 CPU 中,任何时候只有 1 个任务被执行。
  7. 内核的责任是管理任务,协调和切换多个任务依次享用 CPU 。让我们感觉是多个 CPU 在同时运行,也有利于处理模块化的应用,它也负责管理任务间的交流, 系统资源的管理(内存 和I/O )等。
  8. uC/OS-III 是一个抢占式内核, 这意味着 uC/OS-III 总是执行最重要的就绪任务
  9. ISR 响应中断请求设备, 但是 ISR 只做非常少的工作。 ISR 应该标记或发送消息到一个高优先级的任务, 让中断能够快速处理完毕
  10. 系统中加入内核需要额外的支出,因为内核提供服务时需要时间去处理。内核占用 CPU 的时间介于 2% 到 4% 之间。 因为 uC/OS-III是一个软件,添加到目标系统中需要额外的 ROM 和 RAM 。
  11. uC/OS-III 内核需要 1K 到 4K 之间的 RAM , 加上每个任务自己所需的堆栈空间。 至少有 4K 大小 RAM 的处理器才有可能成功移植 uC/OS-III 。
  12. uC/OS-III 允许多个任务拥有相同的优先级。 当多个相同优先级的任务就绪时, 并且这个优先级是目前最高的,uC/OS-III 会分配用户定义的时间片给每个任务去运行。 每个任务可以定义不同的时间片 。
  13. uC/OS-III 保护临界段可以通过锁定调度器代替关中断。 因此关中断的时间会非常少。这样就使 uC/OS-III 可以响应一些非常快的中断源了。
  14. uC/OS-III 允许用户在运行时配置内核。特别的,所有的内核对象如任务、堆栈、信号量、事件标志组、消息队列、 消息、 互斥信号量、 内存分区、 软件定时器等都是在运行时分配的 , 以免在编译时的过度分配。
  15. uC/OS-III 对任务数量、任务大小、优先级数量无限制。每一个任务需要有自己的堆栈空间。实际上, 任务的数量和大小限制于处理器能提供的内存大小。
  16. uC/OS-III 支持任何数量的任务、信号量、 互斥信号量、 事件标志组、 消息队列、 软件定时器、 内存分区。 用户在运行时分配所有的内核对象。
  17. 互斥信号量用于资源管理。它是一个内置优先级的特殊类型信号量, 用于消除优先级反转。 互斥信号量可以被嵌套,因此,任务可申请同一个互斥信号量多达 250 次。当然, 互斥信号量的占有者需要释放同等次数。
  18. uC/OS-III 允许任务停止自身或者停止另外的任务。 停止一个任务意味着这个任务将不再执行直到被其他的任务恢复。 停止可以被嵌套到 250 级。 换句话说, 一个任务可以停止另外的任务多达 250 次。 当然, 这个任务必须被恢复同等次数才有资格再次获得 CPU 。
  19. 可以定义任意数量的一次性的、周期性的、或者两者兼有的软件定时器。 定时器是倒计时的, 执行用户定义的行为一直到计数减为 0 。 每一个定时器可以有自己的行为, 如果一个定时器是周期性的,计数减为 0 时会自动重装计数值并执行用户定义的行为。
  20. uC/OS-III 允许任务等待多个事件的发生。等待中的任务在所有事件发生后被唤醒 。
  21. uC/OS-III 允许 ISR 或者任务直接地发送信号量给其它任务。 这样就避免了必须产生一个中间级内核对象如一个信号量或者事件标志组只为了标记一个任务。提高了内核性能。
  22. 每一个任务可以拥有用户可定义的任务寄存器,不同于 CPU 寄存器。uC/OS-III 能检测指针是否为 NULL 、在 ISR 中调用的任务级服务是否允许、 参数在允许范围内、 配置选项的有效性、 函数的执行结果等。每一个 uC/OS-III 的 API 函数返回一个对应于函数调用结果的错误代号
  23. uC/OS-III 有内置性能测量功能。 能测量每一个任务的执行时间 , 每个任务的堆栈使用情况, 任务的执行次数, CPU的使用情况, ISR 到任务的切换时间 , 任务到任务的切换时间, 列表中的对象的峰值数,关中断、锁调度器平均时间等。
  24. uC/OS-III 被设计于能够根据CPU 的架构被优化, uC/OS-III 所用的大部分数据类型能够被改变, 以更好地适应 CPU 固有的字大小。 优先级调度法则可以通过编写一些汇编语言而获益于一些 特 殊 的 指令如位设置、位清除、计数器清零指令( CLZ )、find-first-one(FF1) 指令。
  25. uC/OS-III 中所有的挂起服务都可以有时间限制, 预防死锁。
  26. uC/OS-III 有时基任务, 时基 ISR 触发时基任务。
  27. uC/OS-III使用了哈希列表结构, 可以大大减少处理延时和任务超时所产生的开支。
  28. uC/OS-III 允许程序员定义 hook 函数。hook 函数允许用户扩展 uC/OS-III 的功能。
  29. 为了测量时间, uC/OS-III 需要一个 16 位或者 32 位的时时间戳计数器。
  30. 每个 uC/OS-III 的内核对象有一个相关联的名字。 这样就能很容易的识别出对象所指定的作用。对象的名字长度没有限制,但是必须以空字符结束。
  31. 每个任务需要创建自己的堆栈。堆栈的数据类型 CPU_STK 。堆栈可以被静态地分配或者通过 malloc() 动态地分配。若任务将不会被删除,堆栈将一直被使用。
  32. 在大部分处理器中,中断在启动时是关闭的。无论如何,在启动时关闭所有的外设中断是最安全的。
  33. uC/OS-III 须创建空闲任务 OS_IdleTask (),当没有其他任务运行时就运行空闲任务。根据配置文件 uC/OS-III 会创建统计任务OS_StatTask() 、定时器任务 OS_TmrTask() 、中断队列处理任务OS_IntQTask() 。
  34. OSTaskCreate() 的第四个参数, 第一次被调用时OSTaskCreate() 接收这个变量, 传递给所创建的任务中的唯一参数"p_arg"。该参数可以是任意类型的指针。
  35. 参数值越小优先级越高。可以设置优先级数值为 1 到 OS_CFG_PRIO_MAX-2 。要避免使用优先级 #0 和优先级 OS_CFG_PRIO_MAX-1 。因为这些是为 uC/OS-III保留的。
  36. 任务的堆栈大 ( 以 CPU_STK 为数据类型而不是字节 ) 。 例如, 如果要分配 1KB 大小的堆栈空间,因为 CPU_STK 是 32 位的,所以这个其值为 256。
  37. 互斥信号量( mutex )是一个内核对象,用于保护共享资源。 任务要访问共享资源就必须先获得 mutex 。mutex的拥有者使用完这个资源后就必须释放这个 mutex 。
  38. 消息队列是一个内核对象, ISR 或任务可以直接发送消息到另一个任务。 发送者制定一个消息并将其发送到目标任务的消息队列。 目标任务等待消息的到达。
  39. 定义消息队列可接受消息的个数。 这个值必须大于 0 。如果消息者发送消息数超过了消息接收任务的承受能力。那么消息将会被丢失。可以通过增加消息队列的大小或者提供消息接收任务的优先级提升其承受能力。
  40. uC/OS-III 定义了一个进入临界段的宏和两个出临界段的宏(退出临界段后是否调用调度器)。
  41. 测得消息是什么时候被发送的,用户就能测得任务接收这个消息所用的时间。读取现在的时间戳并减去消息被发送时的时戳。需注意的是,消息被发送时,等待消息的任务可能不会立即接收到消息,因为 ISR 或更高优先级的任务可能抢占了当前任务。显然,测出的时间还包括了测量时消耗的额外时间。 然而减掉测量时所耗时间就是实际上的时间。
  42. 时间戳的控制单元位于 CPU_TS 中。例如,如果 CPU 速率为 1MHz ,时间戳的速率为 1MHz 。那么CPU_TS 的分辨率为 1 微秒 。
  43. 当任务第一次执行时, 会传入一个变量 “p_arg” 。这是一个指向 void的指针。用于变量的地址、结构体地址、或者函数的地址等。如果需要,可以创建多个相同的任务,使用相同的代码(相同任务体),而产生有不同的运行结果。
  44. 只运行一次的任务结束时必须通过调用 OSTaskDel() 删除自己。 这样可以使系统中的任务数减少。
  45. 一个任务可以创建其它任务( 调 OSTaskCreate() )、停止或者恢复其它 ( 调用 OSTaskSuspned() 和 OSTaskResume()) 、 提交信号量到其它任务、 发送消息到其它任务、 提供共享资源等。 换句话说, 任务不是只被限制于“等待事件”。
  46. 在嵌入式系统中动态地分配堆栈是被允许的,但是,一旦堆栈被动态分配,它就不能被回收。换句话说, 对于有些不需要被删除的任务,动态分配它们的堆栈是一种很好的解决方法。
  47. 可以人工地计算出任务需要的堆栈空间大小,逐级嵌套所有可能被调用的函数,添加被调用函数中所有的参数,添加上下文切换时的CPU 寄存器空间,添加切换到中断时所需的 CPU 寄存器空间,添加处理 ISRs 所需的堆栈空间。把上述的全部相加, 得到的值定义为最小的需求空间。因为我们不可能计算出精确的堆栈空间。通常是再乘以 1.5 以确保任务的安全运行。
  48. 另一种防止堆栈溢出的方法是分配的空间远大于可能需要的。 首先, 当任务创建时其堆栈被清零。 程序运行一段时间后,通过一个低优先级任务, 计算该任务整个堆栈中值为 0 的内存大小。 这是一种非常有效的方法。 注意的是, 程序需用运行很长的时间以让堆栈达到其需要的最大值。
  49. 从用户的观点来看,任务可以是有 5 种状态,休眠状态,就绪状态,运行状态,挂起状态,中断状态 。
  50. 调用 OSTaskSuspend() 会任务无条件地停止运行。 有些时候调用 OSTaskSuspend() 不是为了等待某个事件的发生,而是等待另一个任务调用 OSTaskResume() 函数恢复这个任务。
  51. 任务控制块是被 uC/OS-III 用于维护任务的一个结构体。 每个任务都必须有自的己 TCB 。TCB 中的一些变量可以根据具体应用进行裁剪。用户程序不应该访问这些变量(尤其不能更改它们)。
  52. 有些处理器有硬件寄存器可以自动地检测并确保堆栈不发生溢出, 如果处理器没有这些硬件施,ucos-iii的堆栈检测可以用软件模拟。 然而, 软件模拟不如硬件可靠。
  53. 在 uC/OS-III 初始化的时候,它会创建至少 2 个内部的任务 (OS_IdleTask() 和 OS_TickTask()) ,3 个可选择的任务( OS_StatTask() ,OS_TmrTaks() ,OS_IntQTask() )。这些可选择的任务在编译时由OS_CFG.H 中的配置决定。
  54. 当 CPU 中没有其它就绪任务运行时,空闲会被运行。空闲任务是一个无限循环的不会等待任何事件的任务。空闲任务的每次循环,都会调用 OSIdleTaskHook() 函数,这个函数提供给用户扩展应用,如让处理器进入低功耗模式等。
  55. 使用硬件定时器并被设置为以 10 到 1000Hz 之间的频率产生时基中断,时基中断并不是一定要用 CPU 产生, 事实上, 它可以从其他的具有较精确的周期性时间源中获得,比如电源线( 50-60Hz )等。
  56. 当时基任务执行时,它会遍历挂起队列中所有等待期满的任务或等待事件超时的任务。 它会就绪时基列表中的那些期满、超时的任务。使用轮转法遍历队列(此队列为二维数组的形式)大大减少了遍历队列所占用CPU的时间。
  57. 统计任务能够统计总的 CPU 使用率, 每个任务的 CPU使用率,每个任务的堆栈使用量。
  58. 软件定时器通常需要的频率可由用户设置, 通过软件将时基分频。 换句话说如果时基速率为 1000Hz, 但是想要的定时器速率为 10Hz, 软件定时器任务会每 100 个时基被标记一次。时基任务的优先级要高于定时器任务,定时器任务的优先级需要高于统计任务。
  59. 当一个任务创建了一个具有相同优先级的任务,这个新任务会被添加到该优先级队列的尾部(因为具有相同优先级情况下, 没有理由让新任务先运行)。然而,当一个任务创建了一个具有不同优先级的任务时,这个新的任务就会放到对应优先级列表中的首部。注意:正在运行的任务也被放在就绪列表中。
  60. 会发生调度的调度点:任务被标记或发送消息给另一个任务、任务调用 OSTimeDly() 或 OSTimeDlyHMSM()、任务所等待的事件发生或超时、任务被取消挂起 、新任务被创建 、任务被删除 、内核对象被删除 、任务改变自身的优先级或其它任务的优先级 、任务通过调用OSTaskSuspend() 停止自身、任务调用OSTaskResume() 恢复其它停止了的任务、退出中断服务程序 、通过调用 OSSchedUnlock() 调度器被解锁、调用OSSchedRoundRobinYield() 任务放弃了分配给它的时间片、用户调用OSSched() 。
  61. 任务提交一个事件后调用调度器。 当然, 任务可以一次性提交多个事件, 但在最后一个事件提交后才调用调度器。
  62. uC/OS-III 有 2 种调度方式: OSSched() 被用于任务级。 OSIntExit()被用于中断级。由于中断产生时已经将任务 A 的状态保存在任务 A 的堆栈中,所以 ISR 返回时无需再保存任务 A 的状态,而是直接载入任务 B 的 CPU 寄存器到硬件CPU 寄存器中即可。
  63. 当 uC/OS-III 转向执行另一个任务的时候,它保存了当前任务的 CPU 寄存器到堆栈。并从新任务堆栈中相关内容载入CPU 寄存器。这个过程叫做上下文切换。上下文切换需要一些开支。 CPU 的寄存器越多,开支越大。 上下文切换的时间基本取决于有多少个 CPU 寄存器需要被存储和载入。保存状态寄存器和程序指针寄存器到当前的任务堆栈。保存的顺序与中断发生时 CPU 保存寄存器的顺序相同。
  64. CPU 处理中断有两种模式: 1 所有的中断指向同一个 ISR; 2 每个中断指向各自的 ISR 。ISR 的工作完成后, 用户必须调用 OSIntExit() 告诉 uC/OS-III中断服务程序已经完成。
  65. uC/OS-III 有两种方法处理来自于中断的时间:直接提交和延迟提交。其区别在于如何处置中断中所产生的事件。延迟提交的方式为事件不是直接发送给任务, 而是先发送到中断队列。 然后中断处理任务(其优先级为0)被就绪,这样,事件的提交便可在任务级完成,从而减少了ISR处理的时间。
  66. uC/OS-III 必须有系统时基是普遍的误解。 事实上, 很多低功耗应用中没有系统时基,因为需额外的能量用于维护时基源。换句话说 ,将能量用于维护时基源是不合理的。因为 uC/OS-III 是一个可抢占式内核, 一个事件可以唤醒进入低功耗模式处理器(按键或其它事件)没有时基意味着用户不能再对任务进行延时或超时设置。 用户在研发低功耗产品时可以考虑这个特性。
  67. 任务在挂起队列中是根据优先级分类的。 高优先级任务被放置在队列的头部,低优先级任务被放置在队列的尾部。
  68. 任务不是直接链接到挂起队列中, 而是通过叫OS_PEND_DATA 的结构体作为媒介。 这个媒介在任务被挂起时分配到任务堆栈的。挂起队列中的对应指针指向该结构体。
  69. 延时函数OSTimeDly(),任务调用这个函数后就会被挂起直到期满。以时基为单位,但需注意,当任务在时基中断将要到来时被挂起,那么实际的延时时基会少 1 个时基。这个函数可以有设置为三种模式:相对延时模式,周期性延时模式,绝对延时模式(用于对时间要求很高的应用)。
  70. uC/OS-III 定时器的分辨率决定于时基频率。定时器可以被设置为 3 种模式:一次性定时模式,无初始定时周期模式,有初始定时周期模式 。如果定时器被停止, 那其定时值也将被停止, 直到定时器被恢复时,定时器值继续被递减。不能在定时器的执行代码中等待事件发生。否则定时器任务会被挂起,导致定时器任务崩溃。
  71. uC/OS-III 可能要维护上百个定时器。 使用定时器列表会大大降低更新定时器列表所占用的 CPU 时间。 定时器列表类似于时基列表,以二维数组的形式存储记录。
  72. uC/OS-III提供关中断方式、锁调度器方式、信号量方式、mutex方式保护共享资源。只有任务才允许使用信号量,ISR是不允许的。用信号量保护共享资源不会导致中断延迟。当任务在执行信号量所保护的共享资源时,ISR或高优先级任务可以抢占该任务。
  73. 信号量经常被过度使用。很多情况下,访问一个简短的共享资源时不推荐使用信号量,请求和释放信号量会消耗CPU时间。通过关/开中断能更有效地执行这些操作。信号量会导致一种严重的问题:优先级反转。
  74. 优先级反转是实时系统中的一个常见问题,仅存在于基于优先级的抢占式内核中。uC/OS-III支持一种特殊类型的二值信号量叫做mutex,用于解决优先级反转问题。
  75. 死锁,就是两个任务互相等待对方所占用的资源的情况。除一般的防死锁方式外,uC/OS-II还可以在申请信号量或mutex时允许设置其期限,这样能防止死锁,但是同样的死锁可能稍后再次出现。
  76. uC/OS-III中用于同步的两种机制:信号量和事件标志组。两个任务间可以用一个信号量实现单向同步,用两个信号量实现双向同步。当任务要与多个事件同步时可以使用事件标志。若其中的任意一个事件发生时任务被就绪,叫做逻辑或(OR)。若所有的事件都发生时任务被就绪,叫做逻辑与(AND)。
  77. 有些情况下任务或ISR与另一个任务间进行通信,这种信息交换叫做作业间的通信。可以有两种方法实现这种通信:全局变量、发送消息。需注意的是:任务与ISR通信只能通过全局变量。如果全局变量被ISR改变,任务将不会知道全局变量被改变,除非该任务检测该变量或者ISR标记任务告知该变量被改变。
  78. 消息可以被发送到媒介—消息队列中,也可以直接发送给任务,因为uC/OS-III中每个任务都有其内建的消息队列。如果多个任务等待这个消息时建议将该消息发送到外部的消息队列。当只有一个任务等待该消息时建议直接将消息发送给任务。
  79. 消息中包含一个指向数据的指针、该数据的大小、时间戳变量。该指针可以指向数据区域甚至是一个函数。当然,消息的发送方和消息的接收方都应该知道消息所包含的意义。
  80. 消息队列是先入先出模式(FIFO)。然而,uC/OS-III也可以将其设置为后入先出模式(LIFO)。若任务或ISR发送紧急消息给另一个任务时,后入先出模式是非常有用的,在这种情况下,该紧急消息绕过消息队列中的其他消息。
  81. 任务A发送多个消息给任务B,如果更高优先级的任务抢占了任务B,那么任务A所存放在消息队列中的数据就可能被溢出。解决这个问题的一种方法是在处理中添加流量控制:所有任务在发送消息给任务B之前必须获得信号量。任务B消息队列的空余量为多少,信号量计数值就为多少。
  82. 任务可以等待多个内核对象。然而,uC/OS-III只允许任务同时等待多个信号量或消息队列。换句话说,不能同时等待多个事件标志组或mutex。但这将花费uC/OS-III较多时间去处理。
  83. 可以通过使用编译器提供的函数malloc()和free()动态地分配和释放内存快。然而,在嵌入式实时系统中使用malloc()和free()可能是非常危险的。因为它可能会导致很多内存碎片。
  84. ucos-iii可以创建多个大小不同的内存分区,一个内存分区可被设置为多个大小相同的任务块,用于存储临时性的数据。根据需求设置,但内存块被分配后必须返回给它所在的内存分区,这种管理方式仅会导致内存块块内的碎片。从而减少了内存碎片。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值