当前位置: 首页 > article >正文

Linux高级--3.2.4.1 Linux timer的系统调用方案

timerfd_create 是 Linux 系统中用于创建定时器的系统调用,它为每个定时器提供一个文件描述符,通过 epoll 或者 select 等机制来监听定时器的超时事件。该系统调用为定时器提供了方便的管理方式,特别适用于需要精确控制定时事件的场景。

1. timerfd_create 函数

timerfd_create 用于创建一个定时器文件描述符,该文件描述符可用于等待定时器超时。定时器的超时时间和行为通过 timerfd_settime 来配置。

函数原型:
#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);
参数说明:
  • clockid:

    • 该参数指定所使用的时钟源,可以是以下几种类型之一:
      • CLOCK_REALTIME:系统的实时时钟(墙钟时间)。
      • CLOCK_MONOTONIC:单调时钟(时间不断增加,不会跳跃)。
      • CLOCK_PROCESS_CPUTIME_ID:进程的 CPU 时间。
      • CLOCK_THREAD_CPUTIME_ID:线程的 CPU 时间。

    通常使用 CLOCK_REALTIMECLOCK_MONOTONIC

  • flags:

    • 用于指定定时器的一些特性,通常为 0,表示使用默认行为。可以使用以下标志:
      • TFD_CLOEXEC:设置文件描述符在执行 exec() 时自动关闭。
      • TFD_NONBLOCK:设置非阻塞模式。如果设置该标志,则 read() 时即使定时器未到期也不会阻塞。
返回值:
  • 成功时返回一个定时器文件描述符(非负整数)。
  • 失败时返回 -1,并设置 errno 以表示错误原因。

2. timerfd_settime 函数

timerfd_settime 用于设置或修改定时器的超时值。通过该函数,可以启动、重新设置或取消定时器。

函数原型:
#include <sys/timerfd.h>

int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
参数说明:
  • fd:

    • 定时器文件描述符,通常是通过 timerfd_create 创建的。
  • flags:

    • 该参数用于指定设置定时器的方式,常用的标志是:
      • TIMER_ABSTIME:如果设置该标志,定时器的绝对时间是 new_value 中的时间,而不是相对时间(即从当前时间开始)。否则,new_value 表示相对时间。
  • new_value:

    • 定时器的新超时时间,类型为 struct itimerspec。该结构包含两个字段:
      • it_value:定时器的初始超时值,即定时器首次超时的时间。
      • it_interval:定时器的周期。如果为零,定时器将只触发一次;如果不为零,则定时器会每隔 it_interval 触发一次。

    struct itimerspec 的定义如下:

    struct itimerspec {
        struct timespec it_value;      // 定时器的初始超时值
        struct timespec it_interval;   // 定时器的周期
    };
    

    struct timespec 定义如下:

    struct timespec {
        time_t tv_sec;      // 秒
        long tv_nsec;       // 纳秒
    };
    
  • old_value:

    • 可选参数,用于保存定时器设置之前的值。如果不关心旧值,可以传入 NULL
返回值:
  • 成功时返回 0
  • 失败时返回 -1,并设置 errno

3. read 函数(读取定时器超时事件)

当定时器超时时,文件描述符变为可读,调用 read() 函数来读取超时事件并执行相应的操作。

函数原型:
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
参数说明:
  • fd

    • 定时器文件描述符。
  • buf

    • 用于存储读取数据的缓冲区。对于 timerfd,读取时会返回一个 8 字节的整数,表示定时器被触发的次数。
  • count

    • 要读取的字节数,通常为 8。
返回值:
  • 返回实际读取的字节数。如果定时器没有超时,则读取操作会阻塞。
  • 返回 -1 表示失败,并设置 errno

4. 代码示例

下面是一个简单的示例,演示如何使用 timerfd_createtimerfd_settimeread 创建一个定时器并等待其超时。

代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>

// 宏控制使用绝对时间还是相对时间
// #define USE_ABSTIME   // 如果需要使用绝对时间,取消注释此行

int main() {
    // 创建定时器文件描述符
    int tfd = timerfd_create(CLOCK_REALTIME, 0);
    if (tfd == -1) {
        perror("timerfd_create");
        exit(EXIT_FAILURE);
    }

    // 设置定时器超时时间为 5 秒后触发
    struct itimerspec new_value;
    new_value.it_value.tv_sec = 5;   // 初始超时时间 5 秒
    new_value.it_value.tv_nsec = 0;
    new_value.it_interval.tv_sec = 0;  // 不重复
    new_value.it_interval.tv_nsec = 0;

    // 使用宏控制设置 timerfd 的时间类型
#ifdef USE_ABSTIME
    int flags = TIMER_ABSTIME;  // 使用绝对时间
#else
    int flags = 0;  // 使用相对时间
#endif

    if (timerfd_settime(tfd, flags, &new_value, NULL) == -1) {
        perror("timerfd_settime");
        exit(EXIT_FAILURE);
    }

    // 创建 epoll 实例
    int epfd = epoll_create1(0);
    if (epfd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 将定时器文件描述符添加到 epoll 中
    struct epoll_event ev;
    ev.events = EPOLLIN;   // 定时器可读(超时)
    ev.data.fd = tfd;

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, tfd, &ev) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    // 等待定时器事件触发
    printf("Waiting for timer to expire...\n");
    while (1) {
        struct epoll_event events[1];
        int n = epoll_wait(epfd, events, 1, -1);
        if (n == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        if (events[0].data.fd == tfd) {
            uint64_t expirations;
            ssize_t s = read(tfd, &expirations, sizeof(expirations));
            if (s != sizeof(expirations)) {
                perror("read");
                exit(EXIT_FAILURE);
            }
            printf("Timer expired %llu times\n", expirations);
            break;  // 退出循环
        }
    }

    close(tfd);
    close(epfd);
    return 0;
}

代码解释

  1. 获取当前时间:

    • 通过 clock_gettime(CLOCK_REALTIME, &current_time) 获取当前的系统时间,返回值是 struct timespec 类型,包含秒数 (tv_sec) 和纳秒数 (tv_nsec)。
  2. 计算绝对时间:

    • 绝对时间 模式下,我们要将当前时间与定时器的延迟时间相加。假设定时器设置为 5 秒后超时,new_value.it_value.tv_sec 就是当前时间的秒数加上 5 秒。
    • new_value.it_value.tv_nsec 保持与当前的纳秒数一致。
  3. 设置定时器:

    • flags 根据宏 USE_ABSTIME 的定义来决定是否使用 TIMER_ABSTIME 标志。这样,如果启用绝对时间模式,定时器的超时将是一个绝对时间戳。
    • 如果没有启用 USE_ABSTIME,则使用相对时间,new_value.it_value 就是定时器的延迟时间(5 秒)。
  4. 等待定时器超时并触发:

    • 使用 epoll_wait 等待定时器事件触发,并通过 read 函数获取定时器到期的次数。

总结

  • 对于绝对时间模式,必须基于当前时间计算出定时器的绝对时间戳。
  • 如果使用相对时间,直接设置延迟时间即可。
  • 通过宏 USE_ABSTIME 来方便地选择使用绝对时间还是相对时间。

    https://github.com/0voice


http://www.kler.cn/a/460990.html

相关文章:

  • kubelet状态错误报错
  • Servlet解析
  • Vue2: table加载树形数据的踩坑记录
  • 【AI】最近有款毛茸茸AI生成图片圈粉了,博主也尝试使用风格转换生成可爱的小兔子,一起来探索下是如何实现的
  • 深入理解连接池:从数据库到HTTP的优化之道
  • 共阳极LED的控制与短路问题解析
  • Levenshtein 距离的原理与应用
  • LeetCode - 初级算法 数组(存在重复元素)
  • 应急指挥系统总体架构方案
  • spring入门程序
  • Java List 集合详解:基础用法、常见实现类与高频面试题解析
  • p44 13-表单使用场景以及分类
  • BitNet a4.8:通过4位激活实现1位大语言模型的高效内存推理
  • 嵌入式开发之使用 FileZilla 在 Windows 和 Ubuntu 之间传文件
  • 【循环代码练习阅读一】
  • 设计模式 创建型 单例模式(Singleton Pattern)与 常见技术框架应用 解析
  • nvm node.js 版本管理工具
  • Docker新手:在tencent云上实现Python服务打包到容器
  • 基于SSM(Spring + Spring MVC + MyBatis)框架搭建一个病人跟踪信息管理系统
  • 删除了overlay2 目录下的文件 存储空间(df -h)没有释放
  • (桌面运维学习)通过备份C盘,进行Windows系统的软件初始化
  • STM32G070CB的USART1_RX引脚
  • 排序算法原理及其实现
  • 如何在 Ubuntu 22.04 上安装 Webmin 教程
  • HTML——26.像素单位
  • MF248:复制工作表形状到Word并调整多形状位置