内核节拍驱动

当前Linux多采用无节拍方案,并支持高精度定时器,内核的配置一般会使能NO_HZ(即无节拍,或者说动态节拍)和HIGH_RES_TIMERS。无节拍并不是说系统中没有时钟节拍,而是说这个节拍不再像以前那样周期性地产生。

SoC底层的定时器被实现为一个clock_event_device和clocksource形式的驱动。在clock_event_device结构体中,实现其set_mode()和set_next_event()成员函数;在clocksource结构体中,主要实现read()成员函数。而在定时器中断服务程序中调用clock_event_device的event_handler()成员函数。

static irqreturn_t xxx_timer_interrupt(int irq, void *dev_id)
{
	struct clock_event_device *ce = dev_id;
	// event_handler()成员函数的具体工作是Linux内核根据Linux内核配置和运行情况自行设置的
	ce->event_handler(ce);
	
	return IRQ_HANDLED;
}

/* read 64-bit timer counter */
static cycle_t xxx_timer_read(struct clocksource *cs)
{
	u64 cycles;
	
	/* read the 64-bit timer counter */
	cycles = readl_relaxed(xxx_timer_base + LATCHED_HI);
	cycles=(cycles<<32)|readl_relaxed(xxx_timer_base + LATCHED_LO);
	
	return cycles;
}

static int xxx_timer_set_next_event(unsigned long delta,struct clock_event_device *ce)
{
	unsigned long now, next;
	now = readl_relaxed(xxx_timer_base + LATCHED_LO);
	next = now + delta;
	writel_relaxed(next, xxx_timer_base + SIRFSOC_TIMER_MATCH_0);
	...
}

static void xxx_timer_set_mode(enum clock_event_mode mode, struct clock_event_device *ce)
{
	switch (mode) {
	case CLOCK_EVT_MODE_PERIODIC:
	…
	case CLOCK_EVT_MODE_ONESHOT:
	…
	case CLOCK_EVT_MODE_SHUTDOWN:
	…
	case CLOCK_EVT_MODE_UNUSED:
	case CLOCK_EVT_MODE_RESUME:
	break;
	}
}
static struct clock_event_device xxx_clockevent = {
	.name = "xxx_clockevent"
	.rating = 200,
	.features = CLOCK_EVT_FEAT_ONESHOT,
	.set_mode = xxx_timer_set_mode,
	.set_next_event = xxx_timer_set_next_event,
};

static struct clocksource xxx_clocksource = {
	.name = "xxx_clocksource"
	.rating = 200,
	.mask = CLOCKSOURCE_MASK(64),
	.flags = CLOCK_SOURCE_IS_CONTINUOUS,
	.read = xxx_timer_read,
	.suspend = xxx_clocksource_suspend,
	.resume = xxx_clocksource_resume,
};

static struct irqaction xxx_timer_irq = {
	.name = "xxx_tick"
	.flags = IRQF_TIMER,
	.irq = 0,
	.handler = xxx_timer_interrupt,
	.dev_id = &xxx_clockevent,
};

static void __init xxx_clockevent_init(void)
{
	clockevents_calc_mult_shift(&xxx_clockevent, CLOCK_TICK_RATE, 60);
	// 设置xxx_timer_set_next_event参数delta能接受的值范围
	xxx_clockevent.max_delta_ns = clockevent_delta2ns(-2, &xxx_clockevent);
	xxx_clockevent.min_delta_ns = clockevent_delta2ns(2, &xxx_clockevent);
	
	xxx_clockevent.cpumask = cpumask_of(0);
	clockevents_register_device(&xxx_clockevent);
}

/* initialize the kernel jiffy timer source */
static void __init xxx_timer_init(void)
{
	…
	BUG_ON(clocksource_register_hz(&xxx_clocksource, CLOCK_TICK_RATE));
	BUG_ON(setup_irq(xxx_timer_irq.irq, &xxx_timer_irq));
	xxx_clockevent_init();
}
struct sys_timer xxx_timer = {
	.init = xxx_timer_init,
};

这些函数的结合使得ARM Linux内核底层所需要的时钟得以运行。下面举一个典型的场景,假定定时器的晶振时钟频率为1MHz(即计数器每加1等于1μs),应用程序通过nanosleep()API睡眠100μs,内核会据此换算出下一次定时器中断的delta值为100,并间接调用xxx_timer_set_next_event()去设置硬件让其在100μs后产生中断。100μs后,中断产生,xxx_timer_interrupt()被调用,event_handler()会间接唤醒睡眠的进程并导致nanosleep()函数返回,从而让用户进程继续。

对于多核处理器来说,一般的做法是给每个核分配一个独立的定时器,各个核根据自身的运行情况动态地设置自己时钟中断发生的时刻。而比较低效率的方法则是只给CPU0提供定时器,由CPU0将定时器中断通过IPI(Inter Processor Interrupt,处理器间中断)广播到其他核。