Windows CE的陷阱调度是嵌入式系统开发的一个重要内容,陷阱调度的好坏关系到系统的健壮性和稳定度。陷阱调度看起来只有几个常用语句,故有些开发人员常常会对陷阱调度轻视和在使用上思路模糊。
近期我在一个嵌入式开发项目中就体验到轻视陷阱调度的惨痛教训,因为对陷阱调度没有处理好,后果是严重影响系统稳定性。因此,我认为陷阱调度并不是表面看起来的那么简单。本文分享在此项目过程中对陷阱调度的一些看法。
一.什么是陷阱调度机制?
一般来说,嵌入式操作系统主要由两部分组成:运行在核心态的内核系统和运行在用户态的环境子系统组成。因此,Windows CE系统被划分为两层:执行体和内核。而内核始终运行在核心态下,除了中断服务例程(Interrupt Service Routine,ISR),正在运行的线程是不能抢先内核的。为此,Windows CE为执行体、内核、设备驱动程序等核心态提供了一些基础系统机制。
(1)Windows CE系统机制
Windows CE系统机制包括陷阱调度、执行体对象管理器、各种同步对象以及本地过程调用等。一般来说,可以分成两种对象类型:执行体对象和内核对象。内核以内核对象的形式给执行体提供其它的同步机构,称为“调度程序对象”。包括进程、线程、事件、信号量、互斥体、可等待的定时器、文件等同步对象。每个同步对象有两种状态:“有信号”,“无信号”。内核还提供一组严格定义的、可预测的、使操作系统得以工作的基础设施,这为执行体的高级组件提供了必须的低级功能接口。内核除了执行线程调度外,几乎将所有的策略制定留给了执行体。同时,Windows CE运行中的CPU 会支持两个级别的权限,其中较高级别的权限称为内核态,较低级别的权限称为用户态。
(2)陷阱调度(Trap Dispatching)机制
Windows CE的基本机制之一是陷阱调度,属于内核功能。包括中断调度、延迟过程调用(DPC)、异步过程调用(APC)、异常调度、系统服务调度。
陷阱处理程序是Windows CE用来处理意外事件的硬件机制。当异常或中断发生时,硬件或软件就能检测到它们,并捕获正在执行的线程,CPU会从用户态切换到核心态,将暂停正在处理的事情,把控制转交给内核的陷阱处理程序。同时,该模块还能检测异常和中断的类型,并将控制交给处理相应情况的代码。因此,陷阱调度机制是当异常或者中断发生时,能够保存当前线程状态并转向相应处理的一种系统机制。
在Windows CE系统里,内核通过以下方式来分辨中断和异常:中断是一个异步事件(可以在任何时间产生),不管处理器在执行什么程序。典型的中断由I/O设备、时钟、定时器产生,必要时可以屏蔽中断。而异常是一个同步事件,它是由正在执行的特定代码产生的,重新执行相同的代码会重复产生特定的异常。比如访问非法内存、除数为0等。系统把系统服务也作为异常来处理。
二.陷阱调度核心:中断机制
在Windows CE陷阱调度中最重要之一是中断机制。当陷阱处理程序被调用时,将在记录机器状态时暂时禁用中断,它会创建一个陷阱帧(Trap Frame)来保存被中断线程运行现场,并在合适的时候恢复线程执行时使用。陷阱帧通常是完整的线程描述表的子集。
(1)中断调度
不同的CPU中断机制是不一样的,Windows CE的中断调度程序会将硬件中断级映射到由操作系统识别的中断请求级别(Interrupt ReQuest Level,IRQL)的标准集上。这与线程的调度优先级是完全不同的含义,调度优先级是线程的属性,而IRQL则是中断源的属性。因此,每个CPU都具有一个IRQL设置,其值随着操作系统代码的执行而改变。内核定义了一组可移植的IRQL,如果CPU具有与中断相关的特性,则可以增加IRQL。IRQL按优先级排列中断,并进行中断服务,较高优先级的中断服务可以抢占较低优先级的中断服务。
一般来说,IRQL从高往低到设备都是为硬件中断保留,而DPC和APC级中断是内核和设备驱动器产生的软件中断。低优先级(也称作被动级)实际上并不是真正的中断级,在该级上执行的是普通线程,并允许发生所有的中断。IRQL设置决定了每个处理器可以接收的中断。当核心态线程运行时,可以提高或降低处理器的IRQL来屏蔽一些事件。
如果中断源的IRQL高于当前中断设置,则中断可以中断该处理器;如果中断源的IRQL等于或低于当前中断设置,则中断将被封锁或“屏蔽”,直到一个正在执行的线程降低了IRQL。当产生中断时,陷阱处理程序能提高处理器的IRQL直到与中断源所指定的IRQL相同,这可以保证服务于该中断的处理器不会被同级或较低级的中断抢先。被屏蔽的中断将被另一个处理器处理或阻挡,直到IRQL降低。因为改变处理器的IRQL对操作系统具有如此重要的影响,所以它只能在核心态下改变。
(2)硬件中断
最典型的硬件中断是由I/O设备产生的,当这些设备需要服务时,必须通知处理器。中断驱动的设备允许操作系统通过将指令执行与I/O操作重叠进行,以获得处理器的最大利用率。处理器启动发往设备的I/O传送或来自设备的I/O传送,然后在设备完成传送时执行其它线程。当设备执行完后,中断处理器就能获得服务。定点设备、打印机、键盘、磁盘驱动器以及网卡通常都是中断驱动的。
大多数硬件中断允许设备驱动程序注册其设备的ISR,包含内核所需的设备ISR与中断特定级相联系的所有信息,包括ISR的地址、设备中断的IRQL以及与ISR相联系的内核入口。当中断对象被初始化后,称为调度代码的一些汇编语言代码指令就会被存储在对象中。当中断发生时,这些代码会调用真正的中断调度程序,并传递一个指向中断对象的指针。中断对象包含了第二个调度程序例程所需要的信息,以便定位和正确地调用设备驱动程序提供的ISR。需要两步过程的原因是自硬件完成初始调度后,没有方法可以在初始调度上传递一个指向中断对象的指针。
(3)软件中断
软件也可以产生中断,包括:启动线程调度、处理定时器到时、在特定线程的描述表中异步执行一个过程,以及支持异步I/O操作等。例如,内核可以发布启动线程调度的软件中断,内核也可以禁用中断以使处理器不被中断,但这种情况很少出现,只在处理中断或调度异常的关键时刻才这样做。软件中断由中断调度程序的子模块响应,它确定中断源并将控制转交给处理中断的外部例程(ISR),或转交给响应中断的内核例程。
三.Windows CE如何进行中断处理?
(1)Windows CE中断处理流程
①硬件设备向Kernel发送中断异常的代码,如果检测到这个中断异常,就会被Kernel层的异常处理所截获。然后,中断服务调度程序会调用OAL例程中的OEMInterruptDisable函数,这个函数会通知硬件在处理完这一中断前关闭特殊的中断,但其它的中断仍然处于开放状态,中断服务例程ISR会被调用来决定如何处理这一中断。
②Kernel接收到ISR的返回值可得知如何处理这一中断。它的响应结果之一是忽略掉这一中断不作处理(SYSINTR_NOP),或另一结果是准备执行IST。然后,Kernel引发中断服务调度程序去唤醒中断服务线程。IST是常规的Win32线程,一旦启动后,它会创建必要的EVENT,然后等待该EVENT被激发。中断服务调度通过调用PulseEvent函数来激发EVENT,从而唤醒IST线程运行。当唤醒以后,IST会对中断进行处理。
③当IST处理完成后,还需要调用InterruptDone函数通知Kernel。Kernel则调用OEMInterruptDone函数完成此次中断的处理过程。最后,OAL例程通知硬件设备重新启用中断。
(2)中断处理涉及的几个常用函数
Windows CE在处理中断时会涉及到两类函数的使用,第一类是供OAL调用的ISR函数,例如HookInterrupt函数在OEMInit函数中被调用以关联IRQ和ISR,UnhookInterrupt函数用来终止IRQ和ISR的关联。第二类是供驱动程序调用的IST函数,例如InterruptInitialize函数用来将EVENT对象和逻辑中断号关联并允许中断,InterruptDone函数用来通知中断处理的结束,InterruptDisable函数被驱动程序调用以关闭中断同时取消被InterruptInitialize初始化的EVENT对象。
ISR函数属于OAL层,它将CPU寄存器中的数据移动到内存缓冲区中,但是它不能做更多的工作,其中一个原因是它不能访问用户态的存储区,它要把这些工作交给IST来完成。ISR函数做的另一项工作是进行物理中断号和逻辑中断号的映射。经过ISR后,就能把这一物理中断转换成Windows CE标准的SYSINTR_KEYBOARD逻辑中断。Kernel会根据这个逻辑中断值找到对应的EVENT,从而唤醒IST。
最后,需要提醒一下的是,常常有一些开发人员习惯拖延或忽视陷阱调度的编写。因为轻视陷阱调度这一坏习惯是如此常见,它甚至已经影响到了Windows CE系统的研发。因此,建议从开始时就应该着手进行陷阱调度研发和计划,应该投入大精力把陷阱调度的策略融合到嵌入式产品中。