2020-10-10

Linux内核中高精度定时器使用

  1. 内核中高精度定时器
    在linux内核下编程,特别是驱动编程中,往往HZ的定时器精度不能满足我们的需求;此时,内核为高精度定时器重新设计了一套软件架构,它可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动,例如多媒体应用,音频设备的驱动程序等等。
    timer_list内核定时器,它的精度在毫秒级别,内核提供纳秒级别的高精度定时器 hrtimer。源文件在linux/kernel/hrtimer.c中,接口简单。下面介绍一下相关接口:
    1) 定时器定义与绑定超时回调函数
    内核用一个hrtimer结构来表示一个高精度定时器:
struct hrtimer {
	struct timerqueue_node		node;
	ktime_t				_softexpires;
	enum hrtimer_restart		(*function)(struct hrtimer *);
	struct hrtimer_clock_base	*base;
	u8				state;
	u8				is_rel;
#ifdef CONFIG_TIMER_STATS
	int				start_pid;
	void				*start_site;
	char				start_comm[16];
#endif
};

static struct hrtimer timer;
 
/* 设置回调函数 */
timer.function = hrtimer_hander;

2)定时器初始化

/*
 *  参数timer是hrtimer指针,
 *  参数clock_id有如下常用几种选项:
 *  CLOCK_REALTIME	//实时时间,如果系统时间变了,定时器也会变
 *  CLOCK_MONOTONIC	//递增时间,不受系统影响
 *  参数mode有如下几种选项:
 * 	HRTIMER_MODE_ABS = 0x0,		/* 绝对模式 */
	HRTIMER_MODE_REL = 0x1,		/* 相对模式 */
	HRTIMER_MODE_PINNED = 0x02,	/* 和CPU绑定 */
	HRTIMER_MODE_ABS_PINNED = 0x02, /* 第一种和第三种的结合 */
	HRTIMER_MODE_REL_PINNED = 0x03, /* 第二种和第三种的结合 */
 */
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode)

3)定时器启动

/*
 * 参数timer是hrtimer指针
 * 参数tim是时间,可以使用ktime_set()函数设置时间,
 * 参数mode和初始化的mode参数一致
 */
hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)

4)设置时间

/*单位为秒和纳秒组合*/
ktime_t ktime_set(const long secs, const unsigned long nsecs)/* 设置超时时间,当定时器超时后可以用该函数设置下一次超时时间 */
hrtimer_forward_now(struct hrtimer *timer, ktime_t interval)

5)注意事项:
定时器超时后会调用回调函数,回调函数结构类似这样:

enum hrtimer_restart		(*function)(struct hrtimer *);
 
enum hrtimer_restart {
	HRTIMER_NORESTART,	/* 不重启定时器 */
	HRTIMER_RESTART,	/* 重启定时器 */
};

在回调函数返回前要手动设置下一次超时时间。
另外,回调函数执行时间不宜过长,因为是在中断上下文中,如果有什么任务的话,最好使用工作队列等机制。

6)关闭定时器

int hrtimer_cancel(struct hrtimer *timer)
  1. hrtimer定时器使用实例
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
static struct hrtimer hr_timer;
ktime_t ktime;

/* 定时器回调函数1,定时器回调函数运行一次就停止 */
enum hrtimer_restart hrtimer_hander1 ( struct hrtimer *timer )
{
  printk( KERN_ALERT" hrtimer callback run only once!\n"); 
  return HRTIMER_NORESTART;
}
/*定时器回调函数2,1s运行一次*/
static enum hrtimer_restart hrtimer_handle2(struct hrtimer *timer)
 {
   kt = ktime_set(1,  0);
   printk("hello world! \n");
   hrtimer_forward(timer, kt);
   return HRTIMER_RESTART;
}
static int __init my_hrtimer_init(void )
{
  printk( KERN_ALERT "hr Timer module installing\n");
ktime = ktime_set( 1, 0); 
hrtimer_init( &hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL );
  hr_timer.function = &hrtimer_hander1; // &hrtimer_hander2;
  hrtimer_start( &hr_timer, ktime, HRTIMER_MODE_REL );
  return 0;
}
static void __exit my_hrtimer_exit( void )
{
  int ret;
  ret = hrtimer_cancel( &hr_timer );                                                // 取消定时器执行
  if (ret){
     printk( KERN_ALERT"The timer was still in use!\n");
  }
  printk( KERN_ALERT "hr Timer module uninstalling\n");
  return;
}
 
MODULE_LICENSE("GPL");
module_init(my_hrtimer_init);
modlue_exit(my_hrtimer_exit);
  1. Linux hrtimer的实现方案
    Linux hrtimer的实现是依赖硬件(通过可编程定时器来实现)的支持的,而且此定时器有自己的专用寄存器, 硬中断和频率。支持高精度timer是需要付出硬件成本的。即它是一个硬件时钟。这里所说的硬件时钟特指的是硬件计时器时钟。
    3.1. 硬件时钟数据结构
      和硬件计时器(本文又称作硬件时钟,区别于软件时钟)相关的数据结构主要有两个:
      struct clocksource :对硬件设备的抽象,描述时钟源信息
struct clocksource {
	/*
	 * First part of structure is read mostly
	 */
	char *name;
	struct list_head list;
	int rating;
	cycle_t (*read)(struct clocksource *cs);
	int (*enable)(struct clocksource *cs);
	void (*disable)(struct clocksource *cs);
	cycle_t mask;
	u32 mult;
	u32 shift;
	u64 max_idle_ns;
	unsigned long flags;
	cycle_t (*vread)(void);
	void (*suspend)(struct clocksource *cs);
	void (*resume)(struct clocksource *cs);
#ifdef CONFIG_IA64
	void *fsys_mmio;        /* used by fsyscall asm code */
#define CLKSRC_FSYS_MMIO_SET(mmio, addr)      ((mmio) = (addr))
#else
#define CLKSRC_FSYS_MMIO_SET(mmio, addr)      do { } while (0)
#endif

	/*
	 * Second part is written at each timer interrupt
	 * Keep it in a different cache line to dirty no
	 * more than one cache line.
	 */
	cycle_t cycle_last ____cacheline_aligned_in_smp;

#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
	/* Watchdog related data, used by the framework */
	struct list_head wd_list;
	cycle_t wd_last;
#endif
};
  struct clock_event_device :时钟的事件信息,包括当硬件时钟中断发生时要执行那些操作(实际上保存了相应函数的指针)。本文将该结构称作为“时钟事件设备”。
/**
 * struct clock_event_device - clock event device descriptor
 * @name:		ptr to clock event name
 * @features:		features
 * @max_delta_ns:	maximum delta value in ns
 * @min_delta_ns:	minimum delta value in ns
 * @mult:		nanosecond to cycles multiplier
 * @shift:		nanoseconds to cycles divisor (power of two)
 * @rating:		variable to rate clock event devices
 * @irq:		IRQ number (only for non CPU local devices)
 * @cpumask:		cpumask to indicate for which CPUs this device works
 * @set_next_event:	set next event function
 * @set_mode:		set mode function
 * @event_handler:	Assigned by the framework to be called by the low
 *			level handler of the event source
 * @broadcast:		function to broadcast events
 * @list:		list head for the management code
 * @mode:		operating mode assigned by the management code
 * @next_event:		local storage for the next event in oneshot mode
 * @retries:		number of forced programming retries
 */
struct clock_event_device {
	const char		*name;
	unsigned int		features;
	u64			max_delta_ns;
	u64			min_delta_ns;
	u32			mult;
	u32			shift;
	int			rating;
	int			irq;
	const struct cpumask	*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)(const struct cpumask *mask);
	struct list_head	list;
	enum clock_event_mode	mode;
	ktime_t			next_event;
	unsigned long		retries;
};

上述两个结构内核源代码中有较详细的注解,分别位于文件 clocksource.h 和 clockchips.h 中。需要特别注意的是结构 clock_event_device 的成员 event_handler ,它指定了当硬件时钟中断发生时,内核应该执行那些操作,也就是真正的时钟中断处理函数。
  Linux 内核维护了两个链表,分别存储了系统中所有时钟源的信息和时钟事件设备的信息。这两个链表的表头在内核中分别是 clocksource_list 和 clockevent_devices 。
4. hrtimer是如何实现的呢?
4.1 初始化hrtimer硬件定时器
4.1.1 设置硬件中断
前面已经看到,它有一个硬件中断,为了使此硬件中断能正常工作,肯定需要设置一个硬件中断,其参考代码如下:

static unsigned long my_timer_irqnbr = 25;  //硬件中断号
static struct irqaction my_timer_irqaction = {
	.name		= "My HrTimer",
	.flags		= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
	.handler	= my_timer_interrupt_handler, //中断处理函数
};

setup_irq(my_timer_irqnbr, &my_timer_irqaction);
 设置中断之后,中断处理函数也有了。

4.1.2 初始化硬件时钟相关寄存器并注册此硬件时钟到系统中

static struct clocksource myclocksource = {
	.name	= "my_hrtimer_src",
	.rating = 300,
	.read	= my_get_cycles, //读取COUNT寄存器以获取cycle value
	.mask	= CLOCKSOURCE_MASK(64),
	.flags	= CLOCK_SOURCE_IS_CONTINUOUS,
};

static void __init my_clocksource_init(void)
{
	unsigned long ctrl = 0;
	unsigned long count = (my_timer_freq / HZ);
        ...
	writel(count, my_timer_vaddr + MY_TIMER_COMPARATOR_LOW);
	writel(count, my_timer_vaddr + MY_TIMER_AUTO_INCREMENT);
	ctrl = (MY_TIMER_CTRL_IRQ_ENA   | MY_TIMER_CTRL_COMP_ENA |
	        MY_TIMER_CTRL_TIMER_ENA | MY_TIMER_CTRL_AUTO_INC);
	writel(ctrl, my_timer_vaddr + MY_TIMER_CONTROL);
        ...
	clocksource_calc_mult_shift(&myclocksource, my_timer_freq, 4);
        
        //向系统注册我的硬件时钟,即把它加入clocksource_list
	clocksource_register(&myclocksource);
}

4.1.3 初始化时钟事件设备并注册到系统中

static struct clock_event_device myclockevent = {
	.name		= "my_timer_evt",
	.features		= CLOCK_EVT_FEAT_PERIODIC,
	.set_mode		= my_set_mode,  //通过写寄存器设置clock_event_mode
	.set_next_event	= my_set_next_event, // 通过写寄存器写下一个事件
	.rating		= 300,
	.cpumask		= cpu_all_mask,
};

static void __init my_clockevents_init(unsigned int timer_irq)
{
    myclockevent.irq = timer_irq;

    clockevents_calc_mult_shift(&myclockevent, my_timer_freq, 4);
    myclockevent.max_delta_ns = clockevent_delta2ns(0xffffffff, &myclockevent);
    myclockevent.min_delta_ns = clockevent_delta2ns(0xf, &myclockevent);
    //注册我的时钟事件设备,即把它加入clockevent_devices链表
    clockevents_register_device(&myclockevent);
}

4.2 硬件中处理函数my_timer_interrupt_handler

static irqreturn_t my_timer_interrupt_handler(int irq, void *dev_id)
{
	struct clock_event_device *evt = &myclockevent;
	/* clear the interrupt */
	writel(value, register_addr);
	evt->event_handler(evt);
	return IRQ_HANDLED;
}

硬件中断处理函数很简单,它直接调用clockevent的event_handler函数。前面的初始化中并没有初始化此event_handler,很显然是在使用过程中进行动态初始化的。下面看看hrtimer中是如何初始化此event_handler的。
5. hrtimer如何初始化clock_event_device的event_handler?
hrtimer的中断处理函数hrtimer_interrupt与clock_event_device有关系吗?
此软中断TIMER_SOFTIRQ在run_local_timers函数中通过调用raise_softirq(TIMER_SOFTIRQ);来触发。(注:raise_softirq->raise_softirq_irqoff->__raise_softirq_irqoff)
init_timers(中调用open_softirq(TIMER_SOFTIRQ, run_timer_softirq)😉
run_timer_softirq->
hrtimer_run_pending(Called from timer softirq every jiffy, expire hrtimers,check如果hrtimer_hres_enabled is on<=1>,则执行下面的代码切换到高精度模式)->hrtimer_switch_to_hres->tick_init_highres->
tick_switch_to_oneshot(hrtimer_interrupt)<把hrtimer_interrupt赋值给dev->event_handler,即dev->event_handler = handler;>
看到没有?在每一次时钟软中断处理函数中,都会尝试把hrtimer切换到高精度模式,如果满足条件,就切换,切换之后高精度模式就被激活了,在hrtimer_run_pending检查是否被激活,如果被激活了,下面的代码就不用执行了。

  • 12
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值