12. Linux内核定时器

在嵌入式Linux驱动开发的世界里,时间是一个不可或缺的维度,我们常常需要让设备在“未来的某个时刻”做某事,或者“每隔一段时间”重复做某事。 对于内核开发者而言,内核定时器就是这样一款核心的工具,它允许驱动程序在指定的未来时间点,调度执行一段特定的代码。 它不依赖硬件时钟芯片,而是完全由内核软件层面实现,是驱动中实现“延时”、“周期性任务”和“超时检测”的基础。

12.1. 内核定时器定义

Linux内核定时器是内核提供的一种基于时间触发的同步机制,用于实现“在指定时间后执行特定任务”的功能,本质是一种软定时器, 依赖内核时钟中断和jiffies(系统节拍计数器)实现计时,不具备硬件定时器的高精度,但足以满足绝大多数内核驱动、内核任务的时间触发需求。

内核定时器的核心特性是异步触发、非阻塞,定时器注册后,内核会在指定时间到期时,自动调用预先注册的回调函数,执行用户定义的任务。

内核定时器作用总结如下:

  1. 实现周期性任务(如LED周期性翻转);

  2. 实现超时检测(如设备通信超时、资源获取超时);

  3. 延迟执行特定任务(如延迟初始化硬件、延迟释放资源);

  4. 配合同步机制(如自旋锁),实现安全的周期性硬件操作。

12.2. 内核定时器分类

Linux内核提供多种定时器类型,适配不同精度、不同场景的需求:

  • 普通定时器(timer_list):最基础、最常用,基于jiffies计时,支持一次性/周期性触发,可动态修改触发时间;

  • 高精度定时器(hrtimer):高精度计时,支持纳秒级精度,基于硬件时钟,触发更精准;

  • 延迟工作队列(delayed_work):基于工作队列和定时器,回调函数在进程上下文执行,支持延迟执行、周期性执行;

  • watchdog定时器(watchdog):硬件/软件结合,用于监控系统运行,超时未喂狗则触发系统复位。

本章节主要介绍普通定时器,其他定时器不作展开。

12.3. 内核定时器底层源码解析

Linux内核普通定时器定义于内核源码/include/linux/timer.h,底层依托jiffies、时钟中断和定时器链表实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 内核源码/include/linux/timer.h
struct timer_list {
    struct hlist_node       entry;
    unsigned long           expires;
    void                    (*function)(struct timer_list *);
    u32                     flags;
    unsigned long   cust_data;
#ifdef CONFIG_LOCKDEP
    struct lockdep_map      lockdep_map;
#endif

    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
};

说明:

  1. entry:内核定时器的挂载节点,内核会把所有定时器组织成哈希链表/双向链表统一管理,这个成员就是将当前定时器挂入内核定时器链表的“钩子”。

  2. expires:定时器超时绝对时间。

  3. function:定时器回调函数指针,定时器到期时,内核会自动调用这个函数。

  4. flags:定时器控制标志位。

  5. cust_data:用户自定义私有数据,开发者可以把自定义参数(如设备指针、状态值)存在这里。

  6. lockdep_map:锁依赖调试,用于内核死锁检测、锁顺序验证,驱动开发中完全不用关心。

12.4. 系统节拍计数器

内核定时器的计时基础是jiffies,它是一个全局无符号长整型变量,用于记录系统启动以来的时钟中断次数,其值随时钟中断递增。 在struct timer_list里看到的expires,单位就是jiffies。

jiffies的增长由系统时钟中断驱动,内核有一个配置项HZ(每秒时钟中断次数),可执行以下命令确认系统HZ值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#查看系统HZ值
cat /boot/config-* | grep CONFIG_HZ

#信息输出如下
# CONFIG_HZ_PERIODIC is not set
# CONFIG_HZ_100 is not set
# CONFIG_HZ_250 is not set
CONFIG_HZ_300=y
# CONFIG_HZ_1000 is not set
CONFIG_HZ=300

jiffies和秒换算关系:

1
2
3
4
5
6
7
#换算关系
1 jiffies = 1/HZ 秒

#可得到
HZ=100时, 1 jiffies = 10 毫秒
HZ=300时, 1 jiffies = 3.33 毫秒
HZ=1000时,1 jiffies = 1 毫秒

12.5. 内核定时器标准API函数

以下主要以普通定时器进行说明,普通定时器的标准API包括初始化、启动、重启、停止、查询等。

12.5.1. 定时器初始化

12.5.1.1. timer_setup函数

timer_setup函数用于初始化struct timer_list类型定时器,绑定回调函数,设置定时器标志位,是使用定时器的前置操作,必须在启动定时器前调用。

函数原型:

1
void timer_setup(struct timer_list *timer, void (*function)(struct timer_list *), unsigned int flags);

参数说明:

  • timer:指向需要初始化的定时器结构体指针;

  • function:定时器到期后自动执行的回调函数指针;

  • flags:定时器标志位,通常填0(默认值),用于配置定时器的特殊行为(如是否支持多CPU调度)。

需注意,回调函数必须严格遵循void (*)(struct timer_list *)的原型定义,否则会导致内核编译报错或运行时指针异常;初始化需在启动定时器之前完成。

12.5.2. 定时器启动/重启

12.5.2.1. mod_timer函数

mod_timer函数用于设置定时器的到期时间,若定时器已处于活跃状态(已注册且未到期),则更新其到期时间;若定时器未活跃,则直接启动定时器,是实现定时器周期性触发的核心API。

函数原型:

1
int mod_timer(struct timer_list *timer, unsigned long expires);

参数说明:

  • timer:指向已初始化的定时器结构体指针;

  • expires:定时器到期时间,单位为jiffies,通常设置为“当前jiffies + 时间间隔”。

返回值:

  • int类型,返回1表示定时器已成功更新到期时间或启动;返回0表示定时器未初始化或已被删除。

可重复调用mod_timer函数,用于动态调整定时器的到期时间;若expires设置为小于当前jiffies的值,定时器会立即触发回调函数。

12.5.3. 定时器删除

12.5.3.1. del_timer函数

del_timer函数用于异步删除指定的定时器,若定时器未到期,则取消其触发;若定时器已到期或未活跃,则忽略该操作,属于异步删除方式,不等待回调函数执行完毕。

函数原型:

1
int del_timer(struct timer_list *timer);

参数说明:

  • timer:指向需要删除的定时器结构体指针。

返回值:

  • int类型,返回1表示定时器已成功删除;返回0表示定时器未启动或已删除。

需注意,如果非同步删除,可能存在回调函数正在执行的风险,易引发并发竞态,不建议在中断上下文使用。

12.5.3.2. del_timer_sync函数

del_timer_sync函数用于同步删除指定的定时器,与del_timer的区别是,会等待定时器回调函数执行完毕后,再删除定时器, 确保无并发风险,是驱动开发中推荐的定时器停止方式。

函数原型:

1
int del_timer_sync(struct timer_list *timer);

参数说明:

  • timer:指向需要删除的定时器结构体指针。

返回值:

  • int类型,返回1表示定时器已成功删除;返回0表示定时器未启动或已删除。

推荐在进程上下文使用,避免在原子上下文使用(会导致阻塞);驱动移除时必须调用该函数,避免定时器残留引发资源泄漏。

12.5.4. 定时器状态查询

12.5.4.1. timer_pending函数

timer_pending函数用于查询指定定时器是否处于活跃状态。

函数原型:

1
int timer_pending(const struct timer_list *timer);

参数说明:

  • timer:指向需要查询的定时器结构体指针。

返回值:

  • int类型,返回1表示定时器处于活跃状态;返回0表示定时器未活跃(未启动、已到期或已删除)。

仅用于查询状态,不可用于控制定时器的启动或停止;参数为const指针,不可通过该指针修改定时器成员。

12.5.5. 私有数据访问

12.5.5.1. from_timer宏

from_timer是内核提供的容器of宏,用于从定时器指针(timer),获取其所在的父结构体指针(ptr), 核心用于定时器回调函数中访问私有数据。

宏原型:

1
2
#define from_timer(var, callback_timer, timer_fieldname) \
    container_of(callback_timer, typeof(*var), timer_fieldname)

参数说明:

  • ptr:父结构体指针名,用于接收获取到的父结构体指针;

  • timer:定时器指针;

  • member:定时器在父结构体中的成员名。

返回值:

  • 无显式返回值,通过宏运算直接将父结构体指针赋值给ptr。

需注意,必须确保member参数与父结构体中定时器的成员名完全一致,否则会导致指针越界、系统崩溃;仅用于回调函数中,结合定时器指针获取私有数据。

12.6. 系统节拍转换API函数

12.6.1. 毫秒转系统节拍

12.6.1.1. msecs_to_jiffies函数

msecs_to_jiffies函数是内核标准时间转换API,将用户态常用的毫秒单位时间,转换为定时器所需的jiffies(系统节拍)单位。

函数原型:

1
unsigned long msecs_to_jiffies(const unsigned int msecs);

参数说明:

  • msecs:待转换的毫秒数,为非负整数。

返回值:

  • 转换后的jiffies节拍数,可直接赋值给定时器间隔参数或expires字段。

需注意,输入毫秒数不可为0或负数,否则会导致定时器异常触发;转换结果自动适配系统HZ配置(如HZ=100、HZ=300), 无需手动计算,避免硬编码出错。

12.6.2. 微秒转系统节拍

12.6.2.1. usecs_to_jiffies函数

usecs_to_jiffies函数是内核标准时间转换API,将微秒单位时间转换为jiffies节拍数,适用于更短延时的定时器场景,弥补msecs_to_jiffies最小毫秒级的局限,适配短周期定时需求。

函数原型:

1
unsigned long usecs_to_jiffies(const unsigned int usecs);

参数说明:

  • usecs:待转换的微秒数,非负整数,适用于短周期定时场景。

返回值:

  • 转换后的jiffies节拍数。

需注意,普通定时器基于jiffies,精度受HZ限制,微秒级转换仅为理论值,实际触发精度仍为节拍级;高精度短周期场景建议改用hrtimer高精度定时器。

12.6.3. 系统节拍转毫秒

12.6.3.1. jiffies_to_msecs函数

jiffies_to_msecs函数是内核标准时间转换API,将jiffies系统节拍数,反向转换为毫秒单位,常用于调试打印、日志输出,方便开发者直观查看定时器间隔时长。

函数原型:

1
unsigned int jiffies_to_msecs(const unsigned long j);

参数说明:

  • j:待转换的jiffies节拍数,对应定时器间隔或到期节拍。

返回值:

  • 转换后的毫秒数。

需注意,转换结果受系统HZ配置影响,仅为近似值(普通定时器非高精度);不可用于高精度计时场景,仅做调试、日志展示使用。

12.7. 内核定时器工作流程

以控制led周期闪烁为例,内核定时器工作流程如下:

../_images/timer_0.jpg

12.8. 内核定时器实验

Linux内核定时器回调函数属于软中断上下文(原子上下文),绝对不能睡眠,因此:

  • 优先用:原子操作(简单变量)、自旋锁(复杂共享资源)

  • 禁止用:信号量、互斥体(会引发内核崩溃)

本实验在自旋锁实验基础上添加内核定时器部分,设备树插件完全一致,驱动仅说明修改部分,重复部分不作说明。

本章的示例代码目录为: linux_driver/15_timer

12.8.1. 实验代码讲解

结构体与定时器定义

定时器定义(位于linux_driver/15_timer/timer_led.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 定义 LED 字符设备结构体 */
struct led_chrdev {
    /* 字符设备结构体 */
    struct cdev dev;
    /* 数据寄存器的虚拟地址,用于设置输出的电压 */
    unsigned int __iomem *va_dr;
    /* 数据方向寄存器的虚拟地址,用于设置输入或者输出 */
    unsigned int __iomem *va_ddr;
    /* 引脚高低位 */
    unsigned int hl_pos;
    /* 引脚偏移量 */
    unsigned int led_pin;
    /* LED 的设备树子节点 */
    struct device_node *device_node;
    /* 自旋锁 */
    spinlock_t spinlock;
    /* 定时器 */
    struct timer_list timer;
    /* LED 状态 */
    int led_state;
    /* 定时器间隔时间 */
    unsigned long timer_interval;
};

/* 定义 led_chrdev 结构体指针,用于动态分配内存管理 LED 硬件信息 */
static struct led_chrdev *led_cdev;

将定时器作为设备私有资源,与LED硬件资源、自旋锁绑定,既能通过定时器实现LED周期性控制,又能通过自旋锁保证定时器回调函数与write函数的临界区安全。

定时器初始化

定时器初始化(位于linux_driver/15_timer/timer_led.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
static int pdrv_led_probe(struct platform_device *pdev)
{
    // 内存分配、设备树解析、字符设备注册(省略重复代码)

    /* 初始化自旋锁 */
    spin_lock_init(&led_cdev->spinlock);

    /* 初始化定时器 */
    timer_setup(&led_cdev->timer, led_timer_callback, 0);
    /* timer_interval单位为jiffies
     * 当HZ=100时,此处定时器实际间隔时间为 HZ/2 个 jiffies,即 HZ/2 * (1/HZ)= 0.5秒
     * 当HZ=300时,此处定时器实际间隔时间为 HZ/2 个 jiffies,即 HZ/2 * (1/HZ)= 1秒
     */
    led_cdev->timer_interval = HZ / 2;
    /* 默认关闭 LED */
    led_cdev->led_state = 0;

    // 错误处理(省略重复代码)
}

说明:

  1. 初始化顺序:先初始化自旋锁,再初始化定时器,因为定时器回调函数中会使用自旋锁;

  2. 回调函数绑定:led_timer_callback是定时器到期后执行的回调函数,负责LED电平翻转;

  3. 默认间隔:HZ/2表示间隔为系统节拍的一半,确保LED默认闪烁频率;

  4. 默认状态:led_state = 0表示默认关闭LED。

需注意,probe函数并没有启动定时器,而是在write函数中触发启动。

定时器回调函数

定时器回调函数(位于linux_driver/15_timer/timer_led.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 定时器回调函数 */
static void led_timer_callback(struct timer_list *t)
{
    struct led_chrdev *led_cdev = from_timer(led_cdev, t, timer);
    unsigned int val;

    /* 用于保存中断状态信息 */
    unsigned long flags;

    /* 获取自旋锁并保存中断状态 */
    spin_lock_irqsave(&led_cdev->spinlock, flags);

    /* 读取数据寄存器的值 */
    val = ioread32(led_cdev->va_dr);

    /* 判断 LED 状态 */
    if (led_cdev->led_state) {
        /* 设置高 16 位的使能位 */
        val |= ((unsigned int)0x1 << (led_cdev->led_pin + 16));
        /* 设置低 16 位的对应引脚输出,翻转电平 */
        val ^= ((unsigned int)0x1 << (led_cdev->led_pin));
        /* 将修改后的值写回到数据寄存器 */
        iowrite32(val, led_cdev->va_dr);
    }

    /* 重新启动定时器 */
    mod_timer(&led_cdev->timer, jiffies + led_cdev->timer_interval);

    /* 释放自旋锁并恢复中断状态 */
    spin_unlock_irqrestore(&led_cdev->spinlock, flags);
}

说明:

  • 第4行:从定时器指针t,获取其所在的led_chrdev结构体指针,实现私有数据(如GPIO寄存器地址、引脚偏移量)的访问;

  • 第11行:获取自旋锁,因为回调函数中操作GPIO寄存器(临界区),需与write函数中的GPIO操作互斥,避免并发错乱;

  • 第21行:通过val ^= ((unsigned int)0x1 << (led_cdev->led_pin)) 翻转对应引脚的电平,实现LED闪烁;

  • 第27行:重启定时器,设置下一次到期时间为“当前jiffies + 间隔时间”,实现周期性触发;

  • 第30行:释放自旋锁,避免锁泄漏。

定时器的启动、停止与参数修改

通过write函数接收用户态指令,实现定时器的启动、停止、间隔时间修改,结合LED状态控制,代码片段如下:

write函数(位于linux_driver/15_timer/timer_led.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
static ssize_t pdrv_led_write(struct file *filp, const char __user *buf,
                            size_t count, loff_t *ppos)
{
    /* 用于存储用户输入 */
    char input[32] = {0};
    /* 用于存储用户输入的时间参数 */
    unsigned long interval = 0;
    unsigned long val = 0;
    /* 从文件结构体的私有数据中获取 led_chrdev 结构体指针 */
    struct led_chrdev *led_cdev = filp->private_data;
    /* 用于保存中断状态信息 */
    unsigned long flags;

    /* 打印设备写操作信息 */
    printk("pdrv_led write \r\n");

    /* 从用户空间读取输入 */
    if (copy_from_user(input, buf, min(count, sizeof(input) - 1))) {
        return -EFAULT;
    }

    /* 解析用户输入 */
    if (sscanf(input, "%lu", &val) >= 1) {

    printk("val = %ld \n", val);

    /* 获取自旋锁并保存中断状态 */
    spin_lock_irqsave(&led_cdev->spinlock, flags);

    switch (val) {
        /* 关闭 LED */
        case 0:
            led_cdev->led_state = 0;
            /* 停止定时器 */
            del_timer_sync(&led_cdev->timer);
            /* 读取数据寄存器的值 */
            val = ioread32(led_cdev->va_dr);
            /* 设置高 16 位的使能位 */
            val |= ((unsigned int)0x1 << (led_cdev->led_pin + 16));
            /* 设置低 16 位的对应引脚输出高电平 */
            val |= ((unsigned int)0x01 << (led_cdev->led_pin));
            /* 将修改后的值写回到数据寄存器 */
            iowrite32(val, led_cdev->va_dr);
            break;
        /* 开启 LED 闪烁 */
        case 1:
            led_cdev->led_state = 1;
            /* 启动定时器 */
            mod_timer(&led_cdev->timer, jiffies + led_cdev->timer_interval);
            break;
        /* 修改定时器时间 */
        case 2:
            if (sscanf(input, "%lu %lu", &val, &interval) == 2 && interval > 0) {
                /* 转换为 jiffies */
                led_cdev->timer_interval = msecs_to_jiffies(interval);
                printk("Timer interval set to %lu ms\n", interval);
            } else {
                printk(KERN_ERR "Invalid interval value\n");
            }
            break;

        default:
            /* 释放自旋锁并恢复中断状态 */
            spin_unlock_irqrestore(&led_cdev->spinlock, flags);
            return -EINVAL;
        }
    }

    /* 释放自旋锁并恢复中断状态 */
    spin_unlock_irqrestore(&led_cdev->spinlock, flags);

    return count;
}

说明:

  1. 停止定时器(case 0):设置led_state = 0,调用del_timer_sync同步停止定时器,避免回调函数继续执行,同时将LED置为低电平;

  2. 启动定时器(case 1):设置led_state = 1,调用mod_timer启动定时器,若定时器已活跃,则更新到期时间;

  3. 修改间隔(case 2):将用户输入的毫秒数(interval)通过msecs_to_jiffies转为jiffies,更新timer_interval,下一次定时器到期后,将使用新的间隔时间;

  4. 自旋锁保护:write函数中修改定时器参数、LED状态,与回调函数中的GPIO操作互斥,确保数据一致性;需注意copy_from_user是可能阻塞的函数,自旋锁持有期间禁止任何阻塞/调度,因此所有用户态拷贝、数据解析放在锁外面。

定时器的资源释放

驱动移除时,需同步删除定时器,避免资源泄漏,代码如下:

定时器的资源释放(位于linux_driver/15_timer/timer_led.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int pdrv_led_remove(struct platform_device *pdev)
{
    struct led_chrdev *led_cdev = platform_get_drvdata(pdev);

    /* 打印平台驱动移除信息 */
    printk("pdrv_led remove\n");

    /* 删除定时器 */
    del_timer_sync(&led_cdev->timer);

    /* 销毁设备节点 */
    device_destroy(class, devno);

    /* 删除字符设备 */
    cdev_del(&led_cdev->dev);

    /* 释放设备号 */
    unregister_chrdev_region(devno, DEV_CNT);

    /* 销毁设备类 */
    class_destroy(class);

    return 0;
}

必须使用del_timer_sync而非del_timer,因为驱动移除时,定时器可能仍在活跃状态,同步删除可等待回调函数执行完毕,避免并发风险和资源泄漏。

12.8.2. 编译设备树和驱动

此部分和自旋锁实验完全一致不作过多说明。

编译得到设备树插件lubancat-led-overlay.dtb和驱动模块timer_led.ko。

12.8.3. 程序运行结果

如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。

12.8.3.1. 实验操作

在本节实验中,鲁班猫系列板卡,系统设备树中均默认使能了 LED 的设备功能,需要关闭设备树的leds节点,可以修改leds节点的 status = "okay";status = "disabled";,然后编译设备树进行替换,也可以在板卡中直接使用以下命令关闭系统leds驱动对LED的控制:

1
2
3
4
5
6
7
8
#心跳灯命名可能为sys_status_led或sys_led,需先确认
ls /sys/class/leds/

#如果为sys_status_led
sudo sh -c 'echo 0 > /sys/class/leds/sys_status_led/brightness'

#如果为sys_led
sudo sh -c 'echo 0 > /sys/class/leds/sys_led/brightness'

将led的亮度调为0,与此同时led的触发条件自动变为none,从而取消leds驱动对LED的控制。

设备树插件加载方法和设备树插件实验完全一致,加载设备树插件并重启板卡后使用以下命令加载驱动:

1
2
3
4
5
6
7
8
#加载驱动
sudo insmod timer_led.ko

#信息输出如下
[  145.169694] led platform driver init
[  145.170026] led platform driver probe
[  145.170070] GPIO_BASE address: 0xFDD60000
[  145.170211] major=236, minor=0

通过驱动代码,最后会在/dev下创建led设备,可以使用echo命令来测试我们的led驱动是否正常。 我们使用以下命令控制灯的亮灭:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#停止定时器,关闭LED
sudo sh -c "echo 0 > /dev/timer_led"

#开启定时器,LED闪烁
sudo sh -c "echo 1 > /dev/timer_led"

#定时器间隔为200ms
sudo sh -c "echo 2 200 > /dev/timer_led"

#定时器间隔为500ms
sudo sh -c "echo 2 500 > /dev/timer_led"

#修改定时器间隔为500ms信息打印如下
[ 4312.904165] pdrv_led open
[ 4312.904281] pdrv_led write
[ 4312.904305] val = 2
[ 4312.904323] Timer interval set to 500 ms
[ 4312.904348] pdrv_led release

12.9. 内核定时器使用注意事项

  • 回调函数上下文约束:普通定时器回调函数运行在原子上下文,禁止睡眠、延时、调度操作(如msleep、schedule、copy_from_user、get_user),禁止使用可中断的同步机制(如mutex_lock_interruptible)。

  • 自旋锁配合使用:若回调函数中操作共享资源(如GPIO寄存器、全局变量),需与其他操作(如write、read函数)通过自旋锁保护,避免并发竞态。

  • 定时器启动与停止规范:启动定时器用mod_timer,停止定时器优先用del_timer_sync,避免使用del_timer导致的并发问题;

  • 到期时间设置:expires参数必须是jiffies类型,不可直接使用毫秒/秒,需通过msecs_to_jiffies等宏转换;避免设置过期时间小于当前jiffies。

  • 私有数据访问:通过from_timer宏获取父结构体指针时,必须确保member参数与父结构体中定时器的成员名一致,否则会导致指针越界、系统崩溃。

  • 资源释放:驱动移除、模块退出时,必须删除定时器,避免定时器残留导致回调函数继续执行,引发资源泄漏或系统异常。

  • 避免递归触发:回调函数中重启定时器时,需确保间隔时间合理,避免定时器频繁触发,占用过多CPU资源。