Chinaunix首页 | 论坛 | 博客
  • 博客访问: 181922
  • 博文数量: 20
  • 博客积分: 125
  • 博客等级: 入伍新兵
  • 技术积分: 985
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-08 13:48
个人简介

热爱开源,喜欢分析操作系统架构

文章分类

全部博文(20)

文章存档

2013年(17)

2012年(3)

分类: 嵌入式

2013-04-23 16:56:29

记得当时看linux源码时发现有中断线程化这么一个概念的时候让我很是新鲜,向来中断都是高高在上睥睨茫茫众生,想不到还能被进程拿下马来。当然linux采用这样的机制是为了考虑到实时性的要求,毕竟中断有各种各样的,总不能让一个十万火急的实时性进程等你一个悠哉悠哉相对不是太重要的中断服务结束吧。不过linux的中断线程我倒是没有深入研究,只是知道有此物的存在,今天在MQX这里,我看到了一个实施的案例。虽然MQX没有把中断也任务化了,当时它能够做到高优先级的实时性进程阻塞低优先级的中断,而这个特点ucos和rt thread是不具备的。

之前说过了中断的优先级,现在来来说MQX任务的优先级。每个任务都对于一个唯一的优先级(当然这样优先级也是可以动态调整的),每个优先级都有一个对于的READY_Q_STRUCT结构来管理具有改优先级的任务。关于这些数据结构,我想放到任务一篇里面专门讲,在这里我们只要知道每个优先级都有一个READY_Q_STRUCT,管着一票任务就可以了。而这里每个READY_Q_STRUCT都有一个ENABLE_SR,这个ENABLE_SR在_psp_init_readyqs函数里被赋值:

点击(此处)折叠或打开

  1. n = priority_levels;
  2.    while (n--) {
  3.       q_ptr->HEAD_READY_Q = (TD_STRUCT_PTR)q_ptr;
  4.       q_ptr->TAIL_READY_Q = (TD_STRUCT_PTR)q_ptr;
  5.       q_ptr->PRIORITY = (uint_16)n;

  6.       if (n + kernel_data->INIT.MQX_HARDWARE_INTERRUPT_LEVEL_MAX < ((1 << CORTEX_PRIOR_IMPL) - 1))
  7.         q_ptr->ENABLE_SR = CORTEX_PRIOR(n + kernel_data->INIT.MQX_HARDWARE_INTERRUPT_LEVEL_MAX);
  8.       else
  9.         q_ptr->ENABLE_SR = CORTEX_PRIOR((1 << CORTEX_PRIOR_IMPL) - 2);

  10.       q_ptr->NEXT_Q = kernel_data->READY_Q_LIST;
  11.       kernel_data->READY_Q_LIST = q_ptr++;
  12.    }

是不是挺眼熟的,在中断机制(中)关于DISABLE_SR(这玩意是全局的)的赋值也是用的CORTEX_PRIOR宏。这里n是优先级,我们假设n=3吧,那3号优先级对应的ENABLE_SR=(3+2)<<5=0xA0;假设n=8,则8号优先级对应的ENABLE_SR=(2)<<5=0x40。而前面DISABLE_SR=0x40,希望大家没有忘记。

至于每一个任务结构体td_struct也有一个变量TASK_SR,这个值是在任务的初始化函数_task_init_internal里赋值的:


点击(此处)折叠或打开

  1. td_ptr->TASK_SR = ready_q_ptr->ENABLE_SR;

非常简单的一句话,我们可以知道每个任务的TASK_SR都是对应自己优先级结构体的READY_Q_STRUCT里的ENABLE_SR。

现在已经说了DISABLE_SR、TASK_SR、ENABLE_SR,还有一个ACTIVE_SR,这个变量和DISABLE_SR一样都是全局的,在初始化的时候ACTIVE_SR和DISABLE_SR一样,都是0x40。到了这里,估计大多数人已经被这几个变量搞得头昏眼花了,不过我还不得不有加上最后一个,在全局变量中INTERRUPT_CONTEXT_PTR里面还有一个ENABLE_SR(这个结构体里其他的变量也十分重要):


点击(此处)折叠或打开

  1. typedef struct psp_int_context_struct
  2. {
  3.     /* Address of previous context, NULL if none */
  4.     struct psp_int_context_struct _PTR_ PREV_CONTEXT;

  5.     /* The exception number for this interrupt frame */
  6.     uint_32 EXCEPTION_NUMBER;

  7.     /* Used by the _int_enable function while in the ISR */
  8.     uint_32 ENABLE_SR;

  9.     /* The "task" error code for use by mqx functions while in the ISR */
  10.     uint_32 ERROR_CODE;
  11. } PSP_INT_CONTEXT_STRUCT, _PTR_ PSP_INT_CONTEXT_STRUCT_PTR

有5个关于优先级的变量,我被这5个家伙搞的真是头昏脑胀,因为它们涉及的东西太多了,不止是中断,还有任务、切换等一些列的东西,在这里我只是说这5个家伙在中断里的关系。

之前我们说过arm中断里有一个十分重要的寄存器BASEPRI,它能禁用所有优先级小于等于它的中断(异常),而上述的5个变量全都是和它有关的。

ACTIVE_SR在MQX初始化的时候是和DISABLE_SR一样的,不过在每次任务切换的时候,它都会被赋值为当前任务的TASK_SR,也就改优先级的ENABLE_SR。在dispatch.S中:


点击(此处)折叠或打开

  1. ASM_LABEL(switch_task)
  2.                 /* update kernel structures */
  3.                 str r1, [r0, #KD_CURRENT_READY_Q] /* store addr for active que */
  4.                 str r2, [r0, #KD_ACTIVE_PTR] /* active task descriptor */

  5.                 ldrh r3, [r2, #TD_TASK_SR]
  6.                 strh r3, [r0, #KD_ACTIVE_SR] /* restore priority mask for enabled interrupt for active task */

arm的汇编相对x86还是比较简单的,不愧是精简指令阵营的。上述最后两条汇编指令就是把当前优先级最高的td_struct里的TASK_SR赋值到ENABLE_SR里。如果当MQX的当前任务的优先级是3的话,不管它是运行在任务里,还是被切换到中断里,全局的ACTIVE_SR=0xA0,DISABLE_SR=0x40。

体现上面两个全局变量的作用的是_int_enable和_int_disable。要是放在别的系统里,这两个函数估计就是CPSIE I和CPSID I两条暴力开关全部中断的指令了。可惜MQX里面要复杂许多了,先看_int_disable:



点击(此处)折叠或打开

  1. #define _INT_DISABLE_CODE() \
  2.    if (kernel_data->ACTIVE_PTR->DISABLED_LEVEL == 0) { \
  3.       _PSP_SET_DISABLE_SR(kernel_data->DISABLE_SR); \
  4.    } /* Endif */ \
  5.    ++kernel_data->ACTIVE_PTR->DISABLED_LEVEL

  6. #define _PSP_SET_DISABLE_SR(x) _PSP_SET_ENABLE_SR(x)

  7. #define _PSP_SET_ENABLE_SR(x) { \
  8.     VCORTEX_SCB_STRUCT_PTR tmp = (VCORTEX_SCB_STRUCT_PTR)&(((CORTEX_SCS_STRUCT_PTR)CORTEX_PRI_PERIPH_IN_BASE)->SCB); \
  9.     if ((x & 0xf0) == 0x20) while (1) {}; \
  10.     if (!(tmp->ICSR & (1 << 28))) tmp->SHPR3 = (tmp->SHPR3 & 0xff00ffff) | ((((x) - 0x20) & 0xff) << 16); \
  11.     if ((x & 0xf0) == 0x10) while (1) {}; \
  12.     __set_BASEPRI(x);\
  13. }

我来解释一下上面的代码:首先每个td_struct都有一个DISABLED_LEVEL,这个变量记录的是在改任务作为系统当前任务时调用_int_disable的次数,每次调用(不管是在执行任务状态还是中断状态)DISABLED_LEVEL都会加一。如果是第一次调用的话,系统会采取相应的动作将BASEPRI设为DISABLE_SR也就是0x40,这样只有主优先级的高于2的中断才能触发了。此外这段代码会根据DISABLE_SR的值来调整pendsv中断的优先级,始终保证pendsv的优先级高于BASEPRI,至于为什么这样,谈到调度的时候再说。

而_int_enable基本上和_int_disable差不多,当还是有几点要说明的:

点击(此处)折叠或打开

  1. #define _INT_ENABLE_CODE() \
  2.    if (kernel_data->ACTIVE_PTR->DISABLED_LEVEL) { \
  3.       if (--kernel_data->ACTIVE_PTR->DISABLED_LEVEL == 0) { \
  4.          if (kernel_data->IN_ISR) { \
  5.             _PSP_SET_ENABLE_SR(kernel_data->INTERRUPT_CONTEXT_PTR->ENABLE_SR); \
  6.          } else { \
  7.             _PSP_SET_ENABLE_SR(kernel_data->ACTIVE_SR); \
  8.          } /* Endif */ \
  9.       } /* Endif */ \
  10.    } /* Endif */

首先_PSP_SET_ENABLE_SR的是ACTIVE_SR,这样BASEPRI就是0xA0,优先级大于5的都可以过,比之前松多了。其次IN_ISR是统计中断嵌套的全局变量,如果发生一次中断IN_ISR就加一。从代码我们可以看出如果在中断服务程序中调用_int_enable,它将BASEPRI设置为INTERRUPT_CONTEXT_PTR->ENABLE_SR,不是我们之前的ACTIVE_SR了。

这个INTERRUPT_CONTEXT_PTR是用来记录中断相关数据的,什么中断号啊、优先级啊、错误码啊都是关于中断的相关信息。想象一下,虽然当获取当前中断的这些信息是非常容易的,但若是一个中断被其他高优先级的中断嵌套了几次,那在高优先级的中断服务程序里获取之前嵌套的低优先级中断的相关信息就很难了,而INTERRUPT_CONTEXT_PTR就是为了这个而存在的。


图中涉及3个中断的嵌套,INTERRUPT_CONTEXT_PTR指向最后一个中断的相关信息,而每个中断的INTERRUPT_CONTEXT都指向前一个中断的INTERRUPT_CONTEXT,最开始的INTERRUPT_CONTEXT是一个内容物全为0的结构体。再来说一下为什么在中断中调_int_enable是将BASEPRI设置为INTERRUPT_CONTEXT_PTR->ENABLE_SR,因为中断服务程序可能会修改BASEPRI。比如中断3服务程序修改了BASEPRI为BASEPRI_3,如果在中断3的服务程序里想复原BASEPRI就不应该将BASEPRI设为任务的ACTIVE_SR,而是设为上一个中断的BASEPRI_1,而这就是INTERRUPT_CONTEXT_PTR->ENABLE_SR。

而关于INTERRUPT_CONTEXT_PTR的赋值,在dispatch.S里:



点击(此处)折叠或打开

  1. ASM_LABEL(_int_kernel_isr)
  2.                 cpsid.n i
  3.                 push {lr}

  4.                 GET_KERNEL_DATA r3 /* get the kernel data address */

  5.                 /* increment in interrupt counter */
  6.                 ldrh r1, [r3, #KD_IN_ISR]
  7.                 add r1, r1, #1
  8.                 strh r1, [r3, #KD_IN_ISR]

  9.                 /* create interrupt content */
  10.                 ldr r0, =0 /* error code (set 0) */
  11.                 push {r0} /* store in stack */
  12.                 mrs r2, BASEPRI /* actual priority */
  13.                 mrs r1, IPSR /* exception number */
  14.                 ldr r0, [r3, #KD_INTERRUPT_CONTEXT_PTR] /* previous interrupt content */
  15.                 push {r0-r2} /* store in stack */

  16.                 mrs r0, MSP /* get address of interrupt content */
  17.                 str r0, [r3, #KD_INTERRUPT_CONTEXT_PTR] /* store address of actual interrupt content in kernel data */

      画一个图解释一下


先把这4个玩意压入栈,SP就是INTERRUPT_CONTEXT_PTR,是不是非常巧妙!

到这里中断的机制就介绍完了,相对于其他的实时性系统,MQX的中断机制还是比较复杂的。但是这给系统带来更好的实时特性和较大的灵活性,可以使是MQX的一大亮点。


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