1.中断服务程序ISR
外部事件通常通过中断的方式通知系统,因此硬件中断处理是实时操作系统中的关键功能。
为了以最快速度响应中断,VxWorks中断服务程序执行在独有的上下文,而非任何任务是上下文中。除非系统专门进行过配置,否则不会推迟ISR的执行。
2.针对ISR的VxWorks配置
VxWorks系统默认支持SIR。中断栈可以按大小和附加特性进行配置。中断栈必须足够大,以处理中断嵌套时的最坏情况。
中断栈配置
所有的的ISR使用相同的中断栈空间。系统将在初始化时根据设定的配置参数为中断栈分配空间。其大小必须足够大,以应付中断嵌套时的最坏情况。中断栈大小由宏ISR_STACK_SIZE定义。
注意:某些架构不允许使用独立的中断栈,而需要使用被中断任务的栈空间。在这种架构下,必须确保创建任务时分配了足够大的栈空间,以处理中断嵌套和嵌套调用的最坏情况。
中断栈填充
默认情况下,中断栈空间被填充为0xEE。对栈空间进行填充的做法,对于开发过程中的调试很有帮助。具体可以使用checkStack()函数进行栈空间检查。
在进行系统配置时,建议不要对中断栈空间进行填充,以获得更佳的性能。可以使用配置参数VX_GLOBAL_NO_STACK_FILL关闭栈空间填充功能。
中断栈保护
如果使能了MMU功能,系统就可以通过配置INCLUDE_PROTECT_INTERRUPT_STACK组件,提供对中断栈始末的guard zone保护。
可以通过如下配置参数设置guard zone的大小:
- INTERRUPT_STACK_OVERFLOW_SIZE:设置中断栈上溢大小;
- INTERRUPT_STACK_UNDERFLOW_SIZE:设置中断栈下溢大小;
当添加了guard zone,栈空间的大小通常是MMU大小的整数倍。
3.ISR可用资源
所有的VxWorks功能库,如链表、环形缓冲区,都可以在ISR中使用。然而对于从ISR中调用函数还是有一些限制。
全局变量errno可以作为中断进入与退出代码的一部分而被保存和获取。所以,ISR刻意像其他代码一样引用和修改errno。
4.ISR编程与调试
对于ISR的最基础的要求就是,ISR代码应该尽可能的短。ISR中不应该添加耗时的函数,此外还有一些限制:ISR不能调用会导致ISR阻塞的函数,也不能调用使用浮点数协处理器的函数;对于C++使用也有限制。
非阻塞函数
虽然ISR中可以使用许多VxWorks功能,但是需要注意一些重要的限制条件。这些限制都是源于如下事实:ISR不是运行在常规的任务上下文中,且没有任务控制块,因此所有的ISR都共享同一个栈空间。
因为上述原因,对于ISR的最基础的限制条件就是,不能在ISR中调用会导致ISR自身阻塞的函数。例如,在ISR中不能尝试获取一个信号量,因为如果当前无法获取该信号量,那么内核将尝试把ISR切换到挂起状态。然而,ISR可以释放信号量,从而释放任何在等待该信号量的任务。
内存相关函数malloc()和free()中会尝试获取信号量,所以在ISR中不应该调用这两个函数。因此在ISR中不应该调用任何创建或删除函数。
ISR也不能通过VxWorks驱动执行I/O操作。尽管对I/O系统没有内在的限制条件,大多数的设备驱动仍然需要一个任务上下文环境,因为它们可能阻塞等待设备的任务。一个例外时VxWorks pipe驱动,ISR可以使用该驱动执行写操作。VxWorks还提供了几个供ISR调用的函数,用于向系统控制台中输出信息:logMsg()、kprintf()、kputs()。
浮点数协处理器函数
默认条件下,中断驱动代码不保存和读取浮点数寄存器。如果一个ISR需要执行浮点数指令,那么它必须使用fppArchLib库中提供的函数,对浮点数协处理器中的寄存器进行显示的保存和读取。
共享数据区的间接访问
ISR不应该直接访问共享数据区。一个ISR将继承被它抢占的任务的内存环境,如果该任务不包括共享数据区,那么ISR就不能访问共享内存,否则会导致系统异常。
为了可靠地访问共享数据区,ISR必须使用一个已经包含了共享数据区的任务。该任务在ISR执行完毕后可以执行对共享数据区的相关操作。
中断与任务之间的通信
尽管VxWorks支持直接连接运行在中断级的ISR,但是中断事件通常会传播到任务级代码中。许多VxWorks功能不适用于中断级代码,包括任何设备的I/O(除了pipe)。然而,如下技术可以用于ISR到任务级代码之间的通信。
共享内存与环形缓冲区
ISR可以与任务级代码共享变量、缓冲区和环形缓冲区。
信号量
ISR可以释放任务所需要的信号量(除了互斥信号量和VxMP共享信号量)。
消息队列
ISR可以向等待接收消息队列中的消息的任务发送消息(除了VxMP中使用的共享消息队列)。如果队列已满,则该消息将被丢弃。
管道
ISR可以向供任务读取的管道中写入消息。任务和ISR可以向同一个管道中写入消息。因为ISR不能被阻塞,所以如果管道已满,那么写入的消息将被丢弃。除了write()函数,ISR中不能在管道中触发任何I/O函数。
信号
ISR可以向任务发送信号,从而异步调度相关的信号处理函数。
VxWorks事件
ISR可以向任务发送VxWorks事件。
高中断优先级保留
大多数应用程序都可以使用VxWorks中断。但是有些情况下,类似于关键运动控制或系统失效响应等事件,需要拥有低级别的控制。在这些情况下,最好能够保留最高级别的中断,确保以零延时响应这些事件。为了实现零延时,VxWorks提供了intLockLevelSet()函数,用于将系统范围内的中断锁级别设定到一个特定的级别。如果没有指定一个级别,那么将使用处理器架构所支持的最高级别。
对高中断级别ISR的限制
对链接到未屏蔽的中断优先级(既可以是比intLockLevelSet()函数设置的中断优先级高的级别,也可以是由硬件定义的非屏蔽中断优先级)的ISR由特殊的限制:
- ISR只能通过intVecSet()函数连接
- ISR不能使用任何基于中断锁的VxWorks操作系统功能。否则后果就是,除了系统重启,ISR不能安全地执行任何VxWorks函数调用。
对于I/O的使用限制
通常,ISR不能通过VxWorks驱动执行I/O操作,因为这些操作会导致ISR阻塞。这意味着标准的I/O函数,如printf()和puts()不能用于调试ISR。基础的供ISR调试使用的方法包括:
- 在ISR中使用全局变量,该全局变量可以根据相关数据进行更新。之后就可以在运行时通过shell显示这些变量的值。
- 在ISR中使用如下函数向控制台输出信息:logMsg()、kprintf()、kputs()。
使用全局变量的优点在于:实现简单且对ISR执行的性能影响最小。缺点在于如果系统因为ISR中的某个bug导致挂起,就不能使用shell显示变量的值了。
使用logMsg()函数的优点在于:可以自动向控制台打印消息,相较于kprintf()和kputs(),对于ISR的执行性能的影响更小。缺点在于,如果系统在调用该函数后短暂地挂起了,那么消息可能无法显示在控制台中。原因是由于logMsg()的异步操作首先是把消息写入到一个消息队列,然后logging任务将消息打包再发送到控制台。
使用kprintf()与kputs()的优点在于:它们可以同步地输出消息(使用轮询模式),因此可以在bug出现时精确地反映出ISR已经执行到哪个阶段(也可用于内核启动期间或任务切换钩子函数中)。缺点在于,因为它们使用串口输出信息,所以或显著影响ISR的执行性能。
中断级异常
当一个任务产生了硬件异常,如非法指令或总线错误,那么该任务将被挂起。系统中的其他任务将继续执行,而不被中断。然而,当一个ISR导致了类似的异常,系统中将没有相应的安全保障用于处理这些异常。ISR不具备可以挂起的上下文环境。相反,VxWorks将这些异常描述信息保存在内存空间的一个特殊区域,并执行系统重启。
VxWorks的BootLoader将检测低内存空间中的异常描述信息是否存在,如果检测到,则会在系统控制台中显示出来。BootLoader的“e”命令用于重新显示异常描述信息。类似的异常信息如下:
workQPanic:Kernel work queued from ISR overflowd.
ISR与内核工作队列死机(work queueu panic)
为了减少工作队列溢出,可以使用WIND_JOBS_MAX内核配置参数增加内核工作队列大小。
与内核工作队列死机相关的错误信息如下:
workQPanic:kernel work queue overflow
5.修改系统时钟ISR
可以根据应用程序需要,在usrClock()函数中添加相关的函数。
在系统启动的初始化过程中,系统时钟ISR usrClock()函数被挂接到系统时钟定时器中断上。每个系统时钟中断都将调用usrClock(),用于更新系统tick值并执行调度。
在usrClock()函数中添加代码也要遵守在ISR中调用函数的要求。
6.运行时的ISR信息
针对每个与VxWorks连接的ISR,系统都创建了一个内核对象。ISR对象提供了一种管理系统中所有ISR信息的方法。
可以参考isrLib和isrShow API参考手册,了解用于获取ISR信息的相关函数。
INCLUDE_ISR_SHOW组件提供了isrShow(),INCLUDE_ISR_OBJECT提供了isrLib。
7.ISR与工作队列死机
如果ISR以非常高的频率填充内核工作队列,导致内核处理不过来,那么会将工作队列填满,使得无法再将新的工作项添加到工作队列中。这将导致称为工作队列死机(work queue panic)的致命错误。
为了降低中断响应时间,VxWorks内核在执行关键区代码时不会锁中断,如任务状态改变、上下文切换、重新调度等等。使能中断后,ISR将以尽可能快的速度执行。如果在内核执行关键区代码的过程中产生了一个中断,而ISR同样调用了需要执行关键代码的内核函数,那么内核将延迟关键区代码的执行,知道具备安全执行的条件为止。这个过程是通过内核工作队列实现的。工作队列可以高效地将内核关键区排入队列,并且不会阻塞ISR中非关键区的代码执行。
考虑到性能因素,工作队列被设计为一个具有固定大小的环形缓冲区。可以通过WIND_JOBS_MAX配置参数配置其大小,该大小必须是2的幂次。
工作队列死机(work queue panic)
通常,工作队列死机是由于设备驱动不恰当的写操作导致的。例如:
- 确认中断失败。如果一个ISR未能确认一个中断,那么将导致一直产生相同的中断。
- 在中断上下文执行了太多的操作。为了最佳的实时性能,可以将ISR中的复杂操作转移到与中断源相关的合适优先级的任务中执行。ISR应该使用一个内核调用唤醒该任务(例如semGive()).
- 将设备突发事件作为独立中断处理。应该在关闭设备相关的中断之后在任务上下文中处理突发事件。当没有再检测到未处理的事件且任务将会挂起时,就可以再次使能中断源了。