sigvtalrm在C语言中如何使用?

99ANYc3cd6
预计阅读时长 18 分钟
位置: 首页 C语言 正文

什么是 SIGVTALRM

SIGVTALRM 是一个由 POSIX 标准定义的信号,它的全称是 "Virtual Timer Alarm"(虚拟定时器警报)。

c语言 sigvtalrm
(图片来源网络,侵删)

它的核心作用是:当一个进程的“虚拟运行时间”(virtual time)达到指定值时,内核会向该进程发送 SIGVTALRM 信号。


“虚拟运行时间” (Virtual Time) 是什么?

这是理解 SIGVTALRM 的关键,虚拟运行时间指的是进程在用户态(User Space)下实际执行 CPU 指令的时间

它不包括:

  • 进程在内核态执行系统调用的时间。
  • 进程因为等待 I/O(如读写文件、网络通信)而被阻塞的时间。
  • 进程因为等待其他事件(如等待锁)而被调度器挂起的时间。

与之相对的是实际运行时间(Real Time),即从时钟开始到现在经过的绝对时间,由 real() 函数返回。

c语言 sigvtalrm
(图片来源网络,侵删)

简单比喻: 想象一个工人(进程):

  • 虚拟时间:工人真正在干活的时间。
  • 实际时间:工人从上班到下班的总时长,包括他干活的时间、喝水休息的时间、和同事聊天的时间、上厕所的时间等。

SIGVTALRM 只关心“工人真正在干活”的时间。


如何使用 SIGVTALRM

要使用 SIGVTALRM,你需要用到 <sys/time.h> 头文件中的两个函数:

  1. setitimer(): 用来设置一个定时器。
  2. signal()sigaction(): 用来定义信号处理函数,即当 SIGVTALRM 发生时,程序应该做什么。

setitimer() 函数详解

setitimer() 是设置定时器的核心函数。

#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
  • which: 指定要设置哪个定时器,对于 SIGVTALRM,这个值必须是 ITIMER_VIRTUAL
  • new_value: 一个指向 itimerval 结构体的指针,包含了你希望设置的定时器参数。
  • old_value: 一个指向 itimerval 结构体的指针,用于保存旧的定时器设置,如果不需要,可以传 NULL

itimerval 结构体

这个结构体定义了定时器的工作方式:

struct itimerval {
    struct timeval it_interval; // 定时器的间隔时间
    struct timeval it_value;    // 定时器的初始超时时间
};
struct timeval {
    long tv_sec;  // 秒
    long tv_usec; // 微秒 (1秒 = 1,000,000微秒)
};

itimerval 中的两个 timeval 结构体决定了定时器的两种行为模式:

  1. 单次触发模式:

    • 只设置 it_value,不设置 it_interval(或者将其设为 0)。
    • it_value 指定的时间耗尽后,定时器触发一次 SIGVTALRM 信号,然后停止工作。
  2. 周期性触发模式:

    • 同时设置 it_valueit_interval
    • it_value 指定的时间耗尽后,定时器触发 SIGVTALRM 信号。
    • 定时器会自动重置为 it_interval 指定的时间,并开始下一次倒计时,如此循环往复。

完整代码示例

下面是一个完整的例子,演示了如何使用 SIGVTALRM 实现一个简单的性能剖析工具,计算一段代码在用户态下运行了多长时间。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <string.h>
// 全局变量,用于存储用户态运行的总时间
volatile unsigned long user_time_used = 0;
// 信号处理函数
void signal_handler(int signo) {
    if (signo == SIGVTALRM) {
        printf("Caught SIGVTALRM! This means we've used some CPU time.\n");
        // 每次收到信号,表示已经过去了 it_interval 的时间
        user_time_used += 100000; // 假设我们设置的间隔是 0.1 秒
    }
}
// 模拟一个耗时的计算任务(纯用户态)
void cpu_intensive_task() {
    volatile unsigned long sum = 0;
    for (int i = 0; i < 100000000; ++i) {
        sum += i;
    }
    printf("CPU intensive task finished. Sum = %lu\n", sum);
}
int main() {
    struct itimerval new_value, old_value;
    // 1. 注册信号处理函数
    if (signal(SIGVTALRM, signal_handler) == SIG_ERR) {
        perror("signal");
        exit(EXIT_FAILURE);
    }
    // 2. 设置定时器
    // 设置初始超时时间为 0.1 秒
    new_value.it_value.tv_sec = 0;
    new_value.it_value.tv_usec = 100000; // 0.1 seconds
    // 设置间隔时间为 0.1 秒,实现周期性触发
    new_value.it_interval.tv_sec = 0;
    new_value.it_interval.tv_usec = 100000;
    // 设置 ITIMER_VIRTUAL 定时器
    if (setitimer(ITIMER_VIRTUAL, &new_value, &old_value) == -1) {
        perror("setitimer");
        exit(EXIT_FAILURE);
    }
    printf("Timer set. Starting CPU intensive task...\n");
    printf("The timer will trigger every 0.1 seconds of CPU time.\n");
    // 3. 执行一个纯用户态的任务
    // 在这个任务执行期间,定时器会开始倒计时
    cpu_intensive_task();
    // 4. 停止定时器
    // 将 it_value 和 it_interval 都设为 0,即可停止定时器
    new_value.it_value.tv_sec = 0;
    new_value.it_value.tv_usec = 0;
    new_value.it_interval.tv_sec = 0;
    new_value.it_interval.tv_usec = 0;
    setitimer(ITIMER_VIRTUAL, &new_value, NULL);
    printf("\nTask finished.\n");
    printf("Total user CPU time used (approximate): %lu microseconds\n", user_time_used);
    return 0;
}

代码解释:

  1. signal_handler: 当 SIGVTALRM 被触发时,这个函数会被调用,我们在这里只是简单地打印一条信息,并累加一个计数器。
  2. cpu_intensive_task: 这个函数只包含一个循环,没有 I/O 操作或系统调用,所以它消耗的时间完全是用户态 CPU 时间。
  3. main 函数:
    • 首先注册 SIGVTALRM 的处理函数。
    • 然后调用 setitimer(),设置一个 ITIMER_VIRTUAL 定时器,让它每 0.1 秒(10万微秒)触发一次。
    • 接着调用 cpu_intensive_task(),在函数执行期间,你会看到每隔一段时间,signal_handler 就会被调用一次。
    • 任务结束后,我们停止定时器。
    • 最后打印出累计的用户态时间。

SIGVTALRM vs. SIGALRM

这是一个非常常见的对比点,两者都是定时器信号,但用途不同。

特性 SIGVTALRM (Virtual Timer Alarm) SIGALRM (Real Timer Alarm)
触发依据 虚拟时间 (用户态 CPU 时间) 实际时间 (Wall-Clock Time)
包含时间 不包括内核态和阻塞时间 包括所有时间(用户态、内核态、阻塞)
定时器类型 setitimer(ITIMER_VIRTUAL, ...) setitimer(ITIMER_REAL, ...)alarm()
典型用途 性能剖析、CPU 时间限制、资源监控 实时任务调度、超时控制、程序总运行时间限制

简单总结:

  • 如果你关心的是程序真正干活花了多少 CPU 时间,用 SIGVTALRM
  • 如果你关心的是从开始到现在过去了多久,用 SIGALRM

注意事项

  1. 信号处理函数要尽量简单:信号处理函数中应避免调用不可重入的函数(如 printf, malloc, exit 等),因为信号可能在任何时候打断主程序,在上面的例子中,为了演示方便使用了 printf,但在生产代码中应格外小心。
  2. 信号可能丢失:如果信号处理函数正在执行时,又发生了同类型的信号,这个新的信号可能会被“丢失”(在较旧的系统上)或被排队(在新的系统上,取决于 sigaction 的设置)。
  3. 多线程SIGVTALRM 信号是发送给整个进程的,如果一个多线程程序设置了 SIGVTALRM,那么当定时器到期时,信号会被发送到进程中一个任意的、未被阻塞该信号的线程,在多线程环境中使用 SIGVTALRM 需要特别小心,通常推荐使用 pthread 提供的更精细的定时机制。
-- 展开阅读全文 --
头像
dede后台密码修改器安全吗?能用吗?
« 上一篇 03-01
dede列表缩略图变形如何解决?
下一篇 » 03-01

相关文章

取消
微信二维码
支付宝二维码

目录[+]