【IT168技术文档】Windows CE.NET 确实通过 QueryPerformanceCounter API 为高分辨率计时器提供了现成的解决方案。如果您必须延迟一小段时间,则使用该 API 非常有效,但如果您希望等待一小段时间,又该如何呢?延迟和等待之间的差别在于:延迟比等待消耗更多的 CPU 时间。等待意味着系统中的其他(优先级较低或相等的)线程可以在等待期间执行。
LARGE_INTEGER liDelay;
// Query number of ticks per second
if (QueryPerformanceFrequency(&liDelay))
{
// 1ms delay
liDelay.QuadPart /= 1000;
LARGE_INTEGER liTimeOut;
// Get current ticks
if (QueryPerformanceCounter(&liTimeOut))
{
// Create timeout value
liTimeOut.QuadPart += liDelay.QuadPart;
LARGE_INTEGER liCurrent;
do
{
// Get current ticks
QueryPerformanceCounter(&liCurrent);
// Delay until timeout
} while (liCurrent.QuadPart<liTimeOut.QuadPart);
}
}
以最高优先级(优先级 0)运行上述代码将在延迟期间阻塞整个 OS。
// !!! PSEUDO CODE !!!
HANDLE hTimer = CreateHighResolutionTimer();
SetHighResolutionTimeout(hTimer, GetHighResolutionTimer() + DELAY);
WaitForSingleObject(hTimer, INFINITE);
当我们在优先级 0 运行第二个示例时,线程会在等待期间释放 CPU。因此,在这些小段时间内,其他线程可以取得 CPU 的拥有权并完成它们的工作。遗憾的是,在 Windows CE .NET 中未实现上述 HighResolutionTimer API。
休眠分辨率
如果您已经阅读本文档的简介,则可能会认为它含有一个打字错误:
“Windows CE (...) 具有 1 ms 的内部计时器信号分辨率。对于大多数项目而言,2 ms 的精确度就足够了,(...)”。
如果 CE 具有 1 ms 的分辨率,则您可能认为那也是您可以等待的最小时间。可惜的是,事实并非如此,因为如果我们在系统计时器滴答(重新安排时间滴答)10 μs 之后发出“Sleep(1),”,则休眠计数器会在下一次滴答时启动,并且在接下来的那次滴答时终止。这使我们获得了 1.90 ms 的休眠,而不是预期的 1 ms。一般说来,Sleep(N) 将休眠 N 到 (N+1) ms。
硬件解决方案
PC 硬件体系结构只提供 1 个计时器,它在物理上连接到某根中断线,并且该计时器已经由 Windows CE 内核使用。CE 内核对该计时器进行编程,使其每毫秒生成一个中断,并且将该中断主要用于线程计划程序和其他一些功能。如果 PC 体系结构能够加入一些备用的中断计时器,则我们在应用 x86 CEPC 的时候就不会如此困难。当然,您可以在 ISA 或 PCI 总线上的某个位置添加一个简单的可编程计时器芯片,但为什么不尝试用软件实现高分辨率计时器呢?
对计时器进行重新编程
生成硬实时 1 ms 中断的唯一方法是对 PIT(可编程间隔计时器,在 PC 硬件中通常为 82C54 或衍生物)进行重新编程,使其快于 1ms。OAL 中的分析代码使用了类似的技术(参见 OEMProfileTimerEnable)。Windows CE 用于对 PIT 进行编程的代码位于 OAL(OEM 适应层)中。OAL 源代码文件位于 \WINCE410\PUBLIC\COMMON\OAK\CSP\I486\OAL1 中。Windows CE 使用 timer.c 内部的 InitClock 函数对 PIT 进行编程:
//
// Setup Timer0 to fire every TICK_RATE mS and generate
// interrupt
//
SetTimer0(TIMER_COUNT);
PICEnableInterrupt(INTR_TIMER0, TRUE);
dwReschedPeriod = TIMER_COUNT;
1. 我使用原始路径来指向 OAL 源代码,当然您应该将 OAL 代码从 PUBLIC 树移动到您自己的 BSP,并且在那里修改它。绝对不要在 PUBLIC 树中修改任何代码;Microsoft 可能使用 QFE 更新它。
创建 1 ms 中断的最简便方法是将中断速度加倍并切换该行为。该行为是在主中断服务例程(将在下面对其进行讨论)中编码的。
要使计时器中断的速度加倍,可以用 TIMER_COUNT / 2 加载该计时器,如下所示:
//
// Setup Timer0 to fire every TICK_RATE mS and generate
// interrupt
//
// Twice as fast for software 1ms timer
#define USE_SOFT_1MS
#ifdef USE_SOFT_1MS
SetTimer0(TIMER_COUNT / 2);
#else
SetTimer0(TIMER_COUNT);
#endif
PICEnableInterrupt(INTR_TIMER0, TRUE);
dwReschedPeriod = TIMER_COUNT;
现在,timer0 中断将每 500 微秒 (0.5 ms) 发生一次。
我已经在修改过的代码前后添加了 #ifdefs,以使其能够稍微容易一些返回到原始 CE 代码。
修改 ISR
主 ISR 位于 fwpc.c 内部:
001 ULONG PeRPISR(void)
002 {
003 ULONG ulRet = SYSINTR_NOP;
004 UCHAR ucCurrentInterrupt;
005
006 if (fIntrTime)
007 {
008 //
009 // We're doing interrupt timing. Get Time to ISR.
010 //
011 #ifdef EXTERNAL_VERIFY
012 _outp((USHORT)0x80, 0xE1);
013 #endif
014 dwIntrTimeIsr1 = _PerfCountSinceTick();
015 dwIntrTimeNumInts++;
016 }
017
018 ucCurrentInterrupt = PICGetCurrentInterrupt();
019
020 if (ucCurrentInterrupt == INTR_TIMER0)
021 {
022 if (PProfileInterrupt)
023 {
024 ulRet= PProfileInterrupt();
025 }
026 else
027 {
028 #ifdef SYSTIMERLED
029 static BYTE bTick;
030 _outp((USHORT)0x80, bTick++);
031 #endif
032
033 CurMSec += SYSTEM_TICK_MS;
034 #if (CE_MAJOR_VER == 0x0003)
035 DiffMSec += SYSTEM_TICK_MS;
036 #endif
037 CurTicks.QuadPart += TIMER_COUNT;
038
039 if (fIntrTime)
040 {
041 //
042 // We're doing interrupt timing. Every nth tick is a
043 // SYSINTR_TIMING.
044 //
045 dwIntrTimeCountdown--;
046
047 if (dwIntrTimeCountdown == 0)
048 {
049 dwIntrTimeCountdown = dwIntrTimeCountdownRef;
050 dwIntrTimeNumInts = 0;
051 #ifdef EXTERNAL_VERIFY
052 _outp((USHORT)0x80, 0xE2);
053 #endif
054 dwIntrTimeIsr2 = _PerfCountSinceTick();
055 ulRet = SYSINTR_TIMING;
056 }
057 else
058 {
059 #if (CE_MAJOR_VER == 0x0003)
060 if (ticksleft || (dwSleepMin && (dwSleepMin <= DiffMSec))
061 || (dwPreempt && (dwPreempt <= DiffMSec)))
062 #else
063 if ((int) (CurMSec - dwReschedTime) >= 0)
064 #endif
065 ulRet = SYSINTR_RESCHED;
066 }
067 }
068 else
069 {
070 #if (CE_MAJOR_VER == 0x0003)
071 if (ticksleft || (dwSleepMin && (dwSleepMin <= DiffMSec)) ||
072 (dwPreempt && (dwPreempt <= DiffMSec)))
073 #else
074 if ((int) (CurMSec - dwReschedTime) >= 0)
075 #endif
076 ulRet = SYSINTR_RESCHED;
077 }
078 }
079
080 //
081 // Check if a reboot was requested.
082 //
083 if (dwRebootAddress)
084 {
085 RebootHandler();
086 }
087 }
088 else if (ucCurrentInterrupt == INTR_RTC)
089 {
090 UCHAR cStatusC;
091 // Check to see if this was an alarm interrupt
092 cStatusC = CMOS_Read( RTC_STATUS_C);
093 if((cStatusC & (RTC_SRC_IRQ|RTC_SRC_US)) == (RTC_SRC_IRQ|RTC_SRC_US))
094 ulRet = SYSINTR_RTC_ALARM;
095 }
096 else if (ucCurrentInterrupt <= INTR_MAXIMUM)
097 {
098 // We have a physical interrupt ID, but want to return a SYSINTR_ID
099
100 // Call interrupt chain to see if any installed ISRs handle this
101 // interrupt
102 ulRet = NKCallIntChain(ucCurrentInterrupt);
103
104 if (ulRet == SYSINTR_CHAIN)
105 {
106 ulRet = OEMTranslateIrq(ucCurrentInterrupt);
107 if (ulRet != -1)
108 PICEnableInterrupt(ucCurrentInterrupt, FALSE);
109 else
110 ulRet = SYSINTR_NOP;
111 }
112 else
113 {
114 PICEnableInterrupt(ucCurrentInterrupt, FALSE);
115 }
116 }
117 if (ucCurrentInterrupt > 7 || ucCurrentInterrupt == -2)
118 {
119 __asm
120 {
121 mov al, 020h ; Nonspecific EOI
122 out 0A0h, al
123 }
124 }
125 __asm
126 {
127 mov al, 020h ; Nonspecific EOI
128 out 020h, al
129 }
130 return ulRet;
131 }