发布日期: 7/23/2004 | 更新日期: 7/23/2004
Nat Frampton(Windows Embedded MVP
Real Time Development Corp. 总裁)
适用于:
Microsoft Windows CE .NET with Microsoft Platform Builder 4.0
通过 Microsoft Windows CE .NET,Microsoft 已经升级了 Windows CE 的中断体系结构。该操作系统 (OS) 所具有的处理共享中断的能力极大地扩展了 Windows CE .NET 支持许多中断体系结构的能力。本文从原始设备制造商 (OEM) 和应用程序开发人员的角度探讨了处理中断的方案。本文还探讨了 OEM 适配层 (OAL) 中断服务例程 (ISR) 处理;提供了可安装 ISR,包括一个简单的入门级外壳程序;介绍了中断服务线程 (IST) 中断处理,并提供了一个初始化和执行模板。最后,本文分析了 ISR 和 IST 的延迟根源。
探讨 Microsoft Windows CE .NET 中断体系结构的第一步是定义中断过程中硬件、内核、OAL 和线程交互的总体模型。下图大概说明了这些不同级别的职责以及导致状态变化的转换。
图 1.
该图阐述了中断过程中的主要转换,时间按从左到右的顺序递增。该图的最低层为硬件和中断控制器的状态。次低层是中断服务过程中的内核交互。OAL 描述了主板支持软件包 (BSP) 的职责。最顶层阐述了中断服务所需的应用程序或驱动程序线程交互。该图阐述了单个中断过程中的交互;它表示了 Windows CE .NET 拥有共享中断的新能力。
活动从该图最左侧部分以直线表示的中断开始。生成了一个异常,导致内核 ISR 向量被加载到处理器中。内核 ISR 与硬件交互,禁用所有处理器上的所有具有相同和较低优先级的中断(ARM 和 Strong ARM 体系结构除外)。然后,内核推进到已为该特定中断注册的 OAL ISR。此后,OAL ISR 既可以直接处理中断,也可以使用 NKCallIntChain 遍历已安装的 ISR 列表。主 ISR 或任何已安装的 ISR 随后执行任意工作,并且为该设备返回名为 SYSINTR 的映射中断。如果该 ISR 确定其相关设备没有导致该中断,该 ISR 将返回 SYSINTR_CHAIN,这会使 NKCallIntChain( ) 遍历 ISR 列表以到达链中的下一个中断。ISR 按照它们的安装顺序调用(它们在安装时会在调用列表上创建一个优先级)。
在调用了单个 ISR 或其相关 ISR 链之后,返回值可能为下列值之一:
返回值
操作
SYSINTR_NOP
中断不与设备的任何已注册 ISR 关联。内核启用所有其他中断。
SYSINTR
中断与已知的已注册 ISR 和设备关联。
SYSINTR_RESCHED
中断是由请求 OS 重新调度的计时器到期引起的。
SYSINTR 返回值是我们讨论的重点。一旦 ISR 完成,内核将重新启用处理器上除已识别的中断之外的所有中断。然后,内核将通知与 SYSINTR 值关联的事件。
然后,驱动程序或应用程序的 IST 将能够运行(假设它是准备好运行的最高优先级线程)。IST 将与相关设备通讯,并从完成它的中断交互的设备中读取所有必要的数据。然后,IST 用关联的 SYSINTR 值来调用 InterruptDone( ),以通知它已完成。
内核在接收到 SYSINTR 值的 InterruptDone 时,将重新启用指定的中断。只有从这时开始,才能接收该设备的其他中断。
这只是对 Windows CE .NET 内部活动的中断序列的一个粗略介绍。现在,我们将详细研究上述每个组件及其职责。
OAL ISR 是属于平台的基本中断处理程序。下面是 X86 平台的实际 ISR。配置分析和 ILTiming 支持已被删除。X86 ISR 是所有基于 Windows CE 的平台的代表。它演示了能够处理系统中所有中断的单个 ISR。
该 ISR 的目标是向内核交还引起中断的相关设备的 SYSINTR 号。ISR 执行以下活动序列。
•
从 PICGetCurrentInterrupt (PIC) 中获取当前硬件中断
•
如果该中断是 INTR_TIMER0(系统计时器)
•
更新OS 的 CurMSec 保持时间
•
检查并确认是否已经注册了重新启动地址 (RebootHandler)
•
如果中断是 INTR_RTC
•
ISR 检查并确认闹钟是否已到期 (SYSINTR_RTC_ALARM)
•
如果中断小于 INTR_MAXIMUM
•
调用中断链 (NKCallIntrChain)
•
将 NKCallIntrChain 的返回值设置为该返回值
•
如果中断链未包含中断:(SYSINTR_CHAIN)
映射当前硬件中断 (OEMTranslateIRQ)
如果该中断被注册到 OEMInit 中的 HookInterrupt
从 OEMTranslateIRQ 返回 SYINTR 值
如果该中断未注册,则返回 SYSINTR_NOP
•
启用除当前中断以外的所有中断。(PICEnableInterrupt}
•
完成恰当的中断结束工作以通知 PIC 中断已完成 (EOI)
•
ISR 返回下列值之一:
•
SYSINTR_NOP — 没有任何 ISR 包含该中断
•
SYSINTR_RESCHED — 重新调度计时器已到期
•
SYSINTR — ISR 已经包含该中断
•
SYSINTR_RTC_ALARM — 闹钟已到期
ULONG PeRPISR(void) { ULONG ulRet = SYSINTR_NOP; UCHAR ucCurrentInterrupt; ucCurrentInterrupt = PICGetCurrentInterrupt(); if (ucCurrentInterrupt == INTR_TIMER0) { CurMSec += SYSTEM_TICK_MS; CurTicks.QuadPart += TIMER_COUNT; if ((int) (CurMSec - dwReschedTime) >= 0) ulRet = SYSINTR_RESCHED; } // // Check if a reboot was requested. // if (dwRebootAddress) { RebootHandler(); } } else if (ucCurrentInterrupt == INTR_RTC) { UCHAR cStatusC; // Check to see if this was an alarm interrupt cStatusC = CMOS_Read( RTC_STATUS_C); if((cStatusC & (RTC_SRC_IRQ)) == (RTC_SRC_IRQ)) ulRet = SYSINTR_RTC_ALARM; } else if (ucCurrentInterrupt <= INTR_MAXIMUM) { // We have a physical interrupt ID, return a SYSINTR_ID // Call interrupt chain to see if any installed ISRs handle this // interrupt ulRet = NKCallIntChain(ucCurrentInterrupt); if (ulRet == SYSINTR_CHAIN) { ulRet = OEMTranslateIrq(ucCurrentInterrupt); if (ulRet != -1) PICEnableInterrupt(ucCurrentInterrupt, FALSE); else ulRet = SYSINTR_NOP; } else { PICEnableInterrupt(ucCurrentInterrupt, FALSE); } } if (ucCurrentInterrupt > 7 || ucCurrentInterrupt == -2) { __asm { mov al, 020h ; Nonspecific EOI out 0A0h, al } } __asm { mov al, 020h ; Nonspecific EOI out 020h, al } return ulRet; }
如果 ISR 没有为已经用 OAL 的 OEMInit 中的 HookInterrupt 初始化的中断安装,则该 ISR 将返回适当的 SYSINTR 值。
注 如果只能通过 IST 交互为设备提供服务,则不需要为中断安装可安装的 ISR。通过对 OAL 的 OEMInit 中的 HookInterrupt 进行调用以启用中断就足够了。
ISR 代码是一段非常小且快速的代码。它的执行时间将直接影响整个系统中的中断的延迟。Windows CE 3.0 中引入的中断体系结构更改是能够嵌套中断。在进入 OAL ISR 的那一刻,所有具有较高优先级的中断都已被启用。ISR 可能被占先。如果该 ISR 内部的计时非常关键,则可能要求在该时间段内禁用中断。就像 ISR 执行时间一样,中断被关闭的这一时间将增加平台的最差情形延迟。
当 ISR 交还与特定设备相关的 SYSINTR 时,内核将通知 IST 醒来。处理驱动程序或应用程序内部代码的 IST 中断负责结束中断交互。
可安装的 ISR 是为响应 Windows CE .NET 为嵌入式空间带来的开放性而创建的。OEM 再也不必完全负责平台和应用程序代码了。现在平台提供商和应用程序开发人员都可涉及嵌入式空间这一领域的工作。如果某个应用程序开发人员在使用 Windows CE 3.0 的平台上向开放总线添加了新的设备,OEM 将必须说服该 OEM 将 ISR 添加到该平台。
要将 ISR 安装到平台中,需要完成两个步骤:
•
调用 LoadIntChainHandler 函数以加载包含 ISR 代码的 DLL。
•
必须将 ISR 编码为用 SYSINTR_ . . . 响应进行响应,就像在 OAL ISR 中一样。
LoadIntChainHandler 函数将 ISR 动态链接库 (DLL) 加载到内核的地址空间中。这意味着代码不能调用任何非内核函数,包括任何 C 语言运行时库函数。记住,某些结构到结构赋值会降格为 memcpy 调用,必须检查所有代码以确保不需要任何外部库(即使这些库是由编译器创建的)。
下面的源代码示例说明了一个用于创建可安装的 ISR 的基本外壳程序。有四个函数:
•
DLLEntry — 接收进程和线程附加消息
•
InfoCopy — 在进行任何结构赋值时使用的复制例程
•
IOControl — 任何使用 KernelLibIOControl 的 IST 调用的处理程序
•
ISRHandler — 实际的 ISR
BOOL __stdcall DllEntry( HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved ) { if (dwReason == DLL_PROCESS_ATTACH) {} if (dwReason == DLL_PROCESS_DETACH) {} return TRUE; } // The compiler generates a call to memcpy() for assignments of large objects. // Since this library is not linked to the CRT, define our own copy routine. void InfoCopy( PVOID dst, PVOID src, DWORD size ) { while (size--) { *((PBYTE)dst)++ = *((PBYTE)src)++; } } BOOL IOControl( DWORD InstanceIndex, DWORD IoControlCode, LPVOID pInBuf, DWORD InBufSize, LPVOID pOutBuf, DWORD OutBufSize, LPDWORD pBytesReturned ) { switch (IoControlCode) { case IOCTL_DEMO_DRIVER: // Your I/O Code Here return TRUE; break; default: // Invalid IOCTL return FALSE; } return TRUE; } DWORD ISRHandler( DWORD InstanceIndex ) { BYTE Value; Value = READ_PORT_UCHAR((PUCHAR)IntrAddress ); // If interrupt bit set, return corresponding SYSINTR if ( Value & 0x01 ) { return SYSINTR_DEMO; } else { return SYSINTR_CHAIN; } }
ISR 处理程序代码使用端口 I/O 调用来检查设备的状态。您的方案可能要求复杂得多的询问。如果该设备不是中断源,则返回值将是 SYSINTR_CHAIN。此返回值告诉 NKChainIntr 函数该设备不是中断源,应该评估链中的其他 ISR。如果 ISR 返回有效的 SYSINTR,则 NKChainIntr 将立即返回并且不调用列表中的任何其他 ISR。这将提供优先级排序。第一个加载的可安装 ISR 被首先加载到该列表中(或具有最高优先级),然后将后续可安装 ISR 添加到该列表的底部。由于优先级和执行速度这两方面的原因,应该首先安装链中具有最高优先级的可安装 ISR。
处理来自应用程序或驱动程序的中断需要进行两个步骤的处理。首先,必须使用关联的事件初始化中断。其次,IST 必须等待中断事件以响应内核中的中断。
以下示例代码将设置 IST 并将 IST 与特定的中断相关联。初始化中断的关键步骤包括:
•
创建事件
•
获取 IRO 的系统中断号
•
创建挂起的中断线程 (IST)
•
调用 InterruptInitialize 以创建 IRQ 与事件之间的关联
•
创建未挂起的 IST 可能会导致 InterruptInitialize 失败,因为该事件已经处于被等待状态
•
将线程优先级设置为相应的优先级
•
恢复 IST
Void SetupInterrupt( void ) { // Create an event // g_hevInterrupt = CreateEvent(NULL, FALSE, FALSE, NULL); if (g_hevInterrupt == NULL) { RETAILMSG(1, (TEXT("DEMO: Event creation failed!!!\r\n"))); return; } // Have the OAL Translate the IRQ to a system irq // fRetVal = KernelIoControl( IOCTL_HAL_TRANSLATE_IRQ, &dwIrq, sizeof( dwIrq ), &g_dwSysInt, sizeof( g_dwSysInt ), NULL ); // Create a thread that waits for signaling // g_fRun = TRUE; g_htIST = CreateThread(NULL, // Security 0, // No Stack Size ThreadIST, // Interrupt Thread NULL, // No Parameters Create_SUSPENDED, // Create Suspended &dwThreadID // Thread Id ); // Set the thread priority – arbitrarily 5 // m_nISTPriority = 5; if( !CeSetThreadPriority( g_htIST, m_nISTPriority )) { RETAILMSG(1,(TEXT("DEMO: Failed setting Thread Priority.\r\n"))); return; } // Initialize the interrupt // if ( !InterruptInitialize(g_dwSysInt,g_hevInterrupt,NULL,0) ) { RETAILMSG (1, (TEXT("DEMO: InterruptInitialize failed!!!\r\n"))); return; } // Get the thread started // ResumeThread( g_htIST ); }
需要注意的是,对 InterruptInitialize 的调用仅采用 SYSINTR 值和事件作为参数。内核不知道或者不关心将要等待该事件的线程。这样,就可以建立多种应用程序和驱动程序体系结构。应用程序的简单主循环可以初始化中断, 然后立即等待该事件。中断只能与一个事件关联,并且该事件不能用于对 WaitForMultipleObjects 的调用中。我们将观察一个简单的为中断提供服务的线程。这是大多数实现中的标准解决方案。
本节提供了一个 IST 的示例代码。该 IST 中断处理线程的关键组件包括:
•
等待中断事件。
•
确认有一个来自 OS 的脉动性事件
•
执行任何必要的板级中断处理以完成中断。在该示例中,我们将确认该中断。
•
在尽可能短的时间内处理该中断
•
创建 CELOGDATA 以供在 Kernel Tracker 中查看。
•
检查并确认是否设置了 g_fPRRunning 标志,然后设置 g_hevPRStart 事件。
•
调用 InterruptDone()。
•
在调用 InterruptDone 之前,OS 不会提供此 IRQ 上的其他中断。
•
再次等待中断事件。
DWORD WINAPI ThreadIST( LPVOID lpvParam ) { DWORD dwStatus; BOOL fState = TRUE; // Always chec the running flag // while( g_fRun ) { dwStatus = WaitForSingleObject(g_hevInterrupt, INFINITE); // Check to see if we are finished // if(!g_fRun ) return 0; // Make sure we have the object // if( dwStatus == WAIT_OBJECT_0 ) { // Do all interrupt processing to complete the interaction // with the board so we can receive another interrupt. // if (!( READ_REGISTER_ULONG(g_pBoard Register) & INTR_MASK)) { RETAILMSG(1, (TEXT("DEMO: Interrupt..."))); g_dwInterruptCount ++; } // Finish the interrupt // InterruptDone( g_dwSysInt ); } } return 0; }
该示例读取一个 ULONG 寄存器以确定中断状态。您只需用您的代码替换该代码段。非常关键的一点是,要使 IST 处理尽可能地简单。如果将来需要处理来自该设备的数据:
•
在 IST 中尽可能快速地从该设备获取数据。
•
创建一个事件,以通知某个优先级较低的线程完成该工作。
•
通过 InterruptDone 从该 IST 中立即返回。
•
让优先级较低的线程进一步处理数据。
•
在 IST 与优先级较低的线程之间放置 FIFO 以处理溢出。
从 Windows CE .NET 中的中断体系结构示意图中,可以了解硬件、内核、OAL 与驱动程序/应用程序线程之间的交互。Microsoft 已经提供了多种工具(包括 ILTiming、CEBench 和 Kernel Tracker),以便帮助您评估平台上的 Windows CE .NET 的性能。通过了解导致 ISR 和 IST 延迟的因素,有助于确定调查领域。
正如您在本文前面的中断体系结构示意图中可以看到的,ISR 延迟被定义为从发生中断到 OAL ISR 首次执行之间的时间。因为当中断被关闭时,中断不会在处理器中引发异常,所以第一个导致延迟的因素是系统中的中断被关闭的总时间。在每个机器指令开始执行 时都将检查是否有处理器中断。如果调用了长字符串移动指令,则会锁定中断,从而造成第二个延迟源,即总线访问锁定处理器的时间量。第三个因素是内核导向 OAL ISR 处理程序所花费的时间量。这是一个进程上下文切换。总之,导致 ISR 延迟的因素包括:
•
中断被关闭的时间。
•
总线指令锁定处理器的时间。
•
内核 ISR 的执行时间加上导向 OAL ISR 的时间。
本文前面的体系结构示意图中显示,IST 延迟是从中断发生到执行 IST 中的第一行代码之间的时间量。这与 Windows CE .NET 中的 Microsoft 度量工具的输出不同。Microsoft 工具将 IST 延迟定义为从 OAL ISR 执行结束到 IST 开始之间的时间。因为标准的 ISR 花费的时间很少,您需要将 ISR 延迟和 Microsoft 度量工具所得到的 IST 延迟加起来,才能获得“中断体系结构示意图”中所定义的 IST 延迟。
导致 IST 延迟的第一个因素是本文前面定义的 ISR 延迟。第二个因素是 ISR 执行时间。根据共享中断调用链的长度的不同,此时间是可变的。对于延迟较小的情况,没有必要对永远不会被共享的中断调用 NKCallIntChain。
Windows CE 中的内核函数(如计划程序)被称为 KCALL。在这些 KCALL 执行期间,将设置一个软件标志,以便让计划程序知道它此时不能被中断。仍然将调用 ISR,但用于重新调度 OS 或调度 IST 的返回值将被延迟,直至 KCALL 完成为止。这一不可占先的时间是导致 IST 延迟的第三个因素。最后,内核必须调度 IST。这一上下文切换是导致延迟的最后一个因素。总之,导致 IST 延迟的因素包括:
•
ISR 延迟时间
•
OAL ISR 执行时间
•
OS 执行 KCALL 的时间
•
调度 IST 的时间
通过 Windows CE .NET,Microsoft 已经升级了 Windows CE 中断体系结构。该 OS 所具有的处理共享中断的能力极大地扩展了 Windows CE .NET 支持许多中断体系结构的能力。这一中断体系结构方面的知识可以大大加快调查驱动程序和延迟问题的速度。操作系统交互模型是了解该体系结构的关键。共享中断 已经大大提高了 Windows CE .NET 的开放性,能够支持遍布于不同公司之间以及公司内部的平台提供商和应用程序开发人员方案。了解延迟根源将有助于诊断驱动程序和实时问题。Windows CE .NET 中的中断结构定义完善且易于理解。简而言之,“它不是魔术!”