QNX chapter 3 Clocks, Timers, and Getting a Kick

chapter 3 Clocks, Timers, and Getting a Kick Every So Often

Clocks and timers

现在是时候看看Neutrino中与时间有关的所有东西了。我们将看到您如何以及为什么使用计时器及其背后的理论。然后我们来看看获取和设置实时时钟。
本章使用的ticksize大小为10毫秒,但是QNX Neutrino现在在大多数系统上默认使用1毫秒的滴答大小。这不会影响所讨论问题的实质。
让我们看一个典型的系统,例如汽车。在这辆车上,我们有很多程序,其中大多数程序以不同的优先级运行。其中一些需要响应实际的外部事件(例如刹车或无线电调谐器),而其他一些则需要定期运行(例如诊断系统)。

Operating periodically 定期运行

那么,诊断系统如何“定期运行”?您可以想象汽车CPU中的某个过程执行与以下操作类似的操作:
在这里,我们看到诊断过程将永远运行。它执行一轮诊断,然后进入睡眠状态15秒钟,醒来,再次循环,然后…
回到single-tasking的黑暗,黑暗的日子,一个CPU专供一个用户使用,这类程序是通过睡眠(15)来实现的。代码执行繁忙等待循环。您将计算出CPU的速度,然后编写自己的sleep()函数。在那些日子里,由于计算机上没有其他任何东西在运行,所以这并没有太大的问题,因为没有其他进程关心您在sleep()函数中占用了100%的CPU。
即使在今天,有时我们也会占用100%的CPU来执行定时功能。值得注意的是,nanospin()函数用于获得非常精细的时序,但这样做的代价是以优先级燃烧CPU为代价。
如果您确实必须执行某种形式的“多任务”,则通常是通过中断例程来完成的,该例程会挂断硬件计时器,或者在“繁忙等待”时间内执行,这在一定程度上影响了时间校准。
这通常不用担心。幸运的是,我们已经取得了很大的进步。
回顾“进程和线程”一章中的“调度和现实世界”,是什么导致内核重新调度线程

  • 硬件中断
    • 内核调用
    • 故障(异常)
      在本章中,我们关注列表中的前两项:硬件中断和内核调用。
      当线程调用sleep()时,C库包含最终进行内核调用的代码。该调用告诉内核“将线程搁置固定的时间”。该调用从正在运行的队列中删除线程,并启动计时器。
      同时,内核一直在从计算机的时钟硬件接收常规的硬件中断。假设出于争论的原因,这些硬件中断恰好在10毫秒的间隔内发生。
      让我们重述一下:每次由内核的**时钟中断服务程序(ISR)**处理这些中断之一时,都意味着已经过去了10毫秒。内核通过每次ISR运行时将其每日时间变量增加对应于10毫秒的量来跟踪一天中的时间。
      因此,当内核实现一个15秒的计时器时,它真正要做的就是:
  1. 将变量设置为当前时间加上15秒。
    1. 在时钟ISR中,将此变量与一天中的时间进行比较。
    2. 当一天中的时间等于(或大于)变量时,将线程放回READY队列。
      当有多个计时器未完成时(例如,如果需要在不同时间唤醒多个线程),内核将简单地对请求进行排队,并按时间顺序对它们进行排序-最接近的一个位于队列的开头,并且以此类推。ISR查看的变量是此队列的开头的变量。
      计时器5分巡回赛到此结束。
      实际上,除了第一次见面之外,还有更多的东西。

Clock interrupt sources

那么时钟中断从哪里来?
下图显示了负责生成这些时钟中断的硬件组件(以及PC的一些典型值):
DraggedImage.png
如您所见,PC中的电路产生了一个高速(MHz范围)时钟。然后,该高速时钟由硬件计数器(图中的82C54组件)分频,这会将时钟速率降低到kHz或数百Hz范围(即ISR可以实际处理的范围)。时钟ISR是内核的组成部分,直接与内核本身的数据结构和代码交互。在非x86架构(MIPS,PowerPC)上,会发生类似的事件序列。一些芯片在处理器中内置了时钟。
注意,高速时钟除以整数除数。这意味着速率不会精确到10 ms,因为高速时钟的速率不是10 ms的整数倍。因此,上面示例中的内核ISR实际上可能在9.9999296004 ms之后被中断。
没关系吧?
好吧,当然,这对于我们的15秒计数器来说很好。 15秒是1500个计时器ticks-通过数学运算可以看出,与实际成绩差距大约为106 us:
DraggedImage-1.png
不幸的是,继续进行数学运算,相当于每天608毫秒,或大约每月18.5秒,或每年将近3.7分钟!您可以想象,使用其他除数,误差可能会更大或更小,具体取决于引入的舍入误差。幸运的是,内核知道了这一点并对其进行了纠正。
这个故事的重点是,无论显示的是不错的取整值,实际值都将被选择为下一个更快的值。

Base timing resolution

假设计时器的滴答声仅比10 ms快一点。我可以可靠地睡眠3毫秒吗?不。
考虑一下内核中发生了什么。您发出C库的delay()调用进入睡眠状态,持续3毫秒。内核必须将ISR中的变量设置为某个值。如果将其设置为当前时间,则意味着计时器已过期,您应该立即唤醒。
如果将其设置为比当前时间多一个刻度,则意味着您应该在下一个刻度(最多10毫秒)后醒来。这个故事的寓意是:“不要期望时序分辨率会比输入更好计时器滴答率 Don’t expect timing resolution any better than the input timer tick rate。”

Getting more precision

在Neutrino下,程序可以与内核一起调整硬件除数组件的值(这样内核就可以知道计时器滴答ISR被调用的速率)。我们将在下面的“获取和设置实时时钟”部分。

Timing jitter

您还需要担心另一件事。假设时序分辨率为10毫秒,而您希望20毫秒的超时时间 timeout。从发出delay()调用的时间到函数调用返回的时间,您总会得到正好20毫秒的延迟吗?
绝对不。
原因有两个。第一个非常简单:阻塞(block)时,您将退出运行队列(running queue)。这意味着优先级高的另一个线程现在可能正在使用CPU。 20毫秒到期后,您将被置于该优先级的READY队列的末尾,因此您将不受任何正在运行的线程的支配。
这也适用于正在运行的中断处理程序或运行优先级更高的线程,仅因为您处于就绪状态并不意味着您正在消耗CPU。
第二个原因有些微妙。下图将帮助解释为什么。
DraggedImage-2.png
问题是您的请求与时钟源异步。您无法将硬件时钟与您的请求同步。因此,根据从硬件时钟周期中开始请求的位置,您将获得从刚刚超过20毫秒到不到30毫秒的延迟。
这是关键。时钟抖动是生活中令人难过的事实。解决该问题的方法是提高系统的时序分辨率,以使您的时序在公差范围内。 (我们将在下面的“获取和设置实时时钟”部分中了解如何执行此操作。)请记住,抖动仅发生在第一个刻度上–延迟100秒,时钟0毫秒将延迟大于100秒且小于100.01秒。

Types of timers

我上面显示的计时器类型是相对计时器。选择的超时时间是相对于当前时间的。如果您希望计时器将线程延迟到美国东部时间2005年1月20日12:04:33,则必须计算从“现在”到此的秒数,并为该秒数设置一个相对计时器。因为这是一个相当普通的功能,所以Neutrino实现了一个绝对计时器,该计时器将延迟到指定的时间(而不是指定的时间,例如相对计时器)。
如果您想在等待该日期到来时做点什么?或者,如果您想做某事并每27秒获得一次“kick”呢?您当然无法负担asleep!
正如我们在“进程和线程”一章中讨论的那样,您可以简单地启动另一个线程来完成工作,并且您的线程可能会花费一些时间。但是,由于我们在谈论计时器,因此我们将介绍另一种方法。
您可以根据自己的目标使用定期或单次计时器来执行此操作。定期计时器是一种定期关闭的计时器,它(一遍又一遍地)通知线程某个时间间隔已经过去。一次计时器是一次响起的计时器。
内核中的实现仍然基于与我们在第一个示例中使用的延迟计时器相同的原理。内核花费绝对时间(如果您指定了这样)并将其存储。在时钟ISR中,以通常的方式将存储的时间与一天中的时间进行比较。
但是,在调用内核时,线程不会继续从运行队列中删除,而是继续运行。当一天中的时间达到存储的时间时,内核会通知您的线程已达到指定的时间。

Notification schemes

您如何收到超时通知?使用延迟计时器,您已通过再次准备就绪收到通知。
使用定期计时器和一次性计时器,您可以选择:

  • 发送一个脉冲
    • 发送信号
    • 创建一个线程
      我们已经在“消息传递”一章中讨论了脉冲。 signal是一种标准的UNIX风格的机制,稍后我们将看到线程创建通知类型。

How to fill in thestruct sigevent

让我们快速看一下如何填充struct sigevent结构。无论选择哪种通知方案,都需要填写struct sigevent结构:
DraggedImage-3.png
请注意,以上定义使用匿名联合和结构。仔细检查头文件,您将了解如何在不支持这些功能的编译器上实现此技巧。基本上,有一个#define,它使用命名的联合和结构使它看起来像一个匿名联合。检出<sys / siginfo.h>有关详细信息。
您必须填写的第一个字段是sigev notify成员。这将确定您选择的通知类型:

Pulse notification

要在计时器触发时发送脉冲,请将sigev通知字段设置为SIGEV PULSE并提供一些其他信息:
DraggedImage-4.png
请注意,sigev coid可以是与任何通道的连接(通常(但不一定)是与启动事件的流程相关联的通道)。

Signal notification

Thread notification

在计时器触发时创建线程,请将sigev notify字段设置为SIGEV THREAD并填充以下字段:
DraggedImage-5.png
此通知类型有点吓人!如果计时器触发得足够多,则可能会创建大量线程;如果有更高优先级的线程在等待运行,则可能会消耗系统上的所有可用资源!请谨慎使用!

General tricks for notification

Pulse notification

假设您正在设计一个服务器,该服务器的大部分生命都被RECEIVE阻止,等待消息。收到一条特别的消息,告诉您您等待的时间终于到来,这不是理想的做法吗?这种情况正是您应使用脉冲作为通知方案的地方。
在下面的“使用计时器”部分,我将向您展示一些示例代码,这些代码可用于获取周期性脉冲消息。

Signal notification

另一方面,假设您正在执行某种工作,但不希望该工作永远持续下去。例如,您可能正在等待某个函数调用返回,但是您无法预测需要多长时间。
在这种情况下,使用信号作为通知方案(可能带有信号处理程序)是一个不错的选择(我们稍后将讨论的另一种选择是使用内核超时;也请参见“消息传递”一章中的_NTO_ CHF UNBLOCK)。
在“使用计时器”部分,我们将看到一个使用信号的示例。在信号处理程序中使用浮点运算并不安全。
另外,如果您仍然不打算在应用程序中接收消息,那么使用sigwait()的信号比创建通道去接受pulse要便宜。

Using timers

看完所有这些奇妙的理论之后,我们将注意力转向一些特定的代码示例,以了解您可以使用计时器做什么。
要使用计时器,您必须:

  1. 创建计时器对象。
  2. 确定您希望如何被通知(信号,脉冲或线程创建),并创建通知结构(struct sigevent)。
  3. 确定您希望使用哪种计时器(相对计时器和绝对计时器以及单次计时器与定期计时器)。
  4. 启动它。
    让我们按顺序看一下:

1. Creating a timer

第一步是使用timer_create()创建计时器:
DraggedImage-6.png
clock id参数告诉计时器create()函数要为其创建该计时器的CLOCK_REALTIME。这是POSIX的事-POSIX说在不同的平台上可以有多个CLOCK_REALTIME,但是每个平台都必须至少支持CLOCK_REALTIME时基。
在Neutrino下,有三个时基可供选择:
DraggedImage-7.png
现在,我们将忽略CLOCK SOFTTIME和CLOCK MONOTONIC,但我们将在下面的“其他时钟源”部分中再次介绍它们。

2. Signal, pulse, or thread?

第二个参数是指向结构sigevent数据结构的指针。此数据结构用于通知内核计时器在“触发”时应传递的事件类型。在讨论信号与脉冲与线程创建时,我们讨论了如何在上面填充struct sigevent
因此,您将使用CLOCK REALTIME和指向struct sigevent数据结构的指针来调用timer create(),内核将为您创建一个计时器对象(该对象将在最后一个参数中返回)。
这个计时器对象只是一个小的整数,它充当内核计时器表的索引;认为它是一个“句柄handle”。
在这一点上,没有其他事情发生。您刚刚创建了计时器;你还没有触发它。

3. What kind of timer?

创建了计时器之后,您现在必须确定它是哪种计时器。这是通过计时器settime()的参数组合完成的,该计时器实际上用于启动计时器:
DraggedImage-8.png
timerid参数是您从计时器create()函数调用中获取的值 - 您可以创建一堆计时器,然后分别对它们调用计时器settime()以便在方便时设置和启动它们。
flags参数是您指定绝对还是相对的位置。
如果您通过常量TIMER ABSTIME,则它是绝对的,几乎与您期望的一样。
然后,当您希望计时器关闭时,您传递实际的日期和时间。
如果传递零,则认为计时器是相对于当前时间的。
让我们看看如何指定时间。这是两个数据结构的关键部分(在<time.h>中):
DraggedImage-9.pngDraggedImage-10.png
struct itimerspec中有两个成员:
DraggedImage-11.png
it_value指定计时器应该从现在起关闭多长时间(如果是相对计时器),或者计时器应该何时关闭(如果是绝对计时器)。计时器启动后,它的时间间隔值会指定一个相对值以重新加载计时器,以便它可以再次触发。请注意,为it间隔指定零值会使它成为单触发计时器。
您可能希望创建一个“纯”定期计时器periodic timer,只需将it_interval设置为重载值reload,然后将其值设置为零。不幸的是,该语句的最后一部分为false-将it_value设置为零会禁用计时器。如果要创建纯周期计时器,请将其值设置为等于它间隔并创建计时器作为相对计时器。
这将触发一次(针对it_value延迟),然后以it_interval延迟继续加载。
it值和it interval成员实际上都是struct timespec类型的结构,这是另一种POSIX。该结构使您可以指定亚秒分辨率。第一个成员tv sec是秒数;第二个成员tv nsec是当前秒的纳秒数。
(这意味着您永远不应将tv nsec设置为超过10亿的值,这意味着偏移量将超过一秒钟。)
这里有些例子:
DraggedImage-12.png
这将创建一个一次性计时器,该计时器在美国东部时间2001年4月19日(星期四)00:25开始计时。(有很多函数可以帮助您在人类可读的日期和“自1970年1月1日,格林尼治标准时间00:00:00以来的秒数”之间进行转换。在C库中查看time(),asctime(),ctime(),mktime(),strfime()等)。
(略)

4. A server with periodic pulses

我们首先要看的是要获取定期消息的服务器。最典型的用途是:

  • 服务器维护的客户端请求超时
    • 定期服务器维护周期
      当然,这些东西还有其他专门用途,例如需要定期发送的网络“保持活动”消息,重试请求等。

Server-maintained timeouts

在这种情况下,服务器正在向客户端提供某种服务,并且客户端具有指定超时的能力。
在很多地方都使用此功能。例如,您可能希望告诉服务器,“让我获得15秒的数据价值”,或“等10秒后让我知道”,或“等待数据显示,但如果没有显示2分钟内,超时。这些都是服务器维护超时的示例。客户端将消息发送到服务器,然后阻止。
服务器从计时器接收周期性的消息(也许每秒一次,也许或多或少的频率),并计算接收到的消息数量。当超时消息的数量超过客户端指定的超时时,服务器将使用某种超时指示或到目前为止已累积的数据回复客户端-这实际上取决于客户端/服务器关系的结构。
这是一个服务器的完整示例,该服务器接受来自客户端的两个消息之一和来自脉冲的超时消息。
第一种客户消息类型是:“让我知道是否有任何可用数据,但请不要让我停留5秒钟以上。”
第二种客户端消息类型为“这里有一些数据。”服务器应允许多个客户端被阻止,等待数据,因此必须将超时与客户端相关联。
这是脉冲消息进入的地方。它说:“一秒钟过去了。”为了使代码示例免于压倒性的麻烦,我在每个主要部分之前都添加了一些文本。您可以找到time.c的完整版本。

Declarations

这里的代码的第一部分设置了我们将要使用的各种清单常量,数据结构,并包括了所有所需的头文件。我们将不加评论地提出。

/*
* time1.c *
* Example of a server that receives periodic messages from
* a timer, and regular messages from a client. *
* Illustrates using the timer functions with a pulse.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/siginfo.h>
#include <sys/neutrino.h>
// message send definitions
// messages
#define MT_WAIT_DATA
#define MT_SEND_DATA
// pulses
#define CODE_TIMER
                                // message from client
                                // message from client
                                // pulse from timer
                                // message to client
                                // message to client

DraggedImage-13.png
DraggedImage-14.png
下一部分代码是主线。它负责:创建频道(通过ChannelCreate(),调用setupPulseAndTimer()例程(设置一个每秒一次的计时器,以脉冲作为事件传递方法),然后坐在“永远做do-forever”的循环中,等待脉冲或消息并对其进行处理。请注意,对MsgReceive()的返回值进行了检查-零表示这是一个脉冲(并且我们不做任何强力检查以确保它是我们的脉冲),非零则表示它是一条消息。
脉冲或消息的处理由gotAPulse()和gotAMessage()完成。
DraggedImage-15.png
(code main.c)

setupPulseAndTimer()

在setupPulseAndTimer()中,您会看到代码,我们在其中定义计时器和通知方案的类型。
当我们在上面的文本中谈到定时器函数调用时,我说定时器可以传递信号,脉冲或导致创建线程。
该决定在此处(在setupPulseAndTimer()中进行。注意,我们使用了宏SIGEV PULSE INIT()。通过使用此宏,我们可以有效地将值SIGEV PULSE分配给sigev_notify成员。 (如果我们改用SIGEV SIGNAL * INIT()宏之一,它将传递指定的信号。)请注意,对于脉冲,我们通过ConnectAttach()调用将连接设置回自己,并给它一个唯一标识它的代码(我们选择了清单常量CODE TIMER;我们定义了一些东西)。
事件结构初始化中的最后一个参数是脉冲的优先级。我们选择了SIGEV PULSE PRIO INHERIT(常数1)。这告诉内核在脉冲到达时不要更改接收线程的优先级。
在此函数的底部附近,我们调用timer_create()在内核中创建一个计时器对象,然后用数据填充该数据,该数据说它应该在一秒钟内消失(它的it值成员),并且应该使用一秒重复(它是间隔成员)。请注意,仅在调用计时器settime()时才激活计时器,而不是在创建计时器时将其激活。
DraggedImage-16.png

setupPulseAndTimer()

在setupPulseAndTimer()中,您会看到代码,我们在其中定义计时器和通知方案的类型。当我们在上面的文本中谈到定时器函数调用时,我说定时器可以传递信号,脉冲或导致创建线程。这个决定是在这里(在setupPulseAndTimer())。
注意,我们使用了宏SIGEV PULSE INIT()。通过使用此宏,我们可以有效地将值SIGEV PULSE分配给sigev通知成员。
(如果我们改用SIGEV SIGNAL * INIT()宏之一,它将传递指定的信号。)请注意,对于脉冲,我们通过ConnectAttach()调用将连接设置回自己,并给它一个唯一标识它的代码(我们选择了清单常量CODE TIMER;我们定义了一些东西)。
事件结构初始化中的最后一个参数是脉冲的优先级。我们选择了SIGEV PULSE PRIO INHERIT(常数-1)。这告诉内核在脉冲到达时不要更改接收线程的优先级。
在该函数底部附近,我们调用timer create()在内核中创建一个计时器对象,然后用数据填充该数据,该数据表明该数据应在一秒钟内消失(它的it值成员),并应重新加载它。重复一秒钟(它是间隔成员)。请注意,只有当我们调用时间。settime(),而不是在创建时。
DraggedImage-17.png

gotAPulse()

在gotAPulse()中,您可以看到我们如何实现服务器使客户端超时的功能。我们浏览了客户端列表,并且由于我们知道每秒触发一次脉冲,因此我们只是减少了客户端在超时之前离开的秒数。
如果该值达到零,我们将通过一条消息“ Sorry,timed out”(MT TIMEDOUT消息类型)回复该客户端。您会注意到我们提前准备了此消息(在for循环之外),并且然后根据需要发送它。这只是样式/使用问题-如果您希望进行大量答复,那么一次设置的开销可能是有意义的。如果您不希望进行大量答复,那么根据需要进行设置可能更有意义。
如果超时值尚未达到零,则我们不对其执行任何操作**-客户端仍被阻止**,等待消息显示。
DraggedImage-18.png

gotAMessage()

Notes

有关代码的一些一般说明:如果没有人在等待并且有数据消息到达,或者列表中没有空间容纳新的服务员客户端,则我们将消息打印为标准错误,但从不答复客户端。这意味着有些客户可能坐在那里,永远被REPLY阻止。

  • 我们丢失了他们的接收ID,因此我们以后无法回复他们。这在设计中是有意的。您可以对其进行修改,以分别添加MT NO WAITERS和MT NO SPACE消息,只要检测到这些错误,就可以返回这些消息。
  • 当服务端客户端正在等待,并且有数据供应客户端发送给它时,我们将回复这两个客户端。
    这很关键,因为我们希望两个客户都能解除封锁
  • 我们将提供数据的客户端缓冲区重新用于两次回复。这又是一个样式问题-在较大的应用程序中,您可能必须具有多种类型的返回值,在这种情况下,您可能不想重复使用同一缓冲区。
  • 此处显示的实现使用带有“使用中标志”(clients[i].in_use)的“低调”固定长度数组。由于我的目标不是演示所有者列表的技巧和单链列表管理技术,我已经展示了最容易理解的版本,当然,在您的生产代码中,您可能会使用动态管理的存储块的链表。
  • 当消息到达MsgReceive()时,我们是否确实是“我们的”脉冲的决定是通过弱检查完成的-我们假定(根据注释)所有脉冲都是CODE TIMER脉冲。同样,在生产代码中,您需要检查脉冲的代码值并报告任何异常情况。

Periodic server maintenance cycles 定期服务器维护周期

在这里,对于定期超时消息,我们有一个稍微不同的用法。这些消息纯粹是供服务器内部使用的,通常与客户端完全无关。例如,某些硬件可能要求服务器定期轮询,就像使用网络连接时那样-服务器应该查看连接是否仍处于“启动”状态,而不管来自客户端的任何指示。
如果硬件具有某种“非活动关闭”计时器,则可能会发生另一种情况,例如,由于长时间使一块硬件通电可能会浪费电源,如果没有人使用该硬件,例如10秒钟
再次,这与客户端无关(除了客户端请求将取消此非活动掉电),这只是服务器必须能够为其硬件提供的功能。
在代码方面,这将与上面的示例非常相似,不同的是,除了拥有一个正在等待的客户端列表之外,您只有一个超时变量。
每当计时器事件到达时,该变量都会减少;如果为零,则将导致硬件关闭(或此时您希望执行的其他任何活动)。如果仍然大于零,则不会发生任何事情。
设计中唯一的“难题”是,每当使用硬件的客户端收到一条消息时,您都必须将超时变量重置为其完整值-让某人使用该资源重置“倒数”。
相反,硬件可能需要一定的“预热”时间才能从掉电中恢复。在这种情况下,一旦硬件断电,就必须设置客户端收到请求后,将使用另一个计时器。
此计时器的目的是延迟客户端对硬件的请求,直到硬件再次上电为止。

Timers delivering signals 计时器传送信号

到目前为止,除了一件小事情外,我们几乎已经看到了计时器的所有功能。我们一直在传递消息(通过脉冲),但是您也可以传递POSIX信号。让我们看看如何做到这一点:
DraggedImage-19.png
这是创建发送信号的计时器的最简单方法。计时器触发时,此方法将引发SIGALRM。如果我们实际上提供了一个struct sigevent,我们可以指定我们实际想要得到的信号:
DraggedImage-20.png
这给我们打了SIGUSR1而不是SIGALRM。您可以使用普通的信号处理程序捕获计时器信号,它们没有什么特别的。

Timers creating threads

(略)

Getting and setting the realtime clock and more 获取和设置实时时钟等

(略)

Getting and setting

函数clock gettime()和clock settime()是基于内核函数ClockTime()的POSIX函数。这些功能可用于获取或设置当前时间。不幸的是,将其设置为“硬”调整,意味着您在缓冲区中指定的任何时间都将立即作为当前时间。这可能会产生惊人的后果,尤其是当时间似乎在“向后”移动时,因为时间早于“实际”时间。
通常,仅在加电期间或时间与实时不同步时才应使用此方法设置时钟。也就是说,要使当前时间逐渐变化,可以使用函数ClockAdjust():
DraggedImage-21.png
参数是时钟源(始终使用CLOCK REALTIME),以及新的和旧的参数。新参数和旧参数都是可选的,并且可以为NULL。旧参数仅返回当前调整值。时钟调整的操作是通过新参数控制的,新参数是指向包含两个元素(tick nsec inc和tick count)的结构的指针。基本上,ClockAdjust()的操作非常简单。在下一个滴答计数时钟ticks中,ticksnsec inc中包含的调整将添加到当前系统时钟中。
这意味着要向前移动时间(实时“赶上”),您可以为tick nsec inc指定一个正值。请注意,您永远不会倒退时间!相反,如果您的时钟太快,则可以指定一个较小的负数来打勾nsec inc,这将导致当前时间的增长速度不如预期的快。如此有效,您放慢了时钟,直到它与现实相符。
一条经验法则是,您调整时钟的时间不应超过系统基本计时分辨率的10%(如我们将在下面讨论的功能ClockPeriod()和朋友所示)。

Adjusting the timebase

正如我们在本章中一直说的那样,系统中所有组件的时序分辨率都不会比进入系统的基本时序分辨率更准确。因此,显而易见的问题是,如何设置基本时序分辨率?您可以为此使用ClockPeriod()函数:
DraggedImage-22.png
与上述ClockAdjust()函数一样,新参数和旧参数也是如此。如何获得和/或设置基本时序分辨率的值新参数和旧参数是指向struct Clockperiod结构的指针,该结构包含两个成员nsec和fract。
当前,fract成员必须设置为零(这是飞秒数;我们可能会暂时不使用这种分辨率了!)nsec成员指示基本计时时钟的滴答之间经过了多少纳秒。默认值为10毫秒(在CPU速度大于40 MHz的计算机上为1毫秒),因此nsed成员(如果通过指定旧参数使用调用的“ get”形式)将显示大约1000万纳秒。 (正如我们上面讨论的,在“时钟中断源”中,它不会精确到10毫秒。)
虽然您可以随意尝试将系统的基本时序分辨率设置为小得离谱,但内核会介入并阻止您这样做。通常,您可以将大多数系统设置在1毫秒到数百微秒的范围内。

An accurate timestamp

您的处理器上可能存在一个不符合我们刚刚描述的“基本时序分辨率”规则的时基。一些处理器中内置有一个高频(高精度)计数器,Neutrono可以让您通过ClockCycles()调用来访问它。
例如,在以200 MHz运行的Pentium处理器上,该计数器也以200 MHz递增,因此它可以为您提供定时采样,低至5纳秒。如果您想弄清楚一段代码执行需要多长时间(当然,假设您没有被抢占),这将特别有用。您可以在代码之前和代码之后调用ClockCycles(),然后计算增量。
有关更多详细信息,请参见《中微子库参考》。

Advanced topics

(略)

发布了10 篇原创文章 · 获赞 1 · 访问量 521

猜你喜欢

转载自blog.csdn.net/chenshiming1995/article/details/104932251