硬件定时器如何工作并影响软件性能?

How hardware timers work and affect on software performance?

我想使用异步函数调用。我选择了boost::deadline_timer.

对我来说,硬件定时器是一个特定的硬件(令人惊讶),它独立于CPU工作,并且仅负责监视时间。同时,如果我没理解错的话,它也可以用来设置超时,超时后产生中断。 (计时器)

这样做的主要优点是异步执行。设置定时器的线程可以继续工作,回调函数将在设置定时器的同一个线程中触发。

让我描述一下我看到的效果。

  1. 应用程序包含一个或多个工作线程。例如。他们处理输入项并过滤它们。假设应用程序有 5 个线程,每个线程设置一个计时器(5 秒)。

  2. 应用程序正在运行。例如。当前线程是 thread-3.

  3. 计时器 (thread-0) 已过期并生成(可能是错误的术语)中断。

  4. 线程上下文切换(thread-3 -> thread-0);

  5. 回调函数执行;

  6. 定时器(thread-1)已过期并产生中断。

...

等等

P.S.0. 我明白这不仅仅是多线程应用程序的一种可能情况。

问题:

  1. 我描述的工作过程是否正确?

  2. 我理解正确吗,即使当前线程thread-0它也会导致上下文切换,因为线程必须停止执行当前代码并切换到执行回调函数中的代码?

  3. 如果每个线程设置 100k 或 500k 定时器,对性能有何影响?

  4. 硬件有定时器计数限制吗?

  5. 更新计时器的超时值有多昂贵?

硬件定时器的核心只是一个递增计数器和一组比较器(或使用借位 MSb 作为与 0 的隐式比较的递减计数器)。
将其想象成一个具有专门操作 Increment(或 Decrement)的寄存器,该操作在时钟的每个周期开始(最简单的计数器类型这个操作就是Ripple-counter).
每个周期,计数器值也被馈送到比较器,之前已加载一个值,其输出将是 CPU(作为中断或在专用引脚中)的输入。
在递减计数器的情况下,从 MSb 借位作为值滚过零的信号。
这些定时器通常具有更多功能,例如在达到所需值后停止(一次性)、重置(周期性)、交替输出状态低电平和高电平(方波发生器)以及其他奇特的功能。

一个包里放多少个定时器是没有限制的,当然,电路虽然简单,但还是有成本和space。
大多数 MCU 有一个或两个定时器,当有两个时,想法是将一个用于通用调度,另一个用于与 OS 调度正交的高优先级任务。
值得注意的是,拥有许多硬件定时器(供软件使用)是没有用的,除非也有很多CPUs/MCUs,因为使用软件定时器更容易。
在 x86 上,HPET 定时器实际上最多由 32 个定时器组成,每个定时器有 8 个比较器,从软件 POV 来看总共有 256 个定时器。
这个想法是将每个计时器分配给特定的应用程序。

OS中的应用程序不要直接使用硬件定时器,因为可能有很多应用程序而只有一两个定时器。
所以 OS 所做的是共享计时器。
它通过对定时器进行编程以每 X 个单位时间生成一个中断并通过为此类事件注册一个 ISR(中断服务程序)来实现。
当 thread/task/program 设置定时器时,OS 使用绝对到期时间作为键将定时器信息(周期性与一次性、周期、剩余滴答和回调)附加到优先级队列(请参阅下面的 Peter Cordes 评论)或简单的列表 OSes.
每次调用 ISR 时,OS 都会查看队列并查看顶部的元素是否已过期。

当软件定时器到期时会发生什么是 OS 依赖的。
一些嵌入式和小型 OS 可能会直接从 ISR 的上下文中调用定时器的回调。
如果 OS 没有真正的 thread/task 概念(上下文切换也是如此),这通常是正确的。
其他 OSes 可能会将计时器的回调附加到“即将调用”函数列表中。
该列表将由专门的任务遍历和处理。如果启用了定时器任务,FreeRTOS 就是这样做的。
这种方法使 ISR 保持简短,并允许用更短的周期对硬件定时器进行编程(在许多体系结构中,在 ISR 中中断被忽略,通过 CPU 自动屏蔽中断或通过中断控制器)。
IIRC Windows 做了类似的事情,它在设置软件计时器刚刚过期的线程的上下文中发布一个 APC(异步过程调用)。当线程被调度时,APC 将(作为 window 消息的形式与否,取决于所使用的特定 API)调用回调。如果线程在等待计时器,我认为它只是设置为就绪状态。无论如何,它不会立即安排,但可能会获得优先级提升。

其中 ISR 将 return 仍然 OS 依赖。
OS 可能会继续执行被中断的 thread/task 直到它被调度。在这种情况下,您不会在步骤 3 之后立即执行步骤 4,相反,thread3 将 运行 直到其量程到期。
另一方面,OS 可能会向硬件发出 ISR 结束的信号,然后使用回调调度线程。
如果两个或多个计时器在同一个时间周期内到期,则此方法不起作用,因此更好的方法是执行重新调度,让调度选择最合适的线程。
调度还可以考虑在创建软件计时器期间线程给出的其他提示。
OS 也可能只切换上下文,执行回调并返回 ISR 上下文,继续查看队列。
OS 甚至可以根据计时器的周期和其他提示执行任何操作。

所以它的工作方式与您想象的非常相似,只是在计时器到期时可能不会立即调用线程。

更新计时器并不昂贵。
虽然总的来说总的工作量并不多,但定时器 ISR 应该每秒被调用很多次。
事实上,我什至不确定 OS 是否允许您创建如此庞大数量 (500k) 的计时器。
Windows can manage a lot of timers (and their backing threads) 但可能不是 500k。

有很多计时器的主要问题是,即使每个计时器执行的工作很少,执行的总工作量也可能跟不上滴答的速度。
如果 100 个计时器每 X 个单位(例如 1 毫秒)超时,您有 X/100 个时间单位(例如 10us)来执行每个回调,而回调的代码可能太长而无法在该时间段内执行。
发生这种情况时,回调的调用频率将低于预期。
更多 CPU/cores 将允许一些回调并行执行并减轻压力。

一般来说,如果它们 运行 的速率不同,则您需要不同的计时器,否则,遍历包含 work/data 元素的数据结构的单个计时器就可以了。
如果您的任务受 IO 限制(文件、网络、输入等),多线程可以提供并发性;如果您有一个多处理器系统,则多线程可以提供并行性。