Linux 内核定时器

概述

Linux的内核定时器依赖于内核软中断,当系统硬件中断退出时会便利软件中断的使能位并执行其关联的回掉函数

复制代码
//内核定时器初始化会打开内核TIMER_SOFTIRQ软中断,执行时会执行到run_timer_softirq函数
void
__init init_timers(void) { open_softirq(TIMER_SOFTIRQ, run_timer_softirq) }
//run_timer_softirq函数分析
void run_timer_softirq(struct softirq_action *h)
{
    struct tvec_base *base = this_cpu_ptr(&tvec_bases);
    struct tvec_base是个Per-CPU变量,只在本地CPU有效,
    void __run_timers(struct tvec_base *base)
    {
        spin_lock_irq(&base->lock)
        //找到超时的timer,并将其赋值给base->running_timer,这个值会在多处理器删除时使用
        base->running_timer = timer
        detach_expired_timer(timer, base)
        //将timer从base链表中删除,我们可以看到一个定时器如果没有在链表中,不能说明它已经执行完毕
        spin_unlock_irq(&base->lock)
        call_timer_fn(timer, fn, timer->data)
        spin_lock_irq(&base->lock)
        base->running_timer = NULL
        spin_unlock_irq(&base->lock)
    }
    return __run_timers(base);
}
复制代码

定义定时器

复制代码
//方法1
DEFINE_TIMER(timer, cb, expires, data)
//方法2
struct timer_list timer;
setup_timer(&timer, (*cb)(data), data)
timer.expires = jiffies + 5*HZ
//方法3
struct timer_list timer
init_timer(&timer)
timer.timer.expires = jiffies + 5*HZ
timer.timer.data = data
timer.timer.function = cb
复制代码

添加定时器

//添加定时器是将一个定时器挂到所在链表中,我们可以看到,其最终还是调用了__mod_timer函数。
//__mod_timer函数是mod_timer的底层调用,我们在后面分析
void
add_timer(struct timer_list *timer) { __mod_timer(timer, timer->expires) }

删除定时器

//通过del_timer函数注销一个定时器,如果定时器存在返回1,否则返回0
//del_timer_sync是用在SMP系统中的,在单核系统中和del_timer是一样的,
//del_timer_sync会判断当前定时器是否在其他CPU上运行,如果运行等待其运行完成后删除此定时器
int
del_timer(struct timer_list *timer) int del_timer_sync(struct timer_list *timer)

定时器挂起

//用于判断一个定时器是否存在在链表中,但是此定时器有可能正在运行
int
timer_pending(struct timer_list *timer)

函数分析

复制代码
int __mod_timer(struct timer_list *timer)
{
    base = lock_timer_base(timer)
    //获取tvec_base变量,我们知道base为PerCPU变量,这个函数也顺便给base加锁。
    if (timer_pending(timer)) {
        deteach_timer(timer, 0)
        ret = 1
    }
    //如果timer已经存在,则删除,此处的操作是在加锁状态先的,因此和softirq里面不会重入
    new_base = __get_cpu_var(tvec_base)
    if (base != new_base) {
        //我们不能修改一个正在运行的timer的base,否则del_timer_sync会出问题,后面我们再说这个。
        if (base->running_timer != timer) {
            timer->base =NULL;
            //由于本地中断已经在76行处关闭,因此不开本地中断
            spin_unlock(&base->lock)
            base = new_base;
            //由于前面已经禁用了本地中断,因此这只需加锁
            spin_lock(&base->lock)
            timer->base = base
        }
    }
    //如上代码如果当前timer base不是timer->base则更新timer->base
    internal_add_timer(base, timer)
    //将timer加入本地base中
    spin_unlock_irqrestore(&base->lock, flag)
    //1,释放锁
    //2,恢复中断
    //3,启用内核抢占
    //由以上代码我们可以知道,同一个timer只能被提交一次,
    //但是同一个timer是有可能在不同的cpu上同时运行的,我们可以试想这个一个场景,
    //CPU0注册一个timer,并已经在CPU0上运行了,
    //CPU1也注册了这个timer,因为运行的timer已经在链表上删除了且没有获取base->lock,
    //因此lock_timer_base和timer_pending都可以向下执行并注册成功
    //假设此timer的时间非常短,CPU1的timer在注册完成后由于系统的一次中断立刻执行,
    //此时同一个timer在CPU0和CPU1并发
    //以上是个人的一个理解,如果错误,还请指出
}
//由以上可以知道,在哪个CPU运行增加timer的代码,则timer的回掉函哪个CPU上运行。
del_timer(struct timer_list *timer)
{
    if (timer_pending(timer)) {
        base = lock_timer_base(timer, &flag);
        ret = detach_if_pending(timer, base, true);
        spin_unlock_irqrestore(&base->lock, flag);
    }
    return ret
    //由此函数我们可以知道,此函数只判断timer是否在链表中,然后就直接删除,但是删除时此函数的回掉有可能正在运行,
    //在判断timer_pending(timer)时,理论上来说应该加锁保护,但是此处没有加锁保护,是因为在detach_if_pending中会再次判断是否pending如果pending则函数直接退出,
}
del_timer_sync(struct timer_list *timer)
{
    for(;;) {
        int try_to_del_timer_sync(timer)
        {
            base = lock_timer_base(timer, &flag);
            //判断当前timer是否在运行,如果在运行则等待
            if (base->running_timer != timer) {
                //detach_if_pending如果timer pending返回0
                //如果timer删除成功返回1
                ret = detach_if_pending(timer, base, true);
            }
            spin_unlock_irqrestore(&base->lock, flags)
            return ret;
        
        }
        //如果timer删除成功返回0或者1,负责等待删除
        int ret = try_to_del_timer_sync(timer);
        if (ret >= 0)
            return ret;
        cpu_relax();
    }
}
复制代码

 

posted on   sudochen  阅读(1028)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律

导航

< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5
点击右上角即可分享
微信分享提示