问题
我正在使用 .Net 4.5 创建基于Windows 7的C#WPF 应用程序,其主要功能之一是调用与自定义硬件接口的某些功能,具有一组用户定义的循环时间。例如,用户可以选择每10或20毫秒调用两个函数,每500毫秒调用另一个函数。 用户可以选择的最小周期时间为1毫秒。
起初似乎时间准确,并且根据需要每1毫秒调用一次功能。但我们后来注意到, 1-2%的时间不准确,一些功能被称为迟到5毫秒,而其他功能可能延迟达到100毫秒。即使循环时间大于1毫秒,我们也遇到了线程在应该调用外部函数时睡眠的问题(一个20毫秒的函数可能被称为50毫秒,因为线程正在休眠并且没有调用函数)< / p>
经过分析,我们得出结论,这些延迟是零星的,没有明显的模式,这些延迟背后的主要可能原因是操作系统调度和线程上下文切换,换句话说,我们的线程一直都没有醒来像我们需要它。
由于Windows 7不是RTOS,我们需要找到能否以某种方式解决此问题。但我们确实知道这个问题可以在Windows上修复,因为我们使用具有类似功能的其他工具,可以满足那些时序约束,最大误差为0.7 ms。
我们的应用程序是多线程的,最多同时运行30个线程,其当前峰值CPU使用率约为13%
尝试解决方案
我们尝试了很多不同的东西,时间主要使用秒表计时器和 IsHighResolution 进行测量(其他计时器被使用但我们没有注意到太大差异):< / p>
创建一个单独的线程并赋予其高优先级
结果:无效(使用可怕的Thread.Sleep()
,没有它并使用连续轮询)
使用C#任务(线程池)
结果:改善很少
使用1ms周期的多媒体计时器
结果:无效或更差,多媒体计时器准确唤醒操作系统,但操作系统可能选择运行另一个线程,没有1ms保证,但即便如此,偶尔延迟可能会大得多
创建了一个单独的独立C#项目,它只包含一个while循环和秒表计时器
结果:大多数情况下准确度非常好甚至以微秒为单位,但偶尔线程会休眠
重复第4点,但将进程优先级设置为实时/高
结果:非常好的数字,几乎没有一条消息有明显的延迟。
结论:
从前面我们发现我们有5种可能的行动方案,但我们需要有这些问题经验的人才能指出我们正确的方向:
我们的工具可以进行优化,并以某种方式管理线程,以确保1ms的实时要求。也许优化的一部分是将工具的流程优先级设置为高或实时,但这似乎不是一个明智的决定,因为用户可能同时使用其他几个工具。
我们将工具分为两个过程,一个包含GUI和所有非时间关键操作,另一个包含最少量的时间关键操作并将其设置为高/实时优先级,并使用IPC(像WCF)进程之间的通信。这可以通过两种方式使我们受益
减少其他过程饥饿的可能性,因为正在发生的操作少得多。
这个过程会有更少的线程因此(更少或没有)线程休眠的可能性
注意:接下来的两点将讨论内核空间,请注意我没有关于内核空间和编写驱动程序的信息,因此我可能会对如何使用它做出一些错误的假设。
在内核空间中创建一个驱动程序,每隔1ms使用一次较低级别的中断来触发一个事件,该事件强制线程在进程中执行其指定的任务。
将时间关键组件移动到内核空间,任何与程序主体的接口都可以通过API和回调来完成。
也许所有这些都无效,我们可能需要使用像IntervalZero RTOS平台这样的Windows RTOS扩展?
问题本身
我正在寻找两个答案,我希望他们有良好的资源支持。
这真的是一个线程和上下文切换问题吗?或者我们一直缺少什么东西?
保证5个选项中的哪一个可以解决这个问题,如果有几个,哪个最容易?如果这些选项都无法解决问题,那可以做些什么呢?请记住,我们标记的其他工具确实在Windows上达到了所需的时序精度,并且当CPU处于高负载时,100,000个中的一个或两个时序可以关闭不到2毫秒,这是非常可接受的。
答案 0 :(得分:4)
保证5个选项中的哪一个可以解决此问题?
这取决于您尝试达到的准确度。如果你的目标是说+/- 1ms,你有一个合理的机会在没有第3)到第5点的情况下完成它。第1)和第2点的组合是要走的路:
THREAD_PRIORITY_HIGHEST(2)
作为最大优先级。因此,您必须查看允许访问THREAD_PRIORITY_TIME_CRITICAL (15)
的{{3}}函数。 SetThreadPriority允许访问REALTIME_PRIORITY_CLASS (24)
。注意:以这种优先级运行的代码将推动所有其他代码。你必须使用非常小的计算和非常安全来制作代码。一般说明:全部取决于负载。尽管它不是“实时操作系统”,但Windows可以做得很好。但是,实时系统也依赖于低负载。没有任何保证,即使在RT-OS负载很重的情况下也是如此。
答案 1 :(得分:3)
我怀疑你在用户模式下没有做任何事情,线程的优先级或亲和力将保证你寻求的行为,所以我认为你可能需要类似你的选项3或4,这意味着编写内核模式驱动程序。
在内核模式中,存在IRQL的概念,其中触发以较高级别运行的代码优先于在较低级别运行的代码。用户模式代码在IRQL 0上运行,因此任何更高级别的所有内核模式代码都优先。线程调度程序本身运行在一个更高的级别,2我相信(称为DISPATCH_LEVEL),因此它可以抢占任何优先级的任何预定用户模式代码,包括,我相信,REALTIME_PRIORITY_CLASS。包括定时器在内的硬件中断运行得更高。
如果在较低的IRQL(较高级别的中断处理程序未执行)中有可用的CPU /核心,则硬件计时器将像定时器分辨率一样准确地调用其中断处理程序。
如果还有很多工作要做,那么不应该在中断处理程序(IRQL&gt; DISPATCH_LEVEL)中执行此操作,而是使用中断处理程序安排更大的工作量在DISPATCH_LEVEL“很快”运行,使用延迟过程调用(DPC),它仍然可以防止线程调度程序发生干扰,但不会阻止其他中断处理程序处理它们的硬件中断。
您的选项3可能存在的问题是,触发一个事件以唤醒线程以在IRQL 0运行用户模式代码,它再次允许线程调度程序决定何时执行用户模式代码。您可能需要在DISPATCH_LEVEL中以内核模式执行时间敏感的工作。
另一个问题是中断会在不考虑CPU核心运行的进程上下文的情况下触发。因此,当计时器触发时,处理程序可能会在与您无关的进程的上下文中运行。因此,您可能需要在内核模式驱动程序中执行时间敏感的工作,使用内核空间内存,独立于您的进程,然后在任何结果恢复运行时将任何结果反馈给您的应用程序,并且可以与驱动程序进行交互。 (应用程序可以通过DeviceIoControl API传递缓冲区来与驱动程序进行交互。)
我不建议你实现硬件定时器中断处理程序;操作系统已经做到了。而是使用内核定时器服务来根据定时器中断的OS处理来调用代码。请参阅KeSetTimer和ExSetTimer。在计时器触发后,这两个都可以在DISPATCH_LEVEL处回调您的代码。
并且(即使在内核模式下)系统定时器分辨率默认情况下可能对于1 ms的要求而言太粗糙。
https://msdn.microsoft.com/en-us/library/windows/hardware/dn265247(v=vs.85).aspx
例如,对于在x86处理器上运行的Windows,系统时钟滴答之间的默认间隔通常约为15毫秒
要获得更高的分辨率,您可以
从Windows 2000开始,驱动程序可以调用ExSetTimerResolution例程来更改连续系统时钟中断之间的时间间隔。例如,驱动程序可以调用此例程将系统时钟从其默认速率更改为其最大速率,以提高计时器的准确性。但是,与使用ExAllocateTimer创建的高分辨率计时器相比,使用ExSetTimerResolution有几个缺点。
...
从Windows 8.1开始,驱动程序可以使用ExXxxTimer例程来管理高分辨率计时器。高分辨率定时器的精度仅受系统时钟支持的最大分辨率的限制。相比之下,限制在默认系统时钟分辨率下的定时器的准确性要低得多。
然而,高分辨率定时器要求系统时钟中断至少暂时以更高的速率发生,这往往会增加功耗。因此,驱动程序应仅在定时器精度至关重要时使用高分辨率定时器,并在所有其他情况下使用默认分辨率定时器。