首页 > 代码库 > RTX——第19章 SVC 中断方式调用用户函数(后期补历程)
RTX——第19章 SVC 中断方式调用用户函数(后期补历程)
本章节为大家讲解如何采用 SVC 中断方式调用用户函数。 当用户将 RTX 任务设置为工作在非特权级
模式时,任务中是不允许访问特权级寄存器的,这个时候使用 SVC 中断,此问题就迎刃而解了。
SVC 功能介绍
SVC 用于产生系统函数的调用请求。例如,操作系统通常不让用户程序直接访问硬件,而是通过提供
一些系统服务函数,让用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接
访问硬件。因此,当用户程序想要控制特定的硬件时,它就要产生一个 SVC 异常,然后操作系统提供的
SVC 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。
SVC 这种“提出要求——得到满足”的方式很好:
? 它使用户程序从控制硬件的繁文缛节中解脱出来,而是由 OS 负责控制具体的硬件。
? OS 的代码可以经过充分的测试,从而能使系统更加健壮和可靠。
? 它使用户程序无需在特权级下执行,用户程序无需承担因误操作而瘫痪整个系统的风险。
? 通过 SVC 的机制,还让用户程序变得与硬件无关,因此在开发应用程序时无需了解硬件的操作细节,
从而简化了开发的难度和繁琐度,并且使应用程序跨硬件平台移植成为可能。开发应用程序唯一需要
知道的就是操作系统提供的应用编程接口(API),并且在了解了各个请求代号和参数表后,就可以
使用 SVC 来提出要求了(事实上,为使用方便,操作系统往往会提供一层封皮,以使系统调用的形
式看起来和普通的函数调用一致。各封皮函数会正确使用 SVC 指令来执行系统调用)。其实,严格
地讲,操作硬件的工作是由设备驱动程序完成的,只是对应用程序来说,它们也相当于操作系统的一
部分。如下图所示:
SVC 异常通过执行” SVC”指令来产生。该指令需要一个立即数,充当系统调用代号。 SVC 异常服务
例程稍后会提取出此代号,从而获知本次调用的具体要求,再调用相应的服务函数。例如,
SVC 0x3 ; 调用 3 号系统服务
在 SVC 服务例程执行后,上次执行的 SVC 指令地址可以根据自动入栈的返回地址计算出。找到了 SVC
指令后,就可以读取该 SVC 指令的机器码,从机器码中萃取出立即数,就获知了请求执行的功能代号。
如果用户程序使用的是 PSP,服务例程还需要先执行 MRS Rn, PSP 指令来获取应用程序的堆栈指针。通
过分析 LR 的值,可以获知在 SVC 指令执行时,正在使用哪个堆栈。
注意,我们不能在 SVC 服务例程中嵌套使用 SVC 指令(事实上这样做也没意义),因为同优先级的
异常不能抢占自身。这种作法会产生一个用法 fault。同理,在 NMI 服务例程中也不得使用 SVC,否则将
触发硬 fault。
SVC 触发方式
SVC 的异常号是 11,支持可编程。 SVC 异常可以由 SVC 指令来触发,也可以通过 NVIC 来软件触发
(通过寄存器 NVIC->STIR 触发软中断)。这两种方式触发 SVC 中断有一点不同:软件触发中断是不精
确的,也就是说,抢占行为不一定会立即发生,即使当时它没有被掩蔽,也没有被其它 ISR 阻塞,也不能
保证马上响应。这也是写缓冲造成的,会影响到与操作 NVIC STIR 相临的后一条指令:如果它需要根据中
断服务的结果来决定如何工作(如条件跳转),则该指令可能会误动作——这也可以算是紊乱危象的一种
表现形式。为解决这个问题,必须使用一条 DSB 指令,如下例所示:
MOV R0, #SOFTWARE_INTERRUPT_NUMBER
LDR R1,=0xE000EF00 ; 加载 NVIC 软件触发中断寄存器的地址
STR R0, [R1] ; 触发软件中断
DSB ; 执行数据同步隔离指令
...
但是这种方式还有另一种隐患:如果欲触发的软件中断被除能了,或者执行软件中断的程序自己也是个异
常服务程序,软件中断就有可能无法响应。因此,必须在使用前检查这个中断已经在响应中了。为达到此
目的,可以让软件中断服务程序在入口处设置一个标志。而 SVC 要精确很多,SVC 指令后,只要此时没
有其它高优先级的异常也发生了,SVC 中断服务程序可以得到立即执行。
SVC 的使用
SVC 是用于呼叫 OS 所提供的 API(RTX 是采用的这种方式)。用户程序只需知道传递给 OS 的参数,
而不必知道各 API 函数的地址。 SVC 指令带一个 8 位的立即数,可以视为是它的参数,被封装在指令本身
中,如:
SVC 3 ;呼叫 3 号系统服务
则 3 被封装在这个 SVC 指令中。因此在 SVC 服务例程中,需要读取本次触发 SVC 异常的 SVC 指令,并
提取出 8 位立即数所在的位段,来判断系统调用号。
RTX 使用的 SVC 中断服务号
SVC 的 0 号系统服务被 RTX 系统占用,即 SVC 0,用户只能使用从 1 开始的服务号。而且用户使
用的时候一定要保证从 1 开始,连续递增使用,范围 1 – 255。
RTX 中 SVC 中断方式调用函数方法
用户实现 SVC 中断方式调用函数方法如下(下面以添加两个 SVC 中断为例):
? 第 1 步:添加 SVC_Table.s 文件。
我们在前面讲解 RTX 的源码移植方式时这个文件已经加上。
? 第 2 步:使用属性__svc(x)声明函数,x 从 1 开始,范围 1-255。函数名随便命名,但是 x 的数值一
定要保证是连续递增的。
void __svc(1) SVC_1_FunCall(uint8_t _arg1, uint16_t _arg2, uint32_t _arg3, uint64_t *_arg4);
void __svc(2) SVC_2_FunCall(void);
? 第 3 步:写上面两个函数的实际代码,并将函数名更改成__SVC_x 格式(统一改成这种命名方式是为
了跟 RTX 系统的调用方式__SVC_0 统一),x 是从 1 开始,范围 1-255。 上面声明的两个函数不要动,
这里修改的是实际函数名。 另外用户可以根据需要加上中断开关操作,因为 SVC 中断可以被其它高
优先级的中断抢占。
RTX——第19章 SVC 中断方式调用用户函数(后期补历程)