当前位置:   article > 正文

Epoll和timerfd结合实现定时器

Epoll和timerfd结合实现定时器

Epoll和timerfd结合实现定时器

主要记录不太熟悉的linux系统调用

1、timerfd

01 初始化

int timerfd_create(int clockid, int flags);
  • 1

第一个参数是指定用哪种时钟来计时:

主要有:

  • CLOCK_REALTIME:绝对时间 从1970年1月1日0点0分到目前的时间
  • CLOCK_MONOTONIC:相对时间 从系统启动到现在的时间;

其余省略,可以在man手册中查看

第二个参数flags:

  • TFD_NONBLOCK:非阻塞调用;
  • TFD_CLOSEXEC:fork后关闭共享的文件描述符,一般都会这样指定;

02 settime

函数原型:

int timerfd_settime(int fd, int flags,
                    const struct itimerspec *new_value,
                    struct itimerspec *old_value);
  • 1
  • 2
  • 3

fd:前面初始化的timerfd;

new_val: 用于指定定时器第一次到期的时间和和后续的定时周期;

数据结构 itimerspec

struct itimerspec {
    struct timespec it_interval;  /* Interval for periodic timer */
    struct timespec it_value;     /* Initial expiration */
};

struct timespec {
    time_t tv_sec;                /* Seconds */
    long   tv_nsec;               /* Nanoseconds */
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

由数据结构可以看出timerfd的精确度可以达到纳秒级;

1s = 1e3(ms) = 1e6 (us) = 1e9 (ns)

第一个 it_interval 代表时间间隔:如果为0,就只启动一次,否则会启动多次;

第二个 it_value 代表第一次超时时间:从启动settime到第一次超时,就会中止定时器;

flags :可以为0,即使用相对定时器;

TFD_TIMER_ABSTIME:使用绝对定时器,new_value里的it_value是相对于选择的时钟的绝对时间(假如调用timerfd_settime()时clock的时间是8:00,此时flag为0,使用相对定时器,传递的new_value.it_value是10分钟,那么定时器就会在8:10触发;如果flag使用了TFD_TIMER_ABSTIME标志,使用绝对定时器,那么new_value.it_value就需要传递8:10,使用绝对时间)
TFD_TIMER_CANCEL_ON_SET:只有在timerfd_create()里选择的时钟是CLOCK_REALTIME或CLOCK_REALTIME_ALARM才有意义,并且必须与标志TFD_TIMER_ABSTIME一起使用。当时钟经常发生不连续的变化时,这个定时器是可以取消的。前面也提到过,CLOCK_REALTIME对应的时钟是可变的。
————————————————
版权声明:本文为CSDN博主「爱就是恒久忍耐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/whahu1989/article/details/103722557

old_value :一般设为NULL足矣,否则设定为旧的timerfd的设定值(并不常用);

03 gettime

int timerfd_gettime(int fd, struct itimerspec *curr_value);
  • 1

真正的返回值在curr_value中,返回的是调用该函数的时间离第一次到期的剩余时间

04 实例

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


#define handle_error(msg) \
       do { perror(msg); exit(EXIT_FAILURE); } while (0)

void print_elapsed_time(void);

int main(void)
{
    int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (timerfd == -1)
    {
        handle_error("timerfd_create");
    }

    struct itimerspec new_value = {};
    new_value.it_value.tv_sec  = 10; // 10s
    new_value.it_value.tv_nsec = 0;
    
    new_value.it_interval.tv_sec  = 0; // one shot
    new_value.it_interval.tv_nsec = 0;

    if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1)
    {
        handle_error("timerfd_settime");
    }

    print_elapsed_time();
    printf("timer started\n");


    uint64_t exp = 0;
    while (1)
    {
        int ret = read(timerfd, &exp, sizeof(uint64_t));
        
        if (ret == sizeof(uint64_t)) // 第一次定时到期
        {
            printf("ret: %d\n", ret);
            printf("expired times: %llu\n", exp);
            break;
        }

        // struct itimerspec curr;
        // if (timerfd_gettime(timerfd, &curr) == -1)
        // {
        //     handle_error("timerfd_gettime");
        // }
        // printf("remained time: %lds\n", curr.it_value.tv_sec);

        print_elapsed_time();
    }

    return 0;

}



void print_elapsed_time(void)
{
    static struct timeval start = {};
    static int first_call = 1;

    if (first_call == 1)
    {
        first_call = 0;
        if (gettimeofday(&start, NULL) == -1)
        {
            handle_error("gettimeofday");
        }
    }

    struct timeval current = {};
    if (gettimeofday(&current, NULL) == -1)
    {
        handle_error("gettimeofday");
    }

    static int old_secs = 0, old_usecs = 0;

    int secs  = current.tv_sec - start.tv_sec;
    int usecs = current.tv_usec - start.tv_usec;
    if (usecs < 0)
    {
        --secs;
        usecs += 1000000;
    }

    usecs = (usecs + 500)/1000; // 四舍五入

    if (secs != old_secs || usecs != old_usecs)
    {
    	printf("%d.%03d\n", secs, usecs);
    	old_secs = secs;
    	old_usecs = usecs;
    }
}


  • 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
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

2、epoll

01 基本函数

初始化,监听,阻塞。

#include <sys/epoll.h>

int epoll_create(int size); // 旧版,size取值必须大于0
int epoll_create1(int flags); // 新版,flags可以是0,或者EPOLL_CLOEXEC

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

关键结构体

struct epoll_event {
    uint32_t     events;    /* Epoll events */
    epoll_data_t data;      /* User data variable */
};

typedef union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

02 LT和ET的区别

水平触发就是只要在一个状态就会一直触发,而边缘触发是当环境发生改变的时候才会触发;

epoll默认是水平触发;

03 实例

/*
 * @Author: monifeng 1098264843@qq.com
 * @Date: 2023-05-05 16:40:18
 * @LastEditors: monifeng 1098264843@qq.com
 * @LastEditTime: 2023-05-05 16:59:38
 * @FilePath: /cpp-6.824/test/epoll_timerfd.cpp
 * @Description: 
 * 
 * Copyright (c) 2023 by monifeng, All Rights Reserved. 
 */
#include <sys/timerfd.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/epoll.h>


#define handle_error(msg) \
       do { perror(msg); exit(EXIT_FAILURE); } while (0)

void print_elapsed_time(void)
{
    static struct timeval start = {};
    static int first_call = 1;

    if (first_call == 1)
    {
        first_call = 0;
        if (gettimeofday(&start, NULL) == -1)
        {
            handle_error("gettimeofday");
        }
    }

    struct timeval current = {};
    if (gettimeofday(&current, NULL) == -1)
    {
        handle_error("gettimeofday");
    }

    static int old_secs = 0, old_usecs = 0;

    int secs  = current.tv_sec - start.tv_sec;
    int usecs = current.tv_usec - start.tv_usec;
    if (usecs < 0)
    {
        --secs;
        usecs += 1000000;
    }

    usecs = (usecs + 500)/1000; // 四舍五入

    if (secs != old_secs || usecs != old_usecs)
    {
    	printf("%d.%03d\n", secs, usecs);
    	old_secs = secs;
    	old_usecs = usecs;
    }
}

int main(void)
{
    int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (timerfd == -1)
    {
        handle_error("timerfd_create");
    }

    struct itimerspec new_value = {};
    new_value.it_value.tv_sec  = 3; // 第一次1s到期
    new_value.it_value.tv_nsec = 0;

    new_value.it_interval.tv_sec  = 5; // 后续周期是5s cycle
    new_value.it_interval.tv_nsec = 0;

    if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1)
    {
        handle_error("timerfd_settime");
    }

    print_elapsed_time();
    printf("timer started\n");

    int epollfd = epoll_create1(EPOLL_CLOEXEC);
    if (epollfd == -1) 
    {
        handle_error("epoll_create1");
    }

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = timerfd;
    
    epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd, &ev);

    const int maxEvents = 1; // 也可以设置为1
    struct epoll_event events[maxEvents]; 

    while(1)
    {
        int nfd = epoll_wait(epollfd, events, maxEvents, -1);

        if (nfd > 0)
        {
            for (int i = 0; i < maxEvents; ++i)
            {
                if (events[i].data.fd == timerfd)
                {
                    uint64_t exp = 0;
                    int ret = read(timerfd, &exp, sizeof(uint64_t));
                    if (ret != sizeof(uint64_t))
                    {
                        handle_error("read timerfd");
                    }
                    print_elapsed_time();   // 除第一次1s外,每5s打印一次
                }
            }
        }
    }

    return 0;
}
  • 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
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124

调试结果如下图:

image-20230505170244981

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/471016
推荐阅读
相关标签
  

闽ICP备14008679号