jiffies和HZ的相关学习
最近学习kernel timer的时候,看到了jiffies。
刚好对其不是很熟悉,趁机学习了一把。
参考:https://www.cnblogs.com/arnoldlu/p/7234443.html
0、 概述
HZ是时钟中断产生的频率,即1秒钟产生多少时钟中断。
jiffies是启动后产生的时钟中断的总计数。
时钟初始化的时候,会注册时钟中断,并根据HZ设置时钟中断产生的频率,即多少个tick会产生一个时钟中断。
时钟中断的处理函数中,会去更新jiffies。
1、 jiffies和jiffies_64
在32位系统中,jiffies即是jiffies_64的低32bit.
参考:arch/arm/kernel/vmlinux.lds.S
#ifndef __ARMEB__ // 如果不是Big endian,即为little endian
jiffies = jiffies_64; // 直接取低32bit
#else
jiffies = jiffies_64 + 4; // 偏移4字节,取低32bit。注意这里的+4是地址+4,不是值+4。
#endif
在64位系统中,jiffies即是jiffies_64。
参考:arch/arm64/kernel/vmlinux.lds.S
jiffies = jiffies_64;
2、 jiffies_64的初始化
kernel/timer.c:
u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;
include/linux/jiffies.h:
/*
* Have the 32 bit jiffies value wrap 5 minutes after boot
* so jiffies wrap bugs show up earlier.
*/
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))
看注释可知,将jiffies_64初始化为-300*HZ,目的是让其在启动后5分钟即出现溢出,以尽早暴露问题。
5分钟是怎么来的:
假如HZ为100,即时钟中断产生的频率为1秒钟100次。
-300 * 100 = -30000 , 32位系统里面,用unsinged long表示,即为:0xFFFFFFFFFFFF8AD0。
xFFFFFFFFFFFFFFFF - 0xFFFFFFFFFFFF8AD0 = 0x752F,即29999.
也就是说,再经过30000个时钟中断,jiffies即出现溢出。30000个时钟中断,即300*HZ,即300秒。
3、 jiffies是如何更新的
从以上分析可知,jiffies只是指向了jiffies_64的低4字节,增加jiffies,同时也会增加jiffies_64。
之前不了解这个情况,代码中找了半天增加jiffies的地方,结果没找到。
kernel/time/Timekeeping.c
/*
* Must hold jiffies_lock
*/
void do_timer(unsigned long ticks)
{
jiffies_64 += ticks; // 追加jiffies_64
calc_global_load(ticks);
}
do_timer被调用的地方:
① Tick-common.c (kernel\time):87: do_timer(1);
② Tick-sched.c (kernel\time):83: do_timer(++ticks);
③ Timekeeping.c (kernel\time):2386: do_timer(ticks);
①的调用路径(mt2712平台):
mtk_timer_interrupt -> tick_handle_periodic -> tick_periodic -> do_timer(1)
②的调用路径:
tick_check_idle-->tick_check_nohz-->tick_nohz_update_jiffies-->tick_do_update_jiffies64
tick_nohz_idle_enter/tick_nohz_irq_exit-->tick_nohz_stop_sched_tick-->tick_do_update_jiffies64
tick_setup_sched_timer-->tick_sched_timer-->tick_do_update_jiffies64
③的调用路径:
timer_interrupt -> timer_tick -> xtime_update(1) -> do_timer(ticks);
arm架构,在arch/arm/kernel/Time.c中,有timer_tick的定义,并且只有在部分arm平台,此函数才被调用。
arm64架构中,无timer_tick定义。
①有点意思,看起来像是注册一个timer 中断,定周期的更新jiffies。
mtk_timer_interrupt作为中断处理函数,在mtk_timer_init中被注册。
drivers/clocksource/Mtk_timer.c
注册中断的时候,会根据dts配置,设置时钟中断源clock,并根据HZ,计算出时钟中断的触发频率。
static int __init mtk_timer_init(struct device_node *node)
{
struct mtk_clock_event_device *evt;
struct resource res;
unsigned long rate_src = 0, rate_evt = 0;
struct clk *clk_src, *clk_evt, *clk_bus;
evt = kzalloc(sizeof(*evt), GFP_KERNEL);
if (!evt)
return -ENOMEM;
evt->clk32k_exist = false;
evt->dev.name = "mtk_tick";
evt->dev.rating = 300;
/*
* CLOCK_EVT_FEAT_DYNIRQ: Core shall set the interrupt affinity
* dynamically in broadcast mode.
* CLOCK_EVT_FEAT_ONESHOT: Use one-shot mode for tick broadcast.
*/
evt->dev.features = CLOCK_EVT_FEAT_PERIODIC |
CLOCK_EVT_FEAT_ONESHOT |
CLOCK_EVT_FEAT_DYNIRQ;
evt->dev.set_state_shutdown = mtk_clkevt_shutdown;
evt->dev.set_state_periodic = mtk_clkevt_set_periodic;
evt->dev.set_state_oneshot = mtk_clkevt_shutdown;
evt->dev.tick_resume = mtk_clkevt_shutdown;
evt->dev.set_next_event = mtk_clkevt_next_event;
evt->dev.cpumask = cpu_possible_mask;
evt->gpt_base = of_io_request_and_map(node, 0, "mtk-timer");
if (IS_ERR(evt->gpt_base)) {
pr_err("Can't get resource\n");
goto err_kzalloc;
}
evt->dev.irq = irq_of_parse_and_map(node, 0);
if (evt->dev.irq <= 0) {
pr_err("Can't parse IRQ\n");
goto err_mem;
}
clk_bus = of_clk_get_by_name(node, "bus");
if (!IS_ERR(clk_bus))
clk_prepare_enable(clk_bus);
clk_src = of_clk_get(node, 0);
if (IS_ERR(clk_src)) {
pr_err("Can't get timer clock\n");
goto err_irq;
}
if (clk_prepare_enable(clk_src)) {
pr_err("Can't prepare clock\n");
goto err_clk_put_src;
}
/*
这个地方是个13M的clock
timer: timer@10008000 {
compatible = "mediatek,mt2712-timer",
"mediatek,mt6577-timer";
reg = <0 0x10008000 0 0x80>;
interrupts =
clocks = <&system_clk>;
clock-names = "clk13m";
};
system_clk: dummy13m {
compatible = "fixed-clock";
clock-frequency = <13000000>;
#clock-cells = <0>;
};
*/
rate_src = clk_get_rate(clk_src);
clk_evt = of_clk_get_by_name(node, "clk32k"); // 是不是有32k clock
if (!IS_ERR(clk_evt)) {
evt->clk32k_exist = true;
clk_prepare_enable(clk_evt);
rate_evt = clk_get_rate(clk_evt);
} else {
rate_evt = rate_src; // 没有32k clock的话,使用rate_src
}
if (request_irq(evt->dev.irq, mtk_timer_interrupt,
IRQF_TIMER | IRQF_IRQPOLL, "mtk_timer", evt)) { // 注册timer中断,关键参数evt
pr_err("failed to setup irq %d\n", evt->dev.irq);
if (evt->clk32k_exist)
goto err_clk_disable_evt;
else
goto err_clk_disable_src;
}
evt->ticks_per_jiffy = DIV_ROUND_UP(rate_evt, HZ); // 时钟为rate_evt,即1秒13M个tick,HZ是1秒内时钟中断数量,(rate_evt+(HZ-1))/HZ,得到1个jiffy中有多少个tick。
/* Configure clock source */
mtk_timer_setup(evt, GPT_CLK_SRC, TIMER_CTRL_OP_FREERUN,
TIMER_CLK_SRC_SYS13M, true);
clocksource_mmio_init(evt->gpt_base + TIMER_CNT_REG(GPT_CLK_SRC),
node->name, rate_src, 300, 32,
clocksource_mmio_readl_up);
/* Configure clock event */
if (evt->clk32k_exist)
mtk_timer_setup(evt, GPT_CLK_EVT, TIMER_CTRL_OP_REPEAT,
TIMER_CLK_SRC_RTC32K, false);
else
mtk_timer_setup(evt, GPT_CLK_EVT, TIMER_CTRL_OP_REPEAT,
TIMER_CLK_SRC_SYS13M, false);
clockevents_config_and_register(&evt->dev, rate_evt, 0x3,
0xffffffff);
mtk_timer_enable_irq(evt, GPT_CLK_EVT);
return 0;
err_clk_disable_evt:
clk_disable_unprepare(clk_evt);
clk_put(clk_evt);
err_clk_disable_src:
clk_disable_unprepare(clk_src);
err_clk_put_src:
clk_put(clk_src);
err_irq:
irq_dispose_mapping(evt->dev.irq);
err_mem:
iounmap(evt->gpt_base);
of_address_to_resource(node, 0, &res);
release_mem_region(res.start, resource_size(&res));
err_kzalloc:
kfree(evt);
return -EINVAL;
}