C语言alarm clock如何实现定时与提醒功能?

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

alarm 函数是 C 标准库(在 <unistd.h> 头文件中定义)提供的一个非常有用的工具,用于设置一个定时器,当定时器到期时,内核会向进程发送一个 SIGALRM 信号,这个机制是实现“闹钟”功能的基础。

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

函数原型

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

参数:

  • seconds: 你希望闹钟在多少秒后触发,这是一个无符号整型。

返回值:

  • 成功时:返回之前设置的闹钟剩余的秒数,如果之前没有设置闹钟,则返回 0
  • 失败时:返回 0errno 会被设置为 EINVAL,但这在 Linux 系统上很少见,因为 0 是一个有效的参数)。

关键点:

  • 一个进程只能有一个 alarm 闹钟在运行,如果你在旧的闹钟到期之前调用 alarm,旧的闹钟会被取消,alarm 函数会返回旧闹钟剩余的时间。
  • seconds 参数为 0,则取消任何之前设置的闹钟,并返回之前闹钟剩余的时间。

工作原理:信号

alarm 函数本身并不会让你的程序“暂停”或“休眠”,它的工作流程是:

c语言alarm clock
(图片来源网络,侵删)
  1. 调用 alarm(5),告诉内核:“请在 5 秒后给我发送一个 SIGALRM 信号”。
  2. 你的程序会继续执行后续的代码,不会被阻塞。
  3. 5 秒后,内核向你的进程发送 SIGALRM 信号。
  4. 默认情况下,收到 SIGALRM 信号的进程会终止,这就是为什么如果你只调用 alarm 而不处理信号,程序会在指定时间后自动退出。

要实现一个有用的“闹钟”,我们必须学会如何“捕获”并“处理”这个 SIGALRM 信号,而不是让它默认终止程序。


如何处理信号:signal 函数

为了处理信号,我们需要使用 signal 函数(在 <signal.h> 中定义)。

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

signal 函数允许你为某个信号(如 SIGALRM)指定一个“处理函数”(也叫“信号处理函数”或“信号处理器”)。

参数:

c语言alarm clock
(图片来源网络,侵删)
  • signum: 你想处理的信号编号或宏,对于 alarm,我们使用 SIGALRM
  • handler: 一个指向函数的指针,当信号发生时,这个函数会被调用,它可以是:
    • SIG_IGN: 忽略该信号。
    • SIG_DFL: 恢复该信号的默认行为(对于 SIGALRM,默认行为是终止进程)。
    • 一个自定义函数的地址:这个函数就是你的信号处理函数。

信号处理函数的要求:

  • 它的参数类型必须是 int,代表接收到的信号编号。
  • 它的返回类型必须是 void
  • 重要:信号处理函数应该尽可能简单、快速,避免在处理函数中调用标准 I/O 函数(如 printf)、malloc 等非异步安全(async-signal-safe)的函数,因为在信号处理期间调用它们可能会导致不可预测的行为。

完整示例:一个简单的 5 秒闹钟

这个例子会设置一个 5 秒的闹钟,并打印一条消息,然后等待闹钟触发,触发后,它会打印另一条消息,而不是直接退出。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h> // 用于 exit
// 全局变量,用于表示闹钟是否触发
volatile sig_atomic_t alarm_triggered = 0;
// SIGALRM 信号的处理函数
void alarm_handler(int signum) {
    // 注意:这个函数应该尽量简单
    printf("闹钟响了!\n");
    alarm_triggered = 1; // 设置标志位
}
int main() {
    // 注册 SIGALRM 信号的处理函数
    if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
        perror("无法注册信号处理函数");
        exit(EXIT_FAILURE);
    }
    printf("程序开始,5秒后将触发闹钟...\n");
    // 设置一个 5 秒的闹钟
    alarm(5);
    // 等待闹钟触发
    // 使用一个简单的循环来等待,直到我们的标志位被设置
    // 在实际应用中,这里可能是主程序的业务逻辑
    while (!alarm_triggered) {
        // 空循环,让 CPU 稍作休息
        // 在更复杂的程序中,这里可以执行其他任务
        sleep(1); // 休眠1秒,避免CPU空转
    }
    printf("闹钟处理完毕,程序继续执行...\n");
    return 0;
}

编译和运行:

gcc -o my_alarm my_alarm.c
./my_alarm

预期输出:

程序开始,5秒后将触发闹钟...
(等待5秒)...
闹钟响了!
闹钟处理完毕,程序继续执行...

更实用的示例:带超时的 read

alarm 的一个经典应用是为可能阻塞的系统调用(如 read, write, accept)设置超时。

假设你想从标准输入读取一行数据,但如果用户在 10 秒内没有输入,你就想放弃并提示超时。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 1024
volatile sig_atomic_t timeout = 0;
void timeout_handler(int signum) {
    printf("\n输入超时!\n");
    timeout = 1;
}
int main() {
    char buffer[BUFFER_SIZE];
    int bytes_read;
    // 注册超时处理函数
    if (signal(SIGALRM, timeout_handler) == SIG_ERR) {
        perror("无法注册信号处理函数");
        exit(EXIT_FAILURE);
    }
    printf("请在10秒内输入一些文字: ");
    fflush(stdout); // 确保提示信息立即显示
    // 设置10秒超时
    alarm(10);
    // read 函数可能会阻塞,直到有输入或信号中断
    bytes_read = read(STDIN_FILENO, buffer, BUFFER_SIZE - 1);
    // 如果因为信号而中断,read 会返回 -1,errno 会被设置为 EINTR
    if (bytes_read == -1 && errno == EINTR) {
        if (timeout) {
            // 是我们设置的 alarm 信号导致的超时
            printf("操作因超时被取消,\n");
        } else {
            // 是其他信号导致的
            perror("read 被信号中断");
        }
    } else if (bytes_read > 0) {
        // 成功读取
        buffer[bytes_read] = '\0'; // 确保字符串以 null 
        printf("你输入了: %s\n", buffer);
    }
    // 取消闹钟,防止它在读取完成后还触发
    alarm(0);
    return 0;
}

分析:

  1. 我们设置了 alarm(10)
  2. 程序调用 read 等待用户输入。read 会阻塞程序。
  3. 情况A: 用户在10秒内输入了内容。read 返回,我们处理输入,然后调用 alarm(0) 取消闹钟。
  4. 情况B: 10秒过去了,alarm 触发,发送 SIGALRM 信号。
    • timeout_handler 被调用,打印 "输入超时!" 并设置 timeout = 1
    • read 函数被信号中断,它立即返回 -1errno 被设置为 EINTR (Interrupted system call)。
    • main 函数中的 if 条件判断为真,我们检查 timeout 标志位,确认是超时导致的,并打印相应消息。

alarmsleep 的关系

你可能注意到 sleep 函数也能实现延时。sleep 的内部实现通常就依赖于 alarm

  • sleep(n) 的工作原理大致是:
    1. 调用 alarm(n) 设置一个闹钟。
    2. 调用 pause() 函数(在 <unistd.h> 中),pause 会让进程休眠,直到接收到任何信号。
    3. SIGALRM 信号到达时,pause 返回,进程被唤醒。
    4. sleep 函数会检查 alarm 的返回值,看看是否提前被唤醒,然后计算出实际休眠的时间并返回。

关键区别:

  • alarm 是非阻塞的,设置后立即返回。
  • sleep 是阻塞的,直到指定时间结束或被信号中断。

特性 描述
头文件 <unistd.h>
功能 设置一个定时器,到期后向进程发送 SIGALRM 信号。
核心机制 信号alarm 本身不阻塞程序,需要配合信号处理函数才能实现复杂逻辑。
限制 一个进程只能有一个活动的 alarm,新的调用会取消旧的。
主要用途 实现超时控制(如网络编程中的超时读/写)。
实现周期性任务(通过在信号处理函数中重新调用 alarm)。
简单的延时和提醒功能。
注意事项 信号处理函数必须简洁,避免调用非异步安全函数。

alarm 是 Linux/Unix 系统编程中一个基础且强大的工具,理解它与信号的交互方式是掌握高级 C 语言编程的关键一步。

-- 展开阅读全文 --
头像
dede5.7采集不到内容,是哪里出问题了?
« 上一篇 01-24
织梦黑色高端html5,如何打造极致视觉体验?
下一篇 » 01-24

相关文章

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

目录[+]