定时器智能学习 (定时器使用详解家用)

1. C实现定时器的几种方式

  1. linux下调用系统函数alarm(),setitimer(),sleep(),usleep()(实现微妙定时),
  2. 单纯c语言实现gettimeofday()(微妙定时),time(),
  3. windows可用Sleep()实现微秒级定时

1.1 alarm()

头文件:

#include <unistd.h>

函数原型:

unsigned int alarm(unsigned int seconds);

函数返回值:

成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的 剩余时间,
            否则返回0。不阻塞!!
出错:-1

作用:

调用alarm函数即设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程 发SIGALRM信号,
  默认处理动作是终止当前进程。闹钟返回值是0或者是以前设定的闹 钟时间还余下的秒数。如
  果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然 是以前设定的闹钟时间还余下的
  秒数。

示例:

#include<unistd.h>
#include<stdio.h>

intmain(void)
{
intcounter;
alarm(1);
for(counter=0;1;counter++)
printf("counter=%d",counter);
return0;
}

这个程序的作用是 1秒钟之内不停地数数, 1秒钟到了就被 SIGALRM 信号终止。

1.1.1配合pause函数实现sleep函数!

头文件:

#include <unistd.h>

函数原型:

int pause(void)

函数返回值:

如果信号的处理动作是忽略,则进程继续处于挂起状态,pause 不返回;如果信号的处理动作是捕捉,则调
用了信号处理函数之后 pause 返回-1,errno 设置为 EINTR,所以pause只有出错的返回值。错误码 EINTR
表示“被信号中断”。

作用:

pause函数使调用进程挂起直到有信号递达。如果信号的处理动作是终止进程,则进程终止,pause函数
没有机会返回;

下面我们用 alarm 和 pause 实现 sleep(3)函数。

示例:

#include<unistd.h>
#include<signal.h>
#include<stdio.h>

voidsig_alrm(intsigno)
{
/*nothingtodo*/
}

unsignedintmysleep(unsignedintnsecs)
{
structsigactionnewact,oldact;
unsignedintunslept;
newact.sa_handler=sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags=0;
sigaction(SIGALRM,&newact,&oldact);
alarm(nsecs);
pause();
unslept=alarm(0);
sigaction(SIGALRM,&oldact,NULL);
returnunslept;
}

intmain(void)
{
while(1){
mysleep(2);
printf("Twosecondspassed\n");
}
return0;
}
  1. main 函数调用 mysleep 函数,后者调用 sigaction 注册了 SIGALRM 信号的处理函数 sig_alrm。
  2. 调用 alarm(nsecs)设定闹钟。
  3. 调用 pause 等待,内核切换到别的进程运行。
  4. nsecs 秒之后,闹钟超时,内核发 SIGALRM 给这个进程。
  5. 从内核态返回这个进程的用户态之前处理未决信号,发现有 SIGALRM 信号,其处理函数是 sig_alrm。
  6. 切换到用户态执行 sig_alrm 函数,进入 sig_alrm 函数时 SIGALRM 信号被自动屏蔽,从 sig_alrm 函数返回时 SIGALRM 信号自动解除屏蔽。然后自动执行系统调用 sigreturn 再次进入内核,再返回用户态继续执行进程的主控制流程(main 函数调用的 mysleep 函数)。
  7. pause 函数返回-1,然后调用 alarm(0)取消闹钟,调用 sigaction 恢复SIGALRM 信号以前的处理动作。

学习更多关于定时器内容请私信:定时器全场景全方位学习,

学习定时器操作,定时器基础教学

1.2 setitimer()

常用到的函数:

#include <sys/time.h>
int getitimer (int which, struct itimerval* value);
int setitimer (int which, struct itimerval* newvalue, struct itimerval* oldvalue);

which有三种状态:

  • ITIMER_REAL: 对指定时间值,按自然时间计数, 时间到发出SIGALRM信号.
  • ITIMER_VIRTUAL: 对指定时间值, 当只在用户态时(进程执行的时候)计数, 时间到发出SIGVTALRM信号.
  • ITIMER_PROF: 对指定时间值, 用户态或内核态(进程执行与系统为进程调度)都计数, 时间到, 发出SIGPROF信号, 与ITIMER_VIRTVAL联合, 常用来计算系统内核时间和用户时间.
structtimeval
{
longtv_sec;/*秒*/
longtv_usec;/*微秒*/
};

structitimerval
{
structtimevalit_interval;/*时间间隔*///循环定时时间
structtimevalit_value;/*当前时间计数*///第一次计时时间
};

it_interval用来指定每隔多长时间执行任务, it_value用来保存当前时间离执行任务还有多长时间. 比如说, 你指定it_interval为2秒(微秒为0), 开始的时候我们把it_value的时间也设定为2秒(微秒为0), 当过了一秒, it_value就减少一个为1, 再过1秒, it_value又减少1, 变为0, 这个时候发出信号(告诉用户时间到了, 可以执行任务了), 并且系统自动把it_value的值重置为it_interval的值, 即2秒, 再重新计数.

示例:

#include<sys/time.h>
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
#include<stdlib.h>

staticcharmsg[]="timeisrunningout.\n";
staticintlen;

/*time'sup*/
voidprompt_info(intsigno)
{
write(STDERR_FILENO,msg,len);
}
voidinit_sigaction(void)
{
structsigactiontact;
tact.sa_handler=prompt_info;
tact.sa_flags=0;
sigemptyset(&tact.sa_mask);
sigaction(SIGALRM,&tact,NULL);
}
voidinit_time()
{
structitimervalvalue;
value.it_value.tv_sec=2;
value.it_value.tv_usec=0;
value.it_interval=value.it_value;
/*setITIMER_REAL*/
setitimer(ITIMER_REAL,&value,NULL);
}
intmain(intargc,char**argv)
{
len=strlen(msg);
init_sigaction();
init_time();
while(1);
exit(0);
}

该程序的ITMER_REAL定时器,每隔2秒钟都会发送一个SIGALRM信号,当主函数接收到了这个信号之后,调用信号处理函数prompt_info在标准错误上输出time is running out这个字符串。

对于ITIMER_VIRTUAL和ITIMER_PROF的使用方法类似,当你在setitimer里面设置的定时器为ITIMER_VIRTUAL的时候,你把sigaction里面的SIGALRM改为SIGVTALARM, 同理,ITIMER_PROF对应SIGPROF。

不过,你可能会注意到,当你用ITIMER_VIRTUAL和ITIMER_PROF的时候,你拿一个秒表,你会发现程序输出字符串的时间间隔会不止2秒,甚至5-6秒才会输出一个,那是因为cpu在用户与内核切换之间也会浪费时间,这段时间是不计入在指定时间范围之内的。

1.3 time()或gettimeofday()利用时间差来计算

#include<signal.h>
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<time.h>//包含time()函数
#include<sys/time.h>//包含gettimeofday()函数

staticcharmsg[]="Alljustwanttobebetteryou.\n";
intlen;
statictime_tlasttime;

voidshow_msg(intsigno)
{
write(STDERR_FILENO,msg,len);
}

intmain()
{
structsigactionact;
unionsigvaltsval;

act.sa_handler=show_msg;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(50,&act,NULL);
len=strlen(msg);
time(&lasttime);
while(1)
{
time_tnowtime;
/*获取当前时间*/
time(&nowtime);
/*和上一次的时间做比较,如果大于等于2秒,则立刻发送信号*/
if(nowtime-lasttime>=2)
{
/*向主进程发送信号,实际上是自己给自己发信号*/
sigqueue(getpid(),50,tsval);
lasttime=nowtime;
}
}
return0;
}

如果你想更精确的计算时间差,你可以把 time 函数换成gettimeofday,这个可以精确到微妙。

上面介绍的几种定时方法各有千秋,在计时效率上、方法上和时间的精确度上也各有不同,采用哪种方法,就看你程序的需要。

1.4 sleep实现方法

下面我们来看看用sleep以及usleep怎么实现定时执行任务。

示例

#include<signal.h>
#include<unistd.h>
#include<string.h>
#include<stdio.h>

staticcharmsg[]="Alljustwanttobebetteryou.\n";
intlen;
voidshow_msg(intsigno)
{
write(STDERR_FILENO,msg,len);
}

intmain()
{
structsigactionact;
unionsigvaltsval;
act.sa_handler=show_msg;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(50,&act,NULL);
len=strlen(msg);
while(1)
{
sleep(2);/*睡眠2秒*/
/*向主进程发送信号,实际上是自己给自己发信号*/
sigqueue(getpid(),50,tsval);
}
return0;
}

1.5 clock()

#include<stdio.h>
#include<time.h>
#include<conio.h>

#ifndefCLOCKS_PER_SEC
#defineCLOCKS_PER_SEC1000
#endif

intmain(void)
{
clock_tstart;
longcount=1;
start=clock();
while(1)
{
if((clock()-start)==CLOCKS_PER_SEC)
{
printf("%ld\n",count++);
start=clock();
//break;
}
}
getch();
}

代码抽象出一个定时器函数 void timer(long time)

voidtimer(longtime){
clock_tstart;
longcount=1;
start=clock();
while(1)
{
if((clock()-start)!=(time*CLOCKS_PER_SEC))
{
//时间没有到,啥也不做,空循环
}else{
//时间到了退出循环
//printf("%s","hello");
break;
}

}
}

完整代码

#include<stdio.h>
#include<time.h>
#include<conio.h>

#ifndefCLOCKS_PER_SEC
#defineCLOCKS_PER_SEC1000
#endif
/**
*time的单位为s
*/
voidtimer(longtime){
clock_tstart;
longcount=1;
start=clock();
while(1)
{
if((clock()-start)!=(time*CLOCKS_PER_SEC))
{
//时间没有到,啥也不做,空循环
}else{
//时间到了退出循环
//printf("%s","hello");
break;
}
}
}
intmain(void)
{
for(inti=0;i<10;i++){
timer(1);
printf("%d\n",i);
}
getch();
}