Redian新闻
>
在 Linux 中创建定时器 | Linux 中国

在 Linux 中创建定时器 | Linux 中国

科技
 
导读:这是一个演示如何创建 POSIX 兼容的间隔定时器的教程。                           
本文字数:7368,阅读时长大约:8分钟

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

你可以从 GitHub🔗 github.com 下载下面样例的源代码。

准备 Qt Creator

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

在 Qt Creator 中打开项目

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

项目配置

线程定时器

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

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. #include <signal.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. #include <errno.h>
  8. void expired(union sigval timer_data);
  9. pid_t gettid(void);
  10. struct t_eventData{
  11.     int myData;
  12. };
  13. int main()
  14. {
  15.     int res = 0;
  16.     timer_t timerId = 0;
  17.     struct t_eventData eventData = { .myData = 0 };
  18. /* sigevent 指定了过期时要执行的操作 */
  19.     struct sigevent sev = { 0 };
  20. /* 指定启动延时时间和间隔时间
  21. * it_value和it_interval 不能为零 */
  22.     struct itimerspec its = {   .it_value.tv_sec  = 1,
  23.                                 .it_value.tv_nsec = 0,
  24.                                 .it_interval.tv_sec  = 1,
  25.                                 .it_interval.tv_nsec = 0
  26.                             };
  27.     printf("Simple Threading Timer - thread-id: %d\n", gettid());
  28.     sev.sigev_notify = SIGEV_THREAD;
  29.     sev.sigev_notify_function = &amp;expired;
  30.     sev.sigev_value.sival_ptr = &amp;eventData;
  31. /* 创建定时器 */
  32.     res = timer_create(CLOCK_REALTIME, &amp;sev, &amp;timerId);
  33.     if (res != 0){
  34.         fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
  35.         exit(-1);
  36.     }
  37. /* 启动定时器 */
  38.     res = timer_settime(timerId, 0, &amp;its, NULL);
  39.     if (res != 0){
  40.         fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
  41.         exit(-1);
  42.     }
  43.     printf("Press ETNER Key to Exit\n");
  44.     while(getchar()!='\n'){}
  45.     return 0;
  46. }
  47. void expired(union sigval timer_data){
  48.     struct t_eventData *data = timer_data.sival_ptr;
  49.     printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
  50. }

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

中断信号定时器

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

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

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

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

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

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

  1. handle SIG34 nostop pass

Sig 34 时不停止

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

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

Signal 34 弹出窗口

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

定时器信号窗口

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

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <signal.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <time.h>
  7. #include <unistd.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #define UNUSED(x) (void)(x)
  11. static void handler(int sig, siginfo_t *si, void *uc);
  12. pid_t gettid(void);
  13. struct t_eventData{
  14.     int myData;
  15. };
  16. int main()
  17. {
  18.     int res = 0;
  19.     timer_t timerId = 0;
  20.     struct sigevent sev = { 0 };
  21.     struct t_eventData eventData = { .myData = 0 };
  22. /* 指定收到信号时的操作 */
  23.     struct sigaction sa = { 0 };
  24. /* 指定启动延时的时间和间隔时间 */
  25.     struct itimerspec its = {   .it_value.tv_sec  = 1,
  26.                                 .it_value.tv_nsec = 0,
  27.                                 .it_interval.tv_sec  = 1,
  28.                                 .it_interval.tv_nsec = 0
  29.                             };
  30.     printf("Signal Interrupt Timer - thread-id: %d\n", gettid());
  31.     sev.sigev_notify = SIGEV_SIGNAL; // Linux-specific
  32.     sev.sigev_signo = SIGRTMIN;
  33.     sev.sigev_value.sival_ptr = &amp;eventData;
  34. /* 创建定时器 */
  35.     res = timer_create(CLOCK_REALTIME, &amp;sev, &amp;timerId);
  36.     if ( res != 0){
  37.         fprintf(stderr, "Error timer_create: %s\n", strerror(errno));
  38.         exit(-1);
  39.     }
  40. /* 指定信号和处理程序 */
  41.     sa.sa_flags = SA_SIGINFO;
  42.     sa.sa_sigaction = handler;
  43. /* 初始化信号 */
  44.     sigemptyset(&amp;sa.sa_mask);
  45.     printf("Establishing handler for signal %d\n", SIGRTMIN);
  46. /* 注册信号处理程序 */
  47.     if (sigaction(SIGRTMIN, &amp;sa, NULL) == -1){
  48.         fprintf(stderr, "Error sigaction: %s\n", strerror(errno));
  49.         exit(-1);
  50.     }
  51. /* 启动定时器 */
  52.     res = timer_settime(timerId, 0, &amp;its, NULL);
  53.     if ( res != 0){
  54.         fprintf(stderr, "Error timer_settime: %s\n", strerror(errno));
  55.         exit(-1);
  56.     }
  57.     printf("Press ENTER to Exit\n");
  58.     while(getchar()!='\n'){}
  59.     return 0;
  60. }
  61. static void
  62. handler(int sig, siginfo_t *si, void *uc)
  63. {
  64.     UNUSED(sig);
  65.     UNUSED(uc);
  66.     struct t_eventData *data = (struct t_eventData *) si->_sifields._rt.si_sigval.sival_ptr;
  67.     printf("Timer fired %d - thread-id: %d\n", ++data->myData, gettid());
  68. }

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

总结

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


via: https://opensource.com/article/21/10/linux-timers

作者:Stephan Avenwedde 选题:lujun9972 译者:FigaroCao 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

LCTT 译者 :Figaro Cao
🌟🌟
翻译: 2.0 篇
|
贡献: 431 天
2021-11-02
2023-01-07
https://linux.cn/lctt/FigaroCao
欢迎遵照 CC-BY-SA 协议规定转载,
如需转载,请在文章下留言 “转载:公众号名称”,
我们将为您添加白名单,授权“转载文章时可以修改”。

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
Bodhi Linux 7.0.0 开始测试新的功能和软件包 | Linux 中国Linux 内核 6.1 发布,包含初始 Rust 支持 | Linux 中国最佳 Linux 远程桌面客户端 | Linux 中国如何在 Linux 系统中访问 UEFI 设置 | Linux 中国东京在召唤16-李鸿章小道及其它你现在可以在 Arch Linux 上安装 Unity 7.6 桌面了 | Linux 中国如何在 Linux 中确定运行的是那种初始化系统 | Linux 中国终端基础:在 Linux 终端中创建目录 | Linux 中国Fedora Media Writer:顶级的立付 USB 创建器 | Linux 中国如何在 Arch Linux 中安装 elementary OS 的 Pantheon 桌面 | Linux 中国如何在 Silverblue 上变基到 Fedora Linux 37 | Linux 中国有人拿着计时器看了十几种动物尿尿!Fedora Linux 37 发布 | Linux 中国Gnoppix Linux 22.12 发布 | Linux 中国如何在 Arch Linux 中安装 OpenOffice(新手指南) | Linux 中国使用这个多功能的 Linux 命令转换音频文件 | Linux 中国Linux 中的 su 和 sudo 命令有什么区别? | Linux 中国为什么你要在 Linux 上尝试 Nemo 文件管理器? | Linux 中国学中文视听99篇《金碧辉煌的凡尔赛镜子厅》使用 PCManFM 文件管理器让你的 Linux PC 轻装上阵 | Linux 中国如何在 Linux 中找到一个进程 ID 并杀死它 | Linux 中国如何在 Arch Linux 中安装 Cinnamon 桌面 | Linux 中国在 Linux 上试试这个 Java 文件管理器 | Linux 中国张爱玲是润的祖先,她是逃亡者的榜样如何提高 Ubuntu 和其他 Linux 系统中的扬声器音量 | Linux 中国癌症的分期和分级有何不同?13 个从头开始构建的独立 Linux 发行版 | Linux 中国Linux Mint 的更新管理器现在支持 Flatpak | Linux 中国5 个 htop 替代:增强你的 Linux 系统监控体验 | Linux 中国Kali Linux 发布今年最后一个版本 | Linux 中国通过 SSH 在远程 Linux 系统上执行命令 | Linux 中国在 Linux 中使用 “Converter” GUI 工具转换和操作图像 | Linux 中国天赋“易昺(bǐng)”,创造历史!ChatGPT 进军 B 端?消息称微软将允许企业创建定制版 ChatGPT渔歌子:冬月台风飞雨倾
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。