为提高实时性能,设计和优化 Microsoft Windows CE .NET

为提高实时性能,设计和优化 Microsoft Windows CE .NET
发布日期 : 7/16/2004 | 更新日期 : 7/16/2004

Mike Thomson

Jason Browne

Microsoft Corporation

适用于:

Microsoft Windows CE .NET 和更高版本

摘要:本文从技术角度详细描述了为了增强实时性能特征而设计的对 Microsoft Windows CE 操作系统 (OS) 作出的更改。它还讨论了可用于测试实时性能的工具,并提供了特定硬件配置的有代表性的实时性能测试结果。

本页内容

简介 简介
对内核的更改 对内核的更改
实时测量工具 实时测量工具
性能测量 性能测量
小结 小结

简介

对于高性能的嵌入式应用程序所需要的对时间要求严格的响应来说,实时性能是不可缺少的,例如,电信交换设备、工业自动化和控制系统、医学监视设备和空间导航和制导系统。这样的应用程序必须在指定的时间参数以内实时地传递它们的响应。

实时性能是什么?对于 Microsoft Windows CE .NET OS,以下列表定义了实时性能:

  • 有关高优先级线程调度的担保的上限 — 只针对所有已调度线程中的最高优先级线程。

  • 调度高优先级中断服务例程 (ISR) 过程中有关延迟的担保上限。抢占机制在一个短暂、有限的时间内关闭后内核有很少的空间。

  • 对计划程序和它如何调度线程进行细致的控制。

重要的是应当区分实时系统和实时 OS (RTOS)。实时系统由满足系统要求所需的所有元素(硬件、OS 和应用程序)组成。RTOS 只是完整的实时系统的一个元素,它必须提供足够的功能,才能使全部实时系统能够满足它的要求。

尽管以前的 Windows CE 版本提供了某些 RTOS 功能,但自从 Windows CE 3.0 以后很多重要的内核更改极大地增强了实时性能。Windows CE .NET 内核包含了与 Windows CE 3.0 相同的实时增强功能,除此之外还有某些额外的功能。本文描述了作为 Windows CE .NET 及其以前版本的组成部分的以下更改:

Windows CE .NET

对 x86 平台添加了通过 OEM 定义的变量指定页面池大小的功能。

Windows CE 3.0
  • 增加了线程优先级级别的数目(从 8 到 256)。

  • 更多地控制时间和调度。应用程序可以控制提供给每个线程的时间,并操纵对它们有好处的计划程序。现在,对于与休眠和等待相关的应用程序编程接口 (API),计时器精确到一毫秒。

  • 处理优先级倒置的方法得到改进。

  • 全面支持嵌套中断。

  • ISR 和中断服务线程 (IST) 滞后时间得到减少。

  • 更细粒度的内存管理控制。

此外,本文描述了用来测试内核实时性能的工具,并提供了在三种不同 CPU 上的实时性能测试结果。

对内核的更改

内核是 Windows CE OS 的内部核心,它负责调度和同步线程、处理异常和中断、加载应用程序和管理虚拟内存。在 Windows CE 3.0 中,为了提高性能和减少滞后时间,内核经历了以下几个更改:

  • 将所有内核数据结构移动到物理内存,从而当在内核中执行非抢占代码时极大地避免了转换后备缓冲区 (TLB) 损失。

  • 所有非抢占、但可中断的内核部分(称为 KCALL)被分割成更小的非抢占节。由于增加了节数,这就引入了某些复杂性,但现在抢占机制能够在更短的时间内关闭。

这一节描述为了增强 Windows CE 3.0 的实时性能对内核的进一步更改。

更多优先级级别

内核的计划程序首先使用较高的优先级级别运行某个线程,然后使用相同的优先级以循环方式运行多个线程。为线程指派优先级级别是管理执行速度的一种方式。

Windows CE 3.0 将可用于线程的优先级级别数从 8 增加到 256,0 是最高的优先级,255 是最低的优先级。Windows CE 的前一版本的优先级级别 0 到 7 对应于 Windows CE 3.0 中级别 248 到 255。更多的优先级级别允许开发人员更灵活地控制嵌入式系统的调度,并防止由于限制优先级级别数使随机应用程序降低系统性能。

要指派这些新的优先级,Windows CE 3.0 引入了两个新函数:CeSetThreadPriorityCeGetThreadPriority。新函数与 Windows CE 2.12 中的 SetThreadPriorityGetThreadPriority 函数看起来完全相同,不过新函数接受的数字范围是 0 到 255。

更多地控制时间和调度

Windows CE 3.0 已经改进了计时器性能,计时器和休眠函数调用的精度达到了一毫秒,并且应用程序可以为每个线程设置时间片。

计时器(或系统时钟)是一种速率,由 OS 以此速率生成计时器中断并对其提供服务。以前,计时器也是线程时间片,是线程没有被抢占的情况下可以在系统中运行的最长时间。在 Windows CE 3.0 中,计时器不再直接与线程时间片相关。

以前,OEM 将计时器和时间片作为 OEM 适配层 (OAL) 中的常量设置为大约 25 毫秒。计时器触发时,如果一个线程已做好准备,内核会调度此新的线程。在 Windows CE 3.0 中,计时器总是设置为一毫秒,并且可以对每个线程设置时间片。

通过将计时器从 OEM 定义的数字更改为一毫秒,可以让应用程序执行 Sleep(1) 函数,并预计得到大约一毫秒的精度。当然,这取决于线程的优先级、其他线程的优先级以及是否正在运行 ISR。以前,Sleep(1) 经过一个系统周期后返回,这意味着如果计时器被设置为 25 毫秒,则 Sleep(1) 实际上是 Sleep(25)

计时器中断

现在,内核有几个新的变量,开发人员可以使用它们确定系统时钟是否需要重新调度。通过在适当的时候返回 SYSINTR_NOP 标志而不是 SYSINTR_RESCHED 标志,完整实现的系统时钟 ISR 可以防止内核被重新调度。Nk.lib 导出在 Timer ISR 中使用的以下变量:

  • dwPreempt 是线程被抢占之前的毫秒数。

  • dwSleepMin 是第一次超时(如果有)到期之前的毫秒数,需要重新调度。

  • ticksleft 是已经过去、但尚未被计划程序的休眠队列处理的系统时钟数;因而,非零值将导致重新调度。

在 Timer ISR 中,其他逻辑将优化计划程序,并防止内核执行不必要的工作,如以下代码示例所示。

if (ticksleft || (dwSleepMin && (DiffMSec >= dwSleepMin)) || (dwPreempt && 
     (DiffMSec >= dwPreempt))) return SYSINTR_RESCHED; return SYSINTR_NOP;
OEMIdle 函数

OEM 实现 OEMIdle 函数,在没有要调度的线程时内核将调用该函数。在以前的版本中,计时器时钟会强制 OS 脱离空闲状态,并返回到内核以确定是否线程已做好调度准备。如果没有线程做好准备,内核再次调用 OEMIdle。该操作将导致内核每隔 25 毫秒(或 OEM 指定的其他时间片长度)被激活一次,以确定是否仍然没有要调度的线程。在电池供电的设备上,这样的操作会耗尽宝贵的电池电量。

在 Windows CE 3.0 中,为了在时钟频率较高的情况下减少耗电量,OEMIdle 函数可以让 CPU 进入待机模式一毫秒以上。OEM 通过使用 dwSleepMinDiffMSec 变量来编程设置系统时钟计时器,以便在第一个可用的超时后唤醒。DiffMSec 是自从通过 TimerCallBack 函数检索到最后一次间隔时间以来的当前毫秒值。

硬件计时器的最大超时值可能小于 MAX_DWORD 毫秒值,所以可以编程设置计时器的最大等待时间。在所有情况下,系统从空闲状态返回时,OEMIdle 函数必须使用已经过去的实际毫秒数更新 CurMSecDiffMSecCurMSec 是间隔时间的当前值 £­ 即自从启动以来的毫秒数。

线程时间片

在 Windows CE 3.0 中,线程时间片很灵活,足以使应用程序能够逐个线程地设置时间片。这就让开发人员可以改编计划程序,以满足应用程序的当前需要。为了调整时间片,已经添加了两个新函数:CeGetThreadQuantumCeSetThreadQuantum。这项更改使应用程序能够基于线程完成任务所需要的时间量来设置线程的时间片。通过将任何线程的线程时间片设置为零,循环调度算法可以变为“运行到完成”算法。只有较高优先级的线程或硬件中断才能先于设置为运行到完成的线程执行。

默认时间片是 100 毫秒,但在 OEM 初始化阶段,OEM 可以通过将内核变量 dwDefaultThreadQuantum 设置为大于零的任何值,从而重写系统的默认值。

更改处理优先级倒置的方法

为了有助于缩短响应时间,Windows CE 3.0 更改了它的优先级倒置方法,当低优先级线程拥有一个较高优先级线程所需要的内核对象时,就会发生优先级倒置。Windows CE 使用优先级继承来处理优先级倒置,这时,被阻塞的、拥有较高优先级线程所需要的内核对象的线程将继承更高的优先级。优先级倒置使较低优先级线程能够运行,并释放资源供较高优先级的线程使用。以前,内核处理整个倒置链。从 Windows CE 3.0 开始,内核保证只处理优先级倒置到一个级别的深度。

优先级倒置有两个基本示例。第一个是简单的情况,这种情况下,对优先级倒置的处理从 Windows CE 2.12 到 Windows CE 3.0 没有变化。例如,在有三个处于运行状态的线程时,可以看见这种情况。线程 A 的优先级是 1,线程 B 和 C 优先级较低。如果线程 A 正在运行,并且因为线程 B 拥有线程 A 需要的内核对象而使 A 被阻塞,那么线程 B 的优先级会提高到 A 的优先级级别,以便允许线程 B 运行。然后,如果因为线程 C 拥有线程 B 需要的内核对象而使线程 B 被阻塞,则线程 C 的优先级会提高到 A 的优先级级别,以便允许线程 C 也能运行。

第二个并且是更有趣的情况是,线程 A 可以以比 B 和 C 更高的优先级运行,线程 B 拥有 A 需要的内核对象,线程 B 被阻塞,等待 C 释放它需要的内核对象,而 C 正在运行。在 Windows CE 2.12 中,当 A 运行然后因为 B 而被阻塞时,B 和 C 的优先级都会提高到 A 的优先级,以便使它们能够运行。在 Windows CE 3.0 中,当 A 因为 B 而被阻塞时,只有线程 B 的优先级被提高。通过减少复杂性和更改算法,极大地减少和限制了 Windows CE 中最大的 KCALL。

中断处理和嵌套中断

实时应用程序使用中断作为确保 OS 快速地注意外部事件的方式。在 Windows CE 内,内核和 OAL 经过调整,以便优化对系统的其余部分的中断传递和事件调度。Windows CE 通过将中断处理拆分为下面两个步骤,对性能与实现的容易性进行平衡:中断服务例程 (ISR) 和中断服务线程 (IST)。

每个硬件中断请求线 (IRQ) 都与一个 ISR 相关。允许中断并出现中断时,内核会调用该中断的注册 ISR。ISR 作为中断处理的内核模式部分,将尽可能保持简短。它的责任主要是指引内核启动适当的 IST。

ISR 执行最低程度的处理,并向内核返回中断标识符。内核检查返回的中断标识符,并设置将 ISR 链接到 IST 的相关事件。IST 等待该事件。内核设置事件时,如果 IST 是准备运行的最高优先级线程,IST 将停止等待,并开始执行它的其他中断处理。大多数中断处理实际上发生在 IST 以内。

嵌套中断

在 Windows CE 3.0 以前的版本中,ISR 正在运行时,所有其他中断都会关闭。这将使内核无法处理任何其他中断,直到一个 ISR 已经完成为止。所以如果高优先级中断已做好准备,内核不会处理新的中断,直到当前的 ISR 已经完成操作并返回到内核为止。

为了防止高优先级中断丢失和延迟,Windows CE 3.0 基于优先级添加了对嵌套中断的支持(如果 CPU 或其他相关硬件支持它)。ISR 在 Windows CE 3.0 中运行时,内核将像以前一样运行指定的 ISR,只是禁用了相同和较低优先级的 ISR。如果较高优先级的 ISR 做好运行准备,内核将保存正在运行的 ISR 的状态,并让较高优先级的 ISR 运行。内核可以嵌套 CPU 可支持的最大数量的 ISR。ISR 按硬件优先级的顺序进行嵌套。

大多数情况下,OEM 的当前 ISR 代码不会更改,因为由内核处理详细信息。如果 OEM 在 ISR 之间共享全局变量,则可能需要进行更改,但通常,ISR 不知道它们已经被较高优先级的 ISR 所中断。如果 ISR 定期地执行操作,则可能会发生显而易见的延迟,但延迟只有当较高优先级的 IRQ 被触发时才会发生。

最高优先级的 ISR 结束之后,将执行任何挂起的较低优先级的 ISR。然后,内核继续处理任何被中断的 KCALL。如果线程正在被调度并在 KCALL 中间被中断,则计划程序会继续处理线程。这将使内核能够从它停止的地方继续执行,而不用完全重新启动对线程的调度,从而节约了宝贵的时间。一旦挂起的 KCALL 完成操作,内核将重新调度线程的执行,并开始执行做好运行准备的最高优先级的线程。

中断滞后时间

内核实时性能的一个最重要的特性是能够在指定的时间内服务 IRQ。中断滞后时间主要是指软件中断处理滞后时间 £­ 即从外部中断到达处理器的时间直到中断处理开始的时间所经过的时间。

如果不发生分页操作,Windows CE 3.0 中断滞后时间被限制于内存中锁定的线程。这样就可以计算最糟糕情况下的滞后时间 £­ 到 ISR 的启动和到 IST 的启动的总计时间。然后,在中断被处理以前的时间总量可以通过计算在 ISR 和 IST 中所需要的时间来确定。

ISR 滞后时间

ISR 滞后时间是从 IRQ 在 CPU 中被设置时到 ISR 开始运行时的时间。以下三个与时间相关的变量会影响 ISR 的启动:

  • A 中断在内核中关闭的最长时间。内核很少关闭中断,但如果将它们关闭,则关闭的时间长度会受到限制。

  • B 在内核调度中断和 ISR 被实际调用之间的时间。内核使用该时间确定要运行什么 ISR,并保存在继续之前必须保存的任何寄存器。

  • C 在 ISR 返回到内核和内核实际停止处理中断之间的时间。这是内核通过还原在 ISR 被调用之前被保存的任何状态(例如寄存器)来完成 ISR 操作的时间。

正在测量的 ISR 的启动时间可以基于系统中其他中断的当前状态进行计算。如果中断正在进行,则计算要测量的新 ISR 的启动时间必须考虑到两个因素:所关注的中断已经发生之后将发生的较高优先级中断的数量,以及执行 ISR 所占用的时间。以下示例说明了所得到的启动时间。

Start of ISR =

这里,NISR 是将在已发生所关注的中断之后发生的较高优先级中断的数量;TISR(N) 是执行 ISR 所需要的时间。下面的图 1 说明了该公式。

1. ISR 启动时间公式的图形表示

如果没有发生较高优先级中断 (NISR=0),则前面的公式将简化为以下的代码示例。

Start of ISR = A + B

Windows CE 和 OEM 都会影响执行 ISR 的时间。Windows CE 控制变量 A、B 和 C,它们都会受到限制。OEM 控制 NISR 和 TISR(N),它们都可以极大地影响 ISR 滞后时间。

IST 滞后时间

IST 滞后时间是从 ISR 完成执行(即通知线程)到 IST 开始执行的时间。以下四个与时间相关的变量会影响 IST 的启动时间:

  • B 内核调度中断和 ISR 被实际调用之间的时间。内核使用该时间确定要运行什么 ISR,并保存在继续之前必须保存的任何寄存器。

  • C 在 ISR 返回到内核和内核实际停止处理中断之间的时间。这是内核通过还原在 ISR 被调用之前保存的任何状态(例如寄存器)来完成 ISR 操作的时间。

  • L KCALL 中的最长时间。

  • M 调度线程的时间。

在 ISR 返回到内核并且内核执行某些工作来开始执行 IST 之后最高优先级 IST 开始的启动时间。在 ISR 返回并通知 IST 开始运行之后,IST 启动时间受所有 ISR 的总计时间的影响。下面的示例说明了所得到的启动时间。

Start of highest priority IST =

下图说明了该公式。

2. 最高优先级 IST 启动时间公式的图形表示

Windows CE 和 OEM 都会影响执行 IST 所需的时间。Windows CE 控制变量 B、C、L 和 M,它们都是受限制的。OEM 控制 NISR 和 TISR(N),它们可以极大影响 IST 滞后时间。

Windows CE 3.0 还对 IST 添加了以下限制:链接 ISR 和 IST 的事件处理只能用在 WaitForSingleObject 函数中。Windows CE 3.0 防止 ISR-IST 事件处理被用在 WaitForMultipleObjects 函数中,这意味着内核可以担保触发事件的时间和释放 IST 的时间有一个上限。

内存和实时性能

内核支持几种类型的内核对象,例如,进程、线程、关键节、mutex、事件和信号量。因为 OS 使用虚拟内存,因此所有内核对象都会在虚拟内存中进行分配,因而这些对象的内存是按需分配的。因为按需分配内存可能影响性能,所以无论何时进程开始 OEM 就应当分配内核对象。注意,一旦内核已为内核对象分配了内存,它不会在该对象已被释放以后将内存释放回系统。内核保持该内存池可用;它在需要时重用池中的内存,并在内存池不足时分配更多内存。

可以影响实时性能的内存有三种类型:虚拟、堆和堆栈。

虚拟内存

Windows CE 是充分利用内存管理单元 (MMU) 的、基于虚拟内存的 OS。因为内存分配和解除分配是基于虚拟内存的,所以实时性能可能受到影响。最低级别的内存是虚拟内存,具体地说是虚拟内存 API,例如 VirtualAlloc。在分配虚拟内存的操作期间,内核会搜索可用的物理内存,然后将该内存与进程的虚拟地址空间关联在一起。因为虚拟内存是以 64 KB 为边界分配的,所以虚拟内存 API 不应当用于较小的分配。在处理虚拟内存分配请求期间,内核将从其物理内存池搜索物理内存。取决于正在使用的内存量和内存的碎片情况,搜索内存的时间将各不相同。在实际的托管系统中,所有进程都会从相同的物理内存池访问内存。

为了减少分配虚拟内存的影响,进程应当在继续执行正常处理之前分配和提交所有虚拟内存。

堆内存

虚拟内存之上的下一个级别是堆内存及其相关的 API。堆 API 依赖于虚拟内存 API 用于进行支持的低级别服务。堆中的内存分配不是以 64-KB 为边界的,而是专门用于分配小于 192 KB 的内存。任何超过 192 KB 的分配量都将导致堆管理系统直接使用虚拟内存为分配操作创建单独的堆。

默认堆是在进程被创建时为其创建的。应用程序可以将进程堆用于它的内存分配,并且该堆将相应地增大和缩小。但是,如果在默认堆中内存分配的数量和类型导致堆分成很多碎片,就会导致性能降低。堆分成很多碎片时,它会花费更多时间来尝试为新的内存分配找到空间,这就会影响性能。为了防止在进程中发生碎片,OEM 应当通过为类似的对象创建单独的堆,从而控制内存分配进程。如果存在一个包含大小相同的对象的堆,堆管理器将能够更容易地找到足够大、能够容纳新的分配的可用内存块。但是,即使碎片是可以通过单独的堆来进行管理的,只要在分配内存,仍然有与分配内存相关的开销。

为了降低分配堆内存的影响,在继续执行正常的处理之前,进程应当先分配堆内存。

堆栈内存

在系统中创建新线程时,内核会为堆栈保留内存。为堆栈保留的内存量由在生成模块时被传递给链接器的 /STACK 参数确定。Windows CE 组件的默认堆栈大小是 65 KB。第一次调度线程时,将提交线程的堆栈内存;堆栈内存一次提交一页,并且只在需要时提交。

为了防止初始堆栈内存提交影响性能,应当确保在执行实时处理之前线程被调度至少一次。在 IST 情况下,通常会发生这种情况,这是因为在进入 WaitForSingleObject 循环之前线程将执行几个操作。如果线程只是需要初始堆栈分配,则可以避免进一步的堆栈提交。因为通常在 Windows CE 环境中内存很少,所以,有时内核将从不再需要堆栈内存的线程那里收回堆栈内存。OEM 控制该过程的计时以及传递给 SetOomEvent 的参数。如果传递给 SetOomEvent 的值触发了对堆栈内存的搜寻,则从堆栈删除内存时,内核将挂起每个有可用堆栈的线程。要避免这种情况,请小心设置 SetOomEvent 的内存使用参数。

页面表池大小

在 Windows CE .NET 中,创建了一个新的 OEM 变量,称为 dwOEMPTPoolSize。在 x86 平台上,这个变量允许 OEM 定义被内核用来维护每个进程的内存页列表的虚拟内存页面表数据结构的大小。默认值是 16 页,最大值是 512 页。取决于系统,OEM 必须增加该值,直到找到正确的适当大小为止。页面表池结构的大小可能影响为了将内存映射到相同进程中的不同区域、或映射到不同进程而必须将页面池归零的频率。当页面表数据结构中没有可用的指针时,将发生页面归零。由于安全原因,页必须先归零然后才能被指派给新的进程。将几个页归零的动作可能对实时平台的 IST 滞后时间造成负面影响。但是,OEM 还必须通过使结构变得更大来平衡该问题,这将会增加平均进程切换时间,因为内核需要重置结构中每个页指针的权限。因为该变量只在 Windows CE .NET 和更高版本中可用,因此为使用 Windows CE 3.0 的开发人员创建了快速修复工程 (QFE) 主题,该主题将静态大小设置为页面表池大小。有关详细信息,请参阅 Microsoft Web site,打开 Readme.txt 文件,然后搜索 QFE 33。

实时测量工具

Windows CE 3.0内核的更新包括两个内核级别的工具:中断计时分析 (ILTiming) 和计划程序计时分析 (OSBench)。它们用来测试内核的实时性能,并测量具体的滞后时间。性能数字是特定于硬件的,这取决于 CPU 类型和速度、内存体系结构以及缓存组织和大小。

中断计时分析 (ILTiming)

对 ISR 和 IST 滞后时间的测量已经被结合在 ILTiming 测试工具(以前称为 IntrTime)中,它以源代码的形式提供,并且与 Microsoft Platform Builder 一起分发。进行测量时将使用系统时钟计时器,以便使 ILTiming 可用于运行 Windows CE 的所有硬件平台,因为某些平台不支持单独可用、而没用到的计时器。

在正常情况下,系统时钟会以固定间隔中断内核。然后,相关的系统计时器 ISR 将处理时钟并返回 SYSINTR_NOP 指导内核忽略时钟,或返回 SYSINTR_RESCHED 唤醒计划程序。

ILTiming 测试工具测量滞后时间的方法是:将系统时钟默认值的每隔 n 次时钟周期的 n 值取为 5,即每隔五次系统时钟周期,然后通知特殊的 SYSINTR_TIMING 中断标识符事件。ILTiming 应用程序的主线程等待 SYSINTR_TIMING 中断事件,因而变成 IST。ISR 和 IST 测量值是从时间戳派生的 £­ 即自从上次系统时钟周期后高分辨率计时器的计数器值。

因为 ILTiming 只需要对 OAL 进行特殊的修改,而不会修改内核,所以它可以很容易地改编,并可运行在任何 OEM 平台上。

ILTiming 命令提示符参数

ILTiming 命令提示符参数允许进行以下更改:

  • 将 IST 设置为运行在各种优先级上

  • 每个中断之后刷新或不刷新缓存

  • 更改 ISR 速率和被捕获的中断数

  • 将收集到的结果打印或输出到文件

可用以下 ILTiming 命令提示符参数:

Usage: iltiming [ options ]
Options:
  -p num          Priority of the IST (default 0 ; highest)
  -ni             no idle priority thread 
  -i0             no idle thread (same as -ni)
  -i1             Run idle thread type 1
  -i2             Run idle thread type 2
  -i3             Run idle thread type 3
  -i4             Run idle thread type 4
  -i5             Run idle thread type 5
  -t num          SYSINTR_TIMING interval (default 5)
  -n num          number of interrupts (default 10)
  -all            print all data (default: print summary only)
  -o file         output to file (default: output to debug)
  -h              Display the help screen

IST 可以运行在不同的优先级级别 (-p) 上。默认情况下,应用程序将在每次运行之前刷新缓存。选项 -ncs 禁用 CacheSync 调用。-t 选项设置 ISR 速率,每隔 t 次时钟周期,系统时钟 ISR 返回 SYSINTR_TIMING

ILTiming 还可以创建一个或多个运行在后台的空闲线程。通过允许内核处于必须在运行 IST 之前完成的非抢占内核调用中,这将影响 IST 滞后时间。可用以下五种类型的空闲线程:

  • 空闲线程 1:一个只是运行的线程,不做任何事情

  • 空闲线程 2:一个执行 SetThreadPriority(IDLE) 的线程

  • 空闲线程 3:两个交替执行 SetEventWaitForSingleObject 函数的线程,超时时间为 10 秒。

  • 空闲线程 4:两个交替执行 SetEventWaitForSingleObject 函数的线程,超时时间无限。

  • 空闲线程 5:一个调用 VirtualAllocVirtualFree 的线程,超时时间无限。专门用于刷新缓存和 TLB。

外部中断响应测量

为了快速评估系统的日常实时性能,使用中断计时分析工具足以确定 ISR 和 IST 中断滞后时间。这个方便的方法对所有受支持的处理器有效,但它依靠设备本身的计时器,而这可能会影响测量。

因而,可以用一个更细致的设置来准确地测量 ISR 和 IST 滞后时间。可以设置以下两个机器:

  • 工作站,用于生成外部中断,并测量从 ISR 和 IST 例程收到确认信号所花费的时间。

  • 测试中的设备,用于接收外部中断,并在 ISR 和 IST 例程到达时切换输出行。

测试是在各种压力级别下执行的,测试运行在测试设备上不同优先级的从一到数百个线程的任何地方。

基于 Microsoft Windows NT 4.0 的工作站(配备了 National Instruments PC-TIO-10 数字 I/O 计时器/计数器卡)用来生成中断和时间响应,一个配备相同卡的 CEPC 目标平台用来响应这些中断。Windows NT 软件利用由 National Instruments 提供的驱动程序库,而 Windows CE 软件由 Microsoft 编写。

操作理论很简单:PC-TIO-10 卡有两组计时器,每组有五个计时器。每一组包含一个可提供 200 个十亿分之一秒分辨率的计时器,而其他计时器有一微秒的粒度。此外,该卡包含两组(每组八条)数字 I/O 线,每一组提供一条线可以用于边缘或层次触发的中断。基于 Windows NT 的机器的一条输出线被连接到 CEPC 目标平台的外部中断引脚,并接回基于 Windows NT 的工作站的卡上的计时器。

基于 Windows NT 的工作站声明一条输出线时,它将在 CEPC 目标平台上生成中断,并在 Windows NT 卡上启动 ISR 和 IST 计时器。CEPC 目标平台上的 ISR 通过声明一条该卡上的输出线来确认收到了中断,而这会在基于 Windows NT 的工作站上停止 ISR 计时器,并通知内核来调度 IST。IST 开始运行时,它将声明不同的输出线,从而在基于 Windows NT 的工作站上停止第二个计时器。这时,基于 Windows NT 的工作站可以读取计时器计数器上的值,以确定所生成的中断和 CEPC 目标平台的响应之间的间隔。一旦基于 Windows NT 的工作站已经读取计数器值,它就会立即发出另一个中断,CEPC 目标平台使用该中断将所有输出线设置为待机状态,从而为另一个循环做好准备。

使用上面的测量方法所收集的初步结果确认了 ILTiming 测试结果的准确性。

计划程序计时分析 (OSBench)

OSBench(以前称为 CEBench)是 Windows CE 3.0 和更高版本中附带的性能工具。它用于测试计划程序性能计时,重点测量执行基本内核操作(例如,以下的同步操作)所需要的时间:获得关键节需要多长时间、调度线程等待另一个线程刚刚设置的事件需要多长时间,等等。一旦合适,测试将运行两组指标:进程内的线程到线程,和跨越进程的线程到线程。如果合适,运行测试时可以应用压力套件。

在 Windows CE 中,OSBench 为以下性能指标收集计时示例:

  • 获得/释放关键节(快速方式和传统方式)

  • 等待/通知事件(单独等待并自动重置)

  • 信号量

  • Mutex

  • 使用 Sleep(0) 自动产生

与上述产生/运行情形不同的指标是对互锁 API 和系统调用开销的计时。这些指标是互锁递增/递减、互锁交换和系统 API 调用开销。

以下示例代码说明了 OSBench 命令提示符参数。

Usage: osbench [ options ]
Options:
  -all         Run all tests (default: run only those specified by -t option)
  -t num       ID of test to run (need separate -t for each test)
  -n num       Number of samples per test (default = 100)
  -m addr      Virtual address to write marker values to (default = )
  -list        List test IDs with descriptions
  -v           Verbose : show all measurements
  -o file      Output to CSV file (default: output only to debug)
  -h           Display the help screen
OSBench –list
TestId 0 : CriticalSections
TestId 1 : Event set-wakeup
TestId 2 : Semaphore release-acquire
TestId 3 : Mutex
TestId 4 : Voluntary yield
TestId 5 : PSL API call overhead

与 ILTiming 测量方法一样,QueryPerformanceCounter 函数调用被用于获得计时信息。此外,在调用 QueryPerformanceCounter 的每个计时点,用户可以指定将具体的标记值写入虚拟地址。通过在 OSBench 开始时在命令提示符中提供虚拟地址,可以启用该硬件验证功能。然后,在虚拟地址写入的标记可以用分析器(用外部设备单独计时)进行监视,并用结果仔细检查 QueryPerformanceCounter 计时准确性。类似中断滞后时间的外部测量的设置可以用于该目的。

使用 QueryPerformanceCounter 函数调用来获得时间戳时,在分析结果时必须考虑在特定平台上计数器的频率,以及调用该函数的开销。在最后的计时数字中正确排除测量开销时要小心进行。在每次测试之前,QueryPerformanceCounter 调用将循环进行很多次迭代,并从最后结果中减去平均值。

操作用非常短的时间即可完成时,QueryPerformanceCounter 函数调用的开销将变得很重要。在这些情况中,操作将循环进行固定的次数(即每个样本迭代 (IPS) 次),该次数由每个测试清楚地给出,然后对结果求平均值。如果启用了硬件验证,将为这些情况提供特殊的子标记值。该循环的副作用是无法在操作的每次迭代之间刷新缓存。对于 IPS 等于 1 的其他测试,测试将运行两次,一次有而一次没有对每次迭代的缓存刷新。

以下示例代码显示了 OSBench 测试输出。

==================================================
|  1.00  |  IP =  NO  |  CS =  NO  |       1 IPS
--------------------------------------------------
Event intraprocess :
Time from SetEvent in one thread to a blocked 
              WaitForSingleObject() 
waking in another thread in the same process.
--------------------------------------------------
|  Max Time =         10.057 us
|  Min Time =          5.867 us
|  Avg Time =          6.823 us
==================================================

在测试数字为 1.00 的示例中(其输出显示在上面),操作对进程内事件同步对象计时。IPS1 是 1;每次运行后没有执行 CacheSync (CS);进程间状态 (IP) 显示,第二个进程没有被使用 £­ 两个线程都在相同进程中。100 次操作的最大、最小和平均的结果是如果在命令提示符下没有指定任何内容时的默认值,它以微秒给出。测试的基本套件和 OSBench 程序的总体布局使得添加新的测试用例和测量方法很容易,而这补充了可能有特殊意义的特定内核函数的实现。

性能测量

对 Windows CE .NET 的性能测量数据是在三种不同的 x86 CPU 上采集的。所有测量数据均以微秒为单位,其结果会因为不同的系统负载而有所变化。

ILTiming 测试结果

下表显示了 ISR 和 IST 滞后时间。

这些数字(以微秒为单位)只应当用作具体硬件的性能示例。很多硬件和软件可变因素可能影响这些数字,所以用户应当在他们自己的平台上运行这些测试,但不一定获得相同结果。

1.

CPU

ISR

滞后时间

(微秒)

IST

滞后时间

(微秒)

 

 

最小

最大

平均

最小

最大

平均

AMD K6-2 - 350 MHz

1.6

3.3

1.6

9.2

34.3

21.2

Pentium – 166 MHz

3.3

6.7

4.2

19.2

105.6

62.1

Pentium PIII –500 MHz

3.3

6.7

3.4

10

26.8

17.2

OSBench 测试结果

OSBench 测试运行在所有三种 CPU 上,并且使用了以下两种基本变化形式来计算性能数字:

  • 测试在相同进程中或在两个或更多个进程之间执行。

  • 在没有缓存刷新和有缓存刷新(CacheSync)的情况下执行测试,以便刷新数据和指令缓存。

下表显示了 OSBench 测试的结果。结果时间以微秒为单位,以便执行具体的测试,该测试由第一列的数字表示,并定义在表后面的章节中。

这些数字(以微秒为单位)只应当用作具体设备的性能示例。很多硬件和软件可变因素可能影响这些数字,所以您应当在它们自己的平台上运行这些测试,但不一定获得相同结果。

2.

测试

AMD

K6-2-

350 MHz

Pentium

-166

MHz

Pentium

PIII –

500 MHz

 

 

最小

最大

平均

最小

最大

平均

最小

最大

平均

1

9.219

12.571

9.836

26.819

32.686

27.776

6.705

17.6

7.61

2

10.057

12.571

11.14

28.496

31.848

29.901

8.381

11.734

9.71

3

0.082

0.088

0.082

0.144

0.152

0.145

0.22

0.229

0.221

4

0.067

0.074

0.067

0.143

0.155

0.143

0.221

0.23

0.221

5

9.219

10.057

9.972

27.658

30.172

28.631

6.705

17.6

7.839

6

9.219

12.571

11.258

27.658

45.258

31.162

8.381

11.734

9.659

7

6.705

15.086

7.339

20.115

25.143

21.486

5.029

7.543

5.604

8

20.114

24.305

20.977

67.886

74.591

69.629

11.734

22.629

12.825

9

6.705

14.248

7.322

20.953

30.172

21.664

5.029

6.705

5.367

10

20.114

20.952

20.427

68.724

74.591

69.765

11.734

20.953

12.775

11

6.705

10.895

7.238

21.791

27.658

22.925

5.029

8.381

6.197

12

20.114

25.981

20.325

67.048

83.81

70.18

12.572

21.791

13.164

13

6.705

7.543

7.187

22.629

29.334

23.966

5.867

15.924

6.247

14

20.114

23.467

20.503

69.562

76.267

70.823

12.572

16.762

13.604

15

8.381

10.895

8.533

25.981

31.848

27.166

6.705

16.762

7.576

16

21.79

25.143

22.298

72.077

79.62

74.938

13.41

15.924

14.569

17

8.381

11.733

8.474

26.819

41.067

27.708

6.705

17.6

7.644

18

21.79

25.143

22.154

72.077

85.486

76.013

14.248

23.467

14.798

19

3.352

4.19

3.851

12.572

21.791

13.384

2.515

4.191

3.446

20

15.924

21.79

16.499

52.8

62.858

53.935

9.219

19.277

10.023

21

3.352

7.543

4.046

11.734

16.762

12.817

2.515

5.867

3.353

22

15.924

19.276

16.635

52.8

62.02

53.926

9.219

20.115

10.057

23

2.717

2.74

2.726

5.385

5.466

5.42

2.417

2.514

2.476

24

2.647

2.667

2.653

5.369

5.405

5.373

2.386

2.495

2.463

25

2.83

2.839

2.832

5.932

5.959

5.935

2.618

2.728

2.691

26

16.623

16.656

16.641

46.761

46.785

46.766

9.386

9.451

9.412

27

16.624

16.672

16.65

48.881

48.897

48.886

9.41

9.517

9.462

28

17.302

17.34

17.322

47.307

47.323

47.31

9.634

9.766

9.685

29

1.756

1.765

1.758

3.043

3.053

3.044

1.479

1.507

1.504

30

0.019

0.021

0.019

0.003

0.017

0.004

0.023

0.035

0.023

31

0.019

0.022

0.019

0.003

0.011

0.003

0.023

0.027

0.023

32

0.253

0.255

0.253

0.072

0.08

0.073

0.056

0.067

0.057

33

0.039

0.042

0.039

0.015

0.025

0.015

0.015

0.02

0.015

测试说明

以下说明对应于上表列出的测试:

  • (1) EnterCriticalSection 使用优先级倒置的传统阻塞时间:从较低优先级线程调用 LeaveCriticalSection 直到等待 EnterCriticalSection 调用的较高优先级线程被解除阻塞的时间。

  • (2) EnterCriticalSection 没有优先级倒置的传统阻塞时间:在较高优先级线程调用 EnterCriticalSection(被阻塞)直到较低优先级线程被释放后开始运行之间的时间。

  • (3) EnterCriticalSection 快速路径:非竞争调用 EnterCriticalSection

  • (4) LeaveCriticalSection 快速路径:非竞争调用 LeaveCriticalSection

  • (5) 使用倒置和 CacheSyncEnterCriticalSection:从较低优先级线程调用 LeaveCriticalSection 直到等待 EnterCriticalSection 调用的较高优先级线程被解除阻塞的时间。

  • (6)没有优先级倒置和 CacheSyncEnterCriticalSection 传统阻塞时间:从较高优先级线程调用 EnterCriticalSection(被阻塞)直到较低优先级线程被释放后开始运行的时间。

  • (7) 进程内事件:从一个线程中的 SetEvent 函数通知事件直到被阻塞在相同进程内的 WaitForSingleObject 的线程被释放的时间。

  • (8) 进程间事件:从一个线程中的 SetEvent 通知事件直到被阻塞在不同进程内的 WaitForSingleObject 的线程被释放的时间。

  • (9) 有 CacheSync 的进程内事件:从一个线程中的 SetEvent 通知事件直到被阻塞在相同进程中的 WaitForSingleObject 的线程被释放的时间。

  • (10) 有 CacheSync 的进程间事件:从一个线程中的 SetEvent 通知事件直到被阻塞在不同进程中的 WaitForSingleObject 的线程被释放的时间。

  • (11) 通知进程内的信号量:从较低优先级线程调用 ReleaseSemaphore 直到被阻塞在相同进程中的 WaitForSingleObject 的较高优先级线程被释放的时间。

  • (12) 通知进程间的信号量:从较低优先级线程调用 ReleaseSemaphore 直到被阻塞在不同进程中的 WaitForSingleObject 的较高优先级线程被释放的时间。

  • (13) 有 CacheSync 的通知进程内的信号量:从较低优先级线程调用 ReleaseSemaphore 直到被阻塞在相同进程中的 WaitForSingleObject 的较高优先级线程被释放的时间。

  • (14) 有 CacheSync 的通知进程间的信号量:从较低优先级线程调用 ReleaseSemaphore 直到被阻塞在不同进程中的 WaitForSingleObject 的较高优先级线程被释放的时间。

  • (15) 进程内 Mutex:从较低优先级线程调用 ReleaseMutex 直到被阻塞在相同进程中的 WaitForSingleObject 的较高优先级线程被释放的时间。

  • (16) 进程间 Mutex:从较低优先级线程调用 ReleaseMutex 直到被阻塞在不同进程中的 WaitForSingleObject 的较高优先级线程被释放的时间。

  • (17) 有 CacheSync 的进程内 Mutex:从较低优先级线程调用 ReleaseMutex 直到被阻塞在相同进程中的 WaitForSingleObject 的较高优先级线程被释放的时间。

  • (18) 有 CacheSync 的进程间 Mutex:从较低优先级线程调用 ReleaseMutex 直到被阻塞在不同进程中的 WaitForSingleObject 的较高优先级线程被释放的时间。

  • (19) 进程内线程计时的收益:从线程调用 Sleep(0) 直到相同进程中相同优先级的线程从前面的 Sleep(0) 调用中苏醒过来的时间。

  • (20) 进程间线程计时的收益:从线程调用 Sleep(0) 直到不同进程中相同优先级的线程从前面的 Sleep(0) 调用中苏醒过来的时间。

  • (21) 有 CacheSync 的进程内线程计时的收益:从线程调用 Sleep(0) 直到相同进程中相同优先级的线程从前面的 Sleep(0) 调用中苏醒过来的时间。

  • (22) 有 CacheSync 的进程间线程计时的收益:从线程调用 Sleep(0) 直到不同进程中相同优先级的线程从前面的 Sleep(0) 调用中苏醒过来的时间。

  • (23) 进程内系统 API 调用(往返):不带参数地调用一个作为当前进程一部分的系统 API 并让调用立即返回所需的时间。

  • (24) 进程内系统 API 调用(往返):使用 7 个 DWORD 参数调用一个作为当前进程一部分的系统 API 并让调用立即返回所需的时间。

  • (25) 进程内系统 API 调用(往返):使用 7 个 PVOID 参数调用一个作为当前进程一部分的系统 API 并让调用立即返回所需的时间。

  • (26) 进程间系统 API 调用(往返):不带参数地调用一个在不同进程中的系统 API 并让调用立即返回所需的时间。

  • (27) 进程间系统 API 调用(往返):使用 7 个 DWORD 参数调用一个在不同进程中的系统 API 并让调用立即返回所需的时间。

  • (28) 进程间系统 API 调用(往返):使用 7 个 PVOID 参数调用一个在不同进程中的系统 API 并让调用立即返回所需的时间。

  • (29) 对 Nk.exe 的系统 API 调用(往返):调用内核中的系统 API 并立即返回所需要的时间。

  • (30) InterlockedIncrement:调用 InterlockedIncrement API 的时间。

  • (31) InterlockedDecrement:调用 InterlockedDecrement API 的时间。

  • (32) InterlockedExchange:调用 InterlockedExchange API 的时间。

  • (33) InterlockedTestExchange:调用 InterlockedTestExchange API 的时间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值