《VxWorks7编程指南》笔记(四)——中断服务程序ISR ⽬录
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。
hallstatt
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,但是中断事件通常会传播到任务级代码中。许多Vx
Works功能不适⽤于中断级代码,包括任何设备的I/O(除了pipe)。然⽽,如下技术可以⽤于ISR到任务级代码之间的通信。
共享内存与环形缓冲区
ISR可以与任务级代码共享变量、缓冲区和环形缓冲区。
信号量
ISR可以释放任务所需要的信号量(除了互斥信号量和VxMP共享信号量)。
消息队列
ISR可以向等待接收消息队列中的消息的任务发送消息(除了VxMP中使⽤的共享消息队列)。如果队列已满,则该消息将被丢弃。
管道
ISR可以向供任务读取的管道中写⼊消息。任务和ISR可以向同⼀个管道中写⼊消息。因为ISR不能被阻塞,所以如果管道已满,那么写⼊的消息将被丢弃。除了write()函数,ISR中不能在管道中触发任何I/O函数。
信号
ISR可以向任务发送信号,从⽽异步调度相关的信号处理函数。
VxWorks事件
ISR可以向任务发送VxWorks事件。
⾼中断优先级保留
⼤多数应⽤程序都可以使⽤VxWorks中断。但是有些情况下,类似于关键运动控制或系统失效响应等事件,需要拥有低级别的控制。在这些情况下,最好能够保留最⾼级别的中断,确保以零延时响应这些事件。为了实现零延时,VxWorks提供了intLockLevelSet()函数,⽤于将系统范围内的中断锁级别设定到⼀个特定的级别。如果没有指定⼀个级别,那么将使⽤处理器架构所⽀持的最⾼级别。
对⾼中断级别ISR的限制lookinto
对链接到未屏蔽的中断优先级(既可以是⽐intLockLevelSet()函数设置的中断优先级⾼的级别,也可以是由硬件定义的⾮屏蔽中断优先级)的ISR由特殊的限制:
ISR只能通过intVecSet()函数连接
ISR不能使⽤任何基于中断锁的VxWorks操作系统功能。否则后果就是,除了系统重启,ISR不能安全地执⾏任何VxWorks函数调⽤。
available的用法对于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应该使⽤⼀个内核调⽤唤醒该任务(例如mGive()).
将设备突发事件作为独⽴中断处理。应该在关闭设备相关的中断之后在任务上下⽂中处理突发事件。当没有再检测到未处理的事件且任务将会挂起时,就可以再次使能中断源了。