蚊章汇 - 定时器
type: channel
link: https://linuxcn.bwsl.wang/tag/%E5%AE%9A%E6%97%B6%E5%99%A8/
items: 在 Linux 中创建定时器
items: 用于调度任务的 systemd 定时器
items: 使用 systemd 定时器代替 cron 作业
items: Systemd 定时器：三种使用场景
items: 在 Linux 上使用 systemd 设置定时器
items: 如何使用 systemd 中的定时器

在 Linux 中创建定时器
type: item
date: 2023-01-09T23:02:00+08:00
description: 这是一个演示如何创建 POSIX 兼容的间隔定时器的教程。对开发人员来说，定时某些事件是一项常见任务。定时器的常见场景是看门狗、任务的循环执行，或在特定时间安排事件。在这篇文章中，我将演示如何使用 timer\_create(...) 创建一个 POSIX 兼容的间隔定时器。你可以从 GitHub 下载下面样例的源代码。准备 Qt Creator我使用 Qt Creator 作为该样例的 IDE。为了在 Qt Creator 运行和调试样例代码，请克隆 GitHub 上的仓库，打开 Qt Creator，在 “ 文件 File -> 打开文件或项目…… Open File or Project... ” 并选择 “CMakeLists.txt”：在 Qt Creator 中打开项目选择工具链之后，点击 “ 配置项目 Configure Project ”。这个项目包括三个独立的样例（我们在这篇文章中将只会用到其中的两个）。使用绿色标记出来的菜单，可以在每个样例的配置之间切换，并为每个样例激活在终端运行 “ 在终端中运行 Run in terminal ”（用黄色标记）。当前用于构建和调试的活动示例可以通过左下角的“ 调试 Debug ” 按钮进行选择（参见下面的橙色标记）。项目配置线程定时器让我们看看 simple_threading_timer.c 样例。这是最简单的一个。它展示了一个调用了超时函数 expired 的间隔定时器是如何被创建的。在每次过期时，都会创建一个新的线程，在其中调用函数 expired：#include <stdio.h> #include <stdlib.h> #include <time.h> #include <signal.h> #include <unistd.h> #include <string.h> #include <errno.h> 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()); } 这种方法的优点是在代码和简单调试方面用量小。缺点是由于到期时创建新线程而增加额外的开销，因此行为不太确定。中断信号定时器超时定时器通知的另一种可…

用于调度任务的 systemd 定时器
type: item
date: 2021-06-26T01:57:46+08:00
description: systemd 提供定时器有一段时间了，定时器替代了 cron 功能，这一特性值得看看。本文将向你介绍在系统启动后如何使用 systemd 中的定时器来运行任务，并在此后重复运行。这不是对 systemd 的全面讨论，只是对此特性的一个介绍。快速回顾：cron、anacron 与 systemdcron 可以以几分钟到几个月或更长时间的粒度调度运行一个任务。设置起来相对简单，它只需要一个配置文件。虽然配置过程有些深奥，但一般用户也可以使用。然而，如果你的系统在需要执行的时间没有运行，那么 cron 会失败。anacron 克服了“系统没有运行”的问题。它确保任务将在你的系统再次启动时执行。虽然它旨在给管理员使用，但有些系统允许普通用户访问 anacron。但是，anacron 的执行频率不能低于每天一次。cron 和 anacron 都存在执行上下文一致性的问题。必须注意任务运行时有效的环境与测试时使用的环境完全相同。必须提供相同的 shell、环境变量和路径。这意味着测试和调试有时会很困难。systemd 定时器提供了 cron 和 anacron 二者的优点，允许调度到分钟粒度。确保在系统再次运行时执行任务，即使在预期的执行时间内系统处于关闭状态。它对所有用户都可用。你可以在它将要运行的环境中测试和调试执行。但是，它的配置更加复杂，至少需要两个配置文件。如果你的 cron 和 anacron 配置可以很好地为你服务，那么可能没有理由改变。但是 systemd 至少值得研究，因为它可以简化任何当前的 cron/anacron 工作方式。配置systemd 定时器执行功能至少需要两个文件。这两个是“ 定时器单元 timer unit ”和“ 服务单元 service unit ”。（其执行的）“动作”不仅仅是简单的命令，你还需要一个“作业”文件或脚本来执行必要的功能。定时器单元文件定义调度表，而服务单元文件定义执行的任务。有关的更多详细信息请参考 man systemd.timer 中提供的 .timer 单元。服务单元的详细信息可在 man systemd.service 中找到。单元文件存放在几个位置（在手册页中有列出）。然而，对于普通用户来说，最容易找到的位置可能是 ~/.config/systemd/user。请注意，这里的 user 是字符串 user。示例此示例是一个创建用户调度作业而不是（以 root 用户身份运行的）系统调度作业的简单示例。它将消息、日期和时间打印到文件中。1、首先创建一个执行任务的 shell 脚本。在你的本地 bin 目录中创建它，例如在 ~/bin/schedule-test.sh 中。创建文件：touch ~/bin/schedule-test.sh 然后将以下内容添加到你刚刚创建的文件中：#!/bin/sh echo "This is only a test: $(date)" >> "$HOME/schedule-test-output.txt" 记住赋予你的 shell 脚本执行权限。2、创建 .service 单元调用上面的脚本。在以下位置创建目录与文件：~/.config/systemd/user/schedule-test.service：[Unit] Description=A job to test the systemd scheduler [Service] Type=simple ExecStart=/home/<user>/bin/schedule-test.sh [Install] WantedBy=default.target 请注意 <user> 应该是你的家目录地址，但是单元文件路径名中的 user 实际上是字符串 user。ExecStart 应该提供一个没有变量的绝对地址。例外情况是，对于用户单元文件，你可以用 %h 替换 $HOME。换句话说，你可以使用:ExecStart=%h/bin/schedule-test.sh 这仅用于用户单元文件，而不适用于系统服务，因为在系统环境中运行时 %h 总是返回 /root。其他特殊符号可在 man systemd.unit 的 SPECIFIERS 中找到。因为它超出了本文的范围，所以这就是我们目前需要了解的关于特殊符号的全部内容。3、创建一个 .timer 单元文件，该文件实际上调度你创建的 .service 单元文件。在 .service 单元文件相同位置创建它：~/.config/systemd/user/schedule-test.timer。请注意，文件名仅在扩展名上有所不同，例如一个是 .service，一个是 .timer。[Unit] Description=Schedule a messag…

使用 systemd 定时器代替 cron 作业
type: item
date: 2021-04-18T18:45:00+08:00
description: 定时器提供了比 cron 作业更为细粒度的事件控制。我正在致力于将我的 cron 作业迁移到 systemd 定时器上。我已经使用定时器多年了，但通常来说，我的学识只足以支撑我当前的工作。但在我研究 systemd 系列 的过程中，我发现 systemd 定时器有一些非常有意思的能力。与 cron 作业类似，systemd 定时器可以在特定的时间间隔触发事件（shell 脚本和程序），例如每天一次或在一个月中的特定某一天（或许只有在周一生效），或在从上午 8 点到下午 6 点的工作时间内每隔 15 分钟一次。定时器也可以做到 cron 作业无法做到的一些事情。举个例子，定时器可以在特定事件发生后的一段时间后触发一段脚本或者程序去执行，例如开机、启动、上个任务完成，甚至于定时器调用的上个服务单元的完成的时刻。操作系统维护的计时器当在一个新系统上安装 Fedora 或者是任意一个基于 systemd 的发行版时，作为系统维护过程的一部分，它会在 Linux 宿主机的后台中创建多个定时器。这些定时器会触发事件来执行必要的日常维护任务，比如更新系统数据库、清理临时目录、轮换日志文件，以及更多其他事件。作为示例，我会查看一些我的主要工作站上的定时器，通过执行 systemctl status *timer 命令来展示主机上的所有定时器。星号的作用与文件通配相同，所以这个命令会列出所有的 systemd 定时器单元。[root@testvm1 ~]# systemctl status *timer ● mlocate-updatedb.timer - Updates mlocate database every day Loaded: loaded (/usr/lib/systemd/system/mlocate-updatedb.timer; enabled; vendor preset: enabled) Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago Trigger: Fri 2020-06-05 00:00:00 EDT; 15h left Triggers: ● mlocate-updatedb.service Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Updates mlocate database every day. ● logrotate.timer - Daily rotation of log files Loaded: loaded (/usr/lib/systemd/system/logrotate.timer; enabled; vendor preset: enabled) Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago Trigger: Fri 2020-06-05 00:00:00 EDT; 15h left Triggers: ● logrotate.service Docs: man:logrotate(8) man:logrotate.conf(5) Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Daily rotation of log files. ● sysstat-summary.timer - Generate summary of yesterday's process accounting Loaded: loaded (/usr/lib/systemd/system/sysstat-summary.timer; enabled; vendor preset: enabled) Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago Trigger: Fri 2020-06-05 00:07:00 EDT; 15h left Triggers: ● sysstat-summary.service Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Generate summary of yesterday's process accounting. ● fstrim.timer - Discard unused blocks once a week Loaded: loaded (/usr/lib/systemd/system/fstrim.…

Systemd 定时器：三种使用场景
type: item
date: 2018-12-02T17:34:36+08:00
description: 继续 systemd 教程，这些特殊的例子可以展示给你如何更好的利用 systemd 定时器单元。在这个 systemd 系列教程中，我们已经在某种程度上讨论了 systemd 定时器单元。不过，在我们开始讨论 sockets 之前，我们先来看三个例子，这些例子展示了如何最佳化利用这些单元。简单的类 cron 行为我每周都要去收集 Debian popcon 数据，如果每次都能在同一时间收集更好，这样我就能看到某些应用程序的下载趋势。这是一个可以使用 cron 任务来完成的典型事例，但 systemd 定时器同样能做到：# 类 cron 的 popcon.timer [Unit] Description= 这里描述了下载并处理 popcon 数据的时刻 [Timer] OnCalendar= Thu *-*-* 05:32:07 Unit= popcon.service [Install] WantedBy= basic.target实际的 popcon.service 会执行一个常规的 wget 任务，并没有什么特别之处。这里的新内容是 OnCalendar= 指令。这个指令可以让你在一个特定日期的特定时刻来运行某个服务。在这个例子中，Thu 表示 “在周四运行”，*-*-* 表示“具体年份、月份和日期无关紧要”，这些可以翻译成 “不管年月日，只在每周四运行”。这样，你就设置了这个服务的运行时间。我选择在欧洲中部夏令时区的上午 5:30 左右运行，那个时候服务器不是很忙。如果你的服务器关闭了，而且刚好错过了每周的截止时间，你还可以在同一个计时器中使用像 anacron 一样的功能。# 具备类似 anacron 功能的 popcon.timer [Unit] Description= 这里描述了下载并处理 popcon 数据的时刻 [Timer] Unit=popcon.service OnCalendar=Thu *-*-* 05:32:07 Persistent=true [Install] WantedBy=basic.target当你将 Persistent= 指令设为真值时，它会告诉 systemd，如果服务器在本该它运行的时候关闭了，那么在启动后就要立刻运行服务。这意味着，如果机器在周四凌晨停机了（比如说维护），一旦它再次启动后，popcon.service 将会立刻执行。在这之后，它的运行时间将会回到例行性的每周四早上 5:32.到目前为止，就是这么简单直白。延迟执行但是，我们提升一个档次，来“改进”这个基于 systemd 的监控系统。你应该记得，当你接入摄像头的时候，系统就会开始拍照。假设你并不希望它在你安装摄像头的时候拍下你的脸。你希望将拍照服务的启动时间向后推迟一两分钟，这样你就有时间接入摄像头，然后走到画框外面。为了完成这件事，首先你要更改 Udev 规则，将它指向一个定时器：ACTION=="add", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="e207", TAG+="systemd", ENV{SYSTEMD_WANTS}="picchanged.timer", SYMLINK+="mywebcam", MODE="0666"这个定时器看起来像这样：# picchanged.timer [Unit] Description= 在摄像头接入的一分钟后，开始运行 picchanged [Timer] OnActiveSec= 1 m Unit= picchanged.path [Install] WantedBy= basic.target在你接入摄像头后，Udev 规则被触发，它会调用定时器。这个定时器启动后会等上一分钟（OnActiveSec= 1 m），然后运行 picchanged.path，它会监视主图片的变化。picchanged.path 还会负责接触 webcan.service，这个实际用来拍照的服务。在每天的特定时刻启停 Minetest 服务器在最后一个例子中，我们认为你决定用 systemd 作为唯一的依赖。讲真，不管怎么样，systemd 差不多要接管你的生活了。为什么不拥抱这个必然性呢？你有个为你的孩子设置的 Minetest 服务。不过，你还想要假装关心一下他们的教育和成长，要让他们做作业和家务活。所以你要确保 Minetest 只在每天晚上的一段时间内可用，比如五点到七点。这个跟之前的“在特定时间启动服务”不太一样。写个定时器在下午五点启动服务很简单…：# minetest.timer [Unit] Description= 在每天下午五点运行 minetest.servi…

在 Linux 上使用 systemd 设置定时器
type: item
date: 2018-11-02T06:13:50+08:00
description: 学习使用 systemd 创建启动你的游戏服务器的定时器。之前，我们看到了如何手动的、在开机与关机时、在启用某个设备时、在文件系统发生改变时 启用与禁用 systemd 服务。定时器增加了另一种启动服务的方式，基于……时间。尽管与定时任务很相似，但 systemd 定时器稍微地灵活一些。让我们看看它是怎么工作的。“定时运行”让我们展开本系列前两篇文章中你所设置的 Minetest 服务器作为如何使用定时器单元的第一个例子。如果你还没有读过那几篇文章，可以现在去看看。你将通过创建一个定时器来“改进” Minetest 服务器，使得在服务器启动 1 分钟后运行游戏服务器而不是立即运行。这样做的原因可能是，在启动之前可能会用到其他的服务，例如发邮件给其他玩家告诉他们游戏已经准备就绪，你要确保其他的服务（例如网络）在开始前完全启动并运行。最终，你的 minetest.timer 单元看起来就像这样：# minetest.timer [Unit] Description=Runs the minetest.service 1 minute after boot up [Timer] OnBootSec=1 m Unit=minetest.service [Install] WantedBy=basic.target一点也不难吧。如以往一般，开头是 [Unit] 和一段描述单元作用的信息，这儿没什么新东西。[Timer] 这一节是新出现的，但它的作用不言自明：它包含了何时启动服务，启动哪个服务的信息。在这个例子当中，OnBootSec 是告诉 systemd 在系统启动后运行服务的指令。其他的指令有：OnActiveSec=，告诉 systemd 在定时器启动后多长时间运行服务。OnStartupSec=，同样的，它告诉 systemd 在 systemd 进程启动后多长时间运行服务。OnUnitActiveSec=，告诉 systemd 在上次由定时器激活的服务启动后多长时间运行服务。OnUnitInactiveSec=，告诉 systemd 在上次由定时器激活的服务停用后多长时间运行服务。继续 minetest.timer 单元，basic.target 通常用作 后期引导服务 late boot services 的 同步点 synchronization point 。这就意味着它可以让 minetest.timer 单元运行在安装完 本地挂载点 local mount points 或交换设备，套接字、定时器、路径单元和其他基本的初始化进程之后。就像在第二篇文章中 systemd 单元里解释的那样，targets 就像 旧的运行等级 old run levels 一样，可以将你的计算机置于某个状态，或像这样告诉你的服务在达到某个状态后开始运行。在前两篇文章中你配置的 minetest.service 文件最终看起来就像这样：# minetest.service [Unit] Description= Minetest server Documentation= https://wiki.minetest.net/Main_Page [Service] Type= simple User= ExecStart= /usr/games/minetest --server ExecStartPost= /home//bin/mtsendmail.sh "Ready to rumble?" "Minetest Starting up" TimeoutStopSec= 180 ExecStop= /home//bin/mtsendmail.sh "Off to bed. Nightie night!" "Minetest Stopping in 2 minutes" ExecStop= /bin/sleep 120 ExecStop= /bin/kill -2 $MAINPID [Install] WantedBy= multi-user.target这儿没什么需要修改的。但是你需要将 mtsendmail.sh（发送你的 email 的脚本）从：#!/bin/bash # mtsendmail sleep 20 echo $1 | mutt -F /home/<username>/.muttrc -s "$2" my_minetest@mailing_list.com sleep 10改成：#!/bin/bash # mtsendmail.sh echo $1 | mutt -F /home/paul/.muttrc -s "$2" pbrown@mykolab.com你做的事是去除掉 Bash 脚本中那些蹩脚的停顿。Systemd 现…

如何使用 systemd 中的定时器
type: item
date: 2014-10-12T18:46:00+08:00
description: 我最近在写一些执行备份工作的脚本，我决定使用systemd timers而不是对我而已更熟悉的cron jobs来管理它们。在我使用时，出现了很多问题需要我去各个地方找资料，这个过程非常麻烦。因此，我想要把我目前所做的记录下来，方便自己的记忆，也方便读者不必像我这样，满世界的找资料了。在我下面提到的步骤中有其他的选择，但是这里是最简单的方法。在此之前，请查看systemd.service, systemd.timer,和systemd.target的帮助页面(man)，学习你能用它们做些什么。运行一个简单的脚本假设你有一个脚本叫：/usr/local/bin/myscript ，你想要每隔一小时就运行一次。Service 文件第一步，创建一个service文件，根据你Linux的发行版本放到相应的系统目录(在Arch中，这个目录是/etc/systemd/system/ 或 /usr/lib/systemd/system)myscript.service[Unit] Description=MyScript [Service] Type=simple ExecStart=/usr/local/bin/myscript 注意，务必将Type变量的值设置为"simple"而不是"oneshot"。使用"oneshot"使得脚本只在第一次运行，之后系统会认为你不想再次运行它，从而关掉我们接下去创建的定时器(Timer)。Timer 文件第二步，创建一个timer文件，把它放在第一步中service文件放置的目录。myscript.timer[Unit] Description=Runs myscript every hour [Timer] # 首次运行要在启动后10分钟后 OnBootSec=10min # 每次运行间隔时间 OnUnitActiveSec=1h Unit=myscript.service [Install] WantedBy=multi-user.target 授权 / 运行授权并运行的是timer文件，而不是service文件。# 以 root 身份启动定时器 systemctl start myscript.timer # 在系统引导起来后就启用该定时器 systemctl enable myscript.timer 在同一个Timer上运行多个脚本现在我们假设你在相同时间想要运行多个脚本。这种情况，你需要在上面的文件中做适当的修改。Service 文件像我之前说过的那样创建你的service文件来运行你的脚本，但是在每个service 文件最后都要包含下面的内容:[Install] WantedBy=mytimer.target 如果在你的service 文件中有一些依赖顺序，确保你使用Description字段中的值具体指定After=something.service和Before=whatever.service中的参数。另外的一种选择是(或许更加简单)，创建一个包装脚本来使用正确的顺序来运行命令，并在你的service文件中使用这个脚本。Timer 文件你只需要一个timer文件，创建mytimer.timer，像我在上面指出的。target 文件你可以创建一个以上所有的脚本依赖的target文件。mytimer.target[Unit] Description=Mytimer # Lots more stuff could go here, but it's situational. # Look at systemd.unit man page. 授权 / 启动你需要将所有的service文件和timer文件授权。systemctl enable script1.service systemctl enable script2.service ... systemctl enable mytimer.timer systemctl start mytimer.service Good luck.via: http://jason.the-graham.com/2013/03/06/how-to-use-systemd-timers/作者：Jason Graham 译者：johnhoow 校对：wxy本文由 LCTT 原创翻译，Linux中国 荣誉推出