// 切换低分辨率动态时钟模式、或高分辨率模式
// 调用路径:run_timer_softirq->hrtimer_run_pending
// 函数任务:
// 1.如果高分辨率定时器框架已经激活,则直接返回
// 2.切换到高分辨率模式的条件:
// 2.1 没有开启低分辨率动态时钟
// 2.2 有高分辨率的clocksource
// 2.3 clockevent设备支持单触发模式
// 3.切换到低分辨率动态时钟的条件:
// 3.1 启动时,没有激活高分辨率率定时框架
// 3.2 clockevent设备支持单触发模式
1.1 void hrtimer_run_pending(void)
{
if (hrtimer_hres_active())
return;
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
{
//切换到高分辨率模式
hrtimer_switch_to_hres();
}
}
// 切换低分辨率模式的动态时钟
// 函数任务:
// 1.更新tick device单触发方式,安装nohz事件处理函数
// 2.初始化动态时钟数据结构
// 2.1 标识动态时钟当前为低分辨率模式
// 2.2 动态时钟通过单调时钟基础管理
// 2.3 更新动态时钟到期时间
// 3.更新动态时钟,tick device到期时间
2.1 static void tick_nohz_switch_to_nohz(void)
{
struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
ktime_t next;
local_irq_disable();
//更新tick device单触发方式,安装nohz事件处理函数
if (tick_switch_to_oneshot(tick_nohz_handler)) {
local_irq_enable();
return;
}
//低分辨率动态时钟标识
ts->nohz_mode = NOHZ_MODE_LOWRES;
//低分辨率动态时钟依赖单调时钟
hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
//下次到期时间
next = tick_init_jiffy_update();
//更新动态时钟,tick device到期时间
for (;;) {
hrtimer_set_expires(&ts->sched_timer, next);
if (!tick_program_event(next, 0))
break;
next = ktime_add(next, tick_period);
}
local_irq_enable();
}
// 低分辨率动态事件处理函数
// 函数任务:
// 1.选择cpu负责更新jiffies
// 2.如果距离上次更新jiffies已经有1 jiffy,更新jiffies
// 3.如果动态时钟已经停止,说明当前在idle状态,喂狗,防止发生softlockup
// 4.更新当前进程的运行时间
// 6.更新动态时钟的下个周期时间
// 7.重编程tick device在动态时钟的下个周期时间到期
// 注:tick_do_timer_cpu 保存负责更新jiffies的cpu
2.2 static void tick_nohz_handler(struct clock_event_device *dev)
{
struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
struct pt_regs *regs = get_irq_regs();
int cpu = smp_processor_id();
ktime_t now = ktime_get();
//保证tick device在最近的时间内不会到期
dev->next_event.tv64 = KTIME_MAX;
//选择cpu负责更新jiffies
if (unlikely(tick_do_timer_cpu == TICK_DO_TIMER_NONE))
tick_do_timer_cpu = cpu;
//更新jiffies
if (tick_do_timer_cpu == cpu)
tick_do_update_jiffies64(now);
//动态时钟停止,说明当前在idle状态,喂狗,防止发生softlockup
if (ts->tick_stopped) {
touch_softlockup_watchdog();
ts->idle_jiffies++;
}
//更新进程的运行时间
update_process_times(user_mode(regs));
//重编程clockevent设备
while (tick_nohz_reprogram(ts, now)) {
now = ktime_get();
tick_do_update_jiffies64(now);
}
}
// 切换高分辨率模式
// 函数任务:
// 1.更新clockevent为单触发模式,安装高分辨率事件处理程序
// 2.初始化动态时钟
// 2.1 更新高分辨率激活标志
// 2.2 更新时钟基础为高分辨率标志
// 3.初始化动态时钟
// 4.更新tick device到期时间为时钟基础中所有hrtimer最短的到期时间
3.1 static int hrtimer_switch_to_hres(void)
{
int cpu = smp_processor_id();
struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
unsigned long flags;
local_irq_save(flags);
//更新clockevent为单触发模式,安装高分辨率事件处理程序
if (tick_init_highres()) {
local_irq_restore(flags);
return 0;
}
//高分辨率激活标志
base->hres_active = 1;
base->clock_base[CLOCK_REALTIME].resolution = KTIME_HIGH_RES;
base->clock_base[CLOCK_MONOTONIC].resolution = KTIME_HIGH_RES;
//初始化动态时钟
tick_setup_sched_timer();
//更新tick device到期时间为时钟基础中所有hrtimer最短的到期时间
retrigger_next_event(NULL);
local_irq_restore(flags);
return 1;
}
// 更新clockevnet为单触发模式,安装事件处理程序
// 调用路径:hrtimer_switch_to_hres->tick_switch_to_oneshot
3.2 int tick_init_highres(void)
{
return tick_switch_to_oneshot(hrtimer_interrupt);
}
// 更新clockevnet为单触发模式,安装事件处理程序
// 调用路径:hrtimer_switch_to_hres->...->tick_switch_to_oneshot
// 函数任务:
// 1.更新设备为单触发方式
// 2.安装事件处理程序
// 3.更新广播设备为单触发方式
3.3 int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *))
{
struct tick_device *td = &__get_cpu_var(tick_cpu_device);
struct clock_event_device *dev = td->evtdev;
//更新设备为单触发方式
td->mode = TICKDEV_MODE_ONESHOT;
//安装事件处理程序
dev->event_handler = handler;
//编程clockevent设备为单触发方式
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
//切换广播设备为单触发方式
tick_broadcast_switch_to_oneshot();
return 0;
}
// 初始化动态时钟
// 调用路径:hrtimer_switch_to_hres->tick_setup_sched_timer
// 函数任务:
// 1.初始化动态时钟的hrtimer
// 2.安装动态时钟的回调函数
// 3.更新动态时钟下一个jiffies到期
3.4 void tick_setup_sched_timer(void)
{
struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
ktime_t now = ktime_get();
u64 offset;
//初始化hrtimer
hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
//动态时钟的回调函数
ts->sched_timer.function = tick_sched_timer;
//下一个jiffies到期
hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());
offset = ktime_to_ns(tick_period) >> 1;
do_div(offset, num_possible_cpus());
offset *= smp_processor_id();
hrtimer_add_expires_ns(&ts->sched_timer, offset);
//编程启动动态时钟
for (;;) {
hrtimer_forward(&ts->sched_timer, now, tick_period);
hrtimer_start_expires(&ts->sched_timer,
HRTIMER_MODE_ABS_PINNED);
if (hrtimer_active(&ts->sched_timer))
break;
now = ktime_get();
}
....
}