内核态定时器 timer
需要链接 rt 库
Linux 定时器:精准的时间管理
在各种应用场景中,精确地在特定时间执行某些任务至关重要。无论是周期性地收集系统状态、在预定时间发送告警,还是实现复杂的任务调度,都需要可靠的定时器机制。Linux 内核提供了多种方式来处理时间,其中 POSIX 定时器提供了一种强大且灵活的解决方案。
POSIX 定时器简介
POSIX 定时器是符合 POSIX.1b 标准的实时扩展的一部分,它允许应用程序创建和管理多个高精度定时器。与传统的 sleep()
或 alarm()
等函数相比,POSIX 定时器提供了更精细的控制和更多的功能。
核心概念包括:
- 定时器 ID (
timer_t
): 每个成功创建的定时器都会返回一个唯一的标识符,类型为timer_t
。你可以使用这个 ID 来操作特定的定时器。 - 定时器事件: 当定时器到期时,会产生一个信号或者触发一个线程函数。
- 定时器属性: 可以设置定时器的触发方式(单次或周期性)、初始延迟以及时间间隔。
关键的系统调用
接下来,我们重点介绍用于创建、设置和管理 POSIX 定时器的关键系统调用。
1. timer_create()
: 创建定时器
timer_create()
系统调用用于创建一个新的 POSIX 定时器。
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
timer_t timerid;
struct sigevent sev;
// 设置定时器到期时的通知方式为信号
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGRTMIN; // 使用一个实时信号
sev.sigev_value.sival_ptr = &timerid; // 传递定时器 ID
if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
perror("timer_create");
exit(EXIT_FAILURE);
}
printf("定时器创建成功,ID: %ld\n", (long)timerid);
// ... 后续设置和使用定时器的代码 ...
return 0;
}
代码解释:
- 我们包含了必要的头文件
<signal.h>
、<time.h>
、<stdio.h>
和<stdlib.h>
。 - 声明了一个
timer_t
类型的变量timerid
用于存储定时器 ID,以及一个struct sigevent
类型的变量sev
用于设置定时器到期时的行为。 sev.sigev_notify = SIGEV_SIGNAL;
指定当定时器到期时,通过发送信号的方式通知进程。sev.sigev_signo = SIGRTMIN;
设置发送的信号为实时信号SIGRTMIN
。实时信号比标准信号具有更高的优先级和可靠性。sev.sigev_value.sival_ptr = &timerid;
允许在信号处理函数中获取定时器 ID。timer_create(CLOCK_REALTIME, &sev, &timerid)
调用创建定时器。CLOCK_REALTIME
指定了使用系统实时时钟。其他时钟类型包括CLOCK_MONOTONIC
(单调递增时钟,不受系统时间调整影响)等。&sev
是指向sigevent
结构的指针,定义了定时器到期时的行为。&timerid
是一个指向timer_t
变量的指针,用于接收新创建的定时器 ID。
- 如果
timer_create()
返回 -1,则表示创建失败,我们使用perror()
输出错误信息并退出。 - 成功创建后,我们打印了定时器的 ID。
编译和运行:
gcc timer_create_example.c -o timer_create_example -lrt
./timer_create_example
可能的运行结果:
定时器创建成功,ID: 0
(实际的定时器 ID 可能不同)
2. timer_settime()
: 设置定时器
timer_settime()
系统调用用于启动或停止定时器,并设置其初始延迟和间隔时间。
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#define TIMER_SIG SIGRTMIN
void timer_handler(int sig, siginfo_t *si, void *uc) {
timer_t *tidp;
tidp = (timer_t *)si->si_value.sival_ptr;
printf("[%ld] 定时器到期,信号:%d\n", (long)*tidp, sig);
}
int main() {
timer_t timerid;
struct sigevent sev;
struct itimerspec its;
struct sigaction sa;
// 设置信号处理函数
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(TIMER_SIG, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
// 创建定时器
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = TIMER_SIG;
sev.sigev_value.sival_ptr = &timerid;
if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
perror("timer_create");
exit(EXIT_FAILURE);
}
printf("定时器创建成功,ID: %ld\n", (long)timerid);
// 设置定时器的初始延迟和间隔
its.it_value.tv_sec = 2; // 初始延迟 2 秒
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 1; // 间隔时间 1 秒(周期性定时器)
its.it_interval.tv_nsec = 0;
if (timer_settime(timerid, 0, &its, NULL) == -1) {
perror("timer_settime");
exit(EXIT_FAILURE);
}
printf("定时器已启动,每隔 1 秒触发一次,初始延迟 2 秒...\n");
// 让程序运行一段时间
sleep(10);
// 停止定时器
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 0;
if (timer_settime(timerid, 0, &its, NULL) == -1) {
perror("timer_settime (停止)");
exit(EXIT_FAILURE);
}
printf("定时器已停止。\n");
// 删除定时器
if (timer_delete(timerid) == -1) {
perror("timer_delete");
exit(EXIT_FAILURE);
}
printf("定时器已删除。\n");
return 0;
}
代码解释:
- 我们定义了一个信号处理函数
timer_handler
,当定时器到期并发送TIMER_SIG
信号时,该函数会被调用。 - 在
main
函数中,我们首先使用sigaction()
设置了信号TIMER_SIG
的处理方式,将其与timer_handler
函数关联起来。SA_SIGINFO
标志表示信号处理函数接收附加信息(通过siginfo_t
结构)。 - 创建定时器的步骤与之前的示例相同。
- 我们声明了一个
struct itimerspec
类型的变量its
,用于设置定时器的超时值。its.it_value
指定了定时器的初始延迟。its.it_interval
指定了定时器的间隔时间。如果it_interval
的两个字段都设置为 0,则定时器只触发一次。如果都大于 0,则定时器会周期性地触发。
timer_settime(timerid, 0, &its, NULL)
调用启动或修改定时器。timerid
是要操作的定时器 ID。- 第二个参数
flags
可以是 0 或TIMER_ABSTIME
。如果为 0,则it_value
是相对时间;如果为TIMER_ABSTIME
,则it_value
是绝对时间(基于定时器的时钟)。 &its
是指向itimerspec
结构的指针,包含了新的定时器设置。NULL
表示我们不关心旧的定时器设置。如果需要获取之前的设置,可以传递一个指向itimerspec
结构的指针。
- 程序使用
sleep(10)
暂停 10 秒,以便观察定时器的触发。 - 之后,我们将
its.it_value
和its.it_interval
都设置为 0,从而停止了定时器。 - 最后,我们使用
timer_delete()
系统调用删除了不再需要的定时器。
编译和运行:
gcc timer_settime_example.c -o timer_settime_example -lrt
./timer_settime_example
可能的运行结果:
定时器创建成功,ID: 0
定时器已启动,每隔 1 秒触发一次,初始延迟 2 秒...
[0] 定时器到期,信号:34
[0] 定时器到期,信号:34
[0] 定时器到期,信号:34
[0] 定时器到期,信号:34
[0] 定时器到期,信号:34
[0] 定时器到期,信号:34
[0] 定时器到期,信号:34
[0] 定时器到期,信号:34
定时器已停止。
定时器已删除。
(实际的定时器 ID 和信号编号可能不同,34 对应 SIGRTMIN
)
3. timer_gettime()
: 获取定时器状态
timer_gettime()
系统调用用于获取当前定时器的剩余时间和间隔。
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
int main() {
timer_t timerid;
struct sigevent sev;
struct itimerspec its;
struct itimerspec current_its;
// 创建一个单次触发的定时器
sev.sigev_notify = SIGEV_NONE; // 不发送信号
if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
perror("timer_create");
exit(EXIT_FAILURE);
}
printf("定时器创建成功,ID: %ld\n", (long)timerid);
// 设置定时器在 3 秒后触发一次
its.it_value.tv_sec = 3;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 0;
if (timer_settime(timerid, 0, &its, NULL) == -1) {
perror("timer_settime");
exit(EXIT_FAILURE);
}
printf("定时器已设置为 3 秒后触发。\n");
sleep(1);
// 获取定时器的当前状态
if (timer_gettime(timerid, ¤t_its) == -1) {
perror("timer_gettime");
exit(EXIT_FAILURE);
}
printf("当前剩余时间: %ld 秒 %ld 纳秒\n",
(long)current_its.it_value.tv_sec, (long)current_its.it_value.tv_nsec);
printf("间隔时间: %ld 秒 %ld 纳秒\n",
(long)current_its.it_interval.tv_sec, (long)current_its.it_interval.tv_nsec);
// 等待定时器触发
sleep(3);
// 再次获取定时器状态(应该已经触发)
if (timer_gettime(timerid, ¤t_its) == -1) {
perror("timer_gettime");
exit(EXIT_FAILURE);
}
printf("触发后剩余时间: %ld 秒 %ld 纳秒\n",
(long)current_its.it_value.tv_sec, (long)current_its.it_value.tv_nsec);
printf("触发后间隔时间: %ld 秒 %ld 纳秒\n",
(long)current_its.it_interval.tv_sec, (long)current_its.it_interval.tv_nsec);
if (timer_delete(timerid) == -1) {
perror("timer_delete");
exit(EXIT_FAILURE);
}
printf("定时器已删除。\n");
return 0;
}
代码解释:
- 我们创建了一个单次触发的定时器,设置
sev.sigev_notify = SIGEV_NONE;
表示不发送信号,仅仅使用定时器本身的功能。 - 使用
timer_settime()
设置定时器在 3 秒后触发一次。 - 在等待 1 秒后,我们调用
timer_gettime(timerid, ¤t_its)
来获取当前定时器的状态,并将结果存储在current_its
结构中。 - 我们打印了当前的剩余时间和间隔时间。对于单次触发的定时器,触发后剩余时间通常会接近 0。
- 程序等待 3 秒,让定时器触发。
- 再次调用
timer_gettime()
,可以看到触发后剩余时间通常为 0。
编译和运行:
gcc timer_gettime_example.c -o timer_gettime_example -lrt
./timer_gettime_example
可能的运行结果:
定时器创建成功,ID: 0
定时器已设置为 3 秒后触发。
当前剩余时间: 2 秒 999999999 纳秒
间隔时间: 0 秒 0 纳秒
触发后剩余时间: 0 秒 0 纳秒
触发后间隔时间: 0 秒 0 纳秒
定时器已删除。
(实际的纳秒值可能略有不同)
其他重要的系统调用
除了上述三个核心系统调用外,还有一个用于删除定时器的函数:
timer_delete(timer_t timerid)
: 删除由timer_create()
创建的定时器。一旦删除,与该定时器 ID 关联的资源将被释放。