1 头文件includes.h和config.h
UCOSII要求所有.c文件都必须包含includes.h。在本移植中另外增加了一个config.h,要求所有用户程序必须包含config.h,在config.h中包含includes.h和特定的头文件和配置项。
2 OS_CPU.H
(1)根据ADS编译器的特性,不依赖于编译器的数据类型的程序清单如下:
Typedef unsigned char BOOLEAN;
Typedef unsigned char INT8U;
Typedef signed char INT8S;
Typedef unsigned short INT16U;
Typedef signed short INT16S;
Typedef unsigned int INT32U;
Typedef signed int INT32S;
Typedef float FP32;
Typedef double FP64;
Typedef INT32U OS_STK;
(2)使用软中断做为底层接口
带T变量的ARM7处理器核具有两个指令集,用户任务还可以使用两种不同的处理器模式:用户模式与系统模式(别的模式都不是太适合),组合起来就有四种方式,四种方式对系统资源有不同的访问控制权限。
为了让底层接口函数与处理器状态无关,同时在任务调度时相应的函数不需要知道函数位置,本移植中使用SWI做为底层接口,不同的功能号用来区分不同的函数。软中断功能号的分配列表如下:
__swi(0x00) void OS_TASK_SW(void); /* 任务级任务切换函数 */
__swi(0x01) void _OSStartHighRdy(void); /* 运行优先级最高的任务 */
__swi(0x02) void OS_ENTER_CRITICAL(void); /* 关中断 */
__swi(0x03) void OS_EXIT_CRITICAL(void); /* 开中断 */
__swi(0x80) void ChangeToSYSMode(void); /* 任务切换到系统模式 */
__swi(0x81) void ChangeToUSRMode(void); /* 任务切换到用户模式 */
__swi(0x82) void TaskIsARM(INT8U prio); /* 任务代码是ARM代码 */
__swi(0x83) void TaskIsTHUMB(INT8U prio); /* 任务代码是THUMB */
这里的_swi,是ADS的一个关键字,用它来声明一个不存在的函数,则调用这个函数就在调用这个函数的地方插入一条SWI指令,并且可以指定功能号。
其具体使用规则可见下面的英文注释:
This declares a SWI function taking up to four integer-like arguments and returning up to four results in a value_in_regs structure. This causes function invocations to be compiled inline as an ATPCS compliant SWI that behaves similarly to a normal call to a function.
For a SWI returning no results use:
void __swi(swi_num) swi_name(int arg1,…, int argn);
For example:
void __swi(42) terminate_proc(int procnum);
For a SWI returning one result, use:
int __swi(swi_num) swi_name(int arg1,…, int argn);
For a SWI returning more than 1 result use:
typedef struct res_type { int res1,…,resn;} res_type;
res_type __value_in_regs __swi(swi_num) swi_name(
int arg1,…,int argn);
The __value_in_regs qualifier is used to specify that a small structure of up to four words (16 bytes) is returned in registers, rather than by the usual structure-passing mechanism defined in the ATPCS.See the chapter on Handling Processor Exceptions in the ADS Developer Guide for detailed information
通过上文可知,其大致的用法就是:
无返回形式:
void _swi(swi_num) swi_name(arguments);
返回一个结果到R0中
int _swi(swi_num) swi_name(arguments);
最多可以返回四个结果R0-R3到一个结构struct type{ int a,b,c,d}中
type(返回类型) _value_in_regs(返回多个结果的修饰符) _swi(swi_num) swi_name(arguments);
简单的说就是:一个SWI调用允许带1~4个字型参数和1~4个字型返回值,触发SWI调用时四个参数依次保存在R0~R3中,返回值也存于R0~R3内。所以在软中断服务程序中应函数所需要的参数按顺序保存到R0~R3中。
因为这里涉及到软中断这个问题,先当列一章,摘抄下别人对软中断的理解:
(3)软中断的理解与应用
(a)软中断的概念
SWI(software interrupt)软件中断,由用户定义的中断指令.可以用于用户模式下的程序调用特权操作指令.在实时操作系统中可以通过该机制实现系统调用.一个 SWI 所做的一切就是把模式改变成超级用户并设置 PC 来执行在地址 &08 处的下一个指令!
编程异常通常叫做软中断.软中断是通讯进程之间用来模拟硬中断的一种信号通讯方式。中断源发中断请求或软中断信号后,CPU或接收进程在适当的时机自动进行中断处理或完成软中断信号对应的功能.
软中断是软件实现的中断,也就是程序运行时其他程序对它的中断;而硬中断是硬件实现的中断,是程序运行时设备对它的中断。
1.软中断发生的时间是由程序控制的,而硬中断发生的时间是随机的
2.软中断是由程序调用发生的,而硬中断是由外设引发的
3.硬件中断处理程序要确保它能快速地完成它的任务,这样程序执行时才不会等待较长时间。
软件的上层应用,有时需要对系统进行一些底层操作,但是上层应用所使用的 C 语言又无法进行这样的操作。所以,需要有一个接口,利用这个接口才能碰触到系统的底层,而这个接口就是--SWI 。SWI 就像一个中间人,负责上层应用 和 操作系统底层之间传递一些命令与数据。
(b)软中断的理解于应用
关于软中断的理解可见这两篇文章:
http://blog.csdn.net/panqihe/archive/2007/05/18/1615574.aspx
原文内容摘抄在下,方便自己查阅:
SWI,即software interrupt软件中断。该指令产生一个SWI异常。意思就是处理器模式改变为超级用户模式,CPSR寄存器保存到超级用户模式下的SPSR寄存器,并且跳转到SWI向量。其ARM指令格式如下:
SWI{cond} immed_24
Cond域:是可选的条件码 (参见 ARM汇编指令条件执行详解).
immed_24域:范围从 0 到 224-1 的表达式, (即0-16777215)。用户程序可以使用该常数来进入不同的处理流程。
一、方法1:获取immed_24操作数。
为了能实现根据指令中immed_24操作数的不同,跳转到不同的处理程序,所以我们往往需要在SWI异常处理子程序中去获得immed_24操作数的实际内容。获得该操作数内容的方法是在异常处理函数中使用下面指令:
LDR R0,[LR,#-4]
该指令将链接寄存器LR的内容减去4后所获得的值作为一个地址,然后把该地址的内容装载进R0。此时再使用下面指令,immed_24操作数的内容就保存到了R0:
BIC R0,R0,#0xFF000000
该指令将R0的高8位清零,并把结果保存到R0,意思就是取R0的低24位。
可能还是有人会问:为什么在SWI异常处理子程序中执行这两条指令后,immed_24操作数的内容就保存到了R0寄存器呢?之所以会有这样的疑问,基本都是因为对LR寄存器的作用没了解清楚。下面介绍一下链接寄存器LR(R14)的作用。
寄存器R14(LR寄存器)有两种特殊功能:
·在任何一种处理器模式下,该模式对应的R14寄存器用来保存子程序的返回地址。当执行BL或BLX指令进行子程序调用时,子程序的返回地址被放置在R14中。这样,只要把R14内容拷贝到PC中,就实现了子程序的返回(具体的子程序返回操作,这里不作详细介绍)。
·当某异常发生时,相应异常模式下的R14被设置成异常返回的地址(对于某些异常,可能是一个偏移量,一个较小的常量)。异常返回类似于子程序返回,但有小小的不同(这里不作详细介绍)。
所谓的子程序的返回地址,实际就是调用指令的下一条指令的地址,也就是BL或BLX指令的下一条指令的地址。所谓的异常的返回的地址,就是异常发生前,CPU执行的最后一条指令的下一条指令的地址。
例如:(子程序返回地址示例)
指令 指令所在地址
ADD R2,R1,R3 ;0x300000
BL subC ;0x300004
MOV R1,#2 ;0x300008
BL指令执行后,R14中保存的子程序subC的返回地址是0x300008。
再例如:(异常返回地址示例)
指令 指令所在地址
ADD R2,R1,R3 ;0x300000
SWI 0x98 ;0x300004
MOV R1,#2 ;0x300008
SWI指令执行后,进入SWI异常处理程序,此时R14中保存的返回地址为0x300008。
所以,在SWI异常处理子程序中执行 LDR R0,[LR,#-4]语句,实际就是把产生本次SWI异常的SWI指令的内容(如:SWI 0x98)装进R0寄存器。又因为SWI指令的低24位保存了指令的操作数(如:0x98),所以再执行BIC R0,R0,#0xFF000000语句,就可以获得immed_24操作数的实际内容。
二、方法2:使用参数寄存器。
实际上,在SWI异常处理子程序的实现时,还可以绕开immed_24操作数的获取操作,这就是说,我们可以不去获取immed_24操作数的实际内容,也能实现SWI异常的分支处理。这就需要使用R0-R4寄存器,其中R0-R4可任意选择其中一个,一般选择R0,遵从ATPCS原则。
具体方法就是,在执行SWI指令之前,给R0赋予某个数值,然后在SWI异常处理子程序中根据R0值实现不同的分支处理。例如:
指令 指令所在地址
MOV R0,#1 ; #1给R0
SWI 0x98 ; 产生SWI中断,执行异常处理程序SoftwareInterrupt
ADD R2,R1,R3 ;
;SWI异常处理子程序如下
SoftwareInterrupt
CMP R0, #6 ; if R0 < 6
LDRLO PC, [PC, R0, LSL #2] ; if R0 < 6,PC = PC + R0*4,else next
MOVS PC, LR
SwiFunction
DCD function0 ;0
DCD function1 ;1
DCD function2 ;2
DCD function3 ;3
DCD function4 ;4
DCD function5 ;5
Function0
异常处理分支0代码
Function1
异常处理分支1代码
function2
异常处理分支2代码
function3
异常处理分支3代码
function4
异常处理分支4代码
function5
异常处理分支5代码
在ARM体系结构中,当正确读取了PC的值时,该值为当前指令地址值加8字节,也就是说,对于ARM指令集来说,读出的PC值指向当前指令的下两条指令的地址,本例中就是指向SwiFunction 表头DCD function0 这个地址,在该地址中保存了异常处理子分支function0的入口地址。所以,当进入SWI异常处理子程序SoftwareInterrupt时,如果R0=0,执行LDRLO PC, [PC, R0, LSL #2]语句后,PC的内容即为function0的入口地址,即程序跳转到了function0执行。在本例中,因为R0=1,所以,实际程序是跳转到了function1执行。R0左移2位(LDRLO PC, [PC, R0, LSL #2]),即R0*4,是因为ARM指令是字(4个字节)对齐的DCD function0等伪指令也是按4字节对齐的。
在本方法的实现中,实际指令中的24位立即数(immed_24域)被忽略了, 就是说immed_24域可以为任意合法的值。如在本例中,不一定使用SWI 0x98,还可以为SWI 0x00或者SWI 0x01等等,程序还是会进入SWI异常处理子程序SoftwareInterrupt,然后根据R0的内容跳转到相应的子分支。
http://blog.csdn.net/BelindaBen/archive/2008/03/24/2212302.aspx
原文内容也摘抄如下,方便自己查阅:
SWI 表示 Software Interrupt。在 RISC OS 中使用 SWI 来访问操作系统例程或第三方生产的模块。许多应用使用模块来给其他应用提供低层外部访问。
当 SWI 处理程序得到对特定的例程编号的一个请求的时候,它找到这个例程的位置并执行它,并传递(有关的)任何数据。
它是如何工作的?
首先查看一下如何使用它。一个 SWI 指令(汇编语言)看起来如下:
SWI &02或 SWI "OS_Write0"这些指令实际上是相同的,将被汇编成相同的指令。唯一的不同是第二个指令使用一个字符串来表示 SWI 编号 &02。在使用采用了字符串编号的程序的时候,在执行之前首先查找这个字符串。
在这里我们不想处理字符串,因为它不能给出它要进行什么的一个真实表示。它们通常用于增进一个程序的清晰程度,但不是实际执行的指令。
让我们再次看一下第一个指令:
SWI &02这是什么意思? 字面的意思是进入 SWI 处理程序并传递值 &02。在 RISC OS 中这意味着执行编号是 &02 的例程。
它是如何这么作的? 它如何传递 SWI 编号和进入 SWI 处理程序?
如果你查看内存的开始 32 字节(位于 0-&1C)并反汇编它们(查开实际的 ARM 指令)你将见到如下:
地址 内容 反汇编00000000 : 0..? : E5000030 : STR R0,[R0,#-48]00000004 : .ó?? : E59FF31C : LDR PC,&0000032800000008 : .ó?? : E59FF31C : LDR PC,&0000032C0000000C : .ó?? : E59FF31C : LDR PC,&0000033000000010 : .ó?? : E59FF31C : LDR PC,&0000033400000014 : .ó?? : E59FF31C : LDR PC,&0000033800000018 : .ó?? : E59FF31C : LDR PC,&0000033C0000001C : 2?? : E3A0A632 : MOV R10,#&3200000让我们仔细看一下。
除了第一个和最后一个指令之外(它们是特殊情况)你见到的都是把一个新值装载到 PC (程序计数器)的指令,它们告诉计算机到哪里去执行下一个指令。还展示了这个值是从内存中的一个地址接受来的。(你可以在 !Zap 主菜单上使用“Read Memory”选项去自己查看一下。)
这看起来好象与 SWI 没多少关系,下面做进一步的说明。
一个 SWI 所做的一切就是把模式改变成超级用户并设置 PC 来执行在地址 &08 处的下一个指令! 把处理器转换到超级用户模式会切换掉两个寄存器 r13 和 r14 并用 r13_svc 和 r14_svc 替换它们。
在进入超级用户模式的时候,还把 r14_svc 设置为在这个 SWI 指令之后的地址。
这个实际上就象一个连接到地址 &08 的分支指令(BL &08),但带有用于一些数据(SWI 编号)的空间。
象我说过的那样,地址 &08 包含跳转到另一个地址的一个指令,就是实际的 SWI 程序的地址!
此时你可能会想“稍等一会! 还有 SWI 编号呢?”。实际上处理器忽略这个值本身。SWI 处理程序使用传递来的 r14_svc 的值来获取它。
下面是完成它的步骤(在存储寄存器 r0-r12 之后):
(1)它从 r14 中减去 4 来获得 SWI 指令的地址。
(2)把这个指令装载到一个寄存器。
(3)清除这个指令的高端 8 位,去掉了 OpCode 而只剩下的 SWI 编号。
(4)使用这个值来找到要被执行的代码的例程的地址(使用查找表等)。
(5)恢复寄存器 r0-r12。
(6)使处理器离开超级用户模式。
(7)跳转到这个例程的地址。
通过这两篇文章,应该可以对软件中断有个了解了。
文章评论(0条评论)
登录后参与讨论