|
高精度计时器、时间间隔计时器 级别: 初级
Dinakar Guniguntala (), 资深软件工程师, IBM Sathyan Doraiswamy (), 资深软件工程师, IBM Anand K. Santhanam (), 软件工程师, IBM Srinivasan S. Muthuswamy (), 软件工程师, IBM Rinson Antony (), 软件工程师, IBM Brahmaiah Vallabhaneni (), 软件工程师, IBM
2004 年 9 月 24 日
Linux 是新千年里最杰出的操作系统,而传统的操作系统,如 OS/2 ®,现在正在逐渐淘汰出局。本系列文章是为那些正经受迁移/移植痛苦的开发人员撰写的,可以帮助他们将 OS/2系统驱动和应用程序移植到 Linux 上。本系列文章共分为 3 期,这是是最后一期,重点介绍了如何在 OS/2 和 Linux 中进行计时器调用和 DLL 调用,着眼于两系统之间的映射。
本系列文章希望能够成为在 OS/2 和 Linux 之间进行迁移的一个参考。在迁移过程中任何时候您都可 以来参考它,不过最好是在计划阶段,因为它提供了您在设计迁移方案和策略时应该了解的技巧与警告。
这一次您将研究 OS/2 和 Linux 中的计时器调用和 DLL 调用,还有两个系统之间的映射。
在本系列文章的第 1 部分和 第 2 部分,您已经了解了线程、 semaphores 和 mutexes 等同步机制、内存管理、各种 IPC 机制以及文件管理。
首先,我们来看几种类型的计时器:
- 时间间隔计时器(Interval timers)。
- 开始计时器(Start timers)。
- 终止计时器(Stop timers)。
- 高精度计时器(High resolution timers)。
在 OS/2 中, DosStartTimer 会启动一个重复性的时间间隔计时器,它在每 一个计时器间隔中都会发出一个事件信号量。Linux 没有可以直接与之对应的重复性地发出信号量的调用, 但是您可以组合使用 setitimer 系统调用和 Linux 信号处理机制来实现 这一功能。
在 OS/2 中,计时器值参数的单位是毫秒。在 Linux 中,这一信息由一个 struct itimerval 类型的对象来指定,它的单位既可以是秒也 可以是毫秒。
OS/2 |
Linux |
类别 |
DosStartTimer |
setitimer |
可映射 |
DosStopTimer |
setitimer itimerclear |
可映射 |
在 OS/2 中, DosStartTimer() 系统调用会启动一个异步的、 重复性的时间间隔计时器:
APIRETDosStartTimer(ULONG msec, HSEM hsem, PHTIMER phtimer);
- msec 是以毫秒为单位的时间,是发出事件信号量的时间间隔。
- hsem 是每 msec 长度的时间逝去后发出的 事件信号量的句柄。
- phtimer 是指向计时器句柄的指针。
Linux 没有直接与之对应的每隔一定时间重复发出信号量的 API 调用。使用时间间隔计时器来实现此 功能。时间间隔计时器向进程发送一个 SIGALRM 信号。事件信号量被发送到 信号处事器中,或者等待被发送的信号量的代码位于事件处理器例程之中:
int setitimer (int mode, const struct itimerval *newvalue, struct itimerval *oldvalue);
- mode 必须是 ITIMER_REAL ,以使得 计时器可以实时地递减,并在每次过期时发出一个 SIGALRM 。
- newvalue 是一个指针,指向一个包含了要设置的计时器值的 itimerval 结构体。
- oldvalue 是一个指针,指向一个包含了老的计时器值的 itimerval 结构体。
如果成功,则 setitimer 返回 0(-1 表示错误),并将 errno 设置为适当的值。计时器值由第一个参数 newvalue 指定,其类型为 struct itimerval 。当 newvalue->it_value 非零时, 它会指明下一次计时器过期的时间。当 newvalue ->it_interval 非零时,它指定当计时器过期时重新加载 newvalue ->it_value 所用的值。
在信号处理器中只使用同步安全的(async-safe)函数。例如,在信号处理器中不要使用 pthread 条件变量。
在 OS/2 中, DosStopTimer() 会终止一个异步计时器:
APIRETDosStopTimer(HTIMER htimer);
- htimer 是要终止的计时器的句柄。成功则返回 0,失败则返回非 0。
在 Linux 中,要终止一个时间间隔计算器,首先调用宏 timerclear 来清空结构体 itimerval 的 it_value 域。 然后使用一个清空过的 itimerval 来调用 setitimer 。
将 it_value 设置为 0 可以禁用计时器,不必理会 it_interval 的值。将 it_interval 设置为 0 可以在下一次过期后禁用计时器(假定 it_value 为非零)。
timerclear 是一个宏定义(在 sys/time.h 中):
#define timerclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0)
清单 1 中的代码启动一个在每个计时器间隔重复发出一个事件信号量的异步计时器。
int main(VOID) { HEV hevEvent1 = 0; /* Event semaphore handle */ HTIMER htimerEvent1 = 0; /* Timer handle */ APIRET rc = NO_ERROR; /* Return code */ ULONG ulPostCount = 0; /* Semaphore post count */ ULONG i = 0; /* A loop index */ /* * Create an even semaphore that will be posted on timer interval */ rc = DosCreateEventSem(NULL, /* Unnamed semaphore */ &hevEvent1, /* Handle of semaphore * returned */ DC_SEM_SHARED, /* Indicate a shared semaphore */ FALSE); /* Put in RESET state */ if (rc != NO_ERROR) { printf("DosCreateEventSem error: return code = %u\n", rc); return 1; } /* * Start the timer setting a interval at 1 sec */ rc = DosStartTimer(1000L, /* 1 second interval */ (HSEM) hevEvent1, /* Semaphore to post */ &htimerEvent1); /* Timer handler (returned) */ if (rc != NO_ERROR) { printf("DosStartTimer error: return code = %u\n", rc); return 1; } for (i = 1 ; i < 6 ; i++) { DosWaitEventSem(hevEvent1, SEM_INDEFINITE_WAIT); /* Wait indefinitely */ DosResetEventSem(hevEvent1, /* Reset the semaphore */ &ulPostCount); /* And get count * (should be 1) */ printf("Iteration %u: ulPostCount = %u\n", i, ulPostCount); } /* for loop */ rc = DosStopTimer(htimerEvent1); /* Stop the timer */ if (rc != NO_ERROR) { printf("DosStopTimer error: return code = %u\n", rc); return 1; } DosCloseEventSem(hevEvent1); /* Get rid of semaphore */ return NO_ERROR; }
|
清单 2 中的代码启动一个在每个计时器间隔重复发出一个事件信号量的异步计时器。
sem_t sem; /* callback to handle SIGALRM */ void f (int signo) { /* Here you can post on a semaphore */ sem_post(&sem); } int main () { int i; int rc; /* Declare an itimer object to start a timer */ struct itimerval itimer; /* Initialize the semaphore. */ sem_init(&sem,0,0); /* timer interval is 1 sec. */ itimer.it_interval.tv_sec = 1; itimer.it_interval.tv_usec = 0; /* Time to the next timer expiration */ itimer.it_value.tv_sec = 1; itimer.it_value.tv_usec = 0; /* Install a signal handler to handle SIGALRM */ signal (SIGALRM, f); /* Start a timer with the ITIMER_REAL clock */ i = setitimer (ITIMER_REAL, &itimer, 0); if (i == -1) { perror ("timer st failed ...\n"); exit (0); } for (i = 1 ; i < 6 ; i++) { rc = sem_wait(&sem); if (rc != 0) { perror ("sem_wait:"); return 1; } printf("Iteration %d: \n", i); } /* for loop */ /* Set the timer object value with zero, by using the macro * timerclear and * set the ITIMER_REAL by calling setitimer. * This essentially stops the timer. */ timerclear(&itimer.it_value); setitimer (ITIMER_REAL, &itimer, 0); return 0; }
|
接下来,我们将向您介绍如何获得高精度计时器的当前计时器计数和频率。在 OS/2 中, DosTmrQueryTime() 会返回 Intel 8254 计时器(可编程中断计时器)的时间戳 计数器。Linux 没有可用的 API 可以获得这一信息。您可以使用在 内核头文件中定义的 get_cycles() 内联函数。
联合时钟的频率使用时间戳计数器,因为计数器与频率的比值在不同的时钟上是相同的。
OS/2 |
Linux |
类别 |
DosTmrQueryTime |
get_cycles |
可映射 |
DosTmrQueryFreq |
|
不可映射, 但是您可以通过 /proc/cpuinfo 获得时钟频率;参见 清单 3。 |
在 OS2 中, DosTmrQueryTime() 系统调用从 IRQ0 高精度计时器(Intel 8254)获得 高精度计时器计数的一个快照:
APIRET DosTmrQueryTime(PQWORD pqwTmrTime);
- pqwTmrTime 是返回计时器计数值的输出参数。
在 Linux 中, get_cycles() 内联函数返回系统时钟的时间戳计数器, 它是一个 64 位值。这个函数总是成功的(本质上讲,它执行了一个机器指令):
static inline cycles_t get_cycles (void);
在 OS2 中, DosTmrQueryFreq() 获得 IRQ0 高精度计时器(Intel 8254) 的频率:
APIRET DosTmrQueryFreq (PULONG pulTmrFreq)
- pulTmrFreq 是输出参数, DosTmrQueryFreq 将计时器频率返回到它。
Linux 没有与 DosTmrQueryFreq 相当的调用,但是您可以通过 /proc/cpuinfo 获得 时钟频率。
清单 3 展示了一个用来获取 CPU 频率的用户自定义函数。 它打开 /proc/cpuinfo 文件,搜索“cpu MHz”字符串,然后读取 CPU 频率。这个函数将读取的值的单位转换 为 Hz,然后返回转换后的值。
/* * Macro to compare keywords * strcmp is called only when the first characters are the same */ #define WordMatch(a, b) ( (*(a) == *(b)) && (strcmp(a, b) == 0) ) int GetClockFreq(unsigned long *pulCpuFreq) { char* pcFilename = "/proc/cpuinfo"; char pcReadLine[BUFSIZE]; char pcKeyword[BUFSIZE]; char pcKeyword2[BUFSIZE]; int iDone = 0; /* Flag to determine end of loop */ *pulCpuFreq = 0; memset(pcReadLine,0,BUFSIZE); memset(pcKeyword,0,BUFSIZE); memset(pcKeyword2,0,BUFSIZE); /* * Open the /proc/cpuinfo file */ fstream fpCpuInfo(pcFilename, ios::in); if (fpCpuInfo) { /* * Read a line into the buffer */ while ((fpCpuInfo.getline(pcReadLine, BUFSIZE)) && (iDone == 0)) { /* * Instantiate istrmInput to translate input line into * a stream object */ istrstream istrmInput(pcReadLine); /* * Get first 2 word from the input line and build the keyword */ istrmInput >> pcKeyword; istrmInput >> pcKeyword2; strcat(pcKeyword, " "); strcat(pcKeyword, pcKeyword2); if (WordMatch(pcKeyword, "cpu MHz")) { /* * Get the Mhz value from input line by skipping * past the colon. */ istrmInput >> pcKeyword; istrmInput >> pcKeyword; /* * Convert the char* to float and multiply by 1000000 to * get the clock in Hz */ *pulCpuFreq = (unsigned long)(atof(pcKeyword) * 1000000); iDone = 1; } } /* end of while */ fpCpuInfo.close(); } return ((iDone)?0:-1); }
|
清单 4 展示了 DosTmrQueryTime 和 DosTmrQueryFreq 调用的使用。
void GetTimeStampPair( ) { struct QWORD qwStartTime, qwStopTime; double diff; Unsigned long ulTmrFreq; DosTmrQueryTime(&qwStartTime); /* perform an activity */ diff = 0; DosTmrQueryTime(&qwStopTime); /* Calculate the difference between two times */ diff = qwStopTime.ulLo - qwStartTime.ulLo; /* Query the Intel chip's resolution */ DosTmrQueryFreq(&ulTmrFreq); printf ("Timer Frequency is %ld\n Hz", ulTmrFreq); /* calculate the time take for the assignment operation */ /* diff time/frequency */ printf(" Time taken for the activity %ld\n", diff/ulTmrFreq); } /* end GetTimeStampPair */
|
清单 5 展示了如何通过映射 OS/2 DosTmrQueryTime 和 DosTmrQueryFreq 调用来测量两个操作之间的时间间隔。
/* * This function uses the user defined function GetClockFreq() */ #define NANOSLEEP 10000000 /* 10 ms */ #define MSLEEP NANOSLEEP/1000000 int main (void) { unsigned long t1, t2, jitter, freq; double accum; int ret; struct sched_param sp; struct timespec ts,rem; /* Set the scheduling priority to the real time */ sp.sched_priority = sched_get_priority_max(SCHED_RR); sched_setscheduler(getpid(), SCHED_RR, &sp); mlockall(MCL_FUTURE|MCL_CURRENT); rem.tv_sec = 0; ret = GetClockFreq (&freq); if (ret) { /* error condition */ return -1; } rem.tv_sec = 0; rem.tv_nsec = NANOSLEEP; t1 = get_cycles(); do { ts.tv_sec = rem.tv_sec; ts.tv_nsec = rem.tv_nsec; rem.tv_nsec = 0; ret = nanosleep(&ts, &rem); } while (rem.tv_nsec != 0); t2 = get_cycles(); jitter = t2 - t1; /* How much time elapsed. Number of ticks * divided by frequency of the clock */ accum = (double)jitter/freq; return 0; } | |