Linux环境下实现定时的方法

sleep(精准)和usleep(不精准)

sleep函数是我们编程中非常常见的,它可以使得进程睡眠指定时间之后再执行
它的参数分别为秒级(sleep)和微秒级(usleep 1000000us为1s)

头文件

#include <unistd.h>

函数原型

unsigned int sleep(unsigned int seconds);
int usleep(useconds_t usec);

值得注意的是,sleep和usleep都是可以中断的,即当进程在睡眠状态如果被中断,程序会从sleep或usleep之后的第一行代码开始运行,直接结束睡眠状态

返回值
通过函数原型我们可以观察到,sleep的返回值是无符号整型数据,其返回值分为两种情况:

  • 休眠过程中发生中断,sleep返回剩余的秒数
  • 休眠结束,sleep返回值为0

我们可以使用Ctrl+C方便的实现一个中断,不过此中断的默认处理是退出程序,我们需要使用signal函数自定义信号处理

示例程序

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

void Sig_handler(int num)
{
    printf("The signal val is:%d\n", num);
}

int main()
{
    signal(SIGINT, Sig_handler);
    int num = sleep(10);
    printf("This is sleep return val: %d!!!\n",num);
    return 0;
}

程序编译运行之后,终端选择性输入Ctrl+C,通过查看函数执行过程中打印语句,我们可以观察到两者的区别

  • 发生中断
    在这里插入图片描述
  • 未发生中断
    在这里插入图片描述
    usleep返回值
    usleep和sleep不同的是,如果usleep休眠期间没有中断,则返回0;否则返回-1
    在这里插入图片描述
usleep的误差问题

usleep的参数虽然是微妙级别的,但是其实际使用情况确实不准确的,使用以下示例程序可以观测到其误差率
示例程序

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

/*
timeval结构体
struct timeval
{
  __time_t tv_sec;		// Seconds. 秒 
  __suseconds_t tv_usec;	// Microseconds. 微妙
};

timezone结构体
//具体内容含义本人并不清楚,只是下方的gettimeforday函数需要该结构体,感兴趣的朋友可以自行了解
struct timezone
 {
   int tz_minuteswest;		// Minutes west of GMT.  和Greewich时间差了多少分钟
   int tz_dsttime;		// Nonzero if DST is ever in effect.  日光节约时间的状态
 };

gettimeofday获取当前时间
*/

int main()
{
    struct timeval nowTime;
    struct timezone Tz;

    int i;
    for(i = 0; i < 10; i++)
    {
        usleep(1);
        gettimeofday(&nowTime, &Tz);
        printf("The nowTime.sec is: %ld, nowTime.usec is: %ld\n", 
            nowTime.tv_sec, nowTime.tv_usec);
    }
    
    return 0;
}

程序运行效果
在这里插入图片描述

由上方程序运行效果可以观察到,usleep函数的最大误差可达到1022us,远远超过设定的1us的限制
其实这是由于Linux内核定时器HZ引起的,查看Linux系统的定时器频率可以使用以下命令:

getconf CLK_TCK
# 本人频率为100

当定时器频率为100HZ时,也就意味着Linux核心每秒钟会产生100次timer interrupt,则系统的时钟精度就为1/100s=10ms(本人系统定时误差为1000us=1ms左右,不知为何不是10ms)
如果需要使用usleep尽可能的提高函数精度,可以开启高精度定时器,在编译Linux内核时打开Kernel Features ----》[High Resolution Timer Support] 选项

alarm函数(精准)

alarm函数可以设置定时器,在指定秒数之后内核将向该进程发送SIGALRM信号,定时精度为秒级
首先需要明确的一点是,一个进程只能设置一个定时器,如果在调用alarm之前已经存在了定时器,则定时器被刷新(启动一个新的时间的定时器)

注意:当alarm的参数为0时,进程取消定时器,并且也不会发送SIGALRM信号

与sleep不同的是,alarm可以在调用之后继续执行其他程序,而非挂起进程

头文件

#include <unistd.h>

函数原型

unsigned int alarm(unsigned int seconds);

返回值
alarm的返回值如下:

  • 如果在调用alarm之前已经存在了定时器,则alarm的返回值为上个定时器的剩余时间
  • 如果进程中没有定时器,则alarm返回0
  • 如果alarm调用失败,则函数返回-1

示例程序

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

void timeout_Sig_handler(int num)
{
    printf("The timeout signal val is:%d\n", num);
}

int main()
{
    signal(SIGALRM, timeout_Sig_handler);
    
    alarm(10);
    sleep(1);
    printf("This is alarm timeVal : %d!!!\n", alarm(3));
    pause();    //挂起进程直到捕捉到信号
    
    return 0;
}

分析上述程序可知,程序会输出9,并在3s之后将SIGALRM的值14输出

程序运行结果如下:
在这里插入图片描述

固定间隔触发定时器示例程序

如果我们需要每隔一定时间就出发一次定时器,可以在SIGALRM的处理函数中添加alarm,再添加一个死循环,让进程持续发送定时信号
示例程序如下:

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

int sleepVal = 0;

void timeout_Sig_handler()
{
    printf("The timeout val is: %d\n", sleepVal++);
    alarm(1);
}

int main()
{
    signal(SIGALRM, timeout_Sig_handler);
    alarm(1);
    
    while(1);
    return 0;
}

程序运行效果如下所示:
在这里插入图片描述

select函数(精准)

和之前几个函数相比,select的时间精度可以达到1us,是目前精确定时的主要方案
头文件

//使用man 2 select的结果
/* 根据POSIX.1-2001*/
#include <sys/select.h>

/* 根据先前的标准*/
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

参数介绍

  • nfds:fd_set集合中最大的描述符值加1
  • readfds:用于检查可读性
  • writefds:用于检查可写性
  • exceptfds:用于检查错误事件(带外数据)
  • timeout:执行timeval结构的指针,用于决定select等待I/O的最长时间,如果为空将一直等待

示例代码

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

void setTime(int sec, int usec)
{
    struct timezone tz;
    struct timeval tVal;
    tVal.tv_sec = sec;
    tVal.tv_usec = usec;

    select(0, NULL, NULL, NULL, &tVal);
    gettimeofday(&tVal, &tz);
    
    printf("This tVal.sec is: %ld, tVal.usec is: %ld\n", tVal.tv_sec, tVal.tv_usec);
}

int main()
{
    int i = 0;
    for(i = 0; i < 10; i++)
    {
        setTime(0, 1);
    }
    return 0;
}

运行效果
在这里插入图片描述
根据程序运行效果来看,虽然select号称是精准到1us的精确度,但实际也是受到内核频率限制的,误差和usleep处于同一水平???这就令人十分疑惑,本人能力有限,希望有高人看到之后可以指点一二,传道授业解惑

Logo

更多推荐