Nucleus实时操作系统分析报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
目录
一、NUCLEUS的内核(KERNEL) (3)
1.1 系统启动 (3)
1.2 初始化线程 (4)
1.3 线程调度 (4)
1.3.1 任务的调度 (5)
1.3.2 中断的调度 (9)
1.3.3 操作系统数据结构的保护 (14)
1.4 任务间通信 (16)
1.4.1 消息管道(Pipes) (17)
1.5 任务的同步 (21)
1.6 定时器 (21)
1.7 内存管理 (22)
1.8 输入/输出设备驱动 (23)
N UCLEUS内核总结: (23)
二、NUCLEUS的开发工具 (25)
2.1 N UCLEUS C++ (25)
2.2 N UCLEUS MNT (25)
2.3 N UCLEUS VNET (25)
2.4 N UCLEUS PC+ (25)
2.5 N UCLEUS FILE (25)
2.6 N UCLEUS CLIB (26)
2.7 N UCLEUS GRAFIX (26)
2.8 N UCLEUS EDE (26)
2.9 N UCLEUS UDB (27)
2.10 N UCLEUS DEBUG+ (27)
2.11 第三方产品的支持 (29)
2.12 SDS S INGLE S TEP (29)
三、NUCLEUS的网络支持 (31)
3.1 I NTERNET套件 (31)
Nucleus NET (31)
Nucleus SNMP (31)
Nucleus RMON (32)
Nucleus SP AN (32)
3.2 N UCLEUS W EB S ERV (32)
3.3 N UCLEUS J VI (32)
3.4 N UCLEUS EPILOGUE (32)
Nucleus实时操作系统是Accelerater Technology公司开发的嵌入式RTOS产品,只需一次性购买Licenses,就可以获得操作系统的源码。
Nucleus购买的灵活性比较大:Kernel,Networking,File System,Web Technology,Target Debugger可以分开购买,如果我们只需要微内核的话只要购买Kernal和Debugger,当前的下位机就是这样配置的。
但是,如果以后我们要开发接入服务器和IP Phone的话就必须购买一堆网络协议;上位机要用的话还要购买文件系统。
VxWorks就不是这样,其OS基本部分中已经包括了很多基本的网络协议,只有一些比较特殊的网络协议需要单独购买,比如:SNMP1/2,OSPF2,IPX等。
当然,Nucleus的好处在于各层协议都提供的是源码,Vxworks则不提供。
Nucleus的另一大好处是程序员不用写板支持软件包(BSP),因为操作系统已经开放给程序员,不同的目标板在操作系统BOOT时可以通过修改源码进行不同的配置。
对于程序员来说,写BSP是一项比较繁琐的任务,有了OS的源码这项工作就简单多了,同时调试时也可以跟踪到中断、寄存器那一级,简化了硬件的调试。
VxWorks和pSOS都必须购买标准的BSP模板工具来写板支持软件包,开发工作量就比较大了。
Nucleus对CPU的支持能力比较强,支持当前流行的大多数RISC、CISC、DSP处理器,比如:80x86(实时、保护模式)、68xxx、PowerPC、i960、MIPS、SH、ARM、ColdFire。
Nucleus 系统对于不同的处理器,操作系统的源码大部分是相同的,只有5%的源码是用汇编写的跟CPU 有关。
如果使用另外的CPU,则只用修改5、6个汇编程序就可以进行移植,但是对于不同系列的处理器,它们的Compiler、Linker、Debugger是不同的(要另外购买),而且编译预处理的过程也是不一样的,这就要投入一部分力量去进行操作系统的移植。
当然,这也是Nucleus的一个优点,VxWorks和pSOS都没有源码,换一个系列的CPU操作系统又要重新购买,实际上他们OS开发的工作量并没有多少,但却造成了用户的重复性投资。
当然,Nucleus作为一个非主流的操作系统,其自身也存在着很多不足之处,比如:实时性不够、定时中断管理不可靠、I/O管理太简单、操作系统的调试工具太少等。
下面分别说明。
一、Nucleus的内核(Kernel)
Nucleus的核心是一个实时的多任务内核——Nucleus PLUS,具有以下特性(Accelerater Technology公司宣称的):
可移植性:
Nucleus PLUS可用于大多数流行的处理器。
因为Nucleus PLUS主要是用标准C写的,移植到新的处理器系列相对很直接。
这也就意味着用Nucleus PLUS开发的应用程序也具有很高的可移植性。
可用性:
不像其他的商业内核,Nucleus PLUS的系统调用名直接表明了它的功能。
比如,你可以通过Nu_Create_Task系统调用来创建一个任务。
Nucleus PLUS的系统调用都设计成具有类似的入口参数和返回值类型。
Nucleus PLUS的对象都不隐式地和别的对象相关。
比如,邮箱和任务之间是不相关的。
因此,用户可以利用多个Nucleus PLUS对象之间的结合形成混合系统调用。
配置:
Nucleus PLUS最终是以C库的形式提供给用户,你可以选择所需要的部分链入到你的应用程序中。
但是,其可裁剪性并不好,如果某些系统功能不要的话,必须在Nucleus.h的头文件中加一些宏定义,比如定义:#define NU_ENABLE_STACK_CHECK,那么每个系统调用时都要进行堆栈检查,否则就不进行堆栈检查,可以删除的功能并不多。
VxWorks的可裁剪性就强多了,有300多个独立模块,微内核6K,最小系统<30K,配全了可达到几百K。
特性:
①、快速响应时间:对临界资源的检测时间不依赖于占有该临界资源的线程执行时间的长短,一旦低优先级线程释放掉临界资源(不管其是否执行完),高优先级线程就会抢占运行。
②、每个任务的执行时间和其他任务的处理时间无关。
③、较高吞吐量:随着任务数目的增多,任务的调度时间为常数。
④、可扩展性:利用现有系统调用的结合可得到新的系统调用。
Nucleus PLUS提供其他实时内核都具有的系统服务,比如:任务控制、任务通信、任务同步、内存管理、可编程的定时器、标准的输入/输出设备接口等。
对任务的调度依赖优先级、时间片的方法,所有的操作系统对象(任务、邮箱、队列、管道等)都可以动态地创建和删除。
创建一个对象时,要指定指定其控制块的内存区域和其它的数据要求(堆栈空间等)。
Nucleus在任务调度时,首先查看任务是否可以抢占,如果不能抢占,则一直执行到任务完成或任务放弃时间片;否则,依靠优先级进行调度,先调入优先级最高的任务,对于优先级相同的任务则分享时间片、轮流调度。
1.1系统启动
对于68K系列的CPU,如果采用CrossCode C编译器的话,Nucleus使用的是CrossCode C 的启动函数,标号START是系统的入口点。
START标号在文件start.s中,用汇编及CrossCode C
的宏指令写的。
主要完成68360中断向量表指针VBR 和寄存器基址MBAR 的初始化,以及外部RAM 各分区的初始化,这些分区包括:ram ,data ,malloc ,init ,sys_memory 等。
系统低级初始化完成后,控制就交给初始化线程INT_Initialize 。
1.2 初始化线程
初始化线程是系统开始执行的第一个线程,线程的入口是INT_Initialize ,同时也是系统的主函数,具有另外的标号名main 。
系统初始化首先完成硬件寄存器的配置,包括:存储器片选(CS0~CS7),软件看门狗SWT ,系统周期定时器PIT ,A 口、B 口、C 口引脚功能设定,串行通信控制器的初步配置等。
这些硬件的配置跟目标板有关,需要用户自己用汇编来写。
其次,将系统堆栈指针TCD_System_Stack 初始化为堆栈区stack 的顶部,同时在系统内存区sys_memory 中拿出TMD_HISR_Stack_Size 大小的一片内存用作高级中断服务程序的堆栈HISR_STACK 。
然后,控制交给INC_Initialize 。
INC_Initialize 首先完成操作系统数据结构的初始化,包括:线程控制、邮箱、队列、管道、信号量、事件、分区内存、动态内存、定时器、I/O Driver 等。
其次,调用Application_Initialize ,这一函数由用户编写,完成任务、消息队列等的创建、中断的注册以及应用程序的初始化。
当所有的初始化都完成后,INC_Initialize 调用TCT_Schedule 开始线程的调度。
1.3 线程调度
TCT_Schedule 是线程调度的入口,负责将控制权交给具有最高优先级的高级中断服务程序HISR (TCD_Execute_HISR )或处于就绪状态的最高优先级任务(TCD_Execute_Task )。
当没有任务或HISR 执行时,线程调度就在TCT_Schedule 中做死循环,等待TASK 或HISR 就绪。
HISR 的优先权比任务高,一旦有HISR 就绪,则当前调度的任务将会被挂起,优先调度HISR 。
一旦有TASK 或HISR 就绪,控制就会交给TCT_Control_To_Thread ,在这里将TCD_Execute_HISR 或TCD_Execute_Task 设置为当前线程TCD_Current_Thread ,启动任务时间片定时器,根据线程的不同堆栈类型恢复堆栈,然后执行RTS 或RTE 指令,将控制权交给线程。
Nucleus 的线程有两种类型的堆栈帧:
任务创建时,要建立一个初始堆栈帧,线程入口是TCC_Task_Shell ,该Shell 执行任务的入口程序,通常任务的执行是一个死循环,不停地在等待消息或事件,如果没有消息或事件任务就会挂起,否则往下执行。
如果,任务在Shell 调度中返回,则表示该任务已执行完毕,将任务终止,其状态置为NU_FINISHED 。
HISR 创建时,也要建立一个初始堆栈帧,线程入口是TCC_HISR_Shell 。
HISR_Shell 调度
Interrupt 堆栈类型Solicited 堆栈类型
的是当前具有最高优先级的HISR,直至TCD_Execute_HISR的激活计数器tc_activation_count 为0,才调度同一优先级或低优先级的其它HISR。
HISR_Shell对HISR调度就是执行HISR的入口程序,然后根据激活计数器循环调度,HISR是不能被挂起的。
1.3.1 任务的调度
任务的调度需要用到以下比较重要的数据结构:
TCD_Created_Tasks_List:已创建任务链表的头指针。
TCD_Priority_List[256]:任务控制块TCB的指针数组,每个元素是优先级0~255就绪任务链表的头指针。
TCD_Priority_Groups:按位来定义的长字,对应32组优先级,每bit是一组,负责8个优先级,如果其中任意一个优先级有任务就绪,则该bit置1。
TCD_Sub_Priority_Groups[32]:子优先级组的位映像图,每一元素对应一组优先级。
比如TCD_Sub_Priority_Groups[0]对应优先级0~7,bit0~bit7分别表示优先级0~7。
TCD_Highest_Priority:当前就绪任务的最高优先级,并不表示当前正在运行任务的优先级,如果该任务不能抢占。
TCD_Execute_Task:当前正在执行的任务指针。
TCD_Current_Thread:当前正在执行的线程指针(TASK or HISR)。
Nucleus的任务具有五种状态:executing,ready,suspended,finished,terminated。
Executing:任务正在执行,但是其任务控制块TCB中的状态tc_status仍为NU_READY,只是该任务的指针等于当前线程TCD_Current_Thread。
Ready:任务已就绪,但是有其它任务在运行。
Suspended:任务在等待请求服务完成的过程中被挂起,一旦请求服务完成,任务就会迁移至ready状态。
Finished:该任务的处理已经完成(在TCC_Task_Shell调度中返回)。
一旦任务处于这种状态就不能再执行了,除非任务被复位。
Terminated:任务被Killed。
一旦任务处于这种状态就不能再执行了,除非任务被复位。
Nucleus的每个任务都具有一个0~255的优先级,0表示最高优先级,255表示最低优先级,对于相同优先级的任务分时间片运行,不同优先级的任务则可以发生抢占。
任务的调度涉及到:创建任务、删除任务、复位任务、终止任务、恢复任务、挂起任务等。
①、创建任务(TCC_Create_Task)
任务的创建一般是在Application_Initialize中进行,当然也可以在其它任务中动态地创建和删除任务。
任务创建的流程如下:
②、删除任务(TCC_Delete_Task)
对任务删除时,默认任务是处于finished或terminated状态。
将任务删除,主要是将任务从已创建任务链表中删除,并不能释放与任务相关连的控制块(TCB)和堆栈。
③、复位任务(TCC_Reset_Task)
当任务处于finished或terminated状态时,才能对该任务执行复位,否则返回NU_NOT_TERMINATED,表示任务没有结束或终止。
任务复位主要是将任务控制块中的数据成员重新赋初值,重新创建任务堆栈(任务堆栈复位),将任务状态置为无条件挂起状态NU_PURE_SUSPEND。
④、终止任务(TCC_Terminate_Task)
如果要终止的是当前任务(TCD_Current_Thread),则直接将任务挂起,将任务状态置为NU_TERMINATED。
如果要终止的不是当前任务,则要对任务状态进行判断。
如果任务已经处于finished或terminated状态,则什么也不做。
如果任务处于ready状态,则直接将任务挂起,将任务状态置
为NU_TERMINATED。
如果任务处于suspended状态,则必须释放和该任务相关的所有保护结构后才能将任务终止。
⑤、恢复任务(TCC_Resume_Task)
如果任务可以获得执行所需要的系统资源,比如:对于做NU_Send_To_Pipe系统调用的任务如果消息管道已有空余空间,或者做NU_Reveive_From_Pipe系统调用的任务如果管道中有消息,那么挂起在该管道上的任务就会恢复。
如果任务挂起类型与请求恢复类型一致,则把任务状态置为NU_READY,将任务插入就绪任务优先级链表TCD_Priority_List[task->tc_priority],设置优先级组TCD_Priority_Groups和子优先级组TCD_Sub_Priority_Groups[task->tc_priority/8]中本任务优先级对应的bit,指示本优先级有任务ready。
如果要恢复的任务优先级比当前最高优先级TCD_Highest_Priority要高,且当前任务TCD_Execute_Task可以抢占,则将要恢复的任务置为当前任务,同时会产生任务抢占,返回NU_TRUE,否则返回NU_FALSE。
⑥、挂起任务(TCC_Suspend_Task)
如果任务不能获得执行所需要的系统资源,比如:对于做NU_Send_To_Pipe系统调用的任务如果消息管道已满,或者做NU_Reveive_From_Pipe系统调用的任务如果管道已空,那么任务就会被挂起。
任务挂起首先判断要挂起的是不是当前任务TCD_Current_Thread,如果不是挂起当前任务,则要释放任务的当前保护结构tc_current_protect。
其次,如果任务的状态为该任务优先级就绪任务链表TCD_Priority_List[]中只有这一个任务ready,则要清空该优先级就绪任务链表,同时要清除子优先级组TCD_Sub_Priority_Groups和优先级组TCD_Priority_Groups对应的bit。
如果要挂起的任务具有最高优先级,则要根据优先组和子优先组重新搜索最高优先级,如果其他组中没有任务就绪,则TCD_Highest_Priority=255。
然后根据最高优先级,重新调整TCD_Execute_Task,如果最高优先级为255,则TCD_Execute_Task=NU_NULL。
如果任务的状态为NU_READY且该任务优先级就绪任务链表TCD_Priority_List[]中不只这一个任务ready,则将该任务从优先级就绪任务链表中删除,不用修改子优先级组和优先级组,另外也不用调整最高优先级TCD_Highest_Priority,只是利用最高优先级重新调整TCD_Execute_Task。
如果要挂起的是当前线程TCD_Current_Thread,则将控制交给TCD_Control_To_System,在TCD_Control_To_System中给当前线程创建一个solicited类型的堆栈帧,线程入口是调用TCD_Control_To_System的下一条指令,任务恢复时从这条指令开始继续执行。
TCD_Control_To_System随后又将控制交给TCT_Schedule,TCT_Schedule根据TCD_Execute_HISR或TCD_Execute_Task开始下一轮的任务调度,如果调度过程中发现挂起任务需要的系统资源可以满足,就会把任务恢复,按照优先级重新调度。
任务挂起流程如下:
1.3.2中断的调度
Nucleus的中断分为管理的和非管理的中断。
管理的中断需要向操作系统注册该中断向量,中断产生后通过该中断向量注册的低级中断服务程序(LISR)来激活高级的中断服务程序(HISR)。
LISR主要完成硬件中断的处理,及激活HISR。
HISR的调度类似于任务,具有优先级,可以使用大多数Nucleus的系统调用。
非管理的中断,则不需要通过操作系统进行管理,直接将中断服务程序挂到中断向量表上,上下文的保存与恢复都要用户自己来做,该中断自己不能嵌套,最好不要被管理的中断再次中断否则会引起堆栈出错,而且非管理的中断不能使用绝大多数的Nucleus系统调用,因为它可能会破坏操作系统某些保护的数据结构(当有线程在运行时)。
非管理的中断适用于那些比较频繁的中断,如果通过操作系统来管理这些中断的话,其上下文保存与恢复的时间就比较长,中断的实时性就不能满足要求。
非管理的中断比较简单,类似于以前我们写的中断服务程序,这里就不多说。
下面我们讨论的中断的调度都指的是Nuclesu管理中断的调度。
中断的调度需要用到以下比较重要的数据结构:
TCD_Registered_LISRs[256]:对应68360的256个中断向量。
0表示该中断向量没有注册,是操作系统不能处理的中断(Unhandled_Interrupt);非0表示该中断向量已注册,且其值为在LISR函数指针数组TCD_LISR_Pointers中的索引下标。
TCD_LISR_Pointers[30]:LISR函数指针数组,指向当中断产生时要调用的低级中断服务程序LISR的入口函数。
TCD_Interrupt_Count:表示有多少个中断服务程序(ISRs)正在进行处理。
0:没有中断;1:只有一个中断;>1:中断嵌套。
TCD_Interrupt_Level:允许中断的级别,用来给68360的状态寄存器SR赋值。
0x700表示屏蔽所有中断,0x500表示屏蔽5级及5级以下的中断,0表示打开所有中断。
TCD_Unhandled_Interrupt:系统出错时,表示不能处理的中断向量号。
TCD_Created_HISRs_List:已创建的HISR链表的头指针。
TCD_Active_HISR_Heads[3]:对应HISR优先级0~2,每个数组元素是该优先级已激活HISR 链表的头指针。
TCD_Active_HISR_Tails[3]:对应HISR优先级0~2,每个数组元素是该优先级已激活HISR 链表的尾指针。
TCD_Execute_HISR:当前正在执行或要执行的具有最高优先级的HISR指针。
每个HISR具有一个0~2的优先级,0表示最高优先级,2表示最低优先级,相同优先级的HISR按照先入先出的顺序处理,优先级不同的HISR按照优先级的高低进行调度。
HISR是不能被挂起的,因此其所有的系统调用都要加上NU_NO_SUSPEND参数。
中断的调度包括:中断向量的注册、HISR的创建与删除、上下文的保护与恢复、LISR的执行、HISR的激活以及HISR的调度等。
①、中断向量的注册(TCC_Register_LISR)
一个中断要通过操作系统管理起来,首先要将其中断向量通过系统调用NU_Register_LISR(INT vector, VOID (*new_lisr)(INT), VOID (**old_lisr)(INT))注册起来。
该系统
调用的第2个参数是LISR的入口函数指针,也就是中断产生后要执行的LISR。
中断的注册首先要判断该中断向量是否已经注册过。
如果该中断已经注册过,则利用TCD_Registered_LISRs索引到该中断向量在LISR函数指针数组TCD_LISR_Pointers中的下标,然后将新的LISR(new_lisr)填入TCD_LISR_Pointers。
如果该中断向量没有注册过,则在LISR函数指针数组TCD_LISR_Pointers中找出一个没有使用的单元,将new_lisr填入该单元,同时将该单元的下标填入该中断向量在TCD_Registered_LISRs中对应的单元。
另外,还要判断INT_Loaded_Flag标志,如果该标志为0,则要替换掉当前的中断向量表,否则不修改当前的中断向量表。
②、HISR的创建(TCC_Create_HISR)
HISR也就是中断产生后,要在LISR中激活的高级中断服务程序。
HISR的创建比任务创建简单,不用进行设置任务状态、恢复任务等操作,只需创建一个HISR控制块HCB,初始化HCB中的一些参数,为HISR创建一个Solicited类型的堆栈帧,将该HISR的指针挂到已创建HISR链表TCD_Created_HISRs_List,同时分配一个用户指定的入口函数指针,该函数用来完成真正的中断处理。
③、HISR的删除(TCC_Delete_HISR)
HISR的删除默认HISR处于非激活状态,仅仅是将HISR从已创建HISR链表TCD_Created_HISRs_List中删除,并清除HISR ID标志,并不能释放与HISR相关的内存(控制块、堆栈等),同时也不影响HISR的激活,对HISR的调度可能会产生微小的影响(由于HISR ID被清除)。
一般来说,HISR的删除没有什么意义,除非把跟HISR相关的中断也关掉。
④、上下文的保护(TCT_Interrupt_Context_Save)
通常的中断服务程序对要用到的寄存器都要进行堆栈保护,Nucleus操作系统除了做这些外,还要对当前线程进行保护,使得高级中断服务程序HISR可以抢占任务,让HISR得到快速的响应。
Nucleus在系统空闲(没有线程运行)时,中断堆栈使用的是系统堆栈TCD_System_Stack;如果有线程(任务或HISR)在运行,使用的是任务或HISR堆栈,上下文保护完成之后,则将堆栈切换到系统堆栈TCD_System_Stack。
上下文保护首先将所有中断屏蔽掉,等保护完成之后再将中断打开。
其次判断中断计数器TCD_Interrupt_Count,如果本次中断是中断嵌套,则将TCD_Interrupt_Count加1,然后返回。
如果本次中断不是中断嵌套,则判断当前有没有线程在运行,如果当前有线程在运行,则为当前线程建立一个Interrupt类型的堆栈帧,将当前的堆栈指针保存在线程控制块中,再将堆栈指针切换到系统堆栈顶部TCD_System_Stack,然后返回;如果当前没有线程运行,则直接将堆栈指针切换到系统堆栈顶部TCD_System_Stack,然后返回。
⑤、LISR的执行(TCC_Dispatch_LISR)
中断上下文保护完成后,就要根据中断向量,在LISR指针数组TCD_LISR_Pointers中索引到本中断向量的LISR入口函数指针,然后执行LISR函数,通常LISR要做的只是处理硬件中断及激活HISR。
LISR执行完毕,则将上下文恢复,将控制权交给TCT_ Shedule进行系统调度。
要注意的是,如果某个中断向量没有注册,则会产生系统错误,进入系统错误线程
ERC_System_Error处理,这种错误是致命的错误,导致整个系统进入一个死循环。
⑥、上下文的恢复(TCT_Interrupt_Context_Restore)
上下文恢复首先判断是不是中断嵌套,如果是中断嵌套,则将TCD_Interrupt_Count减1,将堆栈保护的寄存器恢复,然后利用RTE指令从中断返回。
如果不是中断嵌套,则将当前线程TCD_Current_Thread清为0,将堆栈切换到系统堆栈顶部TCD_System_Stack,将控制交给TCT_Shedule进行线程的重新调度,在这里HISR会抢占任务优先运行。
对于没有嵌套的中断恢复,并没有执行RTE指令,从中断产生的指令往下执行,而是将控制交给TCT_Shedule进行重新调度,这类中断恢复可以分三种情况进行分析:i)、系统空闲(做TCD_Shedule死循环)时,产生了中断,则上下文恢复后,再次运行TCD_ Shedule,然后调度由LISR激活的HISR(TCD_Execute_HISR)。
ii)、中断产生时,有一个任务在运行。
由于在上下文保护时,已经给当前任务建立了一个中断类型的堆栈帧,同时LISR运行时没有修改当前任务的状态(没有将当前任务挂起),也没有修改TCD_Execute_Task。
当中断恢复完成之后,TCD_Shedule首先把TCD_Execute_HISR设置为当前线程TCD_Current_Thread,优先调度HISR,如果HISR运行过程中激活了一个比当前被中断任务优先级更高的任务,则TCD_Execute_Task会被修改,等HISR运行完毕,则将TCD_Execute_Task设置成当前线程。
如果TCD_Execute_Task没被修改,则被中断的任务恢复运行;如果有更高级的任务ready,则等高级任务挂起后,被中断的任务才能恢复运行。
iii)、中断产生时,有一个高级中断服务程序HISR在运行。
如果本次中断的HISR优先级比当前的HISR(TCD_Execute_HISR)优先级低,则LISR激活HISR时不会修改TCD_Execute_HISR,中断恢复后,继续执行中断前的HISR(上下文保护时已给当前的HISR建立了一个中断类型的堆栈帧),然后再根据HISR的优先级进行调度。
如果本次中断的HISR优先级比当前的HISR优先级高,则LISR激活HISR时会修改TCD_Execute_HISR,中断恢复后,优先执行优先级高的HISR,等到TCT_Shedule调度到本次被中断的HISR时,被中断的HISR 接着被中断的部分继续执行。
⑦、HISR的激活(TCT_Active_HISR)
HISR是在LISR中被激活的,TCT_Active_HISR只是激活由LISR指定的HISR以及修改TCD_Execute_HISR,并不真正地执行HISR,HISR在TCT_Shedule中才被真正地调度执行。
激活HISR首先根据激活次数hisr->tc_activation_count来判断该HISR是否已被激活。
如果HISR已被激活,则只将激活次数tc_activation_count加1。
如果该HISR没有被激活过,且该HISR的优先级激活链表为空,则将该HISR挂到本优先级激活链表上,同时根据HISR的优先级决定是否修改TCD_Execute_HISR。
如果该HISR没有被激活过,且该HISR的优先级激活链表非空,则直接将该HISR挂到本优先级激活链表的尾指针,不用修改TCD_Execute_HISR,因为本优先级激活链表的头指针就有可能是TCD_Execute_HISR,或者有更高优先级的HISR已被激活。
⑧、HISR的调度
中断恢复后,如果当前的TCD_Execute_HISR是被中断停下来的HISR,则经TCT_Shedule 调度后,被中断的HISR恢复运行。
如果TCD_ Execute_HISR已被修改,是一个新的HISR,则TCT_Shedule会将该HISR放入TCT_HISR_Shell中进行调度。
TCT_HISR_Shell完成HISR的调度。
首先循环调度当前的TCD_Execute_HISR,也就是循环执行HISR创建时用户指定的入口函数,直至其激活次数tc_activation_count等于0。
如果TCD_Execute_HISR只被激活了一次,则HISR的入口函数只会执行一次。
TCD_Execute_HISR调度完毕(激活次数为0),如果TCD_Execute_HISR所在优先级的激活链表只有这一个HISR,则将本优先级激活链表清空(将TCD_Active_HISR_Heads[X]及TCD_Active_HISR_Tails[X]置为NU_NULL),然后从激活链表头指针数组TCD_Active_HISR_Heads中按优先级顺序搜索到一个已被激活的最高优先级的HISR,由此来修改TCD_Execute_HISR,如果没有其他HISR被激活,则TCD_Execute_HISR为空指针。
TCD_Execute_HISR调度完毕,如果TCD_Execute_HISR所在优先级激活链表不只这一个HISR被激活,则将TCD_Execute_HISR从本优先级激活链表删除,将本优先级下一个激活的HISR设置为TCD_Execute_HISR。
最后,为当前正在调度的HISR建立一个solicited类型的堆栈帧,将堆栈指针保存在HISR 控制块中,清除当前线程TCD_Current_Thread,将堆栈切换到系统堆栈顶部TCD_System_Stack。
然后将控制权交给TCT_Shedule重新调度,如果还有其它的HISR被激活,则重复上面的过程;否则进入任务的调度,或在TCT_Shedule死循环,等待HISR被激活或任务ready。
一个完整的中断处理流程如下:
①、中断前系统空闲
②、中断前有任务在运行
③、中断前有HISR在运行
1.3.3操作系统数据结构的保护
由于Nucleus操作系统的线程是可以抢占的,高优先级的任务可以抢占低优先级的任务,HISR可以抢占任务,HISR之间也可以抢占。
如果某个低优先级的任务正在通过系统调用对操作系统的某个数据结构进行操作,比如:正在修改已创建任务链表TCD_Created_Tasks_List,或正在往某个消息管道Pipe中填消息(要修改消息管道的数据成员),这时发生了任务抢占,如果发生抢占的高优先级任务也要修改同一数据结构,就必须等待低优先级的任务完成修改数据结构的系统调用后,再让高优先级的任务运行,否则就会破坏操作系统的数据结构。
这一机制是通过操作系统结构保护(Protect)实现的。
任务运行时如果要修改操作系统的数据结构,就要通过系统调用TCT_Protect (TC_PROTECT *protect) 把该数据结构的保护结构保护起来,当任务对该数据结构的操作结束就会通过系统调用TCT_Unprotect (void)或TCT_Unprotect_Specific (TC_PROTECT *protect) 来释放保护结构。
如果低优先级的任务没有释放保护结构之前,发生了任务抢占,高优先级的任务抢占了低优先级的任务,如果高优先级的任务也要修改同一系统数据结构,那么在做系统调用TCT_Protect 时,就会发现该数据结构的保护结构已经被另一个线程拥有,当前的线程就会暂时被挂起,将控制交给拥有保护结构的线程,等待拥有该保护结构的线程释放掉保护结构后,也就是拥有保护结构的线程在做TCT_ Unprotect时,高优先级的任务才能真正地把控制权抢占过来。
Nucleus共有四种跟线程调度有关的比较重要的保护结构:
TCD_List_Protect:已创建任务链表的保护结构;
TCD_System_Protect:系统保护结构,用于任务调度;
TCD_LISR_Protect:用于LISR的创建和删除;
TCD_HISR_Protect:用于HISR的创建和删除;
保护结构的结构体是这样定义的:
typedef struct TC_PROTECT_STRUCT
{
TC_TCB tc_tcb_poiter; /* 拥有保护结构的线程指针*/
UNSIGNED tc_thread_waiting; /* 有线程在等待该保护结构的标志*/ }TC_PROTECT;
①、数据结构的保护(TCT_Protect)
当线程在操作的数据结构不想因为线程的抢占而破坏时,就要申请对该数据结构的保护,比如:在创建HISR时,就要申请HISR链表的保护TCT_Protect(&TCD_HISR_Protect);在链表插入完成后,就要调用TCT_Unprotect( )释放当前线程拥有的保护结构。
另外,如果线程正在执行的系统调用不想因为线程的抢占而中断时,就要申请系统保护结构的保护TCT_Protect(&TCD_System_Protect),比如:任务的恢复、任务的挂起、消息管道的收发等,这些操作都要申请系统保护结构,当系统调用结束后,就要调用TCT_Unprotect( )释放当前线程拥有的保护结构。
TCT_Protect首先判断要保护的结构是否被其它线程拥有,如果没有其它线程拥有要保护的。