【RTX操作系统教程】第19章 SVC中断方式调用用户函数

标签:
安富莱rtxcosiiiemwinstm32f407 |
分类: RTX及其中间件 |
第19章
SVC中断方式调用用户函数
本章节为大家讲解如何采用SVC中断方式调用用户函数。当用户将RTX任务设置为工作在非特权级模式时,任务中是不允许访问特权级寄存器的,这个时候使用SVC中断,此问题就迎刃而解了。
本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407。
19.1 SVC中断
19.2 RTX中SVC中断方式调用函数方法
19.3 实验例程说明
19.4
19.1 SVC 中断
19.1.1
SVC 功能介绍
SVC用于产生系统函数的调用请求。例如,操作系统通常不让用户程序直接访问硬件,而是通过提供一些系统服务函数,让用户程序使用SVC发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就要产生一个SVC异常,然后操作系统提供的SVC异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。
SVC这种“提出要求——得到满足”的方式很好:
l
l
l
l
SVC异常通过执行”SVC”指令来产生。该指令需要一个立即数,充当系统调用代号。SVC异常服务例程稍后会提取出此代号,从而获知本次调用的具体要求,再调用相应的服务函数。例如,
SVC
在SVC服务例程执行后,上次执行的SVC指令地址可以根据自动入栈的返回地址计算出。找到了SVC指令后,就可以读取该SVC指令的机器码,从机器码中萃取出立即数,就获知了请求执行的功能代号。如果用户程序使用的是PSP,服务例程还需要先执行MRS Rn, PSP指令来获取应用程序的堆栈指针。通过分析LR的值,可以获知在SVC指令执行时,正在使用哪个堆栈。
注意,我们不能在SVC服务例程中嵌套使用SVC指令(事实上这样做也没意义),因为同优先级的异常不能抢占自身。这种作法会产生一个用法fault。同理,在NMI服务例程中也不得使用SVC,否则将触发硬fault。
19.1.2 SVC 触发方式
SVC的异常号是11,支持可编程。SVC异常可以由SVC指令来触发,也可以通过NVIC来软件触发(通过寄存器NVIC->STIR触发软中断)。这两种方式触发SVC中断有一点不同:软件触发中断是不精确的,也就是说,抢占行为不一定会立即发生,即使当时它没有被掩蔽,也没有被其它ISR阻塞,也不能保证马上响应。这也是写缓冲造成的,会影响到与操作NVIC STIR相临的后一条指令:如果它需要根据中断服务的结果来决定如何工作(如条件跳转),则该指令可能会误动作——这也可以算是紊乱危象的一种表现形式。为解决这个问题,必须使用一条DSB指令,如下例所示:
MOV R0, #SOFTWARE_INTERRUPT_NUMBER
LDR
R1,=0xE000EF00
STR
DSB
...
但是这种方式还有另一种隐患:如果欲触发的软件中断被除能了,或者执行软件中断的程序自己也是个异常服务程序,软件中断就有可能无法响应。因此,必须在使用前检查这个中断已经在响应中了。为达到此目的,可以让软件中断服务程序在入口处设置一个标志。而SVC要精确很多,SVC指令后,只要此时没有其它高优先级的异常也发生了,SVC中断服务程序可以得到立即执行。
19.1.3 SVC 的使用
SVC是用于呼叫OS所提供的API(RTX是采用的这种方式)。用户程序只需知道传递给OS的参数,而不必知道各API函数的地址。SVC指令带一个8位的立即数,可以视为是它的参数,被封装在指令本身中,如:
SVC
则3被封装在这个SVC指令中。因此在SVC服务例程中,需要读取本次触发SVC异常的SVC指令,并提取出8位立即数所在的位段,来判断系统调用号。
19.1.4RTX使用的SVC中断服务号
SVC的0号系统服务被RTX系统占用,即SVC
19.2RTX中SVC中断方式调用函数方法
用户实现SVC中断方式调用函数方法如下(下面以添加两个SVC中断为例):
u
我们在前面讲解RTX的源码移植方式时这个文件已经加上。
u
void
__svc(1)
void
__svc(2)
u
//这里的__SVC_1就是函数SVC_1_FunCall
void __SVC_1(uint8_t _arg1, uint16_t _arg2, uint32_t _arg3, uint64_t *_arg4)
{
}
//这里的__SVC_2就是函数SVC_2_FunCall
void __SVC_2(void)
{
}
u
首先使用IMPORT命令声明下,类似C语言中的extern。然后添加到SVC_Table列表下,整体添加后的效果如下(红色字体是用户添加的):
SVC_Cnt
SVC_Count
; Import user SVC functions here.
SVC_Table
; Insert user SVC functions here. SVC 0 used by RTL Kernel.
SVC_End
u
19.3实验例程说明
19.3.1 STM32F103 开发板实验
配套例子:
V4-419_RTX实验_SVC中断方式调用用户函数
实验目的:
1.
实验说明:
1.
#define
OS_RUNPRIV
配置为0的话,任务工作在用户级,配置为1的话,任务工作在特权级。本实验配置为0,所有任务都工作在用户级模式。
2.
SVC指令带一个8位的立即数(范围0-255),可以视为是它的参数,被封装在指令本身中,如:
SVC
SVC 0被RTX系统所使用了,用户只能使用从1开始的服务号。用户使用的时候一定要保证从1开始,而且需要连续的使用。实验中使用了SVC 1和SVC 2。
实验内容:
1. K1按键按下,串口打印。
2. K2键按下,向消息邮箱发送数据。任务AppTaskMsgPro接收到消息后进行消息处理。
3. K3键按下,调用SVC的1号系统服务。
4. 摇杆OK键按下,任务运行在非特权级,调用SVC的2号系统服务,可以在中断中设置NVIC。
5. 各个任务实现的功能如下:
AppTaskUserIF任务
AppTaskLED任务
AppTaskMsgPro任务 :消息处理,等待任务AppTaskUserIF发来的消息邮箱数据。
AppTaskStart任务
RTX配置:
RTX配置向导详情如下:
u
l
允许创建4个任务,实际创建了如下四个任务:
l
创建的4个任务都是采用自定义堆栈方式。
l
设置任务运行在非特权级模式
RTX任务调试信息:
程序设计:
u
static uint64_t
AppTaskUserIFStk[512/8];
static uint64_t
AppTaskLEDStk[256/8];
static uint64_t
AppTaskMsgProStk[512/8];
static uint64_t
AppTaskStartStk[512/8];
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
u
u
int main (void)
{
}
u
static void AppTaskCreate (void)
{
}
u
#define
PoolBlocks
#define
PoolPerBlockSize
os_mbx_declare (mailbox, 10);
static void AppObjCreate (void)
{
}
u
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);
void __SVC_1(uint8_t _arg1, uint16_t _arg2, uint32_t _arg3, uint64_t *_arg4)
{
}
void __SVC_2(void)
{
}
u
void __svc(1) SVC_1_FunCall(uint8_t _arg1, uint16_t _arg2, uint32_t _arg3, uint64_t *_arg4);
SVC_Cnt
SVC_Count
; Import user SVC functions here.
SVC_Table
; Insert user SVC functions here. SVC 0 used by RTL Kernel.
SVC_End
u
__task void AppTaskUserIF(void)
{
}
__task void AppTaskLED(void)
{
}
__task void AppTaskMsgPro(void)
{
}
__task void AppTaskStart(void)
{
}
19.3.2STM32F407开发板实验
配套例子:
V5-419_RTX实验_SVC中断方式调用用户函数
实验目的:
1.
实验说明:
1.
#define
OS_RUNPRIV
配置为0的话,任务工作在用户级,配置为1的话,任务工作在特权级。本实验配置为0,所有任务都工作在用户级模式。
2.
SVC指令带一个8位的立即数(范围0-255),可以视为是它的参数,被封装在指令本身中,如:
SVC
SVC 0被RTX系统所使用了,用户只能使用从1开始的服务号。用户使用的时候一定要保证从1开始,而且需连续的使用。实验中使用了SVC 1和SVC 2。
实验内容:
1. K1按键按下,串口打印。
2. K2键按下,向消息邮箱发送数据。任务AppTaskMsgPro接收到消息后进行消息处理。
3. K3键按下,调用SVC的1号系统服务。
4. 摇杆OK键按下,任务运行在非特权级,调用SVC的2号系统服务,可以在中断中设置NVIC。
5. 各个任务实现的功能如下:
AppTaskUserIF任务
AppTaskLED任务
AppTaskMsgPro任务 :消息处理,等待任务AppTaskUserIF发来的消息邮箱数据。
AppTaskStart任务
RTX配置:
RTX配置向导详情如下:
u
l
允许创建4个任务,实际创建了如下四个任务:
l
创建的4个任务都是采用自定义堆栈方式。
l
设置任务运行在非特权级模式
RTX任务调试信息:
程序设计:
u
static uint64_t
AppTaskUserIFStk[512/8];
static uint64_t
AppTaskLEDStk[256/8];
static uint64_t
AppTaskMsgProStk[512/8];
static uint64_t
AppTaskStartStk[512/8];
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
u
u
int main (void)
{
}
u
static void AppTaskCreate (void)
{
}
u
#define
PoolBlocks
#define
PoolPerBlockSize
os_mbx_declare (mailbox, 10);
static void AppObjCreate (void)
{
}
u
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);
void __SVC_1(uint8_t _arg1, uint16_t _arg2, uint32_t _arg3, uint64_t *_arg4)
{
}
void __SVC_2(void)
{
}
u
void __svc(1) SVC_1_FunCall(uint8_t _arg1, uint16_t _arg2, uint32_t _arg3, uint64_t *_arg4);
SVC_Cnt
SVC_Count
; Import user SVC functions here.
SVC_Table
; Insert user SVC functions here. SVC 0 used by RTL Kernel.
SVC_End
u
__task void AppTaskUserIF(void)
{
}
__task void AppTaskLED(void)
{
}
__task void AppTaskMsgPro(void)
{
}
__task void AppTaskStart(void)
{
}
19.4总结
本章节主要大家讲解了如何采用SVC中断方式调用用户函数,更多SVC中断方面的知识可以看Cortex-M3或者M4权威指南。