阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

演示如何创建 POSIX 兼容的间隔定时器

83次阅读
没有评论

共计 3947 个字符,预计需要花费 10 分钟才能阅读完成。

导读 这是一个演示如何创建 POSIX 兼容的间隔定时器的教程。

对开发人员来说,定时某些事件是一项常见任务。定时器的常见场景是看门狗、任务的循环执行,或在特定时间安排事件。在这篇文章中,我将演示如何使用 timer_create(…) 创建一个 POSIX 兼容的间隔定时器。

准备 Qt Creator

我使用 Qt Creator 作为该样例的 IDE。为了在 Qt Creator 运行和调试样例代码,请克隆 GitHub 上的仓库,打开 Qt Creator,在“文件 File -> 打开文件或项目……Open File or Project…”并选择“CMakeLists.txt”:

演示如何创建 POSIX 兼容的间隔定时器
在 Qt Creator 中打开项目

选择工具链之后,点击“配置项目 Configure Project”。这个项目包括三个独立的样例(我们在这篇文章中将只会用到其中的两个)。使用绿色标记出来的菜单,可以在每个样例的配置之间切换,并为每个样例激活在终端运行“在终端中运行 Run in terminal”(用黄色标记)。当前用于构建和调试的活动示例可以通过左下角的“调试 Debug”按钮进行选择(参见下面的橙色标记)。

演示如何创建 POSIX 兼容的间隔定时器 项目配置

线程定时器

让我们看看 simple_threading_timer.c 样例。这是最简单的一个。它展示了一个调用了超时函数 expired 的间隔定时器是如何被创建的。在每次过期时,都会创建一个新的线程,在其中调用函数 expired:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
void expired(union sigval timer_data);
pid_t gettid(void);
struct t_eventData{int myData;};
int main()
{
    int res = 0;
    timer_t timerId = 0;
    struct t_eventData eventData = {.myData = 0};
/* sigevent 指定了过期时要执行的操作 */
    struct sigevent sev = {0};
/* 指定启动延时时间和间隔时间
* it_value 和 it_interval 不能为零 */
    struct itimerspec its = {   .it_value.tv_sec  = 1,
                                .it_value.tv_nsec = 0,
                                .it_interval.tv_sec  = 1,
                                .it_interval.tv_nsec = 0
                            };
    printf("Simple Threading Timer - thread-id: %d\n", gettid());
    sev.sigev_notify = SIGEV_THREAD;
    sev.sigev_notify_function = &expired;
    sev.sigev_value.sival_ptr = &eventData;
/* 创建定时器 */
    res = timer_create(CLOCK_REALTIME, &sev, &timerId);
    if (res != 0){fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
        exit(-1);
    }
/* 启动定时器 */
    res = timer_settime(timerId, 0, &its, NULL);
    if (res != 0){fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
        exit(-1);
    }
    printf("Press ETNER Key to Exit\n");
    while(getchar()!='\n'){}
    return 0;
}
void expired(union sigval timer_data){
    struct t_eventData *data = timer_data.sival_ptr;
    printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
}

这种方法的优点是在代码和简单调试方面用量小。缺点是由于到期时创建新线程而增加额外的开销,因此行为不太确定。

中断信号定时器

超时定时器通知的另一种可能性是基于 内核信号。内核不是在每次定时器过期时创建一个新线程,而是向进程发送一个信号,进程被中断,并调用相应的信号处理程序。

由于接收信号时的默认操作是终止进程(参考 signal 手册页),我们必须要提前设置好 Qt Creator,以便进行正确的调试。

当被调试对象接收到一个信号时,Qt Creator 的默认行为是:

  1. 中断执行并切换到调试器上下文。
  2. 显示一个弹出窗口,通知用户接收到信号。

这两种操作都不需要,因为信号的接收是我们应用程序的一部分。

Qt Creator 在后台使用 GDB。为了防止 GDB 在进程接收到信号时停止执行,进入“工具 Tools -> 选项 Options”菜单,选择“调试器 Debugger”,并导航到“本地变量和表达式 Locals & Expressions”。添加下面的表达式到“定制调试助手 Debugging Helper Customization”:

handle SIG34 nostop pass

演示如何创建 POSIX 兼容的间隔定时器
Sig 34 时不停止

你可以在 GDB 文档 中找到更多关于 GDB 信号处理的信息。

接下来,当我们在信号处理程序中停止时,我们要抑制每次接收到信号时通知我们的弹出窗口:

演示如何创建 POSIX 兼容的间隔定时器
Signal 34 弹出窗口

为此,导航到“GDB”标签并取消勾选标记的复选框:

演示如何创建 POSIX 兼容的间隔定时器
定时器信号窗口

现在你可以正确的调试 signal_interrupt_timer。真正的信号定时器的实施会更复杂一些:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define UNUSED(x) (void)(x)
static void handler(int sig, siginfo_t *si, void *uc);
pid_t gettid(void);
struct t_eventData{int myData;};
int main()
{
    int res = 0;
    timer_t timerId = 0;
    struct sigevent sev = {0};
    struct t_eventData eventData = {.myData = 0};
/* 指定收到信号时的操作 */
    struct sigaction sa = {0};
/* 指定启动延时的时间和间隔时间 */
    struct itimerspec its = {   .it_value.tv_sec  = 1,
                                .it_value.tv_nsec = 0,
                                .it_interval.tv_sec  = 1,
                                .it_interval.tv_nsec = 0
                            };
    printf("Signal Interrupt Timer - thread-id: %d\n", gettid());
    sev.sigev_notify = SIGEV_SIGNAL; // Linux-specific
    sev.sigev_signo = SIGRTMIN;
    sev.sigev_value.sival_ptr = &eventData;
/* 创建定时器 */
    res = timer_create(CLOCK_REALTIME, &sev, &timerId);
    if (res != 0){fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
        exit(-1);
    }
/* 指定信号和处理程序 */
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handler;
/* 初始化信号 */
    sigemptyset(&sa.sa_mask);
    printf("Establishing handler for signal %d\n", SIGRTMIN);
/* 注册信号处理程序 */
    if (sigaction(SIGRTMIN, &sa, NULL) == -1){fprintf(stderr, "Error sigaction: %s\n", strerror(errno));
        exit(-1);
    }
/* 启动定时器 */
    res = timer_settime(timerId, 0, &its, NULL);
    if (res != 0){fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
        exit(-1);
    }
    printf("Press ENTER to Exit\n");
    while(getchar()!='\n'){}
    return 0;
}
static void
handler(int sig, siginfo_t *si, void *uc)
{UNUSED(sig);
    UNUSED(uc);
    struct t_eventData *data = (struct t_eventData *) si->_sifields._rt.si_sigval.sival_ptr;
    printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
}

与线程定时器相比,我们必须初始化信号并注册一个信号处理程序。这种方法性能更好,因为它不会导致创建额外的线程。因此,信号处理程序的执行也更加确定。缺点显然是正确调试需要额外的配置工作。

总结

本文中描述的两种方法都是接近内核的定时器的实现。不过,即使 timer_create(…) 函数是 POSIX 规范的一部分,由于数据结构的细微差别,也不可能在 FreeBSD 系统上编译样例代码。除了这个缺点之外,这种实现还为通用计时应用程序提供了细粒度控制。

阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配

腾讯云新客低至 82 元 / 年,老客户 99 元 / 年

代金券:在阿里云专用满减优惠券

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-07-24发表,共计3947字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中