Linux 定时器应用【转】
Linux 定时器应用
实验目的
阅读 Linux 相关源代码,学习 Linux 系统中的时钟和定时器原理,即,ITIMER_REAL
实时计数,ITIMER_VIRTUAL 统计进程在用户模式执行的时间,ITIMER_PROF 统计进程
在用户模式和核心模式下的执行时间。
理解这些定时器的实现机制。
掌握操作定时器的命令,掌握定时器的使用。
实验内容
ITIMER_REAL 实时计数;ITIMER_VIRTUAL 统计进程在用户模式(进程本身执行)
执行的时间;ITIMER_PROF 统计进程在用户模式(进程本身执行)和核心模式(系统代表
进程执行)下的执行时间,与 ITIMER_VIRTUAL 比较,这个计时器记录的时间多了该进程
核心模式执行过程中消耗的时间。
针对一个计算 fibonacci 数的进程,设定三个定时器,获取该进程在用户模式的运行时
间,在核心模式的运行时间,以及总的运行时间。
实验提示
一、一个应用定时器的简单例子
我们首先来看一个关于 ITIMER_REAL 定时器的例子。在这个例子里面我们将会设置
一个 ITIMER_REAL 类型的定时器,它每过一秒都会发出一个信号,等到定时到达的时候
(即定时器时间值减到 0),程序将统计已经经过的时间。下面是具体的代码:
/*我们在使用 signal 和时钟相关的结构体之前,需要包含这两个头文件*/
#include <signal.h>
#include <sys/time.h>
/*声明信号处理函数,信号相关内容将在第八章同步机制中讲述,读者在这里只要明白这个函数是
在进程收到信号的时候调用就可以了*/
static void sig_handler(int signo);
long lastsec,countsec; /*这两个变量分别用来保存上一秒的时间和总共花去的时间*/
int main(void)
{
struct itimerval v; /*定时器结构体,结构体内容请参阅第三节中的介绍*/
long nowsec,nowusec; /*当前时间的秒数和微秒数*/ /*注册 SIGUSR1 和 SIGALARM 信号的处理函数为 sig_handler*/
if(signal(SIGUSR1,sig_handler)==SIG_ERR)
{
printf("Unable to create handler for SIGUSR1\n");
exit(0);
}
if(signal(SIGALRM,sig_handler)==SIG_ERR)
{
printf("Unable to create handler for SIGALRM\n");
exit(0);
}
/*初始化定时器初值和当前值*/
v.it_interval.tv_sec=9;
v.it_interval.tv_usec=999999;
v.it_value.tv_sec=9;
v.it_value.tv_usec=999999;
/*调用 setitimer 设置定时器,并将其挂到定时器链表上,这个函数的三个参数的含义分
别是设置 ITIMER_REAL 类型的定时器,要设置的值存放在变量 v 中,该定时器设置前
的值在设置后保存的地址,如果是这个参数为 NULL,那么就放弃保存设置前的值*/
setitimer(ITIMER_REAL,&v,NULL);
lastsec=v.it_value.tv_sec;
countsec=0;
/*该循环首先调用 getitimer 读取定时器当前值,再与原来的秒数比较,当发现已经过了
一秒后产生一个 SIGUSR1 信号,程序就会进入上面注册过的信号处理函数*/
while(1)
{
getitimer(ITIMER_REAL,&v);
nowsec=v.it_value.tv_sec;
nowusec=v.it_value.tv_usec;
if(nowsec==lastsec-1)
{
/*每过一秒,产生一个 SIGUSR1 信号*/
raise(SIGUSR1);
lastsec=nowsec;
countsec++; /*记录总的秒数*/
}
}
}
/*信号处理函数*/
static void sig_handler(int signo)
{
switch(signo)
{
/*接收到的信号是 SIGUSR1,打印 One second passed*/
case SIGUSR1: printf("One second passed\n");
break;
/*定时器定时到达*/
case SIGALRM:
{
printf("Timer has been zero,elapsed %d seconds\n",countsec);
lastsec=countsec;
countsec=0;
break;
}
}
}
上面的程序比较简单,主要是给大家看一下定时器的设置和读取的方法。下面我们将上
面的程序稍作修改,利用本章第一节介绍过的与进程相关的三种定时器来统计一个进程的用
户模式时间、核心模式时间、CPU 时间和总的进程执行时间。
二、统计关于进程的时间
我们在第一节介绍过和 Linux 进程相关的定时器有三种。ITIMER_REAL 实时计数;
ITIMER_VIRTUAL 统计进程在用户模式(进程本身执行)执行的时间;ITIMER_PROF 统
计进程在用户模式(进程本身执行)和核心模式(系统代表进程执行)下的执行时间,与
ITIMER_VIRTUAL 比较,这个计时器记录的时间多了该进程核心模式执行过程中消耗的时
间。通过在一个进程中设定这三个定时器,我们就可以了解到一个进程在用户模式、核心模
式以及总的运行时间。下面的这个程序除了定义了三个定时器和信号处理过程以外,其它的
地方和上面的程序完全相同。
#include <signal.h>
#include <sys/time.h>
static void sig_handler(int signo);
long countsec,lastsec,nowsec;
int main(void)
{
struct itimerval v;
/*注册信号处理函数*/
if(signal(SIGUSR1,sig_handler)==SIG_ERR)
{
printf("Unable to create handler for SIGUSR1\n");
exit(0);
}
if(signal(SIGALRM,sig_handler)==SIG_ERR)
{
printf("Unable to create handler for SIGALRM\n");
exit(0);
}
v.it_interval.tv_sec=10;
v.it_interval.tv_usec=0;
v.it_value.tv_sec=10;
v.it_value.tv_usec=0;
/*调用 setitimer 设置定时器,并将其挂到定时器链表上,这个函数的三个参数的含义分
别是设置何种类型的定时器;要设置的值存放在变量 v 中;该定时器设置前的值在设置
后保存的地址,如果是这个参数为 NULL,那么就放弃保存设置前的值*/
setitimer(ITIMER_REAL,&v,NULL);
setitimer(ITIMER_VIRTUAL,&v,NULL);
setitimer(ITIMER_PROF,&v,NULL);
countsec=0;
lastsec=v.it_value.tv_sec;
while(1)
{
getitimer(ITIMER_REAL,&v);
nowsec=v.it_value.tv_sec;
if(nowsec==lastsec-1)
{
if(nowsec<9)
{
/*同上面一样,我们每隔一秒发送一个 SIGUSR1 信号*/
raise(SIGUSR1);
countsec++;
}
lastsec=nowsec;
}
}
}
static void sig_handler(int signo)
{
struct itimerval u,v;
long t1,t2;
switch(signo)
{
case SIGUSR1:
/*显示三个定时器的当前值*/
getitimer(ITIMER_REAL,&v);
printf("real time=%.ld secs %ld
usecs\n",9-v.it_value.tv_sec,999999-v.it_value.tv_usec);
getitimer(ITIMER_PROF,&u);
printf("cpu time=%ld secs %ld
usecs\n",9-u.it_value.tv_sec,999999-u.it_value.tv_usec);
getitimer(ITIMER_VIRTUAL,&v);
printf("user time=%ld secs %ld
usecs\n",9-v.it_value.tv_sec,999999-v.it_value.tv_usec); /*当前 prof timer 已经走过的微秒数*/
t1=(9-u.it_value.tv_sec)*1000000+(1000000-u.it_value.tv_usec);
/*当前 virtual timer 已经走过的微秒数*/
t2=(9-v.it_value.tv_sec)*1000000+(1000000-v.it_value.tv_usec);
/*计算并显示 kernel time*/
printf("kernel time=%ld secs %ld
usecs\n\n",(t1-t2)/1000000,(t1-t2)%1000000);
break;
case SIGALRM:
printf("Real Timer has been zero,elapsed %d seconds\n",countsec);
exit(0);
break;
}
}
从上面的程序可以看出来,ITIMER_REAL 定时器运行的时间就是总运行时间,
ITIMER_PROF 定时器的运行时间就是 CPU 花在该进程上的所有时间。ITIMER_VIRTUAL
定时器运行的时间是进程在用户模式的运行时间。ITIMER_PROF 定时器的运行时间减去
ITIMER_VIRTUAL 定时器的运行时间就是进程在核心模式的运行时间。
三、更进一步的进程时间统计
上面的程序只在很短的时间内统计了进程在各种状态的执行时间。但是进程并没有真正
的负载作业,和现实中的进程差距比较大。下面我们要继续修改上面的程序,让这个进程做
点“事情”,然后我们再来看看在和实际情况比较相近的状态下定时器统计到的进程在各个
状态下的时间。在这个程序里面我们将创建两个子进程,加上父进程总共三个进程,这三个
进程分别调用 fibonacci()计算 fibonacci 数。在计算之前我们初始化定时器,完成之后,我们
将读取定时器,然后来统计进程相关的各种时间。
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
long unsigned int fibonacci(unsigned int n); /*计算 fibonacci 数的函数*/
static void par_sig(int signo); /*父进程的信号处理函数*/
static void c1_sig(int signo); /*子进程 1 的信号处理函数*/
static void c2_sig(int signo); /*子进程 2 的信号处理函数*/
/*用于分别记录父,子 1,子 2 进程 real time 过的总秒数*/
static long p_realt_secs=0,c1_realt_secs=0,c2_realt_secs=0;
/*用于分别记录父,子 1,子 2 进程 virtual time 过的总秒数*/
static long p_virtt_secs=0, c1_virtt_secs=0,c2_virtt_secs=0;
/*用于分别记录父,子 1,子 2 进程 proft time 过的总秒数*/
static long p_proft_secs=0,c1_proft_secs=0,c2_proft_secs=0;
/*用于分别取出父,子 1,子 2 进程的 real timer 的值*/
static struct itimerval p_realt,c1_realt,c2_realt;
/*用于分别取出父,子 1,子 2 进程的 virtual timer 的值*/ static struct itimerval p_virtt,c1_virtt,c2_virtt;
/*用于分别取出父,子 1,子 2 进程的 proft timer 的值*/
static struct itimerval p_proft,c1_proft,c2_proft;
int main()
{
long unsigned fib=0;
int pid1,pid2;
unsigned int fibarg=39;
int status;
struct itimerval v;
long moresec,moremsec,t1,t2;
pid1=fork();
if(pid1==0)
{
/*设置子进程 1 的信号处理函数和定时器初值*/
signal(SIGALRM,c1_sig);
signal(SIGVTALRM,c1_sig);
signal(SIGPROF,c1_sig);
v.it_interval.tv_sec=10;
v.it_interval.tv_usec=0;
v.it_value.tv_sec=10;
v.it_value.tv_usec=0;
setitimer(ITIMER_REAL,&v,NULL);
setitimer(ITIMER_VIRTUAL,&v,NULL);
setitimer(ITIMER_PROF,&v,NULL);
fib=fibonacci(fibarg); /*计算 fibonacci 数*/
/*取出子进程 1 的定时器值*/
getitimer(ITIMER_PROF,&c1_proft);
getitimer(ITIMER_REAL,&c1_realt);
getitimer(ITIMER_VIRTUAL,&c1_virtt);
/*通过定时器的当前值和各信号发出的次数计算子进程 1 总共用的 real time,cpu
time,user time 和 kernel time。moresec 和 moremsec 指根据定时器的当前值计算
出的自上次信号发出时过去的 real time,cpu time,user time 和 kernel time。计算
kernel time 时,moresec 和 moremsec 为 kernel time 的实际秒数+毫秒数*/
moresec=9-c1_realt.it_value.tv_sec;
moremsec= (1000000-c1_realt.it_value.tv_usec)/1000;
printf("Child 1 fib=%ld , real time=%ld sec,%ld
msec\n",fib,c1_realt_secs+moresec,moremsec);
moresec=9-c1_proft.it_value.tv_sec;
moremsec=(1000000-c1_proft.it_value.tv_usec)/1000;
printf("Child 1 fib=%ld , cpu time=%ld sec,%ld
msec\n",fib,c1_proft_secs+moresec,moremsec);
moresec=9-c1_virtt.it_value.tv_sec;
moremsec=(1000000-c1_virtt.it_value.tv_usec)/1000;
printf("Child 1 fib=%ld , user time=%ld sec,%ld
msec\n",fib,c1_virtt_secs+moresec,moremsec);
t1=(9-c1_proft.it_value.tv_sec)*1000+(1000000-c1_proft.it_value.tv_usec)/1000+c1_proft_secs*10000;
t2=(9-c1_virtt.it_value.tv_sec)*1000+(1000000-c1_virtt.it_value.tv_usec)/1000+c1_v
i rtt_secs*10000;
moresec=(t1-t2)/1000;moremsec=(t1-t2)%1000;
printf("Child 1 fib=%ld , kernel time=%ld sec,%ld msec\n",fib,moresec,moremsec);
fflush(stdout);
exit(0);
}
else
{
pid2=fork();
if(pid2==0)
{
/*设置子进程 2 的信号处理函数和定时器初值*/
signal(SIGALRM,c2_sig);
signal(SIGVTALRM,c2_sig);
signal(SIGPROF,c2_sig);
v.it_interval.tv_sec=10;
v.it_interval.tv_usec=0;
v.it_value.tv_sec=10;
v.it_value.tv_usec=0;
setitimer(ITIMER_REAL,&v,NULL);
setitimer(ITIMER_VIRTUAL,&v,NULL);
setitimer(ITIMER_PROF,&v,NULL);
fib=fibonacci(fibarg);
/*取出子进程 2 的定时器值*/
getitimer(ITIMER_PROF,&c2_proft);
getitimer(ITIMER_REAL,&c2_realt);
getitimer(ITIMER_VIRTUAL,&c2_virtt);
/*通过定时器的当前值和各信号发出的次数计算子进程 2 总共用的 real
time,cpu time,user time 和 kernel time。moresec 和 moremsec 指根据定时
器的当前值计算出的自上次信号发出时过去的 real time,cpu time,user
time 和 kernel time。计算 kernel time 时,moresec 和 moremsec 为 kernel
time 的实际秒数+毫秒数*/
moresec=9-c2_realt.it_value.tv_sec;
moremsec=(1000000-c2_realt.it_value.tv_usec)/1000;
printf("Child 2 fib=%ld , real time=%ld sec,%ld
msec\n",fib,c2_realt_secs+moresec,moremsec);
moresec=9-c2_proft.it_value.tv_sec;
moremsec=(1000000-c2_proft.it_value.tv_usec)/1000;
printf("Child 2 fib=%ld , cpu time=%ld sec,%ld
msec\n",fib,c2_proft_secs+moresec,moremsec);
moresec=9-c2_virtt.it_value.tv_sec;
moremsec=(1000000-c2_virtt.it_value.tv_usec)/1000;
printf("Child 2 fib=%ld , user time=%ld sec,%ld
msec\n",fib,c2_virtt_secs+moresec,moremsec);
t1=(9-c2_proft.it_value.tv_sec)*1000+(1000000-c2_proft.it_value.tv_usec)/
1000+c2_proft_secs*10000;
t2=(9-c2_virtt.it_value.tv_sec)*1000+(1000000-c2_virtt.it_value.tv_usec)/1
000+c2_virtt_secs*10000;
moresec=(t1-t2)/1000;
moremsec=(t1-t2)%1000;
printf("Child 2 fib=%ld , kernel time=%ld sec,%ld
msec\n",fib,moresec,moremsec);
fflush(stdout);
exit(0);
}
else
{
/*设置父进程的信号处理函数和定时器初值*/
signal(SIGALRM,par_sig);
signal(SIGVTALRM,par_sig);
signal(SIGPROF,par_sig);
v.it_interval.tv_sec=10;
v.it_interval.tv_usec=0;
v.it_value.tv_sec=10;
v.it_value.tv_usec=0;
setitimer(ITIMER_REAL,&v,NULL);
setitimer(ITIMER_VIRTUAL,&v,NULL);
setitimer(ITIMER_PROF,&v,NULL);
fib=fibonacci(fibarg);
/*取出父进程的定时器值*/
getitimer(ITIMER_PROF,&p_proft);
getitimer(ITIMER_REAL,&p_realt);
getitimer(ITIMER_VIRTUAL,&p_virtt);
/*通过定时器的当前值和各信号发出的次数计算子进程 1 总共用的
real time,cpu time,user time 和 kernel time。moresec 和 moremsec 指根据
定时器的当前值计算出的自上次信号发出时过去的 real time,cpu
time,user time 和 kernel time。计算 kernel time 时,moresec 和 moremsec
为 kernel time 的实际秒数+毫秒数*/
moresec=9-p_realt.it_value.tv_sec;
moremsec=(1000000-p_realt.it_value.tv_usec)/1000;
printf("Parent fib=%ld , real time=%ld sec,%ld
msec\n",fib,p_realt_secs+moresec,moremsec);
moresec=9-p_proft.it_value.tv_sec;
moremsec=(1000000-p_proft.it_value.tv_usec)/1000;
printf("Parent fib=%ld , cpu time=%ld sec,%ld
msec\n",fib,p_proft_secs+moresec,moremsec);
moresec=9-p_virtt.it_value.tv_sec;
moremsec=(1000000-p_virtt.it_value.tv_usec)/1000;
printf("Parent fib=%ld , user time=%ld sec,%ld
msec\n",fib,p_virtt_secs+moresec,moremsec);
t1=(9-p_proft.it_value.tv_sec)*1000+(1000000-p_proft.it_value.tv_usec)/1
000+p_proft_secs*10000;
7t2=(9-p_virtt.it_value.tv_sec)*1000+(1000000-p_virtt.it_value.tv_usec)/10
00+p_virtt_secs*10000;
moresec=(t1-t2)/1000;
moremsec=(t1-t2)%1000;
printf("Parent fib=%ld , kernel time=%ld sec,%ld
msec\n",fib,moresec,moremsec);
fflush(stdout);
waitpid(0,&status,0);
waitpid(0,&status,0);
exit(0);
}
printf("this line should never be printed\n");
}
}
long unsigned fibonacci(unsigned int n)
{
if(n==0)
return 0;
else if(n==1||n==2)
return 1;
else
return (fibonacci(n-1)+fibonacci(n-2));
}
/*父进程信号处理函数;每个 timer 过 10 秒减为 0,激活处理函数一次,相应的计数器加 10*/
static void par_sig(int signo)
{
switch(signo)
{
case SIGALRM:
p_realt_secs+=10;
break;
case SIGVTALRM:
p_virtt_secs+=10;
break;
case SIGPROF:
p_proft_secs+=10;
break;
}
}
/*子进程 1 的信号处理函数,功能与父进程的信号处理函数相同*/
static void c1_sig(int signo)
{
switch(signo)
{ case SIGALRM:
c1_realt_secs+=10;
break;
case SIGVTALRM:
c1_virtt_secs+=10;
break;
case SIGPROF:
c1_proft_secs+=10;
break;
}
}
/*子进程 2 的信号处理函数,功能与父进程的信号处理函数相同*/
static void c2_sig(int signo)
{
switch(signo)
{
case SIGALRM:
c2_realt_secs+=10;
break;
case SIGVTALRM:
c2_virtt_secs+=10;
break;
case SIGPROF:
c2_proft_secs+=10;
break;
}
}
在上面的程序中,我们为三个进程使用了三个不同的信号处理函数,这是因为每个进程
需要处理的数据不同。如果在 Linux 中运行这个程序,那么我们就可以看到下面的结果:
Child 1 fib=63245986 , real time=20 sec,250 msec
Child 1 fib=63245986 , cpu time=6 sec,840 msec
Child 1 fib=63245986 , user time=6 sec,800 msec
Child 1 fib=63245986 , kernel time=0 sec,40 msec
Child 2 fib=63245986 , real time=20 sec,380 msec
Child 2 fib=63245986 , cpu time=6 sec,850 msec
Child 2 fib=63245986 , user time=6 sec,840 msec
Child 2 fib=63245986 , kernel time=0 sec,10 msec
Parent fib=63245986 , real time=20 sec,290 msec
Parent fib=63245986 , cpu time=6 sec,870 msec
Parent fib=63245986 , user time=6 sec,820 msec
Parent fib=63245986 , kernel time=0 sec,50 msec
大家可以试着运行一下,看看结果是不是如上所示。当然每个人得到的具体时间可能都
不同,这个要看具体机器的运算速度。