- 打卡等级:以坛为家II
- 打卡总天数:432
- 最近打卡:2025-05-01 16:19:56
荣誉版主
- 积分
- 4263
|
发表于 2023-7-19 22:14:12
|
显示全部楼层
本帖最后由 CosyOS 于 2023-9-10 12:18 编辑
CosyOS 的三大技术特点
今天,我们从实际出发,来谈一谈CosyOS的技术特点,而不是采用“假大空”、“高大上”的方式,如“某某风格”、“面向某某”,等华丽的词藻。
一:内核对象静态创建
CosyOS的内核对象,一般采用静态创建方式,这样可以一步完成创建,简单易用。
调用API操作内核对象时,一般均输入内核对象的名称,而非句柄指针,具体请参考API相关说明。
二:所有内核全局不关总中断(零中断延迟)
此前已做过多次论述,此处不再详述,只再重复一句话:
★★★只要您的用户中断不是最低优先级就可实现零中断延迟★★★
三:独家技术实现系统服务的可重入,使51彻底摆脱可重入栈全面提速
系统服务分为滴答服务、任务服务、中断同步服务、中断异步服务等,各类服务分别调用互不干涉。
首先您需知道,系统滴答、PendSV、任务临界区中,三者之间是互斥访问的。详情请参阅《CosyOS-所有内核全局不关中断原理.md》。
滴答服务:仅在系统滴答/定时器0中断中调用,所以不会重入。
中断异步服务:服务的结构体指针入中断服务栈后在PendSV中执行,所以不会重入。
中断同步服务:采用特别的优化,实现了服务函数的可重入(形参和局部变量均为寄存器变量),
再配合互斥访问机制,实现了全局资源(全局变量、消息邮箱、消息队列)的互斥访问。
详情请参阅源码,关于互斥访问机制,请参阅《CosyOS-所有内核全局不关中断原理.md》。
任务服务:
任务服务大体上可分为三类,需重点讲解一下:
1:本地代码
do{ \
进入任务临界区; \
本地执行服务; \
退出任务临界区; \
}while(false)
由于是本地独立的代码,并非调用函数,所以不存在重入的问题。
CosyOS仅在两种情况下才会采用本地代码:
(1):对于51单片机来说,调用函数的代码量(仅指调用过程,即传参)比本地代码的代码量还要大(如定时中断、定时查询);
(2):如果不采用本地代码将无法实现服务功能(如发送私信)。
2:调用服务函数(无返回值)
do{ \
进入任务临界区; \
调用服务函数(); \
}while(false)
由于是在进入任务临界区之后才调用的服务函数,所以服务函数是不会重入的,也不需要可重入。
服务函数在返回时会自动退出任务临界区。
3:调用服务函数(有返回值)
( \
__enter_critical() || true ? 调用服务函数() : 返回一个其它值 \
)
采用三目运算,巧妙的实现了在进入任务临界区之后再调用服务函数并返回值。三目运算的优势在于可进行常量优化。
__enter_critical()返回后,|| true,其结果必然为真,编译后将不再做条件判断,而是直接调用服务函数并返回值。“为假时返回一个其它值”也会被优化掉。
下面以启动任务为例加以说明:
/* API */
#define /*|ecode|*/uStartTask(task, state) sUSV_StartTask(&vTaskHandle_##task, state)
/* 服务 */
#define sUSV_StartTask(thdl, state) \
( \
__enter_critical() || true ? __start_task(thdl, !state ? __READY__ : __READY__ | __SUSPENDED__) : false \
)
C51应用示例:
uStartTask(sensor_task, __READY__);
编译后:
C:0xC026 12DA0B LCALL enter_critical(C:DA0B)
C:0xC029 7E14 MOV R6,#0x14
C:0xC02B 7F06 MOV R7,#0x06
C:0xC02D E4 CLR A
C:0xC02E FD MOV R5,A
C:0xC02F 12D4E8 LCALL _start_task(C:D4E8)
可见,编译后为三步:
1、调用enter_critical();
2、传参(R6、R7为任务句柄的指针,R5为任务状态);
3、调用__start_task()。
优化掉了其它无用的环节,厉害了吧?
对于这一类服务又分为两种情况:
一、在服务函数内会一气呵成,最后退出任务临界区并返回常量或寄存器变量。
。。。。。。
进入任务临界区 → 本地执行服务 → 退出任务临界区并返回
。。。。。。
这一类服务函数,在退出任务临界区之前是不会重入的,在退出之后是可重入的。
由于这一类服务比较简单,不再举例说明。
二、会中途退出任务临界区并触发任务调度,当再次回到当前位置时(任务切换回来时),会再次进入任务临界区并继续执行服务,而后再退出任务临界区并返回常量或寄存器变量。
。。。。。。
第一阶段:进入任务临界区 → 服务入[vTASKING] → 退出任务临界区并触发任务调度
PendSV中切换任务(可能)
第二阶段:再次进入任务临界区 → 继续执行服务 → 退出任务临界区并返回
。。。。。。
这一类服务是含有超时机制的服务,共包括八大服务:等待或获取二值信号量、获取互斥信号量、获取计数信号量、等待标志组、任务中接收飞信、任务中接收私信、任务中接收邮件、任务中接收消息。
具体过程:(vTASKING 为当前任务节点的指针)
1、在第一阶段,已将内核对象指针存入vTASKING->ptr;
2、而后在PendSV中,会访问非延时阻塞(超时阻塞)状态的各任务节点->ptr,以调整任务状态、查找最高优先级的就绪任务;
3、在第二阶段,再次进入任务临界区后,将vTASKING->ptr赋值给内核对象指针变量,继续执行服务。
这一类服务函数,在第一阶段是不会重入的,第二阶段也是不会重入的,但服务函数可能已经重入了(因为可能切换过其它任务)。
对于51来说,内核对象指针可能已经改变(因为内核对象指针变量有可能是内存变量,服务重入后指针将改变),
所以需要将vTASKING->ptr再次赋值给内核对象指针变量以恢复原值,然后再执行服务。
下面以获取二值信号量为例加以说明:
/* API */
#define /*|res|*/uTakeBin(bin, tc) sUSV_TakeBin(bin, tc)
/* 服务 */
#define sUSV_TakeBin(bin, tc) \
( \
__enter_critical()||true?__take_bin((bool *)&bin, tc):false \
)
/* 服务函数 */
bool __take_bin(bool _XDATA_MEM_ *p, tDelay tc) // p为二值信号量指针变量,对于51来说,_XDATA_MEM_为xdata
{
if(*p) goto __RET_TRUE;
if(!tc) goto __RET_FALSE;
vDELAY_STMR[vTASKING->TID] = tc; // 赋值超时时间给延时定时器
vTASKING->state = __BLOCKED__; // 赋值当前任务状态为阻塞
vTASKING->blocktype = __BINARY__ | sizeof(bool); // 赋值阻塞类型为二值信号量阻塞,指针为单字节数据类型
vTASKING->ptr = p; // 将二值信号量指针存入vTASKING->ptr
suTaskScheduler; // 退出任务临界区并触发任务调度,当再次回到当前位置时,服务函数可能已经重入,对于51来说,内核对象指针可能已经改变
p = (bool *)vTASKING->ptr; // 所以需要将vTASKING->ptr重新赋值给二值信号量指针变量
if(*p) goto __RET_TRUE; // 再次访问二值信号量指针变量
__RET_FALSE:
mExitCritical;
return false;
__RET_TRUE:
*p = false; // 再次访问二值信号量指针变量
mExitCritical;
return true;
}
可见,通过上述方法,可完美实现这一类服务的可重入,使51彻底摆脱可重入栈全面提速。
上述方法虽然会导致251、Arm等内核执行服务的性能稍有下降,但对于51来说,性能却有质的提升。
而且此法还适用于任何MCU和编译器,都能确保安全可靠和卓越性能。
对于51来说,在类型定义(os_redef.h)和服务函数中,内核对象指针变量一律指定指针域为xdata,这样操作不仅可显著的减少内存占用和代码量,还可提速。
对于51来说,用户需注意,内存模型一定要选择Large。
以上就是CosyOS的三大技术特点。(完)
|
|