Chinaunix首页 | 论坛 | 博客
  • 博客访问: 46280
  • 博文数量: 15
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2013-09-06 15:00
文章分类

全部博文(15)

文章存档

2013年(15)

我的朋友

分类: 嵌入式

2013-10-15 15:12:55

原文地址:MQX心得之系统调用 作者:shaomengchao

正如前文所说,一旦MQX启用了用户权限任务,就会涉及系统调用的概念。提出所谓用户权限的概念就是为了限制一个任务对系统的影响,防止不健全或是恶意的任务破坏系统的稳定性。这样的机制多适用于windows或linux这样大型的可以自由安装软件的操作系统,毕竟我们这个世界还是充满着各种恶意软件。但在实时性系统中确比较少见,我想因为实时性系统较一般的操作系统要求高,其相应软件编写大多都是高效、面向底层、具有针对性的专门化软件。要更高效的完成任务,要求软件编写者对实时性系统有着深刻的理解,相应的也要解除系统对于他们编写软件的优先级限制。

当然随着实时性系统功能越多,其架构越来越复杂,要求一个用户理解越来越多的代码渐渐的变得不太现实(比如我们几十兆的MQX源码)。于是MQX就可根据用户的选择来决定是否启用用户级权限和系统调用机制。而所谓的系统调用,在arm里面是由SVC指令来完成的。下面举个创建任务的例子:

点击(此处)折叠或打开

  1. _task_id _usr_task_create
  2. (
  3.     _processor_number processor_number,
  4.     _mqx_uint template_index,
  5.     uint_32 parameter
  6. )
  7. {
  8.     MQX_API_CALL_PARAMS params =
  9.         {(uint_32)processor_number, (uint_32)template_index, (uint_32)parameter, 0, 0};

  10.     return _mqx_api_call(MQX_API_TASK_CREATE, ?ms);
  11. }

对于一个系统调用有两个重要的参数:一个是系统调用号,用来告诉系统我们要调用的是哪一个服务,这里是MQX_API_TASK_CREATE;另一个是要调用服务的参数结构体

点击(此处)折叠或打开

  1. typedef struct {
  2.     /*! \brief Parameter 0. */
  3.     uint_32 param0;
  4.     /*! \brief Parameter 1. */
  5.     uint_32 param1;
  6.     /*! \brief Parameter 2. */
  7.     uint_32 param2;
  8.     /*! \brief Parameter 3. */
  9.     uint_32 param3;
  10.     /*! \brief Parameter 4. */
  11.     uint_32 param4;
  12. } MQX_API_CALL_PARAMS, _PTR_ MQX_API_CALL_PARAMS_PTR
   最多可以塞入5个参数,这里用了三个:processor_number,template_index, parameter。

点击(此处)折叠或打开

  1. ASM_LABEL(_mqx_api_call)

  2. #if MQX_ENABLE_USER_MODE
  3.                 push {lr}
  4.                 svc SVC_MQX_FN
  5. #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异常。


点击(此处)折叠或打开

  1. ASM_LABEL(_svc_handler)
  2.                 cpsid.n i

  3.                 tst lr, #0x4 /* test EXC_RETURN number in LR bit 2 */
  4.                 ite eq /* if zero (equal) then */
  5.                 mrseq r1, MSP /* Main Stack was used, put MSP in R1 */
  6.                 mrsne r1, PSP /* else, Process Stack was used, put PSP in R1 */
  7. /* ldr r0, [r1, #0] *//* get stacked R0 from stack */
  8.                 ldr r1, [r1, #24] /* get stacked PC from stack */
  9.                 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里。

点击(此处)折叠或打开

  1. ASM_LABEL(_mqx_api_call_handler_prologue)
  2.                 mrs r1, PSP

  3.                 /* modify stack - return adress from svc */
  4.                 ldr r0, =_mqx_api_call_handler
  5.                 /* bic r0, r0, #1 */
  6.                 str r0, [r1, #24] /* set stacked PC to requested mqx fn */

  7.                 /* modify stack - return address from mqx api */
  8.                 ldr r0, =_mqx_api_call_handler_epilogue
  9.                 /* bic r0, r0, #1 */
  10.                 str r0, [r1, #20] /* set stacked LR to _mqx_fn_handler_epilogue */

  11.                 /* get active task descriptor */
  12.                 GET_KERNEL_DATA r0
  13.                 ldr r0, [r0, #KD_ACTIVE_PTR]

  14.                 /* set task flag to privilege mode */
  15.                 ldr r1, [r0, #TD_FLAGS]
  16.                 bic r1, r1, #TASK_USER_MODE
  17.                 str r1, [r0, #TD_FLAGS]

  18.                 /* privilege mode */
  19.                 mov r0, #0
  20.                 msr CONTROL, r0

  21.                 cpsie.n i

  22.                 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里。


点击(此处)折叠或打开

  1. uint_32 _mqx_api_call_handler
  2.     (
  3.         // [IN] API number - number of wrapped function
  4.         MQX_API_NUMBER_ENUM api_no,
  5.         // [IN] generic parameter - direct use with called MQX API fn
  6.         MQX_API_CALL_PARAMS_PTR params
  7.     )
  8. {
  9.     uint_32 res = -1;
  10.     uint_32 param0 = params->param0;
  11.     uint_32 param1 = params->param1;
  12.     uint_32 param2 = params->param2;
  13.     uint_32 param3 = params->param3;
  14.     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结束就自然返回到这里了。


点击(此处)折叠或打开

  1. ASM_LABEL(_mqx_api_call_handler_epilogue)
  2.                 push {r0, r1}

  3.                 /* get active task descriptor */
  4.                 GET_KERNEL_DATA r0
  5.                 ldr r0, [r0, #KD_ACTIVE_PTR]

  6.                 /* set task flag to privilege mode */
  7.                 ldr r1, [r0, #TD_FLAGS]

  8.                 tst r1, #0x10 /* #MQX_USER_TASK */
  9.                 beq _mqx_api_call_handler_epilogue_end

  10.                 orr r1, r1, #TASK_USER_MODE
  11.                 str r1, [r0, #TD_FLAGS]

  12.                 /* user mode, proc stack */
  13.                 mov r0, #3
  14.                 msr CONTROL, r0

  15. ASM_LABEL(_mqx_api_call_handler_epilogue_end)
  16.                 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内部的实现机制还是有点复杂的,不过研究这个也是非常有意思的。




阅读(1203) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~