80T资源合集下载
链接:https://pan.quark.cn/s/5643428d4f9f
在 Linux 系统编程中,我们之前学习了
alarm 函数,它能提供秒级的定时功能。但在很多对时间精度要求较高的场景下(比如多媒体处理、游戏服务器帧率控制),秒级精度实在是“太粗糙”了。而且
alarm 是一次性的,想要循环触发必须重复调用。
今天我们要介绍一位更强大的“时间管理大师”——
setitimer 函数。它不仅支持微秒(us)级精度,还自带周期性循环功能。
setitimer?虽然
alarm 简单好用,但它有两个致命弱点:
alarm,这会导致时间漂移。
setitimer 完美解决了这两个问题。
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
which(计时方式)决定了定时器如何计时,以及超时后发送什么信号。虽然有三种,但实际开发中 99% 的情况只用第一种:
ITIMER_REAL (最常用):自然计时法(墙上时钟)。无论进程是在运行、睡眠还是被切换,时间都会流逝。超时发送
SIGALRM。
ITIMER_VIRTUAL:只计算进程在用户态执行的时间。超时发送
SIGVTALRM。
ITIMER_PROF:计算进程在用户态 + 内核态执行的时间。超时发送
SIGPROF。
new_value(定时间隔)这是最核心的参数,类型为
struct itimerval。它是一个嵌套结构体:
struct timeval {
long tv_sec; // 秒
long tv_usec; // 微秒 (1秒 = 1000000微秒)
};
struct itimerval {
struct timeval it_interval; // 周期性时间(间隔时间)
struct timeval it_value; // 第一次触发的时间(初始时间)
};
it_value:设置第一次定时器多久后触发。如果设为 0,表示关闭定时器。
it_interval:第一次触发后,每隔多久再次触发。如果设为 0,表示只触发一次(类似
alarm)。
old_value(传出参数)用于保存上一次设置的定时器剩余时间。通常传入
NULL 即可。
需求:程序启动后,等待 2 秒钟第一次触发,之后每隔 1 秒钟触发一次打印 “Hello World”。
注意:由于默认动作是终止进程,我们必须使用
signal捕捉SIGALRM信号。
代码实现 (timer_loop.c):
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
// 信号处理函数
void my_handler(int signo) {
printf("Hello World! (收到信号: %d)
", signo);
}
int main() {
// 1. 注册信号捕捉,否则收到 SIGALRM 进程会直接终止
signal(SIGALRM, my_handler);
struct itimerval new_t;
// 2. 设置“周期”时间 (it_interval) -> 1秒
new_t.it_interval.tv_sec = 1;
new_t.it_interval.tv_usec = 0;
// 3. 设置“第一次触发”时间 (it_value) -> 2秒
new_t.it_value.tv_sec = 2;
new_t.it_value.tv_usec = 0;
// 4. 启动定时器,使用自然时间 (ITIMER_REAL)
int ret = setitimer(ITIMER_REAL, &new_t, NULL);
if (ret == -1) {
perror("setitimer error");
return -1;
}
printf("定时器已启动:2秒后首发,之后每1秒触发一次...
");
// 保持进程运行
while(1);
return 0;
}
运行结果:
$ gcc timer_loop.c -o timer_loop
$ ./timer_loop
定时器已启动:2秒后首发,之后每1秒触发一次...
(等待2秒...)
Hello World! (收到信号: 14)
(等待1秒...)
Hello World! (收到信号: 14)
(等待1秒...)
Hello World! (收到信号: 14)
^C <-- 按 Ctrl+C 结束
需求:实现一个
my_alarm(n) 函数,功能等同于
alarm(n),即 n 秒后触发一次,不循环。
代码实现 (my_alarm.c):
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
// 简单的信号捕捉
void handler(int signo) {
printf("BOMB! 定时器时间到!
");
}
unsigned int my_alarm(unsigned int seconds) {
struct itimerval new_t, old_t;
// 设置为不循环 (it_interval = 0)
new_t.it_interval.tv_sec = 0;
new_t.it_interval.tv_usec = 0;
// 设置首次触发时间
new_t.it_value.tv_sec = seconds;
new_t.it_value.tv_usec = 0;
// 启动定时器,并获取旧值到 old_t
setitimer(ITIMER_REAL, &new_t, &old_t);
// 返回旧定时器的剩余秒数
return old_t.it_value.tv_sec;
}
int main() {
signal(SIGALRM, handler);
printf("启动 3 秒定时器...
");
my_alarm(3);
while(1);
return 0;
}
运行结果:
$ ./my_alarm
启动 3 秒定时器...
(等待3秒)
BOMB! 定时器时间到!
(之后不再输出,因为 it_interval 为 0)
| 特性 | alarm() | setitimer() |
|---|---|---|
| 精度 | 秒 (s) | 微秒 (us) |
| 触发模式 | 单次触发 | 单次 或 周期性循环 |
| 参数复杂度 | 简单 (int) | 复杂 (嵌套结构体) |
| 剩余时间获取 | 通过返回值 | 通过
old_value 参数 |
| 信号类型 | SIGALRM | SIGALRM / SIGVTALRM / SIGPROF |
tv_sec 和
tv_usec。如果只设置秒,微秒字段包含随机垃圾值,可能会导致定时器立即触发或行为异常。信号处理:使用
setitimer 时,几乎总是需要配合信号捕捉(
signal 或
sigaction),否则默认动作通常是弄死进程。周期理解:
it_value 决定什么时候开始,
it_interval 决定频率。如果只想要单次定时,记得把
it_interval 设为 0。
setitimer 是 Linux 高级编程中处理时间的利器,虽然现在有了更现代的
timerfd 系列函数,但理解
setitimer 对于掌握操作系统的时间切片和信号机制依然至关重要。
