Linux 低精度和高精度 定时器分析

怀着用户态定时器、内核态定时器、内核抽象硬件定时器之间的联系,经过网上查阅、代码翻阅,遂有此文。

注:使用内核源码linux-2.6.22分析的(低版本的内核分析起来更容易)。内核从2.6.16开始加入了高精度定时器架构。

用户态定时器(struct k_itimer)相关接口:timer_create、timer_settime、timer_delete等。

内核态的定时器分为低精度定时器(struct timer_list)、高精度定时器(struct hrtimer)、抽象硬件定时器(struct clock_event_device)。

低精度定时器的相关接口:init_timer、mod_timer、del_timer等。

高精度定时器的相关接口:hrtimer_init、hrtimer_cancel等。

抽象硬件定时器(struct clock_event_device)的相关接口:clockevents_register_device、clockevents_release_device等。

这里从硬件定时器(struct clock_event_device)由下往上进行梳理。

之前一直有个问题困扰着我(在下面进行了阐述):

既然内核用struct clock_event_device抽象出timer硬件设备,假设时钟频率为f,那么设置timer硬件自动重载寄存器(TIMx_ARR)为(1/HZ)/(1/f)=f/HZ的时机又是在哪呢?想着无外乎通过抽象出的结构体struct clock_event_device 设置timer硬件自动重载寄存器(TIMx_ARR)。

注 HZ:内核配置项CONFIG_HZ

一:clock_event_device & tick_device 初始化

struct clock_event_device {
	const char		*name;
	unsigned int		features;
	unsigned long		max_delta_ns;
	unsigned long		min_delta_ns;
	unsigned long		mult;
	int			shift;
	int			rating;
	int			irq;
	cpumask_t		cpumask;
	int			(*set_next_event)(unsigned long evt,
						  struct clock_event_device *);
	void			(*set_mode)(enum clock_event_mode mode,
					    struct clock_event_device *);
	void			(*event_handler)(struct clock_event_device *);
	void			(*broadcast)(cpumask_t mask);
	struct list_head	list;
	enum clock_event_mode	mode;
	ktime_t			next_event;
};

为了理解低精度定时器和高精度定时器,这里还要引入结构体struct tick_device。

struct tick_device {
	struct clock_event_device *evtdev;
	enum tick_device_mode mode;
};

可以看到是基于struct clock_event_device的封装。它们之间的关系借用linuxer前辈的话就是“低精度timer依赖周期性tick,而hrtimer不依赖周期性tick”。 

参考链接:时间子系统 - 蜗窝科技

从低精度定时器(struct timer_list)定义的数据结构看不出来和struct clock_event_device的联系,关联处在struct tick_device发生中断时执行的中断处理函数。

struct timer_list {
	struct list_head entry;
	unsigned long expires;

	void (*function)(unsigned long);
	unsigned long data;

	struct tvec_t_base_s *base;
#ifdef CONFIG_TIMER_STATS
	void *start_site;
	char start_comm[16];
	int start_pid;
#endif
};

struct tick_device是基于struct clock_event_device的封装,所以就是struct clock_event_device的中断函数。要知道struct clock_event_device注册中断函数就要从内核启动后clock_event_device和tick_device的初始化说起了。

从start_kernel内核启动开始:

kernel/time/tick-common.c
static struct notifier_block tick_notifier = {
	.notifier_call = tick_notify,
};

init/main.c
asmlinkage void __init start_kernel(void)
{
    ...
	tick_init();
    ...
	init_timers();
	hrtimers_init();
    ...
	time_init();
    ...

}

//函数调用栈
tick_init();
    clockevents_register_notifier(&tick_notifier);


time_init();
    system_timer->init();
        clockevents_register_device(&clockevent_xxx);
            clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev);
                notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
                    nb->notifier_call(nb, val, v);
                        tick_check_new_device(dev);
                            tick_setup_device(td, newdev, cpu, cpumask);
                                tick_setup_periodic(newdev, 0);
                                    tick_set_periodic_handler(dev, broadcast);                                

                        

不同machine定义各自的time.c,不管怎么说都会殊途同归实现结构体struct sys_timer和函数指针init的初始化,在init函数中调用函数clockevents_register_device()注册struct clock_event_device,接着调用前面tick_init注册的tick_notifier.notifier_call,即tick_notify,根据CLOCK_EVT_NOTIFY_ADD调用tick_check_new_device函数进行tick device的初始化,如果已经已经初始化好的的tick device是否有更换更高精度clock event device的需求,这里只关注tick device(TICKDEV_MODE_PERIODIC)的初始化。

到此为止完成了clock_event_device & tick_device 初始化,但一开始的疑惑:timer硬件自动重载寄存器(TIMx_ARR)初始化为(1/HZ)/(1/f)=f/HZ的位置?

翻了下代码有三种初始化方式:

1.在不同machine下的sys_timer.init实现timer硬件自动重载寄存器(TIMx_ARR)初始化。

arch/arm/mach-davinci/time.c
static void __init davinci_timer_init(void)
{
	static char err[] __initdata = KERN_ERR
		"%s: can't register clocksource!\n";

	/* init timer hw */
	timer_init();
    ..
}
struct sys_timer davinci_timer = {
	.init   = davinci_timer_init,
};

函数调用栈:
time_init();
    system_timer->init();
        timer_init();
            timer32_config(&timers[i]);
                	/* reset counter to zero, set new period */
	                davinci_writel(0, t->tim_reg);
                	davinci_writel(t->period, t->prd_reg);

2.在不同machine下的clock_event_device.set_next_event实现timer硬件自动重载寄存器(TIMx_ARR)初始化。。

arch/arm/mach-davinci/time.c
static int davinci_set_next_event(unsigned long cycles,
				  struct clock_event_device *evt)
{
	struct timer_s *t = &timers[TID_CLOCKEVENT];

	t->period = cycles;
	timer32_config(t);
	return 0;
}
static struct clock_event_device clockevent_davinci = {
	.name		= "timer0_0",
	.features       = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
	.shift		= 32,
	.set_next_event	= davinci_set_next_event,
	.set_mode	= davinci_set_mode,
};

函数调用栈:
tick_check_new_device(dev);
    tick_setup_device(td, newdev, cpu, cpumask);
        tick_setup_periodic(newdev, 0);
            clockevents_program_event(dev, next, ktime_get())
                dev->set_next_event((unsigned long) clc, dev);
                    timer32_config(&timers[i]);
                	    /* reset counter to zero, set new period */
	                    davinci_writel(0, t->tim_reg);
                	    davinci_writel(t->period, t->prd_reg);
                

            

3.在不同machine下的clock_event_device.set_mode实现timer硬件自动重载寄存器(TIMx_ARR)初始化。 

arch/arm/mach-davinci/time.c
static void davinci_set_mode(enum clock_event_mode mode,
			     struct clock_event_device *evt)
{
	struct timer_s *t = &timers[TID_CLOCKEVENT];

	switch (mode) {
	case CLOCK_EVT_MODE_PERIODIC:
		t->period = CLOCK_TICK_RATE / (HZ);
		t->opts = TIMER_OPTS_PERIODIC;
		timer32_config(t);
		break;
	case CLOCK_EVT_MODE_ONESHOT:
		t->opts = TIMER_OPTS_ONESHOT;
		break;
	case CLOCK_EVT_MODE_UNUSED:
	case CLOCK_EVT_MODE_SHUTDOWN:
		t->opts = TIMER_OPTS_DISABLED;
		break;
	}
}
static struct clock_event_device clockevent_davinci = {
	.name		= "timer0_0",
	.features       = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
	.shift		= 32,
	.set_next_event	= davinci_set_next_event,
	.set_mode	= davinci_set_mode,
};

函数调用栈:
tick_check_new_device(dev);
    tick_setup_device(td, newdev, cpu, cpumask);
        tick_setup_periodic(newdev, 0);
            clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);
                dev->set_mode(mode, dev);
                    timer32_config(t);
                	    /* reset counter to zero, set new period */
	                    davinci_writel(0, t->tim_reg);
                	    davinci_writel(t->period, t->prd_reg);
                
                

            

第三种方式就是timer硬件自动重载寄存器(TIMx_ARR)初始化为(1/HZ)/(1/f)=f/HZ的位置

二:低精度定时器的梳理

书接上节,从低精度定时器(struct timer_list)定义的数据结构看不出来和struct clock_event_device的联系,联系在函数tick_set_periodic_handler()初始化的中断函数。

static inline void tick_set_periodic_handler(struct clock_event_device *dev,
					     int broadcast)
{
	dev->event_handler = tick_handle_periodic;
}

 上节我们说明了何时给timer硬件定时器自动重载寄存器(TIMx_ARR)的初始化,这里又指定了中断处理函数。接下来就是中断发生时中断处理函数的梳理:

arch/arm/mach-davinci/time.c
static irqreturn_t timer_interrupt(int irq, void *dev_id)
{
	struct clock_event_device *evt = &clockevent_davinci;

	evt->event_handler(evt);
	return IRQ_HANDLED;
}
kernel/time/tick-common.c
void tick_handle_periodic(struct clock_event_device *dev)
{
	int cpu = smp_processor_id();
	ktime_t next;

	tick_periodic(cpu);

	if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
		return;
	/*
	 * Setup the next period for devices, which do not have
	 * periodic mode:
	 */
	next = ktime_add(dev->next_event, tick_period);
	for (;;) {
		if (!clockevents_program_event(dev, next, ktime_get()))
			return;
		tick_periodic(cpu);
		next = ktime_add(next, tick_period);
	}
}

函数调用栈
evt->event_handler(evt);
    tick_periodic(cpu);
    

 在函数tick_periodic()中进行低精度定时器的处理,包括但不限于唤醒软中断,在软中断中查看是否又到期的低精度定时器进行处理。详细的处理逻辑可以参考:

Linux时间子系统之五:低分辨率定时器的原理和实现-CSDN博客

三:高精度定时器的梳理 

高精度定时器的数据结构如下,看起来也是没和struct clock_event_device有什么直接的联系。

struct hrtimer {
	struct rb_node			node;
	ktime_t				expires;
	enum hrtimer_restart		(*function)(struct hrtimer *);
	struct hrtimer_clock_base	*base;
	unsigned long			state;
#ifdef CONFIG_HIGH_RES_TIMERS
	enum hrtimer_cb_mode		cb_mode;
	struct list_head		cb_entry;
#endif
#ifdef CONFIG_TIMER_STATS
	void				*start_site;
	char				start_comm[16];
	int				start_pid;
#endif
};

高精度定时器的到期时间是被编程到该clock_event_device中(注意和低精度定时器的区别,低精度到期时间并不会影响clock_event_device的计数值,在低精度定时器下clock_event_device对应的计数值为f/HZ)。这样就能理解linuxer前辈的话(“低精度timer依赖周期性tick,而hrtimer不依赖周期性tick”)了,也能理解高精度模式下依赖HZ的tick_device的中断函数中只考虑低精度定时器而不需要考虑高精度定时器,因为高精度定时器的到期时间转换为计数值直接编程到clock_event_device中。

第一节的第2个给timer硬件自动重载寄存器(TIMx_ARR)初始化的方式就是高精度定时器的到期时间转换为计数值编程到clock_event_device。

有关高精度定时器详细的处理逻辑可以参考:

Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现_时间子系统之高精度 timer 实现-CSDN博客

四:用户态定时器

 也称POSIX interval timer,通过下面内核的数据结构可以看到是对高精度定时器(struct hrtimer)的封装,关于它出现的原因,也有业界前辈帮我们总结了。

参考:Linux时间子系统之(三):用户空间接口函数

struct k_itimer {
	struct list_head list;		/* free/ allocate list */
	spinlock_t it_lock;
	clockid_t it_clock;		/* which timer type */
	timer_t it_id;			/* timer id */
	int it_overrun;			/* overrun on pending signal  */
	int it_overrun_last;		/* overrun on last delivered signal */
	int it_requeue_pending;		/* waiting to requeue this timer */
#define REQUEUE_PENDING 1
	int it_sigev_notify;		/* notify word of sigevent struct */
	int it_sigev_signo;		/* signo word of sigevent struct */
	sigval_t it_sigev_value;	/* value word of sigevent struct */
	struct task_struct *it_process;	/* process to send signal to */
	struct sigqueue *sigq;		/* signal queue entry. */
	union {
		struct {
			struct hrtimer timer;
			ktime_t interval;
		} real;
		struct cpu_timer_list cpu;
		struct {
			unsigned int clock;
			unsigned int node;
			unsigned long incr;
			unsigned long expires;
		} mmtimer;
	} it;
};

五:小结

说下个人理解:高精度定时器&低精度定时器、高精度模式&低精度模式 的硬件定时器驱动频率都可高达MHZ,这里的低精度模式是指低精度定时器依赖CONFIG_HZ的tick 中断,而高精度模式下的高精度定时器就不再依赖CONFIG_HZ的tick 中断了,直接将高精度定时器的到期时间转换为计数值编程到clock_event_device。

至此linux 时间子系统脉络算是稍微有所理解了吧。

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值