25 KiB
第一章、任务篇
一、任务优先级分组
可在系统配置文件中配置任务优先级分组。
共有六个分组可供选择,8级、16级、32级、64级、128级、256级,您也可自定义级数。
如您配置的分组为8级,则最高优先级为7级,最低优先级为0级。
二、时间片模式
CosyOS有三种时间片的定义模式,全局时间片、算法时间片、自定义时间片,需在系统配置文件中定义。
1. 全局时间片 所有任务采用统一的时间片。
2. 算法时间片 不同优先级的任务时间片不同,采用一个算法公式来描述。
3. 自定义时间片 不同优先级的任务时间片不同,用户自定义各优先级的时间片。
全局时间片简单高效,时间片轮转调度性能为最佳;算法时间片、自定义时间片可实现不同优先级任务精细的时间片控制。
时间片的取值范围:1~65535,单位为滴答周期。
三、安全运行时
安全运行时是CosyOS的安全关键技术之一,是为了防止某任务长期独占或超时使用处理器。当任务累计运行时间超过安全运行时,仍不能被比它优先级低的任务所切换,则进入超时状态并放弃处理器的使用权,直到系统空闲任务运行时才能恢复。
可在系统配置文件中设置安全运行时的开启或关闭。
安全运行时的取值范围:0~65535,单位为时间片,0为无限长。
所有空闲任务无法应用安全运行时,创建任务时,安全运行时输入0即可。
安全运行时的应用技巧:开发初期,所有任务的安全运行时可设置为0;待开发测试阶段中后期,再根据任务各自的特点及测试经验合理的调整为有效值。
四、支持任务/钩子
-
系统任务
由CosyOS自主创建并自动启动的任务。
1. 任务管理器(Taskmgr) 优先级固定为最高优先级。
2. 调试任务(Debugger) 优先级固定为最高优先级,用做命令行和任务管理器的串口接收解析。
3. 启动任务(Starter) 优先级固定为比最高优先级低一级,通过调用启动钩子来启动用户任务,而后删除自身。
4. 系统空闲任务(Sysidle) 优先级固定为最低优先级0级,是系统级的空闲任务。 -
系统钩子
由CosyOS自主创建并自动调用的函数,并由用户来写代码。
1. 初始化钩子(init_hook) 在主函数中首先被调用,适用于初始化时钟、GPIO、寄存器等工作。
2. 启动钩子(start_hook) 在启动任务中被调用,用于启动用户任务。用户可在启动钩子内调用API来启动用户任务。
3. 滴答钩子(tick_hook) 每个系统滴答周期,在SysTick_Handler/定时器0中断中都会被调用一次,适用于每滴答周期/秒/分/时/...做一次的工作。
4. 空闲钩子(idle_hook) 在系统空闲任务中被调用,用户可添加自己的代码做一些没有实时性要求的工作。
5. 挂起服务钩子(pendsv_hook) 详情见全局变量访问相关介绍及说明。
6. 全局变量钩子(gvar_hook) 用于更新全局变量的副本。详情见全局变量访问相关介绍及说明。 -
定时任务/钩子
包括定时中断任务/钩子和定时查询任务/钩子,其实质都是软件定时器中断。
1. 定时中断任务/钩子 由用户设定定时时间,当定时器溢出时,系统将自动恢复/调用与其绑定的任务/钩子并自动重复定时(如果您已开启定时器的自动重装载)。
2. 定时查询任务/钩子 由用户设定定时时间,当定时器溢出后,系统在每个滴答周期都会查询用户定义的事件,若事件为真,系统将自动恢复/调用与其绑定的任务/钩子并自动重复定时(如果您已开启定时器的自动重装载)。系统初始化后,所有定时查询定时器的值均为零,相当于已经溢出,系统已经开始查询用户定义的事件了。
3. 任务与钩子的区别 由于定时中断钩子、定时查询钩子都是在SysTick_Handler/定时器0中断中被调用,所以只有相对精简的代码才适合创建钩子,耗时长的代码应创建为任务。再有,钩子相当于比任务具有更高的优先级,可更及时的被执行。 -
一般用户任务
对于一般用户任务,只要优先级为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. 如果启用了任务管理器,那它一定是任务队列上的第一个任务(头节点)。
4. 唯一的例外,系统空闲任务是任务队列上的最后一个任务(尾节点),其它所有任务都会排在它的前面。
CosyOS任务管理器真实的反应了整个任务队列的当前形态。
其中任务ID(TID)是按任务的启动顺序动态分配的(从1开始++),真实的反应了任务的启动顺序。
下方示例中消失的任务(TID:2)为系统启动任务(Starter),任务启动完成后,该任务默认被自动删除。

—————————— CosyOS-任务管理器 ——————————-———————— CosyOS-启动钩子 ————————
七、任务调度
CosyOS的任务调度类型包括 抢占式调度 、 时间片轮转调度 。不同优先级的任务实行抢占式调度,
相同优先级的任务按先入先出实行时间片轮转调度,“入”指的是加入到任务队列,即启动任务。
CosyOS的任务调度时机分为 定时调度 与 临时调度 。
- 定时调度
在每个系统滴答周期,SysTick_Handler/定时器0中断,都会触发一次任务调度。 - 临时调度
1. 当前任务被阻塞、挂起、超时、停止、删除时,会立即触发任务调度。
2. 恢复任务时,如果恢复后该任务可以运行并且它的优先级高于当前任务的优先级,会立即触发任务调度。
3. 设置任务优先级时,如果该任务可以运行并且它的新优先级高于当前任务的优先级,会立即触发任务调度。
4. 设置阻塞时间为零或清除阻塞时,该任务会转为就绪状态,如果它的优先级高于当前任务的优先级,
会立即触发任务调度。
5. 归还互斥信号量完成时,如果发生过优先级继承(该任务的优先级被提升),会恢复该任务的原优先级,
并立即触发任务调度。
6. 用户通过调用服务,直接触发的任务调度,包括任务级任务调度和中断级任务调度。
CosyOS的任务调度策略可最大程度的实现尽可能实时高效的任务切换,使CosyOS的实时性更为优异。
八、任务栈模式
CosyOS把任务栈的处理方式称之为任务栈模式,共包括三种类型,MSP模式、PSP模式、MSP+PSP模式。
1.MSP模式 任务切换时,先拷贝主栈内容至当前任务栈(保存现场);再拷贝新任务栈内容至主栈(恢复现场)。
2.PSP模式 每个任务的任务栈都是主栈,哪个任务运行时,哪个任务的任务栈(当前任务栈)就是当前主栈。
3.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-任务管理器 ——————————
-
相关名词解释
Name:任务名称
TID:任务ID
PRI:任务优先级
STA:任务状态
CPU:CPU使用率
RAM:任务栈大小(字节数) -
RAM项说明
示例1:32B/s64B,系统采用每调度监控计算出的在概率上必然存在的最大任务栈占用为32Bytes,任务栈已 静态创建 了64Bytes。
示例2:32B/m64B,系统采用每调度监控计算出的在概率上必然存在的最大任务栈占用为32Bytes,任务栈已 malloc 分配了64Bytes。
示例3:32B/r40B,系统采用每调度监控计算出的在概率上必然存在的最大任务栈占用为32Bytes,任务栈已 realloc 分配了40Bytes。
每调度监控:是每次任务调度时都会监控,并假定本次会切换任务、需要现场保护,计算当前任务需要多大的任务栈。这种假定在概率上必然早晚都会发生,但不一定是什么时候(也许是即将发生、也许是一万年以后、也许是时间的尽头),从而提前预判任务栈溢出的风险。
任务调度包括定时调度与临时调度,对于定时调度来说,就是系统滴答中断触发的,所以,用户在开发测试阶段可适当定义一个较小的 系统滴答周期,可提升 每调度监控 提前预判任务栈溢出风险的机率。
1、当任务栈重分配发生时,就需要重新调整任务栈的size了。
如:32B/r40B,此时就需要重新调整任务栈的大小为至少32个字节。
当这种情况发生时,还会有相应的报警提示用户,rts:任务栈重分配发生,虽然成功了,但仍要提醒用户任务栈定义小了。
2、当 “前面的字节数” 大于 “后面的字节数” 时,就需要重新调整任务栈的size了。
如:56B/m32B,此时就需要重新调整任务栈的大小为至少56个字节。
当这种情况发生时,还会有相应的报警提示用户,ots:在概率上、在未来必然会发生的任务栈溢出(虽然现在可能并未发生)。
-
任务栈之初定义
1、对于51及251的MSP模式,当任务创建模式非静态创建时,任务栈的初始定义可以小(因为有重分配机制),而后根据监控及报警情况再增大。
2、对于251的PSP模式及Arm的MSP+PSP模式,任务栈的初始定义一定要够大,否则可能会运行不起来或死机,而后再根据测试情况重新调整任务栈。
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启用“每调度监控”的充分必要条件是:启用任务管理器,即系统配置中,“DEBUG接口设置”必须打勾。
“每调度监控”属于“任务栈监控”,是CosyOS任务管理器的标配选项。
PSVFIFO:中断挂起服务缓存(PendSV_FIFO)的深度,如果您已开启PendSV_FIFO监控,会显示此项,历史最大值/设置值。
Task-PC:任务PC监控,如果您已开启任务PC监控,会显示此项。
当任务管理器被调度运行时,上一个被切换的任务入栈的PC值。当有任务出现代码运行卡死或死循环时,此功能可用来帮助用户锁定具体位置,以便查找BUG。注意,PC值为下一条汇编指令的地址。
某任务出现代码运行卡死或死循环的表现:任务始终为就绪状态,且占用较高的CPU使用率,比它优先级低的任务一直都没有机会运行。
SysTick:系统滴答时间统计,如果您已开启系统滴答时间统计,会显示此项。
注意:系统滴答时间是SysTick_Handler运行的平均时间,并非是任务切换时间。
Alarm:报警信息,如果有报警,会显示此项。
omq:消息队列溢出。
otq:任务队列溢出;
osr:安全运行时超时。
otp:任务优先级超出范围。
rts:任务栈重分配发生,虽然成功了,但仍要提醒用户任务栈定义小了。
任务栈重分配机制被设计用来在正式的产品中抵御任务栈溢出的风险,用户在开发测试阶段应避免这种情况的发生。
ots:在概率上、在未来必然会发生的任务栈溢出(虽然现在可能并未发生)。
Fault:故障信息,如果有故障,会显示此项。
mmn:消息节点内存分配失败;
mtn:任务节点内存分配失败;
mts:任务栈内存分配失败;
rts:任务栈内存重分配失败;
ots:任务栈溢出;
fst:启动任务失败;
erm:中断中接收消息错误(中断中不可接收来自动态消息队列的消息)。
opd:中断挂起服务缓存溢出。
任务管理器现支持如下命令:
- taskmgr,启动任务管理器。
- exit,退出任务管理器。
- taskmgr /s=...,任务管理器更新速度,取值范围:[50~5000]ms。
注意:命令仅支持小写字母,结尾必须加回车换行(\r\n)。
第二章、中断篇
什么是零中断延迟?
零中断延迟并非是中断响应时间为零,而是指当引入了RTOS以后,中断响应时间仍然能够达到MCU内核特性的响应时间,即只要中断发生,就能按中断优先级立即抢占,不存在指令级延误。也就是说,中断响应时间不受RTOS影响,与裸机编程是一样的。
零中断延迟的意义
MCU的核心价值在于中断的使用,实现对紧急事件的及时响应并处理。如果RTOS内核以关闭总中断的方式来处理内核服务、保护临界段,则意味着会丢失对高速并发事件的响应,并导致处理延误。而“丢失响应”往往是致命的,“处理延误”可能会导致不良后果。
- 误区
即使关闭了总中断,用户中断被触发后标志位仍可置位,当开启总中断后仍可响应中断。
首先,这已经导致了“处理延误”,如果超出允许范围,后果是可想而知的。
其次,如果关闭总中断期间,某个用户中断发生了两次或多次(并发),当开启总中断后仅能响应并处理一次,即“丢失响应”。 - 示例
高速通讯(丢帧)
高速PWM(失调、影响调制精度)
高速信号采样(丢失采样、采样点偏离)
事实上,RTOS以关闭总中断的方式来保护临界段,是最为直接有效的,可实现最少的指令。CosyOS通过特殊的方法来保护临界段,必然是走了一条弯路,需要执行更多的指令,牺牲了整体的运行效率,但同时确换取了零中断延迟,这一点与RTOS通过牺牲整体的运行效率来换取实时性是相同的道理。
零中断延迟是CosyOS的宗旨,是原则和底线,必须坚决贯彻落实。
零中断延迟基本原理
- 服务层中,SysTick、PendSV、任务临界区,三者间是互斥访问的。换言之,整个服务层是一个大临界区。
- 所有内核服务(中断本地服务除外),均在服务层(临界区)执行,从而保证服务的“操作流”不会被打断。
- 中断本地服务采用互斥访问机制。
服务详解
- 任务服务 是指在任务中调用的服务,并在任务临界区中执行。
- 滴答服务 是指在滴答中调用的服务,并在滴答中执行。 用户可在滴答钩子、定时中断钩子、定时查询钩子中调用滴答服务。
- 中断服务 是指在用户中断中调用的服务,分为中断本地服务和中断挂起服务。
- 中断本地服务 是指在中断中调用并直接本地执行的服务,需要互斥访问机制相配合。
- 中断挂起服务 是指在中断中调用的服务不在本地直接执行,而是把服务的相关信息存入局部的结构体中, 再把结构体指针存入中断挂起服务缓存,再触发PendSV,而后在PendSV中执行。
互斥访问机制
-
用户中断中读全局变量
重入访问:如果变量正在被写入,仍可成功读取变量。
首先,用户需定义全局变量的副本,并按照指定方式来更新副本。中断中读全局变量时,
需调用 iWhichGVarToRead 来询问:应该读哪一个全局变量?返回0读正本,返回1读副本。 -
用户中断中接收邮件
重入访问:如果邮箱正在被写入,仍可成功接收邮件(将读取写入过程所读取的局部邮箱)。
互斥访问:如果邮箱正在被写入,将返回失败(false)。 -
用户中断中接收消息
互斥访问:如果队列正在被访问,将返回失败(NULL)。
中断挂起服务缓存
CosyOS-II 中断挂起服务缓存队列,已实现所有内核的MCU均为先入先出队列(FIFO)。
中断挂起服务缓存队列,有必要先入先出吗?答案是确定的,原因在于当中断并发时,服务的执行顺序要符合“不同中断间可能存在的”和“同一个中断中必然存在的”提请服务的时序逻辑关系。再有,“入队列”与“出队列”的过程必须是原子操作,操作流不能被打断。
中断使用注意事项
- 在CosyOS中,中断是系统脱管的,对中断的使用与裸机编程是一样的。
- 只要用户中断不是最低优先级,就可实现零中断延迟。其实,即使是最低优先级,也是零中断延迟的,只不过系统中断可能会与其抢占。所以用户中断还是尽量避免使用最低优先级。
- 用户中断应遵循快进快出的原则,仅对事件做紧急处理,而把可以稍缓的工作转为在任务中执行(中断服务任务)。
- 用户中断的最大执行时间应远小于系统滴答周期,这将促使整个系统更加良性的运行。