sched_clock为什么要retry

本文深入探讨了sched_clock机制的工作原理,详细介绍了sched_clock如何通过epoch_cyc和epoch_ns的更新来确保时间读取的准确性,并解释了这些值是如何在sched_clock_postinit函数中初始化以及在update_sched_clock函数中更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面讲了sched_clock 会返回当前从arch_time 读取的时间,但是计算的时候有个epoch_cyc 和 epoch_ns也需要实时更新.
unsigned long long notrace sched_clock(void)
{
    u64 cyc, res;
    unsigned long seq;
    struct clock_read_data *rd;

    do {
        seq = raw_read_seqcount(&cd.seq);
        rd = cd.read_data + (seq & 1);

        cyc = (rd->read_sched_clock() - rd->epoch_cyc) &
              rd->sched_clock_mask;
        res = rd->epoch_ns + cyc_to_ns(cyc, rd->mult, rd->shift);
    } while (read_seqcount_retry(&cd.seq, seq));

    return res;
}
那这两个值是在哪里更新的呢?
答案是sched_clock_postinit
void __init sched_clock_postinit(void)
{
    /*
     * If no sched_clock() function has been provided at that point,
     * make it the final one one.
     */
    if (cd.actual_read_sched_clock == jiffy_sched_clock_read)
        sched_clock_register(jiffy_sched_clock_read, BITS_PER_LONG, HZ);

    update_sched_clock();

    /*
     * Start the timer to keep sched_clock() properly updated and
     * sets the initial epoch.
     */
    hrtimer_init(&sched_clock_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    sched_clock_timer.function = sched_clock_poll;
    hrtimer_start(&sched_clock_timer, cd.wrap_kt, HRTIMER_MODE_REL);
}
这个函数会初始化一个high time,这个high time的expiry time,也就是第二个参数是在sched_clock_register的时候更新的
    /* Calculate how many nanosecs until we risk wrapping */
    wrap = clocks_calc_max_nsecs(new_mult, new_shift, 0, new_mask, NULL);
    cd.wrap_kt = ns_to_ktime(wrap);
这个high time的callback函数是sched_clock_poll。
static enum hrtimer_restart sched_clock_poll(struct hrtimer *hrt)
{
    update_sched_clock();
    hrtimer_forward_now(hrt, cd.wrap_kt);

    return HRTIMER_RESTART;
}
这个函数会调用hrtimer_forward_now 来让持续推动high time向前,会在update_sched_clock->update_clock_read_data 中更新epoch_cyc 和 epoch_ns
static void update_clock_read_data(struct clock_read_data *rd)
{
    /* update the backup (odd) copy with the new data */
    cd.read_data[1] = *rd;

    /* steer readers towards the odd copy */
    raw_write_seqcount_latch(&cd.seq);

    /* now its safe for us to update the normal (even) copy */
    cd.read_data[0] = *rd;

    /* switch readers back to the even copy */
    raw_write_seqcount_latch(&cd.seq);
}
注意这个函数更新的时候分两次更新。
static inline void raw_write_seqcount_latch(seqcount_t *s)
{
       smp_wmb();      /* prior stores before incrementing "sequence" */
       s->sequence++;
       smp_wmb();      /* increment "sequence" before following stores */
}
在raw_write_seqcount_latch 函数中会增加s->sequence++
这也是为什么在sched_clock 会retry的原因。
unsigned long long notrace sched_clock(void)
{
    u64 cyc, res;
    unsigned long seq;
    struct clock_read_data *rd;

    do {
        seq = raw_read_seqcount(&cd.seq);
        rd = cd.read_data + (seq & 1);

        cyc = (rd->read_sched_clock() - rd->epoch_cyc) &
              rd->sched_clock_mask;
        res = rd->epoch_ns + cyc_to_ns(cyc, rd->mult, rd->shift);
    } while (read_seqcount_retry(&cd.seq, seq));

    return res;
}
在read_seqcount_retry->__read_seqcount_retry 中retry的条件如下。如果前两次读的s->sequence 不一样,就继续读。
static inline int __read_seqcount_retry(const seqcount_t *s, unsigned start)
{
    return unlikely(s->sequence != start);
}

不一样的原因就是sched_clock在读的时候update_clock_read_data也在更新s->sequence。因此会持续读,直到update_clock_read_data 更新完毕
<think>我们正在讨论Linux内核中的sched_clock函数。根据提供的引用,我们可以总结如下: 1. sched_clock提供了一种快速获取当前时间戳的方法,通常用于调度和性能分析[^1]。 2. 它被设计为低开销、高精度的时钟源,用于测量时间间隔,如进程运行时间、等待时间等[^3]。 3. 在多核系统中,sched_clock需要处理不同CPU之间的时钟同步问题[^2][^4]。 4. 当系统启动时,sched_clock可能不稳定(sched_clock_stable),因此内核有相应的处理机制[^1]。 5. sched_clock返回的时间单位是纳秒,但实际精度取决于硬件[^4]。 原理: - sched_clock通常基于硬件提供的计数器(如TSC、ARM的CNTVCT等),这些计数器在CPU上独立运行。 - 由于每个CPU的计数器可能不同步,因此内核需要维护一个偏移量(__sched_clock_offset)来调整,使得所有CPU返回的时间在同一个时间线上[^1][^4]。 - 在调度过程中,内核使用sched_clock来记录时间戳,例如在进程切换时记录运行时间(clock_task)[^3]。 - 当需要跨CPU比较时间时,内核会通过补偿机制(如引用[2]和[4]中描述的)来调整时间戳,以减小不同CPU间计数器的差异。 此外,引用[4]提到,在计算目标CPU上的时间戳时,会使用如下公式: $$ \text{now} = (\text{sched\_clock}() - \text{this\_rq}() \rightarrow \text{timestamp\_last\_tick}) + \text{rq} \rightarrow \text{timestamp\_last\_tick} $$ 这实际上是将本地CPU的时间戳调整为目标CPU的时间戳,通过减去本地CPU最近一次tick的时间戳,再加上目标CPU最近一次tick的时间戳。 总结:sched_clock是内核中一个关键的计时函数,它提供了高精度、低开销的时间戳,用于调度、性能统计等。其核心原理是利用硬件计数器和软件偏移量来提供一致的时间视图。 §§相关问题§§ 1. sched_clock和gettimeofday有什么区别? 2. 在多核系统中,如何确保sched_clock的同步? 3. sched_clock的精度如何?受哪些因素影响? 4. 什么是sched_clock_stable?它在什么情况下会被设置? 5. 在进程调度中,sched_clock具体用于哪些场景?</think>### Linux内核中`sched_clock`的作用与原理 #### 一、核心作用 `sched_clock`是Linux内核中**高精度、低开销**的时间源,主要用于: 1. **调度计时** - 统计进程运行时间(如`rq->clock_task`)[^3] - 计算调度延迟(如唤醒延迟、抢占延迟) - 记录进程切换时间戳(例如`now = sched_clock()`)[^4] 2. **性能分析** - 提供纳秒级时间戳用于性能追踪(如ftrace) 3. **时间补偿** - 跨CPU同步时校正时间偏差(如`now = (sched_clock() - this_rq->timestamp_last_tick) + rq->timestamp_last_tick`)[^4] #### 二、实现原理 1. **硬件基础** - 基于CPU的**周期计数器**(如x86的TSC、ARM的CNTVCT) - 直接读取寄存器,避免系统调用开销 $$ \text{time} = \frac{\text{cycle\_count}}{\text{frequency}} \times 10^9 \text{ (纳秒)} $$ 2. **多核同步机制** - 每个CPU维护独立偏移量(`__sched_clock_offset`)[^1] - 通过CMPSXCHG原子操作同步远程CPU时钟: ```c if (cmpxchg64(ptr, old_val, val) != old_val) goto again; // 引用[2] ``` - 当检测到远程时钟偏差时动态补偿[^4] 3. **运行时优化** - 启动阶段:`static_branch_unlikely(&sched_clock_running)`检查避免未初始化调用[^1] - 稳定状态:若`sched_clock_stable=1`则直接使用硬件时钟 - 中断统计:`clock_task`扣除中断时间以精确统计任务执行[^3] #### 三、关键特性对比 | 特性 | `sched_clock` | 传统时钟(如jiffies) | |--------------|------------------------|----------------------| | 精度 | 纳秒级 | 毫秒级 | | 开销 | 极低(直接读寄存器) | 中等(需内核调用) | | 多核一致性 | 动态补偿偏移量 | 全局统一 | | 用途 | 调度/性能关键路径 | 通用时间管理 | #### 四、典型应用场景 1. **调度器决策** ```c // 计算进程实际运行时间 u64 runtime = sched_clock() - p->last_run_time; ``` 2. **负载均衡** 比较不同CPU队列的时间戳以迁移任务[^4] 3. **实时性监控** 检测中断延迟或调度延迟超限 > **稳定性注意**:当系统暂停(如挂起)时,`sched_clock_stable`会被置0,此时强制使用慢速时钟源确保时间连续性[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值