[Linux用户空间编程-3]:Linux定时机制的几种实现方法

Posted 文火冰糖的硅基工坊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Linux用户空间编程-3]:Linux定时机制的几种实现方法相关的知识,希望对你有一定的参考价值。

作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客

本文网址:https://blog.csdn.net/HiWangWenBing/article/details/123376014


目录

前言:

定时器的常见使用方法

1. 使用sleep()和usleep()

2. 使用信号量SIGALRM + alarm()

3. 利用POSIX定时器:time.h

4. 使用RTC hardware device(适合实时性要求高的场合)

5. 使用select()


前言:

定时器Timer应用场景非常广泛,在Linux下,有以下几种方法:

(1)sleep

(2)SIGALRM + alarm()

(3)POSIX time

(4)使用RTC hardware device

(5)select

定时器的常见使用方法

1. 使用sleep()和usleep()

其中sleep精度是1秒,usleep精度是1微妙

使用这种方法缺点比较明显,在Linux系统中,sleep类函数不能保证精度,尤其在系统负载比较大时,sleep一般都会有超时现象。

while(1)

        sleep(1);

周期性的被调度器唤醒,调度执行。

2. 使用信号量SIGALRM + alarm()

这种方式的精度能达到1秒,其中利用了Linux系统的信号量机制,

首先注册信号量SIGALRM处理函数,调用alarm(),设置定时长度,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

#include <stdio.h>

#include <signal.h>

void timer_handler(int sig)

    if(SIGALRM == sig)

    

        printf("timer\\n");

        alarm(1); //再次发送alarm, 1表示1s后发送alarm

    

    return ;

int main()

    signal(SIGALRM, timer_handler); //relate the signal and function

    alarm(1);    //trigger the timer_handler, 1表示发送alarm的延时时间

    getchar();

    return 0;

alarm方式虽然很好,但是无法首先低于1秒的精度。

3. 利用POSIX定时器:time.h

(1)概述

最强大的定时器接口来自POSIX时钟系列,其创建、初始化以及删除一个定时器的行动被分为三个不同的函数:

  • timer_create():创建定时器
  • timer_settime():初始化定时器
  • timer_delete:    销毁定时器

(2)代码示例

#include <stdio.h>  
#include <signal.h>  
#include <time.h>  
#include <string.h>  
#include <stdlib.h>  
#include <unistd.h>  
  
# 定时回调函数,函数会被定时中断周期的调用
void timer_thread(union sigval v)  
  
    # v.sival_int从定时器软中断/Signal传递过来的信息
    printf("timer_thread function! %d\\n", v.sival_int);  
  
  
int main()  
  
    # 定时器标识
    timer_t timerid;  

    # 定时软中断信息
    struct sigevent evp;  
    //清零初始化
    memset(&evp, 0, sizeof(struct sigevent));   
    
    # 定时器的创建者传递给定时器的处理者的信息:
    evp.sigev_value.sival_int = 111;
    # 定时线程被通知的方式,启动一个新的线程来调用callback函数
    evp.sigev_notify = SIGEV_THREAD;   
    # 定时回调函数
    evp.sigev_notify_function = timer_thread;        
  
     # 创建定时器
    // int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid);  
    // clockid:定时器的类型:
    // CLOCK_REALTIME,CLOCK_MONOTONIC,CLOCK_PROCESS_CPUTIME_ID,CLOCK_THREAD_CPUTIME_ID  
    // evp--存放环境值的地址,结构成员说明了定时器到期的通知方式和处理方式等  
    // timerid--用户存放返回的定时器标识符  
    
    if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)  
      
        perror("fail to timer_create");  
        exit(-1);  
      
  
     # 设置定时器的参数:
    // XXX int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);  
    // timerid--定时器标识  
    // flags--0表示相对时间,1表示绝对时间  
    // new_value--定时器的新初始值和间隔,如下面的it  
    // old_value--取值通常为0,即第四个参数常为NULL,若不为NULL,则返回定时器的前一个值  
      
    //第一次间隔it.it_value这么长,以后每次都是it.it_interval这么长,就是说it.it_value变0的时候会装载it.it_interval的值  
    struct itimerspec it;  
    it.it_interval.tv_sec = 1;  
    it.it_interval.tv_nsec = 0;  
    it.it_value.tv_sec = 1;  
    it.it_value.tv_nsec = 0;  
  
    if (timer_settime(timerid, 0, &it, NULL) == -1)  
      
        perror("fail to timer_settime");  
        exit(-1);  
      
  
    pause();  
  
    return 0;  
  

# 获得定时器当期的计数值
/* 
 * int timer_gettime(timer_t timerid, struct itimerspec *curr_value); 
 * 获取timerid指定的定时器的值,填入curr_value 
 * 
 */  

(3)CLOCK_MONOTONIC与CLOCK_REALTIME的区别:

CLOCK_MONOTONIC:是monotonic time,monotonic time字面意思是单调时间,实际上它指的是系统启动以后流逝的时间,这是由变量jiffies来记录的。系统每次启动时jiffies初始化为0,每来一个timer interrupt,jiffies加1,也就是说它代表系统启动后流逝的tick数。jiffies一定是单调递增的。

CLOCK_REALTIME:是wall time,wall time字面意思是挂钟时间,实际上就是指的是现实的时间,这是由变量xtime来记录的。系统每次启动时将CMOS上的RTC时间读入xtime,这个值是"自1970-01-01起经历的秒数、本秒中经历的纳秒数",每来一个timer interrupt,也需要去更新xtime。
wall time不一定是单调递增的。因为wall time是指现实中的实际时间,如果系统要与网络中某个节点时间同步、或者由系统管理员觉得这个wall time与现实时间不一致,有可能任意的改变这个wall time。最简单的例子是,我们用户可以去任意修改系统时间,这个被修改的时间应该就是wall time,即xtime,它甚至可以被写入RTC而永久保存。一些应用软件可能就是用到了这个wall time,比如以前用vmware workstation,一启动提示试用期已过,但是只要把系统时间调整一下提前一年,再启动就不会有提示了,这很可能就是因为它启动时用gettimeofday去读wall time,然后判断是否过期,只要将wall time改一下,就可以欺骗过去了。

因此,如果需要严格的不受系统时间影响的定时器,则需要使用CLOCK_MONOTONIC类型。

4. 使用RTC hardware device(适合实时性要求高的场合)

RTC机制利用系统硬件中断提供的Real Time Clock机制,

通过读取RTC硬件/dev/rtc,通过ioctl()设置RTC频率,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

#include <stdio.h>

#include <linux/rtc.h>

#include <sys/ioctl.h>

#include <sys/time.h>

#include <sys/types.h>

#include <fcntl.h>

#include <unistd.h>

#include <errno.h>

#include <stdlib.h>

int main(int argc, char* argv[])

    unsigned long i = 0;

    unsigned long data = 0;

    int retval = 0;

    int fd = open ("/dev/rtc", O_RDONLY);

    if(fd < 0)

    

        perror("open");

        exit(errno);

    

    /*Set the freq as 4Hz*/

    if(ioctl(fd, RTC_IRQP_SET, 1) < 0)

    

        perror("ioctl(RTC_IRQP_SET)");

        close(fd);

        exit(errno);

    

    /* Enable periodic interrupts */

    if(ioctl(fd, RTC_PIE_ON, 0) < 0) 

    

        perror("ioctl(RTC_PIE_ON)");

        close(fd);

        exit(errno);

    

    while(1)

    

          # 周期性的被硬件唤醒

          # 平时被阻塞在内核设备的同步锁上。

        if(read(fd, &data, sizeof(unsigned long)) < 0)

        

                # 出错处理

            perror("read");

            close(fd);

            exit(errno);

        

        printf("timer\\n");

    

    /* Disable periodic interrupts */

    ioctl(fd, RTC_PIE_OFF, 0);

    close(fd);

    return 0;

优点:这种方式比较方便,利用了系统硬件提供的RTC,精度可调,而且非常高。

缺点:

(1)需要在内核空间写内核驱动程序,并虚拟出一个硬件设备。

(2)用户空间程序通过read(fd, &data, sizeof(unsigned long)阻塞在该设备上。

(3)内核中断服务程序周期性唤醒阻塞read上上的用户空间程序。

由于该应用程序是有内核中断服务程序唤醒的,因此实时性比较高。

5. 使用select()

这种方法比较冷门,通过使用select(),来设置定时器;

原理利用select()方法的第5个参数,第一个参数设置为0,三个文件描述符集都设置为NULL,第5个参数为时间结构体,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

#include <sys/time.h>

#include <sys/select.h>

#include <time.h>

#include <stdio.h>

/*seconds: the seconds; mseconds: the micro seconds*/

void setTimer(int seconds, int mseconds)

    struct timeval temp;

    temp.tv_sec = seconds;

    temp.tv_usec = mseconds;

              # select被一直阻塞,直到超时后返回,起到了定时的作用。

    select(0, NULL, NULL, NULL, &temp);

    printf("timer\\n");

    return ;

int main()

    int i;

    for(i = 0 ; i < 100; i++)

        setTimer(1, 0);

    return 0;

这种方法精度能够达到微妙级别,网上有很多基于select()的多线程定时器,说明select()稳定性还是非常好。

总结:如果对系统要求比较低,可以考虑使用简单的sleep(),毕竟一行代码就能解决;

如果系统对精度要求比较高,则可以考虑RTC机制和select()机制。


作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客

本文网址:

以上是关于[Linux用户空间编程-3]:Linux定时机制的几种实现方法的主要内容,如果未能解决你的问题,请参考以下文章

[架构之路-37]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之:内存与IO访问中断定时与延时

[架构之路-37]:目标系统 - 系统软件 - Linux OS硬件设备驱动必须熟悉的六大工作机制之:内存与IO访问中断定时与延时

Linux编程入门五定时器

linux笔记 2-3 系统延迟以及定时机制

研究任务linux内存管理机制——内核空间

Linux内核MMU机制实现讲解