好的,我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 getitimersetitimer 函数,它们用于管理和控制进程的间隔计时器(interval timers)。这些计时器可以在指定的时间后产生信号(通常是 SIGALRM, SIGVTALRM, SIGPROF),从而实现定时执行代码、测量程序执行时间等功能。


1. 函数介绍 見出しへのリンク

getitimersetitimer 是一对 Linux 系统调用,用于获取和设置进程的间隔计时器(interval timers)。

  • 间隔计时器 (Interval Timer): 这是内核为每个进程维护的一个或多个计时器。每个计时器都有一个类型,当计时器超时(倒计时到 0)时,内核会向进程发送一个特定的信号
  • setitimer: 设置指定类型计时器的超时时间重载时间
    • 超时时间 (Value): 从现在开始,计时器倒计时多久后第一次超时并发送信号。
    • 重载时间 (Interval): 每次超时后,计时器自动重置并重新开始倒计时的时间。如果重载时间为 0,则计时器是一次性的;如果非 0,则计时器是周期性的。
  • getitimer: 获取指定类型计时器的当前剩余时间(Value)和设置的重载时间(Interval)。

这提供了一种基于信号的定时机制,不同于 sleepnanosleep 的主动睡眠,也不同于 alarm 的单一秒级定时器。

你可以把间隔计时器想象成一个可以设置闹钟重复周期的高级闹钟:

  • 你可以说:“5 秒后响一次,然后每 2 秒再响一次”(周期性)。
  • 或者:“3 秒后响一次,之后不再响”(一次性)。

2. 函数原型 見出しへのリンク

#include <sys/time.h> // 必需

// 获取间隔计时器的当前设置
int getitimer(int which, struct itimerval *curr_value);

// 设置间隔计时器
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

3. 功能 見出しへのリンク

  • getitimer: 查询由 which 指定的计时器的当前状态(剩余时间和重载时间),并将结果存储在 curr_value 指向的 struct itimerval 结构体中。
  • setitimer: 根据 new_value 设置由 which 指定的计时器。如果 old_value 非 NULL,则在设置新值之前,将计时器的旧值(设置前的剩余时间和重载时间)存储在 old_value 指向的结构体中。

4. 参数 見出しへのリンク

共同参数 見出しへのリンク

  • int which: 指定要操作的计时器类型。Linux 上有三种主要类型:
    • ITIMER_REAL: 实时计时器。
      • 时钟源: 系统实时时间(墙上时钟时间)。
      • 超时信号: SIGALRM
      • 用途: 测量真实世界流逝的时间。
    • ITIMER_VIRTUAL: 虚拟计时器。
      • 时钟源: 进程在用户态(执行程序代码)下花费的 CPU 时间。
      • 超时信号: SIGVTALRM
      • 用途: 测量进程自身执行代码所用的时间,不包括内核态时间和被阻塞的时间。
    • ITIMER_PROF: 性能计时器(Profile Timer)。
      • 时钟源: 进程在用户态内核态(执行系统调用等)下花费的总 CPU 时间。
      • 超时信号: SIGPROF
      • 用途: 常用于性能分析(profiling),测量程序执行(包括系统调用)的总 CPU 时间。

setitimer 特有参数 見出しへのリンク

  • const struct itimerval *new_value: 指向一个 struct itimerval 结构体的指针,该结构体定义了新的计时器设置。
  • struct itimerval *old_value: 指向一个 struct itimerval 结构体的指针,用于接收计时器的旧设置。如果不需要旧值,可以传入 NULL

struct itimerval 结构体 見出しへのリンク

这两个函数都使用 struct itimerval 来表示时间间隔:

struct itimerval {
    struct timeval it_interval; // 重载时间 (Interval)
    struct timeval it_value;    // 当前值/超时时间 (Value)
};

其中 struct timeval 定义了秒和微秒:

struct timeval {
    time_t      tv_sec;         // 秒
    suseconds_t tv_usec;        // 微秒 (0 - 999,999)
};
  • it_value: 当前计时器的剩余时间。当调用 setitimer 时,它指定了第一次超时的时间。当调用 getitimer 时,它返回计时器当前还剩多少时间。
  • it_interval: 重载时间。指定计时器超时后,自动重新开始计时的时间间隔。如果这个值为 0,则计时器是一次性的。

5. 返回值 見出しへのリンク

  • 成功时: 返回 0。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EINVAL which 无效或时间值无效,EFAULT 指针无效等)。

6. 相似函数,或关联函数 見出しへのリンク

  • alarm: 一个更简单的函数,只操作 ITIMER_REAL 计时器,且精度为秒。alarm(seconds) 大致等价于 setitimer(ITIMER_REAL, ...),其中 it_value.tv_sec = secondsit_interval.tv_sec = 0
  • signal, sigaction: 用于设置当计时器超时时(收到 SIGALRM, SIGVTALRM, SIGPROF 信号)应执行的操作。
  • nanosleep, clock_nanosleep: 提供高精度的主动睡眠,不基于信号。
  • clock_gettime, clock_settime: 用于获取和设置各种系统时钟,更现代和灵活。
  • timer_create, timer_settime, timer_gettime: POSIX 定时器 API,功能更强大,支持多种时钟源和多种通知方式(包括信号和线程通知),是 setitimer 的现代替代品。

7. 示例代码 見出しへのリンク

示例 1:使用 ITIMER_REALSIGALRM 实现超时 見出しへのリンク

这个例子演示了如何使用 ITIMER_REAL 计时器在 3 秒后发送 SIGALRM 信号来中断一个可能长时间运行的操作。

#include <sys/time.h> // setitimer, getitimer, ITIMER_REAL, struct itimerval
#include <signal.h>   // signal, SIGALRM
#include <unistd.h>   // pause, write
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit
#include <errno.h>    // errno
#include <string.h>   // strerror

volatile sig_atomic_t alarm_received = 0;

// SIGALRM 信号处理函数
void alarm_handler(int sig) {
    // 在信号处理函数中,应只调用异步信号安全的函数
    write(STDERR_FILENO, "Timeout! SIGALRM received.\n", 27);
    alarm_received = 1;
}

int main() {
    struct itimerval timer;
    const char *msg = "Doing some work that might take a long time...\n";
    const char *msg_len = "Done.\n";

    // 1. 设置 SIGALRM 信号处理函数
    if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
        perror("signal SIGALRM");
        exit(EXIT_FAILURE);
    }

    // 2. 配置 ITIMER_REAL 计时器
    // it_value: 3 秒后超时
    timer.it_value.tv_sec = 3;
    timer.it_value.tv_usec = 0;
    // it_interval: 0 表示一次性计时器,超时后不重载
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 0;

    printf("Setting ITIMER_REAL to expire in 3 seconds.\n");

    // 3. 启动计时器
    if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
        perror("setitimer");
        exit(EXIT_FAILURE);
    }

    // 4. 模拟一个可能长时间运行的操作
    write(STDOUT_FILENO, msg, 46);

    // 5. 等待操作完成或超时
    // 这里用 pause() 模拟等待,实际可能是 read(), write(), connect() 等阻塞调用
    while (!alarm_received) {
        // 在实际应用中,这里可能是真正的阻塞操作
        // 为了演示,我们用 pause() 等待信号
        printf("Waiting for work to complete or timeout...\n");
        pause(); // 进程挂起,直到收到信号
    }

    write(STDOUT_FILENO, msg_len, 6);
    printf("Program finished after timeout.\n");

    // 6. (可选) 检查计时器状态
    struct itimerval curr_timer;
    if (getitimer(ITIMER_REAL, &curr_timer) == -1) {
        perror("getitimer");
    } else {
        printf("Current ITIMER_REAL setting:\n");
        printf("  it_value: %ld.%06ld seconds (should be 0)\n",
               (long)curr_timer.it_value.tv_sec, (long)curr_timer.it_value.tv_usec);
        printf("  it_interval: %ld.%06ld seconds\n",
               (long)curr_timer.it_interval.tv_sec, (long)curr_timer.it_interval.tv_usec);
    }

    return 0;
}

代码解释:

  1. 定义一个全局的 volatile sig_atomic_t 变量 alarm_received 用于信号处理函数和主循环间通信。
  2. 定义 SIGALRM 的信号处理函数 alarm_handler。当计时器超时,内核会发送 SIGALRM 信号,该函数会被调用,打印消息并设置标志。
  3. main 函数中,使用 signal() 注册 SIGALRM 处理函数。
  4. 定义一个 struct itimerval 变量 timer
  5. 设置 timer.it_value 为 3 秒,表示 3 秒后第一次超时。
  6. 设置 timer.it_interval 为 0,表示这是一次性计时器。
  7. 调用 setitimer(ITIMER_REAL, &timer, NULL) 启动计时器。
  8. 模拟一个长时间运行的操作(这里只是打印一条消息)。
  9. 进入一个循环,等待操作完成或超时。这里用 pause() 模拟等待,实际应用中可能是 read, write, connect 等阻塞调用。
  10. SIGALRM 信号到达,alarm_handler 被调用,设置 alarm_received 为 1,主循环退出。
  11. 程序结束。
  12. 最后,调用 getitimer 检查计时器的当前状态(超时后,it_value 应该接近 0)。

示例 2:使用 ITIMER_VIRTUAL 测量 CPU 时间 見出しへのリンク

这个例子演示如何使用 ITIMER_VIRTUAL 来测量进程在用户态执行代码所花费的 CPU 时间。

#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h> // clock()

volatile sig_atomic_t vt_alarm = 0;

void vt_alarm_handler(int sig) {
    write(STDERR_FILENO, "Virtual timer expired! (SIGVTALRM)\n", 35);
    vt_alarm = 1;
}

// 模拟一个消耗 CPU 的函数
void cpu_intensive_task() {
    volatile unsigned long sum = 0;
    for (unsigned long i = 0; i < 1000000000UL; ++i) {
        sum += i;
    }
    // 使用 sum 防止编译器优化掉循环
    if (sum % 2 == 0) {
        // do nothing
    }
}

int main() {
    struct itimerval timer;

    if (signal(SIGVTALRM, vt_alarm_handler) == SIG_ERR) {
        perror("signal SIGVTALRM");
        exit(EXIT_FAILURE);
    }

    // 设置 ITIMER_VIRTUAL 在 2 秒 CPU 用户态时间后超时
    timer.it_value.tv_sec = 2;
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 0; // 一次性
    timer.it_interval.tv_usec = 0;

    printf("Setting ITIMER_VIRTUAL to expire after 2 seconds of user CPU time.\n");
    printf("Starting CPU-intensive task...\n");

    if (setitimer(ITIMER_VIRTUAL, &timer, NULL) == -1) {
        perror("setitimer ITIMER_VIRTUAL");
        exit(EXIT_FAILURE);
    }

    // 执行消耗 CPU 的任务
    cpu_intensive_task();

    if (vt_alarm) {
        printf("Task was interrupted by virtual timer.\n");
    } else {
        printf("Task completed before virtual timer expired.\n");
    }

    // 检查计时器状态
    if (getitimer(ITIMER_VIRTUAL, &timer) == -1) {
        perror("getitimer ITIMER_VIRTUAL");
    } else {
        printf("ITIMER_VIRTUAL status after task:\n");
        printf("  it_value: %ld.%06ld seconds\n",
               (long)timer.it_value.tv_sec, (long)timer.it_value.tv_usec);
        printf("  it_interval: %ld.%06ld seconds\n",
               (long)timer.it_interval.tv_sec, (long)timer.it_interval.tv_usec);
    }

    return 0;
}

代码解释:

  1. 设置 SIGVTALRM 信号处理函数。
  2. 配置 ITIMER_VIRTUAL 计时器,在进程使用 2 秒用户态 CPU 时间后超时。
  3. 调用 setitimer 启动计时器。
  4. 执行一个消耗大量 CPU 时间的函数 cpu_intensive_task
  5. 如果在函数执行完毕前计时器超时,vt_alarm 标志会被设置,表示任务被中断。
  6. 最后检查计时器状态。

示例 3:使用 ITIMER_REAL 实现周期性操作 見出しへのリンク

这个例子演示如何使用 ITIMER_REAL 实现一个周期性的“心跳”信号,每秒触发一次。

#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h> // C11 原子操作

volatile atomic_int heartbeat_count = 0;

void heartbeat_handler(int sig) {
    // 原子性地增加计数
    atomic_fetch_add(&heartbeat_count, 1);
    // write 是异步信号安全的
    write(STDERR_FILENO, "Heartbeat (SIGALRM)!\n", 21);
}

int main() {
    struct itimerval timer;
    int count_before_exit = 5;

    if (signal(SIGALRM, heartbeat_handler) == SIG_ERR) {
        perror("signal SIGALRM");
        exit(EXIT_FAILURE);
    }

    // 设置周期性计时器:立即启动,每 1 秒触发一次
    timer.it_value.tv_sec = 1;    // 1 秒后第一次超时
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 1; // 之后每 1 秒超时一次 (周期性)
    timer.it_interval.tv_usec = 0;

    printf("Setting periodic ITIMER_REAL (heartbeat every 1 second)...\n");
    printf("Will exit after %d heartbeats.\n", count_before_exit);

    if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
        perror("setitimer periodic");
        exit(EXIT_FAILURE);
    }

    // 等待指定次数的心跳
    while (atomic_load(&heartbeat_count) < count_before_exit) {
        pause(); // 等待信号
    }

    printf("Received %d heartbeats. Exiting.\n", count_before_exit);

    // 停止计时器 (设置所有时间为 0)
    struct itimerval stop_timer = {{0, 0}, {0, 0}};
    if (setitimer(ITIMER_REAL, &stop_timer, NULL) == -1) {
        perror("setitimer stop");
    }

    return 0;
}

代码解释:

  1. 使用 atomic_intatomic_fetch_add 来安全地在信号处理函数中增加计数。
  2. 配置 ITIMER_REAL 计时器:
    • it_value: 1 秒后第一次超时。
    • it_interval: 1 秒,使计时器周期性地每秒超时一次。
  3. 调用 setitimer 启动周期性计时器。
  4. 在主循环中使用 pause() 等待信号。
  5. 每次收到 SIGALRM 信号,信号处理函数打印消息并增加计数。
  6. 当计数达到预定值时,主循环退出。
  7. 通过设置计时器的 it_valueit_interval 都为 0 来停止计时器。

示例 4:使用 setitimerold_value 参数 見出しへのリンク

这个例子演示如何使用 old_value 参数来保存和恢复计时器的旧设置。

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

void alarm_handler(int sig) {
    write(STDERR_FILENO, "SIGALRM received.\n", 18);
}

void print_timer(const char *name, const struct itimerval *timer) {
    printf("%s:\n", name);
    printf("  Current value: %ld.%06ld seconds\n",
           (long)timer->it_value.tv_sec, (long)timer->it_value.tv_usec);
    printf("  Interval: %ld.%06ld seconds\n",
           (long)timer->it_interval.tv_sec, (long)timer->it_interval.tv_usec);
}

int main() {
    struct itimerval old_timer, new_timer;

    if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
        perror("signal SIGALRM");
        exit(EXIT_FAILURE);
    }

    // 1. 设置一个初始的计时器 (5秒后超时,不重复)
    new_timer.it_value.tv_sec = 5;
    new_timer.it_value.tv_usec = 0;
    new_timer.it_interval.tv_sec = 0;
    new_timer.it_interval.tv_usec = 0;

    printf("Setting initial timer for 5 seconds.\n");
    if (setitimer(ITIMER_REAL, &new_timer, NULL) == -1) {
        perror("setitimer initial");
        exit(EXIT_FAILURE);
    }

    sleep(2); // 等待 2 秒

    // 2. 获取当前计时器状态 (应该剩余约 3 秒)
    if (getitimer(ITIMER_REAL, &old_timer) == -1) {
        perror("getitimer before override");
        exit(EXIT_FAILURE);
    }
    print_timer("Timer status after 2 seconds", &old_timer);

    // 3. 设置一个新计时器 (1秒后超时),并保存旧设置
    new_timer.it_value.tv_sec = 1;
    new_timer.it_value.tv_usec = 0;
    new_timer.it_interval.tv_sec = 0;
    new_timer.it_interval.tv_usec = 0;

    printf("\nOverriding timer to expire in 1 second, saving old setting.\n");
    if (setitimer(ITIMER_REAL, &new_timer, &old_timer) == -1) {
        perror("setitimer override");
        exit(EXIT_FAILURE);
    }

    print_timer("Old timer setting saved", &old_timer);

    printf("Waiting for 1-second timer to expire...\n");
    pause(); // 等待 1 秒计时器超时

    // 4. 恢复旧的计时器设置
    printf("\nRestoring old timer setting.\n");
    if (setitimer(ITIMER_REAL, &old_timer, NULL) == -1) {
        perror("setitimer restore");
        exit(EXIT_FAILURE);
    }

    if (getitimer(ITIMER_REAL, &new_timer) == -1) {
        perror("getitimer after restore");
        exit(EXIT_FAILURE);
    }
    print_timer("Timer status after restore", &new_timer);

    printf("Waiting for restored timer to expire...\n");
    pause(); // 等待恢复的计时器超时

    printf("Restored timer expired. Program finished.\n");
    return 0;
}

代码解释:

  1. 设置一个 5 秒后超时的初始计时器。
  2. 等待 2 秒后,调用 getitimer 获取当前计时器状态(剩余约 3 秒)。
  3. 调用 setitimer(ITIMER_REAL, &new_timer, &old_timer) 设置一个 1 秒后超时的新计时器,并将旧计时器的设置(剩余约 3 秒)保存在 old_timer 变量中。
  4. 等待 1 秒计时器超时。
  5. 调用 setitimer(ITIMER_REAL, &old_timer, NULL) 将计时器恢复为之前保存的设置(约 3 秒)。
  6. 再次调用 getitimer 验证恢复是否成功。
  7. 等待恢复的计时器超时。

重要提示与注意事项:

  1. 精度: setitimer 的时间精度是微秒tv_usec),而 nanosleep 和 POSIX 定时器 (timer_) 支持纳秒精度。对于高精度需求,后者是更好的选择。
  2. 信号处理: 使用 setitimer 必然涉及信号处理。必须小心编写信号处理函数,只使用异步信号安全的函数。
  3. 竞态条件: 在设置信号处理函数和启动计时器之间,或者在检查标志和调用 pause 之间,可能存在竞态条件。使用 sigsuspend 可以更安全地处理。
  4. 现代替代: timer_create, timer_settime 等 POSIX 定时器函数提供了更强大和灵活的功能,例如可以选择不同的通知方式(信号、线程特定信号、过期计数等)和不同的时钟源(CLOCK_REALTIME, CLOCK_MONOTONIC 等),是 setitimer 的推荐现代替代方案。

总结:

getitimersetitimer 提供了一套基于信号的进程间隔计时器机制。它们可以用于实现超时、周期性任务和 CPU 时间测量等功能。理解三种计时器类型(ITIMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF)及其对应的信号是掌握这些函数的关键。虽然它们功能强大,但在现代编程中,timer_ 系列的 POSIX 定时器通常被认为是更优的选择。