45 KiB
第一章、基础篇
任务优先级分组
可在系统配置文件中配置任务优先级分组。
共有六个分组可供选择,8级、16级、32级、64级、128级、256级,您也可自定义分组或级数。
CosyOS的任务优先级,数值越大优先级越高,数值越小优先级越低,最低优先级固定为0级。
如您配置的分组为8级,则最高优先级为7级,最低优先级为0级。
时间片模式
CosyOS有三种时间片的定义模式,全局时间片、算法时间片、自定义时间片,需在系统配置文件中定义。
- 全局时间片 所有任务采用相同的时间片。
- 算法时间片 不同优先级的任务,时间片不同,采用一个算法公式来描述。
- 自定义时间片 不同优先级的任务,时间片不同,用户自定义各优先级的时间片。
全局时间片,简单高效,时间片轮转调度性能为最佳;算法时间片、自定义时间片,可实现不同优先级的、精细的时间片控制。
时间片的取值范围:1~65535,单位为滴答周期。
安全运行时
安全运行时是CosyOS的安全关键技术之一,可防止某任务长期独占或超时使用处理器。
当任务累计运行时间超过安全运行时,仍不能被比它优先级低的任务所切换,则进入超时状态并放弃处理器的使用权,直到系统空闲任务运行时才能恢复。
可在系统配置文件中设置安全运行时的开启或关闭。
安全运行时的取值范围:0~65535,单位为时间片,0为无限长。
所有空闲任务无法应用安全运行时,创建任务时,安全运行时输入0即可。
安全运行时的应用技巧:
1、开发阶段,所有任务的安全运行时可先设置为0;
2、测试阶段,计算和评估各任务的有效运行时间;
3、在有效运行时间的基础上累加一定的补偿,再重新调整安全运行时。
支持任务/钩子
-
系统任务
由CosyOS自主创建并自动启动的任务。
1、任务管理器(Taskmgr) 优先级固定为最高优先级。
2、调试任务(Debugger) 优先级固定为最高优先级,用做命令行和任务管理器的串口接收解析。
3、启动任务(Starter) 优先级固定为比最高优先级低一级,通过调用启动钩子来启动用户任务,而后删除自身。
4、系统空闲任务(Sysidle) 优先级固定为最低优先级0级,是系统级的空闲任务。 -
系统钩子
由CosyOS自主创建并自动调用的函数,并由用户来写代码。
1、初始化钩子(init_hook) 在主函数中首先被调用,适用于初始化时钟、GPIO、寄存器等工作。
2、启动钩子(start_hook) 在启动任务中被调用,用于启动用户任务。
3、空闲钩子(idle_hook) 在系统空闲任务中被调用,用户可添加自己的代码做一些没有实时性要求的工作。
4、滴答钩子(tick_hook) 每个系统滴答周期,在系统滴答中断中都会被调用一次,适用于每滴答周期/秒/分/时/日/月/年/...做一次的工作。
5、挂起服务钩子(pendsv_hook) 用于安全执行包含读访问的中断挂起服务。
6、全局变量钩子(gvar_hook) 用于更新全局变量的副本。 -
定时任务/钩子
包括定时中断任务/钩子和定时查询任务/钩子,其实质都是软件定时器中断。
1、定时中断任务/钩子 由用户设定定时时间,当定时器溢出时,系统将自动恢复/调用与其绑定的任务/钩子并自动重复定时(如果您已开启定时器的自动重装载)。
2、定时查询任务/钩子 由用户设定定时时间,当定时器溢出后,系统在每个滴答周期都会查询用户定义的事件,若事件为真,系统将自动恢复/调用与其绑定的任务/钩子并自动重复定时(如果您已开启定时器的自动重装载)。系统初始化后,所有定时查询定时器的值均为零,相当于已经溢出,系统已经开始查询用户定义的事件了。
任务与钩子的区别 由于定时中断钩子、定时查询钩子都是在系统滴答中断中被调用,所以只有相对精简的代码才适合创建钩子,耗时长的代码应创建为任务。再有,钩子相当于比任务具有更高的优先级,可更及时的被执行。 -
一般用户任务
对于一般用户任务,只要优先级为0级,就是空闲任务。用户空闲任务内都要有阻塞事件,以保证所有空闲任务都有机会被运行。
最高优先级固定分配给任务管理器和调试任务,建议用户任务不要使用。
任务状态
CosyOS的任务状态较传统RTOS划分更为详细,以便用户在使用任务管理器监控任务时,能够更加精准的判断各任务的运行情况。
具体划分如下:
注:[]为在任务管理器中显示的任务状态
- 运行状态[RDY] 任务正在运行。
- 就绪状态[RDY] 任务已经准备就绪,可以参与任务调度并运行了。
- 延时阻塞状态[DLY] 由阻塞延时而导致任务进入阻塞状态,直到延时时间结束或被清除转为就绪状态。
- 超时阻塞状态 由调用含超时机制的服务而导致任务进入阻塞状态,直到成功或超时或被清除转为就绪状态。
1、二值信号量阻塞状态[BIN] 由二值信号量等待或获取而导致任务进入阻塞状态,直到成功或超时或被清除转为就绪状态。
2、互斥信号量阻塞状态[MUT] 由互斥信号量获取而导致任务进入阻塞状态,直到成功或超时或被清除转为就绪状态。
3、计数信号量阻塞状态[SEM] 由计数信号量获取而导致任务进入阻塞状态,直到成功或超时或被清除转为就绪状态。
4、等待标志组阻塞状态[GRP] 由事件标志组等待而导致任务进入阻塞状态,直到成功或超时或被清除转为就绪状态。
5、接收私信阻塞状态[ TM] 由接收私信而导致任务进入阻塞状态,直到收到私信或超时或被清除转为就绪状态。
6、接收飞信阻塞状态[FET] 由接收飞信而导致任务进入阻塞状态,直到收到飞信或超时或被清除转为就绪状态。
7、接收邮件阻塞状态[MAL] 由接收邮件而导致任务进入阻塞状态,直到收到邮件或超时或被清除转为就绪状态。
8、接收消息阻塞状态[MSG] 由接收消息而导致任务进入阻塞状态,直到收到消息或超时或被清除转为就绪状态。 - 浮动状态[RDY]
浮动状态是临时的就绪状态。
超时阻塞的任务,当阻塞条件解除时,会转为浮动状态;
浮动状态的任务,当阻塞条件具备时,会转回阻塞状态。
浮动状态在任务管理器中显示为就绪状态。 - 超时状态[OSR]
当任务累计运行时间超过安全运行时,仍不能被比它优先级低的任务所切换,则进入超时状态并放弃处理器的使用权,直到系统空闲任务运行时才能恢复。 - 挂起状态[SPD]
任务暂停运行,除任务状态变为挂起外,任务的其它相关信息和数据均保持不变,当恢复任务后,可继续挂起时的断点运行。
运行状态的任务被挂起,恢复后为就绪状态;其它状态的任务被挂起,恢复后为原状态。 - 停止状态
任务停止运行,不可恢复。
1、任务栈溢出停止[!OF] 由任务栈溢出而导致任务停止运行。
2、任务栈重分配失败停止[!RF] 由任务栈重分配失败而导致任务停止运行。 - 删除/未启动状态(Deleted)
任务被删除或未启动,若想再次运行,必须重新启动该任务。

—————————— CosyOS-任务状态图 ——————————
任务队列
CosyOS全局只有一个任务队列,为双向循环链表,所有已启动且未删除的任务,都会在队列上排队。
- 排队原则
1、 在队列上从头至尾,按任务优先级由大到小排列。
2、 如果任务优先级相同,则按任务启动顺序排列(先启动的任务排在前面)。
3、 如果启用了任务管理器,那它一定是任务队列上的第一个任务(头节点),Debugger是第二个任务。
4、 唯一的例外,系统空闲任务是任务队列上的最后一个任务(尾节点),其它所有任务都会排在它的前面。
CosyOS任务管理器 真实的反应了整个任务队列的当前形态。
任务ID(TID)是按任务的启动顺序动态分配的(从1开始++),真实的反应了任务的启动顺序。
下方示例中消失的任务(TID:2)为系统启动任务(Starter),任务启动完成后,该任务默认被自动删除。

—————————— CosyOS-任务管理器 ——————————-———————— CosyOS-启动钩子 ————————
任务调度
CosyOS的 任务调度方式 包括 抢占式调度、时间片轮转调度。
不同优先级的任务实行抢占式调度,相同优先级的任务按任务队列中的排列顺序实行时间片轮转调度。
CosyOS的 任务调度时机 分为 定时调度 与 临时调度。
- 定时调度
在每个系统滴答周期,系统滴答中断都会触发一次任务调度。 - 临时调度
一、自动调度
当满足特定条件时,由系统自动触发的临时性的任务调度。
1、启动任务时,会立即触发任务调度。
2、设置任务优先级时,会立即触发任务调度。
3、归还互斥信号量完成时,如果发生了优先级继承(任务优先级被提升),会恢复该任务的原优先级,并立即触发任务调度。
4、当前任务被阻塞、挂起、删除时,将不再具备运行权限,会立即触发任务调度。
5、恢复任务时,如果该任务被恢复后为就绪状态,并且它的优先级高于当前任务的优先级,会立即触发任务切换。
6、设置阻塞时间为零或清除阻塞时,该任务会转为就绪状态,如果它的优先级高于当前任务的优先级,会立即触发任务切换。
7、给予/归还二值信号量时,如果有任务因获取该信号量而阻塞,如果该任务的优先级高于当前任务的优先级,会立即触发任务切换。
8、给予/归还计数信号量时,如果有任务因获取该信号量而阻塞,如果该任务的优先级高于当前任务的优先级,会立即触发任务切换。
9、发送私信至某任务时,如果该任务因接收私信而阻塞,如果该任务的优先级高于当前任务的优先级,会立即触发任务切换。
A、发送飞信时,如果有任务因接收该信箱的飞信而阻塞,如果该任务的优先级高于当前任务的优先级,会立即触发任务切换。
B、发送邮件时,如果有任务因接收该邮箱的邮件而阻塞,如果该任务的优先级高于当前任务的优先级,会立即触发任务切换。
C、发送消息时,如果有任务因接收该队列的消息而阻塞,如果该任务的优先级高于当前任务的优先级,会立即触发任务切换。
二、手动调度
由用户手动触发的临时性的任务调度。
当无法满足自动调度条件,用户又期望特定线程能够更及时的运行并处理事件,可采用手动调度方式。
CosyOS的任务调度策略可最大程度的实现尽可能实时高效的任务切换,使CosyOS的实时性更为优异。
软件定时器
CosyOS的软件定时器分为三类,延时定时器、定时中断定时器、定时查询定时器。
- 延时定时器 每个任务各有一个,用来在此任务中实现阻塞时间计数。
- 定时中断定时器 每个定时中断任务/钩子绑定一个定时中断定时器,至多可以有64个,定时器ID从0开始,用户通过调用API进行定时操作后,定时器将自动开始计数。
- 定时查询定时器 每个定时查询任务/钩子绑定一个定时查询定时器,至多可以有64个,定时器ID从0开始,用户通过调用API进行定时操作后,定时器将自动开始计数。
第二章、中断篇
什么是零中断延迟?
零中断延迟并非是中断响应时间为零,而是指当引入了RTOS以后,中断响应时间仍然能够达到MCU内核特性的响应时间,即只要中断发生,就能按中断优先级立即抢占,不存在指令级延误。也就是说,中断响应时间不受RTOS影响,与裸机编程是一样的。
零中断延迟的意义
MCU的核心价值在于中断的使用,实现对紧急事件的及时响应并处理。如果RTOS内核以关闭总中断的方式来处理内核服务、保护临界段,则意味着可能会丢失对高速中断的响应,并导致处理延误。而 “丢失响应” 往往是致命的,“处理延误” 可能会引发不良后果。
- 误区
即使关闭了总中断,中断被触发后标志位仍可置位,当开启总中断后仍可响应中断。
首先,这已经导致了“处理延误”,如果延误时间超出了允许范围,危害是可想而知的。
其次,如果关闭总中断期间,某个高速中断发生了两次或多次,当开启总中断后,也仅能响应并处理一次,即“丢失响应”。而有的事件,发生一次就得处理一次,如果有遗漏将导致致命的错误。 - 示例
高速通讯(接收丢帧)
高速捕获(丢失脉冲)
高速PWM
高速ADC
事实上,RTOS以关闭总中断的方式来保护临界段,是最为直接有效的,可实现最少的指令。CosyOS通过特殊的方法来保护临界段,必然是走了一条弯路,需要执行更多的指令,牺牲了整体的运行效率,但同时却换取了零中断延迟,这一点与RTOS通过牺牲整体的运行效率来换取实时性(对紧急事件的优先处理)是相同的道理。
零中断延迟是CosyOS的宗旨,是原则和底线,必须坚决贯彻落实。
零中断延迟原理
CosyOS-实时运行模型
- 该模型充分展示了CosyOS的运行机制,揭示了CosyOS零中断延迟的奥妙。
- 中断层 【用户中断按中断优先级实时抢占、零中断延迟】
- 用户中断
-> 中断本地服务的执行
-> 中断挂起服务的装载
- 用户中断
- 服务层 【内核服务】
- SysTick [minpri]
-> 软件RTC/定时器计数
-> 恢复定时任务
-> 调用定时钩子/滴答钩子(滴答服务的执行) - PendSV [minpri]
-> 中断挂起服务的执行
-> 任务调度/切换 - 任务临界区 [关闭SysTick/PendSV]
-> 任务服务的执行
- SysTick [minpri]
- 任务层 【不同优先级的任务抢占式调度,相同优先级的任务时间片轮转调度】
- Taskmgr[maxpri]
- Debugger[maxpri]
- Starter[maxpri-1]
- 一般用户任务[maxpri-1 ~ minpri+1:1]
- 用户空闲任务[minpri:0]
- 系统空闲任务[minpri:0]
零中断延迟基本原理
- 服务层中,SysTick、PendSV、任务临界区,三者间是互斥访问的。换言之,整个服务层是一个大临界区(服务层临界区)。
- 所有内核服务(中断本地服务除外),均在 “服务层临界区” 执行,从而保证服务的 “操作流” 不会被打断。
- 中断本地服务采用互斥访问机制。
服务详解
- 任务服务 是指在任务中调用的服务,并在任务临界区中执行。
- 滴答服务 是指在滴答中调用的服务,并在滴答中执行。 用户可在滴答钩子、定时中断钩子、定时查询钩子中调用滴答服务。
- 中断服务
是指在用户中断中调用的服务,分为 中断本地服务 和 中断挂起服务。
- 中断本地服务 是指在用户中断中调用并在本地直接执行的服务,需要互斥访问机制相配合。
- 中断挂起服务
是指在用户中断中调用的服务不在本地直接执行,而是把服务的相关内容存入局部的结构体中,
再把结构体指针存入中断挂起服务缓存队列(PendSV_FIFO),再触发PendSV,而后在PendSV中执行。
互斥访问机制
- 中断中读全局变量
重入访问:如果变量正在被写入,仍可成功读取变量。
首先,用户需定义全局变量的副本,并按照指定方式来更新副本。中断中读全局变量时,
需调用 iWhichGVarToRead 来询问:应该读哪一个全局变量?返回0读正本,返回1读副本。 - 中断中接收邮件
重入访问:如果邮箱正在被写入,仍可成功接收邮件(将读取写入过程所读取的局部邮箱)。
互斥访问:如果邮箱正在被写入,将返回失败(false)。 - 中断中接收消息
互斥访问:如果队列正在被访问,将返回失败(NULL)。
中断挂起服务缓存
CosyOS-II 中断挂起服务缓存队列(PendSV_FIFO),已实现所有内核的MCU均为先入先出队列(FIFO)。
中断挂起服务缓存队列,有必要先入先出吗?答案是确定的,如在同一中断中先后提请的各项服务,可能会存在时序逻辑关系。
再有,“入队列”与“出队列”的过程必须是原子操作,“操作流”不能被打断。
中断使用注意事项
- 在CosyOS中,中断是系统脱管的,用户对中断的使用与裸机编程是一样的。
- 只要中断不是最低优先级,就可实现零中断延迟。因此,用户中断应尽量避免使用最低优先级。[注1]
- 用户中断应遵循快进快出的原则,对事件仅做必要的紧急处理,再以同步方式通知 中断服务任务 做后续处理。
- 用户中断的最大执行时间应远小于系统滴答周期,这将促使整个系统更加良性的运行。
注1:其实,即使用户中断是最低优先级,也可能是零中断延迟的(当任务临界区未使用BASEPRI寄存器时),只不过
系统中断可能会与其抢占。而系统中断相对来说还是会占用更多的时间,可能会导致用户中断丢失响应或处理延误。
零中断延迟技术对比
CosyOS与其它RTOS零中断延迟技术的对比。
- FreeRTOS
FreeRTOS的零中断延迟有着很大的局限性。
仅Cortex-M3及以上含BASEPRI寄存器的内核支持在脱管的高优先级中断中实现零中断延迟,但同时却不能调用系统服务。
1、不能调用服务又如何实现同步?让用户自己实现同步又如何保证被同步任务的及时调度?实时性又何从谈起?
2、托管的低优先级中断无法实现零中断延迟。
3、其它内核无法实现零中断延迟。 - Keil RTX4/5
由SysTick[minpri]、PendSV[minpri]、SVC[minpri+1],构成服务层临界区,再配合互斥访问指令[LDREX/STREX/...]实现零中断延迟。
1、仅Cortex-M3及以上支持互斥访问指令的内核支持零中断延迟,其它内核无法实现零中断延迟。
2、建议用户中断不要使用最低两级优先级,以免被系统中断抢占,导致丢失响应或处理延误。
3、支持零中断延迟的中断中可随意调用服务。 - CosyOS-I/II
由SysTick[minpri]、PendSV[minpri]、任务临界区,构成服务层临界区,再配合互斥访问机制实现零中断延迟。
1、所有内核均可实现零中断延迟,只要用户中断不是最低优先级就可实现零中断延迟。
2、建议用户中断不要使用最低优先级,以免被系统中断抢占,导致丢失响应或处理延误。
3、支持零中断延迟的中断中可随意调用服务。
零中断延迟技术对比:
| RTOS | MCU条件 | 中断优先级条件 | 可随意调用服务? |
|---|---|---|---|
| FreeRTOS | BASEPRI寄存器 | 脱管的高优先级 | :tw-274c: |
| Keil RTX4/5 | 互斥访问指令[LDREX/STREX/...] | 高于最低两级优先级 | :tw-2705: |
| CosyOS-II | 1. 类[JBC]原子访问指令 2. 类[STR]原子访问指令 && GNU 3. 类[LDREX/STREX]互斥访问指令 |
高于最低优先级 | :tw-2705: |
第三章、线程通信
互斥信号量
仅适用于在任务中对任务级公共资源的资源同步(互斥访问)。
有优先级继承机制,以抑制优先级反转的发生。
互斥信号量均支持递归,最大嵌套深度:255。
二值信号量
二值信号量常用于行为同步、资源同步。
资源同步时,可实现对全局公共资源的资源同步。
由于在中断中获取时不能阻塞,可能会导致访问失败。
计数信号量
计数信号量常用于行为同步、资源管理。
行为同步时,尤其适用于并发同步(生产速度大于消费速度)。
事件标志组
事件标志组适用于对于一类事件的行为同步。
- 注意事项
1、事件标志组必须声明,因为需通过声明做类型定义。
2、任务中的部分应用,需用户自行进入任务临界区执行。
私信
- 典型特征:随意定义、灵活多变。
- 私信是CosyOS独创的一种任务间通信方式,其实质就是任务线程的形参,并以“调用任务”的方式发送私信。
它的主要优势在于易用性,私信参数的数量、名称、类型,都可随意定义,与普通函数定义形参如出一辙。 - 注意事项
1、私信只能发送给任务,中断中不能发送私信。
2、如果接收处理速度低于发送速度,会导致信息覆盖。 - 私信报警
当您使用私信时,编译后会有报警提示。
具体表现为,您定义的每个私信参数都对应一个未引用的局部变量(参数),名称为私信参数的名称后面加_。
如下方示例,某个任务创建了私信,共有三个参数p、a、b,则相应的会有报警p_、a_、b_,三个未引用的局部变量(参数)。
但这并不会影响私信功能的正常使用,用户可不必理会或在C51、C251、C/C++标签页屏蔽掉相应的报警。

8051内核,报警为warning C280;
80251内核,报警为warning C47;
Cortex-M内核,报警为warning: #177-D。
当私信的创建模式为智能创建模式时:
8051内核还另有报警R3_、R5_、R7_,三个未引用的局部变量;
80251内核还另有报警DR0_、DR4_、R11_,三个未引用的参数。
飞信
- 典型特征:极简类型、极速通信。
- 数据类型是极简的,针对不同的MCU内核,都要求数据类型必须是原子访问类型。
1、8051内核,飞信固定为1字节无符号类型(uint8_t);
2、80251内核,飞信固定为2字节无符号类型(uint16_t);
3、Arm32内核,飞信固定为4字节无符号类型(uint32_t)。 - 通信速率是极速的,仅使用一个变量(信箱),同时即是消息又是新消息标志。
- 注意事项
1、飞信为0时,表示无消息;飞信非0时,表示有消息;因此,用户传输的有效消息必须为真值。
2、信箱仅能保存一条飞信,如果接收处理速度低于发送速度,会导致信息覆盖。
3、每个信箱应该仅允许一个线程从中接收飞信。
消息邮箱
- 典型特征:任意类型、数据拷贝。
- 数据类型是任意的,CosyOS的消息邮箱支持随意定义数据类型,包括结构体、数组等。
- 注意事项
1、消息邮箱仅能保存一封邮件,如果收件速度低于发件速度,会导致邮件覆盖。
2、消息邮箱虽支持随意定义数据类型,但建议不要太大,以免影响系统实时性。
3、消息邮箱必须声明,因为需通过声明做类型定义。
4、每个邮箱应该仅允许一个线程从中接收邮件。
消息队列
- 典型特征:并发发送、指针引用。
- 队列类型
静态队列:收发消息的效率高,队列占用固定的内存;
动态队列:收发消息的效率低,接收消息后,队列内存可被回收。 - 注意事项
1、每个消息队列,用户应当自己明确消息的类型和size,此事与CosyOS无关。
2、每一处发送消息,消息缓存都是独享的,可静态创建(定义静态数组)或动态创建(动态内存分配)。
3、对于动态创建的消息缓存,在消息使用完以后,用户应当自己释放消息指针。
4、每个消息队列应该仅允许一个线程从中接收消息。
第四章、线程同步
事件同步
处理一个事件
- 一次同步
信号或标志 :【同步过程:设置 -> 查询{清除 + 处理事件}】;
二值信号量 :【同步过程:给予 -> 获取】;
阻塞和清除 :【同步过程:清除 -> 阻塞】;
恢复和挂起 :【同步过程:恢复 -> 挂起】;
设置优先级 :【同步过程:提升 -> 拉低】; - 并发同步
计数信号量 :【同步过程:给予 -> 获取】; - 无限同步
二值信号量 :【同步过程:给予 -> 等待】;【终止同步:上锁 -> 等待】;
处理一类事件
- 一次同步
事件标志组 :【同步过程:设置位 -> 查询组/等待组{依次查询位{清除位 + 处理事件}}】; - 并发同步
计数信号量 :每个事件都做为一个独立的事件,分别采用计数信号量实现并发同步。
消息同步
【同步过程:发送 -> 接收】;
- 一次同步
飞信 :极简类型、极速通信;
私信 :随意定义、灵活多变;
消息邮箱 :任意类型、数据拷贝; - 并发同步
消息队列 :并发发送、指针引用;
补充说明
- 一次同步 :事件的发生不存在并发,发生一次即处理一次。
- 并发同步 :事件的发生存在瞬时并发,导致“事件处理线程”来不急处理。
- 无限同步 :仅给予一次,“事件处理线程”便可无限次的、周期性的处理事件;通过上锁,可终止同步。
- 有的事件,发生一次就得处理一次,如果有遗漏将导致致命的错误。对于这种情况,如果存在并发,就必须采用并发同步工具;
- 有的事件,即使累计发生了多次未处理(并发),仍然仅处理一次即可。对于这种情况,虽存在并发,仍可采用一次同步工具。
第五章、资源与访问
资源
- 分类一
1、局部资源;
2、公共资源:任务级公共资源、全局公共资源; - 分类二
1、不会重入资源;
2、会重入资源:可重入资源、不可重入资源;
访问
- 分类一
只读访问、只写访问、读写访问(自运算); - 分类二
可重入访问、互斥访问;
临界区
CosyOS的临界区可分为 任务临界区、服务层临界区、全局临界区。
-
任务临界区
任务级的临界区保护,仅关闭系统中断(SysTick、PendSV)。
任务临界区不会破坏零中断延迟,当需要任务级的临界区保护时,可以考虑。 -
服务层临界区
【任务临界区 + 系统滴答 + 挂起服务调用/钩子】,即CosyOS实时运行模型中的服务层,是系统级的临界区保护。
具体过程:
1、任务中:在任务临界区中访问;
2、滴答中:直接访问即可;
3、中断中:采用挂起服务调用/钩子来访问;
# 挂起服务调用
void iPendSVC(fp);
# 挂起服务钩子
void pendsv_hook(void);
服务层临界区不会破坏零中断延迟,当需要系统级的临界区保护时,应首先予以考虑。
- 全局临界区
系统级的临界区保护,一般会关闭总中断。
具体方式:
8051/80251:操作EA,会关闭总中断。
Arm:
1、操作PRIMASK,会关闭总中断。
2、操作FAULTMASK,会关闭总中断。
3、操作BASEPRI,可实现不同掩蔽范围的全局临界区保护,不会关闭总中断。
全局临界区会破坏零中断延迟,应做为最后的选项,慎重使用。
CosyOS内核中从来不会进入全局临界区,提供此项服务只是为了便于用户对全局公共资源和程序过程的保护。
任务临界区、全局临界区,都支持嵌套功能,最大嵌套深度:255。
-
何时应用临界区
1、对于CosyOS已经提供服务支持的功能,用户直接调用API实现即可,无需考虑临界区问题。
2、只有在任务中访问“事件标志组”和“非原子全局变量”时,才需要用户自行进入“任务临界区”,参见《API用户参考手册》。
3、用户对其它公共资源和程序过程的保护,才需要考虑采用“临界区”或其它互斥访问方式来实现。 -
临界区应用原则
临界区应遵循快进快出的原则,临界段代码的执行时间应远小于系统滴答周期,这将促使整个系统更加良性的运行。
互斥访问
各访问者对同一个目标资源的访问过程是互相排斥的,即每次只允许一个访问者访问目标资源,目标资源是不可重入资源。
下述为CosyOS推荐方案:
- 任务级公共资源
1、互斥信号量:访问过程耗时的、实时性要求不高的,应尽量采用互斥信号量。
2、任务临界区:访问过程迅速的、实时性要求较高的,可考虑任务临界区。 - 全局公共资源
1、服务层临界区:即可实现全局成功的互斥访问,又不会破坏零中断延迟,是最优方案,应首先予以考虑。
2、二值信号量:弊端是如果获取失败将导致访问失败,而从概率上来说,获取失败是必然会发生的。
3、全局临界区:弊端是全局临界区会破坏零中断延迟,应做为最后的选项,慎重使用。
第六章、定时服务
我们知道,MCU都有硬件定时器和定时器中断,当定时器溢出时,CPU可以响应中断并调用定时器中断服务程序。
然而,硬件定时器的数量十分有限,在较复杂的应用中可能会不够用。
因此,CosyOS提供了软件定时器,以弥补硬件定时器数量的不足,同时也支持“定时服务”功能,来模拟定时器中断。
下面,简单对比一下MCU、FreeRTOS、CosyOS,三者间“定时服务”的区别:
-
MCU
-----------调用
中断响应 ——> 定时器中断服务程序 -
FreeRTOS
-----------调用
守护任务 ——> 定时器回调函数 -
CosyOS
-----------调用
系统滴答 ——> 定时中断/查询钩子
-----------恢复
系统滴答 ——> 定时中断/查询任务
FreeRTOS 是在守护任务中统一调用所有的定时器回调函数,所有“定时服务”享有相同的优先级,即守护任务的优先级。
CosyOS则分为两种情况:
1、在系统滴答中调用定时中断/查询钩子,“定时服务”享有系统滴答的优先级(高于任务);
2、在系统滴答中恢复定时中断/查询任务,“定时服务”的优先级即任务优先级(用户定义)。
注意:只有极为精简的代码才可创建为钩子,否则会对系统实时性造成不利影响。
可见,CosyOS的“定时服务”,优先级都可由用户灵活配置。
关于CosyOS的定时任务/钩子的应用示例可参考CosyOS源码:
1、任务管理器 Taskmgr,为定时查询任务,\System\taskmgr.c;
2、调试任务 Debugger,为定时中断任务,\System\debug.c;
3、调试钩子 debug_hook,为定时查询钩子,\System\debug.c。
第七章、任务管理器
可在系统配置文件中配置任务管理器的相关选项。
可使用超级终端或串口调试助手,作为任务管理器的输出窗口。

—————————— CosyOS-任务管理器 ——————————
-
Name:任务名称
-
TID:任务ID
-
PRI:任务优先级
-
STA:任务状态
-
CPU:CPU使用率
-
RAM:任务栈大小(字节数),下面举例说明:
32B/s64B,系统采用每调度监控计算出的在概率上必然存在的最大任务栈占用为32Bytes,任务栈已 静态创建 了64Bytes。
32B/m64B,系统采用每调度监控计算出的在概率上必然存在的最大任务栈占用为32Bytes,任务栈已 malloc 分配了64Bytes。
32B/r40B,系统采用每调度监控计算出的在概率上必然存在的最大任务栈占用为32Bytes,任务栈已 realloc 分配了40Bytes。 -
PSVFIFO:中断挂起服务缓存(PendSV_FIFO)的深度,如果您已开启PendSV_FIFO监控,会显示此项,历史最大值/设置值。
-
Task-PC:任务PC监控,如果您已开启任务PC监控,会显示此项。
当任务管理器被调度运行时,上一个被切换的任务入栈的PC值。
当有任务出现代码运行卡死或死循环时,此功能可用来帮助用户锁定具体位置,以便查找BUG。注意,PC值为下一条汇编指令的地址。
某任务出现代码运行卡死或死循环的表现:任务始终为就绪状态,且占用较高的CPU使用率,比它优先级低的任务一直都没有机会运行。 -
SysTick:系统滴答时间统计,如果您已开启系统滴答时间统计,会显示此项。
注意:系统滴答时间是系统滴答中断运行的平均时间,并非是任务切换时间。 -
Alarm:报警信息,如果有报警,会显示此项。
omq:消息队列溢出。
otq:任务队列溢出;
osr:安全运行时超时。
otp:任务优先级超出范围。
rts:任务栈重分配发生,虽然成功了,但仍要提醒用户任务栈定义小了。
任务栈重分配机制被设计用来在正式的产品中抵御任务栈溢出的风险,用户在开发测试阶段应避免这种情况的发生。
ots:在概率上、在未来必然会发生的任务栈溢出(虽然现在可能并未发生)。 -
Fault:故障信息,如果有故障,会显示此项。
mmn:消息节点内存分配失败;
mtn:任务节点内存分配失败;
mts:任务栈内存分配失败;
rts:任务栈内存重分配失败;
ots:任务栈溢出;
fst:启动任务失败;
erm:中断中接收消息错误(中断中不可接收来自动态消息队列的消息)。
opd:中断挂起服务缓存溢出。 -
任务管理器相关命令
1、taskmgr,启动任务管理器。
2、exit,退出任务管理器。
3、taskmgr /s=...,任务管理器更新速度,取值范围:[50~5000]ms。
注意:命令仅支持小写字母,结尾必须加回车换行(\r\n)。
第八章、任务栈
任务栈模式
CosyOS把任务栈的处理方式称之为任务栈模式,共包括三种类型,MSP模式、PSP模式、MSP+PSP模式。
- MSP模式 任务切换时,先拷贝主栈内容至当前任务栈(保存现场);再拷贝新任务栈内容至主栈(恢复现场)。
- PSP模式 每个任务的任务栈都是主栈,哪个任务运行时,哪个任务的任务栈(当前任务栈)就是当前主栈。
- MSP+PSP模式 中断时为Handler模式,入栈入的是主栈;任务时为Thread模式,入栈入的是当前任务栈。
对于51来说,CosyOS采用了搬栈这一传统方案,也就是MSP模式;
对于Arm来说,CosyOS所采用的方法当然是主流的双栈指针了,也就是MSP+PSP模式;
对于251来说,CosyOS支持MSP模式、PSP模式,这两种栈模式供用户选择。
-
PSP模式与MSP+PSP模式的异同
PSP模式,中断入栈也入任务栈,相比MSP+PSP模式,需要更大的任务栈,也就是需要更大的edata内存。 -
MSP模式的优势
凡事都是辩证的,有一利必有一弊。
MSP模式虽然在任务切换时会保存并恢复现场,导致效率较低,但只要主栈和内存池足够大,就可确保所有任务栈永不溢出。
当然,前提是任务栈得是动态的,CosyOS将自动启用任务栈重分配机制,可有效抵御任务栈溢出的风险。
这一点是PSP模式、MSP+PSP模式所不具备的。PSP模式、MSP+PSP模式,一旦任务栈溢出,既成事实,数据覆盖可能已经发生,无法挽回。 -
任务栈重分配机制
任务栈重分配机制,被设计用来在正式的产品中抵御任务栈溢出的风险。
在概率上,也许测试一年也不会碰到一次任务栈溢出,但大批量产品投入使用后不一定哪天就会溢出。
如果内存足够大,问题也好解决,但51/251的内存有限,既要节约着用还不要栈溢出就得用点策略了。
任务栈重分配机制的意义正在于此。
重点是,任务栈重分配机制是做为一种补救手段,不建议用户过分依赖。
开发调试时,还是要配置够用的任务栈,保证测试阶段不会溢出(不会发生重分配)。
任务栈重分配次数多了会导致内存碎片,如果过分依赖的话,反而会导致内存更加不够用。
任务栈重分配机制,就是为了当万一哪一天任务栈溢出了呢?能够满血复活。
启用条件:
1、对于8051,当任务创建模式非静态创建时,CosyOS将自动启用任务栈重分配机制。
2、对于80251,当任务栈模式为MSP模式、任务创建模式非静态创建时,CosyOS将自动启用任务栈重分配机制。
任务栈监控
CosyOS的任务栈监控包括每调度监控、入栈监控、线程入口监控。
- 每调度监控
每次任务调度时都会假定本次会切换任务、需要现场保护,计算当前任务需要多大的任务栈。
这种假定在概率上必然早晚都会发生,但不一定是什么时候(也许是即将发生、也许是一万年以后、也许是时间的尽头),从而提前预判任务栈溢出的风险。
应用技巧:
任务调度包括定时调度与临时调度,对于定时调度来说,就是系统滴答中断触发的,所以,用户在开发测试阶段可调整不同的 系统滴答周期,可提升“每调度监控”提前预判任务栈溢出风险的机率。
启用条件:
CosyOS启用“每调度监控”的充分必要条件是启用任务管理器,即系统配置中,“DEBUG接口设置”必须打勾。 - 入栈监控
当任务切换时,对当前任务栈进行计算,并做相关处理。 - 线程入口监控
线程入口监控现已取消,原因是虽然有效,但不实用。
任务栈定义
-
任务栈初定义
1、对于51及251的MSP模式,当任务创建模式非静态创建时,任务栈的初始定义可以小(可为零,因为有重分配机制),而后再根据监控情况重新调整任务栈。
2、对于251的PSP模式及Arm的MSP+PSP模式,任务栈的初始定义一定要够大,否则可能会运行不起来或死机,而后再根据监控情况重新调整任务栈。 -
任务栈调整
1、当任务栈重分配发生时,就需要重新调整任务栈的size了。
如:32B/r40B,此时就需要重新调整任务栈的大小为至少32个字节。
当这种情况发生时,还会有相应的报警提示用户,rts:任务栈重分配发生,虽然成功了,但仍要提醒用户任务栈定义小了。
2、当 “前面的字节数” 大于 “后面的字节数” 时,就需要重新调整任务栈的size了。
如:56B/m32B,此时就需要重新调整任务栈的大小为至少56个字节。
当这种情况发生时,还会有相应的报警提示用户,ots:在概率上、在未来必然会发生的任务栈溢出(虽然现在可能并未发生)。
3、对于251的PSP模式,由于中断入栈也入任务栈,所以需要用户自己根据中断的使用情况来计算一个中断嵌套入栈的最大size(不必包括最低优先级的中断入栈),累加到“前面的字节数”中,看是否会大于 “后面的字节数”,再行调整。 -
任务栈补偿
1、在开发测试阶段,用户应尽量设法模拟各种情况的发生,最好是频繁发生,包括中断的发生。只要你的产品支持的功能、情况,都要设法模拟到。
2、通过足够时间的测试,统计出各任务在任务管理器当中的“RAM”项中,系统采用每调度监控(假定入栈)计算出的在概率上必然存在的最大任务栈占用,而后在此值的基础上,再累加一个补偿增量,再重新定义任务栈size。
示例:经过长期的测试以后,某任务的任务栈为:56B/m64B,可重新定义任务栈的size为:56 + X,X为补偿增量。
原因是无论 每调度监控 如何的假定入栈,也可能会存在着更大的任务栈需求,只是未能监测到,所以必须增加一定的补偿。
而这个补偿量的大小,每个任务都可根据剩余内存的大小而灵活配置,最好能尽量大一些,尤其是对于没有任务栈重分配机制的情况来说。
没有任务栈重分配机制的情况:
1、任务创建模式为静态创建;
2、251的PSP模式;
3、ARM。
这个补偿增量对于51来说可描述为2的整数倍:2 x N,N为每调度监控未能监测到的N次的函数嵌套调用。
对于251、ARM来说则无法描述。
附 录、调用服务注意事项
CosyOS-II 在临界区中或关闭总中断时调用服务注意事项
- 在任务中,以下服务不支持在临界区中(包括任务临界区和全局临界区)或关闭总中断时调用:
1、启动任务:uStartTask(task, status)、uStartTask_Ready(task)、uStartTask_Suspend(task)。
2、互斥信号量获取:uTakeMut(mut, tc)。
3、二值信号量等待:uWaitBin(bin, tc)。
4、二值信号量获取:uTakeBin(bin, tc)。
5、计数信号量获取:uTakeSem(sem, tc)。
6、接收私信:uRecvTaskMsg(tc)。
7、接收飞信:uRecvFetion(tbox, tc)。
8、接收邮件:uRecvMail(mail, mbox, tc)。
9、接收消息:uRecvMsg(que, tc)。
A、等待标志组:uWaitFlagGroup(group, tc)。 - 其它会导致任务调度/切换的服务,虽支持在临界区中或关闭总中断时调用,但也必须等到最终退出临界区或开启总中断后才能切换任务。
CosyOS-II 在中断中调用服务注意事项
- 中断中写全局变量
一、当用户在中断中调用iWriteGVar(gv, lv)、iWriteGAry(gp, lp, size)、iWriteGStr(gs, ls)写全局变量时,局部变量要做为专用:
1、如果局部变量被更新,之后就必须调用服务写同一个全局变量。
2、调用服务以后,该局部变量在本次中断中不可被改变,因为写全局变量为挂起服务。 - 中断中发送邮件
一、当用户在中断中调用iSendMail(mbox, mail)发送邮件时,mail所指向的局部邮箱要做为专用:
1、如果局部邮箱被更新,之后就必须调用服务发送邮件至同一个邮箱。
2、服务调用以后,该局部邮箱在本次中断中不可被改变,因为发送邮件为挂起服务。
二、当邮箱的数据类型为非原子访问类型时,中断最好不要发送邮件给中断,除非这个中断的发生频率足够低,可确保在PendSV中最终完成发送以后,这个中断才会再次发生,否则会有出错的风险。