正如前文所说,一旦MQX启用了用户权限任务,就会涉及系统调用的概念。提出所谓用户权限的概念就是为了限制一个任务对系统的影响,防止不健全或是恶意的任务破坏系统的稳定性。这样的机制多适用于windows或linux这样大型的可以自由安装软件的操作系统,毕竟我们这个世界还是充满着各种恶意软件。但在实时性系统中确比较少见,我想因为实时性系统较一般的操作系统要求高,其相应软件编写大多都是高效、面向底层、具有针对性的专门化软件。要更高效的完成任务,要求软件编写者对实时性系统有着深刻的理解,相应的也要解除系统对于他们编写软件的优先级限制。
当然随着实时性系统功能越多,其架构越来越复杂,要求一个用户理解越来越多的代码渐渐的变得不太现实(比如我们几十兆的MQX源码)。于是MQX就可根据用户的选择来决定是否启用用户级权限和系统调用机制。而所谓的系统调用,在arm里面是由SVC指令来完成的。下面举个创建任务的例子:
-
_task_id _usr_task_create
-
(
-
_processor_number processor_number,
-
_mqx_uint template_index,
-
uint_32 parameter
-
)
-
{
-
MQX_API_CALL_PARAMS params =
-
{(uint_32)processor_number, (uint_32)template_index, (uint_32)parameter, 0, 0};
-
-
return _mqx_api_call(MQX_API_TASK_CREATE, ?ms);
-
}
对于一个系统调用有两个重要的参数:一个是系统调用号,用来告诉系统我们要调用的是哪一个服务,这里是MQX_API_TASK_CREATE;另一个是要调用服务的参数结构体
-
typedef struct {
-
/*! \brief Parameter 0. */
-
uint_32 param0;
-
/*! \brief Parameter 1. */
-
uint_32 param1;
-
/*! \brief Parameter 2. */
-
uint_32 param2;
-
/*! \brief Parameter 3. */
-
uint_32 param3;
-
/*! \brief Parameter 4. */
-
uint_32 param4;
-
} MQX_API_CALL_PARAMS, _PTR_ MQX_API_CALL_PARAMS_PTR
最多可以塞入5个参数,这里用了三个:processor_number,template_index, parameter。
-
ASM_LABEL(_mqx_api_call)
-
-
#if MQX_ENABLE_USER_MODE
-
push {lr}
-
svc SVC_MQX_FN
-
#endif
而对于_mqx_api_call,只是两个简单的指令,把lr压入栈,进入SVC异常。在这里要特别说明一下,我们之前的两个参数到哪里去了,根据arm的定义在函数调用的时候,若参数小于4个,就按顺序启用R0~R3传递参数,而参数超过4个的就把多余的参数保存在堆栈里传递。具体内容详见《rocedure
Call Standard for the ARM Architecture》
The base standard provides for passing arguments in core registers (r0-r3)
and on the stack. For subroutines that take a small number of parameters, only
registers are used, greatly reducing the overhead of a call.
现在我们可以知道R0=MQX_API_TASK_CREATE,R1=¶ms。然后继续我们的SVC异常。
-
ASM_LABEL(_svc_handler)
-
cpsid.n i
-
-
tst lr, #0x4 /* test EXC_RETURN number in LR bit 2 */
-
ite eq /* if zero (equal) then */
-
mrseq r1, MSP /* Main Stack was used, put MSP in R1 */
-
mrsne r1, PSP /* else, Process Stack was used, put PSP in R1 */
-
/* ldr r0, [r1, #0] *//* get stacked R0 from stack */
-
ldr r1, [r1, #24] /* get stacked PC from stack */
-
ldrb r1, [r1, #-2] /* get the immediate data from the instruction */
之前说过一共对4个操作使用了SVC,系统调用只是其中之一。那在SVC异常里是如何识别是要启用哪一种操作呢,就靠上面的这段汇编码了。
我来解释一下上述代码。SVC异常是在系统权限下,使用MSP堆栈。根据LR的第2个bit来判断之前触发SVC函数使用的堆栈是PSP还是MSP。由于返回函数就是_mqx_api_cal触发SVC之后的紧接着的PC地址,由于在_mqx_api_cal使用的是PSP,所以我们就从PSP里提取出arm自动堆栈的8个寄存器的值,外加SVC之前那个PUSH
LR。堆栈里的PC里面获取SVC的吗,也就是SVC_MQX_FN。这样MQX就知道触发SVC的目的是完成一次系统调用,这个具体的完成步骤在_mqx_api_call_handler_prologue里。
-
ASM_LABEL(_mqx_api_call_handler_prologue)
-
mrs r1, PSP
-
-
/* modify stack - return adress from svc */
-
ldr r0, =_mqx_api_call_handler
-
/* bic r0, r0, #1 */
-
str r0, [r1, #24] /* set stacked PC to requested mqx fn */
-
-
/* modify stack - return address from mqx api */
-
ldr r0, =_mqx_api_call_handler_epilogue
-
/* bic r0, r0, #1 */
-
str r0, [r1, #20] /* set stacked LR to _mqx_fn_handler_epilogue */
-
-
/* get active task descriptor */
-
GET_KERNEL_DATA r0
-
ldr r0, [r0, #KD_ACTIVE_PTR]
-
-
/* set task flag to privilege mode */
-
ldr r1, [r0, #TD_FLAGS]
-
bic r1, r1, #TASK_USER_MODE
-
str r1, [r0, #TD_FLAGS]
-
-
/* privilege mode */
-
mov r0, #0
-
msr CONTROL, r0
-
-
cpsie.n i
-
-
bx lr
继续画一个图解释一下:
这里PSP堆栈里的PC和LR都被修改了,然后清除当前任务的用户模式状态,修改当前权限为privilege,然后退出异常。
退出异常这看似很简单,但其实内有乾坤。我们返回堆栈PSP的内容已经被修改了,返回的PC指向_mqx_api_call_handler,LR=_mqx_api_call_handler_epilogue,R0=MQX_API_TASK_CREATE,R1=¶ms。自然的,我们回到了函数_mqx_api_call_handler里。
-
uint_32 _mqx_api_call_handler
-
(
-
// [IN] API number - number of wrapped function
-
MQX_API_NUMBER_ENUM api_no,
-
// [IN] generic parameter - direct use with called MQX API fn
-
MQX_API_CALL_PARAMS_PTR params
-
)
-
{
-
uint_32 res = -1;
-
uint_32 param0 = params->param0;
-
uint_32 param1 = params->param1;
-
uint_32 param2 = params->param2;
-
uint_32 param3 = params->param3;
-
uint_32 param4 = params->param4;
注意一下_mqx_api_call_handler的参数,是不是和_mqx_api_cal一样啊,要知道前两个参数是通过R0和R1传递的,现在R0和R1不是正好是之前_mqx_api_cal传进来的MQX_API_TASK_CREATE和¶ms嘛。之后的一个switch里面就是系统调用的具体内容,由于这时的权限是privilege,很多在用户权限任务里不能完成的事情,在这里就可以完成,所谓系统调用也就是这个样子。
跳过中间一大堆内容,函数就通过return
res返回了。其实系统返回值是保存在R0里的,而系统返回到哪里呢?这个就要看LR了,之前在从异常返回时LR被赋值为_mqx_api_call_handler_epilogue,那现在_mqx_api_call_handler结束就自然返回到这里了。
-
ASM_LABEL(_mqx_api_call_handler_epilogue)
-
push {r0, r1}
-
-
/* get active task descriptor */
-
GET_KERNEL_DATA r0
-
ldr r0, [r0, #KD_ACTIVE_PTR]
-
-
/* set task flag to privilege mode */
-
ldr r1, [r0, #TD_FLAGS]
-
-
tst r1, #0x10 /* #MQX_USER_TASK */
-
beq _mqx_api_call_handler_epilogue_end
-
-
orr r1, r1, #TASK_USER_MODE
-
str r1, [r0, #TD_FLAGS]
-
-
/* user mode, proc stack */
-
mov r0, #3
-
msr CONTROL, r0
-
-
ASM_LABEL(_mqx_api_call_handler_epilogue_end)
-
pop {r0, r1, pc}
_mqx_api_call_handler_epilogue就是完成一些收尾工作,恢复用户标志,恢复用户权限,都是之前操作的逆操作,为返回用户任务做好准备。而所谓的返回用户任务也就是那么简单的一句:
pop {r0, r1, pc}
R0和R1自然是对应之前的压栈push {r0,
r1}操作,现在弹出来了,那PC是什么呢。大家记得最开始的_mqx_api_call所做的第一件事就是push
lr吧,这两个是互相对应的。在PSP堆栈图中,我们发现这个PC其实就是之前压入的LR,也就是调用_mqx_api_call的函数_usr_task_create的返回地址。
从_usr_task_create里返回,我们就这样完成了系统调用,虽然使用系统调用非常简单,但MQX内部的实现机制还是有点复杂的,不过研究这个也是非常有意思的。
阅读(3339) | 评论(0) | 转发(1) |