定时器流程

1、itimerval——设置定时器的初值和周期值

2、setitimer——设置定时器

3、sigaction()/signal()——处理信号,执行定时功能

4、SIGALRM——触发信号

itimerval(设置定时器的初值和周期值)

itimerval 是一个结构体,它在 POSIX 兼容的操作系统中定义于 <sys/time.h> 头文件中。这个结构体用于 setitimer 系统调用,以便获取或设置进程的虚拟定时器和实时定时器。

结构体定义:

struct itimerval {
struct timeval it_interval; /* 定时器重复触发的时间周期 */
struct timeval it_value; /* 从当前时间到定时器第一次触发的剩余时间 */
};

成员解释:

  • it_interval:这是一个 timeval 结构,它指定了定时器的间隔时间,即定时器重复触发的时间周期。timeval 结构包含两个字段:tv_sec(秒)和 tv_usec(微秒)。

  • it_value:这也是一个 timeval 结构,它指定了定时器的到期时间,即从当前时间到定时器第一次触发的剩余时间。

setitimer(设置定时器)

setitimer 是 UNIX 和类 UNIX 系统(包括 Linux)中的一个系统调用,用于设置进程的虚拟、实时或性能定时器。这个系统调用提供了一种机制,允许进程接收定时信号,例如,SIGALRM

函数原型:

int setitimer(int which, const struct itimerspec *new_value, struct itimerspec *old_value);

参数:

  • which:指定要设置的定时器类型,可以是以下之一:
    • ITIMER_REAL:实时定时器,基于系统时钟。
    • ITIMER_VIRTUAL:虚拟定时器,基于进程的虚拟执行时间。
    • ITIMER_PROF:性能定时器,用于采样程序的执行时间。
  • new_value:指向 itimerspec 结构的指针,该结构包含两个 timespec 结构,分别指定定时器的初值和周期值。timespec 结构包含 tv_sec(秒)和 tv_nsec(纳秒)。
  • old_value:指向 itimerspec 结构的指针,用于存储定时器被设置前的旧值。如果设置为 NULL,则忽略旧值。

要使用 setitimer 创建一个周期性执行的任务,你需要设置定时器的初值(it_value)和周期值(it_interval),这两者都定义在 itimerspec 结构体中。周期值设置了定时器触发的频率,而初值设置了第一次触发前的时间间隔。

signal()/ sigaction()函数

在 UNIX 和类 UNIX 系统中,signalsigaction 都是用来处理信号的函数。信号是一种特殊的软件中断,当程序接收到某些事件(比如用户按下 Ctrl+C 或者定时器到期)时,操作系统会发送信号给程序。

signal 是一个用于处理异步事件的机制,它允许程序对操作系统发送的信号进行响应。在 UNIX 和类 UNIX 系统(包括 Linux)中,信号是一种软件中断,用于通知进程发生了某个事件,如用户按下中断键(通常是 Ctrl+C),或者系统检测到一个错误条件。

信号可以由多种不同的源触发,包括硬件事件、软件条件或系统调用。它们通常用于中断正在执行的进程,以便进程可以采取适当的行动,比如清理资源、记录日志或优雅地终止。

捕获并自定义处理信号:进程可以通过 signal 函数来捕获信号,并为其定义自定义的处理函数。这个处理函数将在信号到达时被调用,如下。

// SIGALRM 信号处理函数
void signal_handler(int signum) {
printf("SIGALRM received!\n");
// 这里可以放置你想要周期性执行的代码
}

// 设置信号处理函数
signal(SIGALRM, signal_handler);

sigaction 函数是 signal 函数的现代替代品,它提供了更多的功能和更好的可移植性。

#include <signal.h>
void (*sigaction(int signum, const struct sigaction *act, struct sigaction *oldact))(int);
  • signum 是要处理的信号的编号。

  • act 是指向 sigaction 结构的指针,该结构定义了信号的处理方式。

  • oldact 是指向 sigaction 结构的指针,用于存储旧的信号处理函数。

使用 sigaction 时,可以更安全地处理信号,并且可以设置信号处理函数的额外属性,如 sa_mask(信号屏蔽字),sa_flags(信号处理标志),以及 sa_handler(信号处理函数)。

signal()与 sigaction()区别

signal 是较旧的函数,它有一些限制。例如,你不能在 signal 函数中使用某些会阻塞的函数,因为这可能会导致程序无法响应其他信号。而且,signal 函数本身的限制也比较多,比如它不能处理一些新的信号。

sigaction 是一个更新的函数,它解决了 signal 的一些问题。使用 sigaction,你可以更安全地处理信号,并且可以设置更多的信号处理选项。例如,可以告诉操作系统在处理信号时是否应该暂停其他信号的接收,这样可以避免在处理一个信号时被另一个信号打断。

SIGALRM(定时器信号)

SIGALRM(Signal Alarm)是一个在 UNIX 和类 UNIX 系统(包括 Linux)中广泛使用的信号,它用于处理定时器到期事件。当一个进程设置了闹钟(alarm clock),并在指定的时间间隔后,SIGALRM 信号会被发送给该进程。这个信号可以用来提醒进程执行某些操作,或者在特定的时间点触发某些事件。

示例

以下是一个简单的 C 程序示例,演示如何使用 setitimerSIGALRM 信号来创建一个周期性的定时器:

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>

// SIGALRM 信号处理函数
void signal_handler(int signum) {
printf("SIGALRM received!\n");
// 这里可以放置你想要周期性执行的代码
}

int main() {
struct itimerspec its;

// 设置信号处理函数
signal(SIGALRM, signal_handler);

// 设置定时器的周期值和初值
its.it_value.tv_sec = 2; // 2 秒后触发
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 2; // 每隔 2 秒触发一次
its.it_interval.tv_nsec = 0;

// 使用 setitimer 设置定时器
if (setitimer(ITIMER_REAL, &its, NULL) == -1) {
perror("setitimer failed");
return 1;
}

// 无限循环,等待定时器触发
while (1) {
pause(); // pause 函数会使进程休眠,直到收到 SIGALRM 或其他信号
}

return 0;
}

在这个示例中,首先设置了一个信号处理函数 signal_handler 来处理 SIGALRM 信号。然后,使用 setitimer 函数设置了一个周期性的定时器,每隔 2 秒触发一次 SIGALRM 信号。最后,使用 pause 函数进入一个无限循环,等待信号的到来。

信号处理函数 signal_handler 是一个特殊的函数,它在信号到达时被操作系统调用。在这个例子中,signal_handler 函数只是简单地打印一条消息到控制台。每次定时器触发时,signal_handler 都会被调用一次。

注意事项 :

  • 信号处理函数应该尽可能地短小,避免执行复杂的操作,因为这可能会影响系统的信号处理效率。
  • 在信号处理函数中,应该避免使用可能会阻塞的系统调用或库函数。
  • 如果需要长时间运行的操作,应该在信号处理函数中安排这些操作在进程的主体中异步执行。

设定定时器与 while 循环有本质的区别

  1. 异步执行: 信号处理函数是异步执行的,这意味着它们是由操作系统在信号到达时调用的,而不是程序的主执行流程(如 while 循环)的一部分。这意味着信号处理函数可以在程序的任何地方被调用,而不仅仅是在 while 循环中。

  2. 实时性: 使用 setitimer 和信号处理函数可以确保某些操作以固定的频率发生,即使程序的主线程正在忙于其他任务。这在需要实时响应的情况下非常有用,例如在嵌入式系统或实时操作系统中。

  3. 主线程的阻塞while 循环会阻塞程序的主线程,直到循环条件不再满足。在循环内部执行的任务会连续不断地运行,直到循环结束。而信号处理函数则是在 while 循环之外独立执行的,它们不会阻塞主线程,也不会影响主线程的执行顺序。

  4. 周期性任务的实现方式: 在 while 循环中实现周期性任务通常需要在循环内部使用延时函数(如 sleep),这会导致程序的整体执行速度降低,因为主线程必须等待延时结束才能继续执行。而使用 setitimer 和信号处理函数,周期性任务是由操作系统在后台管理的,不会影响主线程的执行。

总结来说,setitimer 和信号处理函数提供了一种异步和实时的方式来周期性地执行任务,而 while 循环提供了一种同步和阻塞的方式来执行任务。选择哪种方式取决于具体需求和程序的设计。