基于嵌入式操作系统 VxWorks 的多任务并发程序设计( 5
――中断与任务
作者:宋宝华  e-mail:[email][email protected][/email]  出处:软件报
中断处理是整个运行系统中优先级最高的代码,可以抢占任何任务级代码运行。中断机制是多任务环境运行的基础,是系统实时性的保证。几乎所有的实时多任务操作系统都需要一个周期性系统时钟中断的支持,用以完成时间片调度和延时处理。 VxWorks 提供 tickAnnounce() ,由系统时钟中断调用,周期性地触发内核。
为了快速响应中断, VxWorks 的中断服务程序( ISR )运行在特定的空间。不同于一般的任务,中断服务程序没有任务上下文,不包含任务控制块,所有的中断服务程序使用同一中断堆栈,它在系统启动时就已根据具体的配置参数进行了分配和初始化。在 ISR 中能使用的函数类型与在一般任务中能使用的有些不同,主要体现在:
1 ISR 中不能调用可能导致 blocking 的函数,例如:
(a) 不能以 semTake 获取信号量,因如果该信号量不可利用,内核会试图让调用者切换到 blocking 态;
(b)malloc free 可能导致 blocking ,因此也不能使用;
(c) 应避免进行 VxWorks I/O 系统操作(除管道外);
(d) 应避免在 ISR 中进行浮点操作。
2 )在 ISR 中应以 logMsg 打印消息,避免使用 printf
3 )理想的 ISR 仅仅调用 semGive 等函数,其它的事情交给 semTake 这个信号量的任务去做。一个 ISR 通常作为通信或同步的发起者,它采用发送信号量或向消息队列发送一个消息的方式触发相关任务至就绪态。 ISR 几乎不能作为信息的接收者,它不可以等待接收消息或信号量。

11.中断服务程序

VxWorks 中与中断相关的重要 API 函数或宏有:
1 intConnect() :中断连接,将中断向量与 ISR 入口函数绑定
SYNOPSIS STATUS intConnect
     (
       VOIDFUNCPTR *  vector,/* interrupt vector to attach to    */
       VOIDFUNCPTR    routine, /* routine to be called         */
       int        parameter /* parameter to be passed to routine */
      );
intConnect 只是调用了下文将要介绍的 intHandlerCreate() intVecSet() 函数。
2 INUM_TO_IVEC(intNum) :将中断号转化为中断向量的宏。与 INUM_TO_IVEC 对应的还有一个 IVEC_TO_INUM(intVec) ,实现相反的过程。 INUM_TO_IVEC IVEC_TO_INUM 的具体定义与特定的 BSP 有关,例如:
/* macros to convert interrupt vectors <-> interrupt numbers */
#define IVEC_TO_INUM(intVec)    ((int) (intVec))
#define INUM_TO_IVEC(intNum)    ((VOIDFUNCPTR *) (intNum))
结合 1 2 可知一般挂接一个中断服务程序的调用为:
intConnect(INUM_TO_IVEC(INTERRUPT_LEVEL),(VOIDFUNCPTR)interruptHandler,i);
1:中断服务程序
/* includes */
#include "vxWorks.h"
#include "intLib.h"
#include "taskLib.h"
#include "sysLib.h"
#include "logLib.h"
 
/* function prototypes */
void interruptHandler(int);
void interruptCatcher(void);
 
/* globals */
#define INTERRUPT_NUM 2
#define INTERRUPT_LEVEL 65
#define ITER1 40
#define LONG_TIME 1000000
#define PRIORITY 100
#define ONE_SECOND 100
 
void interruptGenerator(void) /* task to generate the SIGINT signal */
{
  int i, j, taskId, priority;
  STATUS taskAlive;

    
    
    
    
 
  if ((taskId = taskSpawn("interruptCatcher", PRIORITY, 0x100, 20000, (FUNCPTR)
    interruptCatcher, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
    logMsg("taskSpawn interruptCatcher failed\n", 0, 0, 0, 0, 0, 0);
 
  for (i = 0; i < ITER1; i++)
  {
    taskDelay(ONE_SECOND); /* suspend interruptGenerator for one second */
    /* check to see if interruptCatcher task is alive! */
    if ((taskAlive = taskIdVerify(taskId)) == OK)
    {
      logMsg("++++++++++++++++++++++++++Interrupt generated\n", 0, 0, 0, 0, 0,
        0);
      /* generate hardware interrupt 2 */
      if ((sysBusIntGen(INTERRUPT_NUM, INTERRUPT_LEVEL)) == ERROR)
        logMsg("Interrupt not generated\n", 0, 0, 0, 0, 0, 0);
    }
    else
     /* interruptCatcher is dead */
      break;
  }
  logMsg("\n***************interruptGenerator Exited***************\n\n\n\n", 0,
    0, 0, 0, 0, 0);
}
 
void interruptCatcher(void) /* task to handle the interrupt */
{
  int i, j;
  STATUS connected;
 
  /* connect the interrupt vector, INTERRUPT_LEVEL, to a specific interrupt
  handler routine ,interruptHandler,  and pass an argument, i */
  if ((connected = intConnect(INUM_TO_IVEC(INTERRUPT_LEVEL), (VOIDFUNCPTR)
    interruptHandler, i)) == ERROR)
    logMsg("intConnect failed\n", 0, 0, 0, 0, 0, 0);
 
  for (i = 0; i < ITER1; i++)
  {
    for (j = 0; j < LONG_TIME; j++)
      ;
    logMsg(" Normal processing in interruptCatcher\n", 0, 0, 0, 0, 0, 0);
  }
  logMsg("\n+++++++++++++++interruptCatcher Exited+++++++++++++++\n", 0, 0, 0,
    0, 0, 0);
}
 
void interruptHandler(int arg) /* signal handler code */
{
  int i;
 
  logMsg("-------------------------------interrupt caught\n", 0, 0, 0, 0, 0, 0);
  for (i = 0; i < 5; i++)
    logMsg("interrupt processing\n", 0, 0, 0, 0, 0, 0);
}
程序中的 sysBusIntGen() 调用将产生一个 bus 中断,这个函数与特定的 BSP 密切相关,其原型为:
STATUS sysBusIntGen
(
int intLevel, /* bus interrupt level to generate */
int vector /* interrupt vector to generate (0-255) */
);
为了在同一中断源的几种中断服务程序中进行切换,我们应使用如下方式:
vector = INUM_TO_IVEC(some_int_vec_num);
oldfunc = intVecGet (vector);
newfunc = intHandlerCreate (routine, parameter);
intVecSet (vector, newfunc);
...
intVecSet (vector, oldfunc); /* use original routine */
...
intVecSet (vector, newfunc); /* reconnect new routine */
其中, intHandlerCreate 函数的原型为:
FUNCPTR intHandlerCreate
(
FUNCPTR routine, /* routine to be called */
int parameter /* parameter to be passed to routine */
);
它被用于创建一个中断服务程序,在此之后,通过 intVecSet() 函数我们就可以将 intHandlerCreate() 创建的结果与中断向量绑定, intVecSet() 函数的原型为:
void intVecSet
(
FUNCPTR * vector, /* vector offset */
FUNCPTR function /* address to place in vector */
);

12.中断控制

12.1中断执行过程

硬件中断发生时,代码运行的上下文会发生切换,在进入中断处理前,需要保存当前运行的上下文。对于一些无 RTOS 的单片机系统,这些工作由硬件和编译器共同完成,向量表在编译完成后就填充完成,再写入存储器中,系统运行时不能修改向量表来重新绑定中断入口函数。在 VxWorks 系统中,除了需要保存通常的寄存器环境外,还需要完成栈切换等;另外还要求中断入口运行时绑定、平台移植性、中断嵌套等,所以 VxWorks 本身也参与中断封装的管理。 VxWorks 进行中断封装的伪代码如下:
* 00  e8 kk kk kk kk call  _intEnt * 通知内核
* 05  50   pushl %eax  * 保存寄存器
* 06  52   pushl %edx
* 07  51   pushl %ecx
* 08  68 pp pp pp pp pushl $_parameterBoi * push BOI param
* 13  e8 rr rr rr rr call  _routineBoi  * call BOI routine
* 18  68 pp pp pp pp pushl $_parameter  * 传中断入口参数
* 23  e8 rr rr rr rr call  _routine   * 调用中断处理 C 函数
* 28  68 pp pp pp pp pushl $_parameterEoi * push EOI param
* 33  e8 rr rr rr rr call  _routineEoi  * call EOI routine
* 38  83 c 4 0c   addl  ?, %esp   * pop param
* 41  59   popl  %ecx  * 恢复寄存器
* 42  5a    popl  %edx
* 43  58   popl  %eax
* 44  e9 kk kk kk kk jmp  _intExit * 通过内核退出

12.2中断使能/禁止

VxWorks 提供两个重要 API
1 intLock() :使中断禁止
2 intUnlock() :开中断
可以用 intLock/intUnlock 提供最高级别的互斥机制以保护临界区域不被打断,例如:
oldlevel = intLock();
/* XXX 寄存器 */
XXX_REG_WRITE(pChan, XXX_UBRDIV, XXX_CNT0_115200 |  XXX_CNT1_VAL); 
intUnlock(oldlevel);
intLock() 禁止中断后,当前执行的任务将一直继续,中断处理和任务调度得不到执行,直到该任务主动调用 intUnLock 解锁中断为止。对于 intLock unLock 的使用,我们要注意如下几点:
1 )不要在中断禁止期间调用 vxWorks 系统函数,否则有可能意外使能中断,违反临界代码的设计意图。另外, intLock 也不能屏蔽调度,如果在中断禁止代码区使用系统调用,就可能出现任务调度,其他任务的运行可能会解锁中断;
2 )中断禁止对系统的实时性有很大的影响,在解决执行代码和中断处理互斥问题才可使用,并且应使中断禁止时间尽可能的短。对于任务间的互斥问题,可以使用 taskLock() taskUnLock() 来解决;
3 )有些 CPU 中断是分级,我们可以用 intLockLevelSet() intLockLevelGet() 来操作中断闭锁的级别。缺省情况下, taskLock 禁止所有等级的中断。
至此,我们可以对“互斥”问题进行一个系统的总结,主要有如下几种方法:
1 intLock 禁止中断:解决任务和 ISR 之间的互斥问题;
  int lock = intLock();
  //. . critical region that cannot be interrupted
  intUnlock(lock);
2 taskLock 禁止优先级抢占调度:当当前任务正在运行时,除了中断服务程序外,高优先级的任务也不允许抢占 CPU
  taskLock();
  //. . critical region that cannot be interrupted .
  taskUnlock();
3 )二进制信号量或互斥信号量。
semTake (semMutex, WAIT_FOREVER);
  //. . critical region, only accessible by a single task at a time .
semGive (semMutex);
总的来说,在实时系统中采取“禁止中断”的方法会影响系统对外部中断及时响应和处理的能力;而“禁止优先级抢占调度”方法阻止了高优先级的任务抢先运行,在实时系统中也是不适合的。因此,信号量无疑是解决互斥问题的最好方法。