Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2149005
  • 博文数量: 361
  • 博客积分: 10828
  • 博客等级: 上将
  • 技术积分: 4161
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-20 14:34
文章分类

全部博文(361)

文章存档

2011年(132)

2010年(229)

分类: LINUX

2010-04-28 14:03:49

Linux下的时间概念

这一章我们学习Linux的时间表示和计算函数
时间的表示
时间的测量
计时器的使用
1.时间表示 在程序当中,我们经常要输出系统当前的时间,比如我们使用date命令的输出结果。这个时候我们可以使用下面两个函数

  1. time_t time(time_t *tloc);
  2. char *ctime(const time_t *clock);

time 函数返回从1970年1月1日0点以来的秒数。存储在time_t结构之中。不过这个函数的返回值对于我们来说没有什么实际意义。这个时候我们使用第二个 函数将秒数转化为字符串。 这个函数的返回类型是固定的:一个可能值为。 Thu Dec 7 14:58:59 2000 这个字符串的长度是固定的为26


2.时间的测量 有时候我们要计算程序执行的时间。比如我们要对算法进行时间分析。这个时候可以使用下面这个函数。

  1. int gettimeofday(struct timeval *tv,struct timezone *tz);

  2. strut timeval
  3. {
  4.   long tv_sec; /* 秒数 */
  5.   long tv_usec; /* 微秒数 */
  6. };

gettimeofday将时间保存在结构tv之中。tz一般我们使用NULL来代替。

  1. void function()
  2. {
  3.     unsigned int i,j;
  4.     double y;
  5.     for(i=0;i<1000;i++)
  6.     for(j=0;j<1000;j++)
  7.     y=sin((double)i);
  8. }

  9. main()
  10. {
  11.     struct timeval tpstart,tpend;
  12.     float timeuse;

  13.     gettimeofday(&tpstart,NULL);
  14.     function();
  15.     gettimeofday(&tpend,NULL);
  16.     timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec)+
  17.     tpend.tv_usec-tpstart.tv_usec;
  18.     timeuse/=1000000;
  19.     printf("Used Time:%fn",timeuse);
  20.     exit(0);
  21. }

这个程序输出函数的执行时间,我们可以使用这个来进行系统性能的测试,或者是函数算法的效率分析。在我机器上的一个输出结果是: Used Time:0.556070


3.计时器的使用 Linux操作系统为每一个进程提供了3个内部间隔计时器。
ITIMER_REAL:减少实际时间。到时的时候发出SIGALRM信号。
ITIMER_VIRTUAL:减少有效时间(进程执行的时间)。产生SIGVTALRM信号。
ITIMER_PROF:减少进程的有效时间和系统时间(为进程调度用的时间)。这个经常和上面一个使用用来计算系统内核时间和用户时间。产生 SIGPROF信号。
具体的操作函数是:
getitimer 函数得到间隔计时器的时间值,保存在value中 。setitimer函数设置间隔计时器的时间值为newval,并将旧值保存在oldval中。 which表示使用三个计时器中的哪一个。 itimerval结构中的it_value是减少的时间,当这个值为0的时候就发出相应的信号了。 然后设置为it_interval值。

  1. int getitimer(int which,struct itimerval *value);
  2. int setitimer(int which,struct itimerval *newval,
  3. struct itimerval *oldval);

  4. struct itimerval
  5. {
  6.     struct timeval it_interval;
  7.     struct timeval it_value;
  8. }

  9. #define PROMPT "时间已经过去了两秒钟na"

  10. char *prompt=PROMPT;
  11. unsigned int len;

  12. void prompt_info(int signo)
  13. {
  14.     write(STDERR_FILENO,prompt,len);
  15. }

  16. void init_sigaction(void)
  17. {
  18.     struct sigaction act;
  19.     act.sa_handler=prompt_info;
  20.     act.sa_flags=0;
  21.     sigemptyset(&act.sa_mask);
  22.     sigaction(SIGPROF,&act,NULL);
  23. }

  24. void init_time()
  25. {
  26.     struct itimerval value;
  27.     value.it_value.tv_sec=2;
  28.     value.it_value.tv_usec=0;
  29.     value.it_interval=value.it_value;
  30.     setitimer(ITIMER_PROF,&value,NULL);
  31. }

  32. int main()
  33. {
  34.     len=strlen(prompt);
  35.     init_sigaction();
  36.     init_time();
  37.     while(1);
  38.     exit(0);
  39. }

这个程序每执行两秒中之后会输出一个提示。



高精度计时器、时间间隔计时器
级别: 初级

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;
}
阅读(2043) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~