Linux内核开发基础-低精度timer_list和高精度hrtimer定时器

本文深入解析Linux内核中的定时器实现,包括低精度和高精度定时器的原理、数据结构及主要API,通过实例展示如何在驱动开发中应用。

Linux内核定时器开发-低精度和高精度定时器

上篇文章讲解了如何正确的使用内核延时函数,在进行驱动开发时,可能会经常用到精确地延时操作。除此之外,如果要实现一个定时任务,那就需要用到定时器。作为一项基础功能需求,Linux内核提供了定时器相关的实现。下面就具体看一下,Linux内核所提供的定时器实现。

定时器种类

为了适应不同的应用场景,Linux内核提供了两种定时器:低精度和高精度定时器。低精度定时器基于硬件的周期性中断实现,其定时周期的粒度为1/HZms,例如,内核HZ为1000,那么低精度的定时器最小定时时间为1ms;高精度定时器可以实现ns级的定时,不过,实际的定时周期粒度与CPU的主频有关,比如,桌面级的CPU一般都是GHZ级别,那么,其定时粒度可以达到ns级别,而对于嵌入式CPU,其主频一般在百兆级别,那么定时粒度就只能达到us级别了。在进行开发时,需要根据实际场景,选择合适的定时器来实现功能。那么,实际开发时,如何使用这两类定时器呢?下面两节,就来具体看一下。

低精度定时器

上文说了,低精度定时器依赖于内核时钟中断实现,内核中所有的此类定时器会组成一个链表,处理时钟中断时,内核会检查定时器列表是否有到时的定时器,如果有,就会调用定时器处理函数,进行相关的处理。

这里需要强调的是,执行此类定时器的处理函数时处于“软中断”上下文,软中断是中断下半部处理的一种机制,“软中断”上下文是原子性的,不可以执行可能会引起系统调度或可能睡眠的操作,比如,kmalloc、copy_from_user、msleep、mutex_lock等等。如果在定时器处理函数中调用了上述函数,比如,msleep函数,会引其内核模块崩溃,严重的会导致系统崩溃,所以,使用时切记小心!

基本数据结构

struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct hlist_node	entry;
	unsigned long		expires;
	void			(*function)(unsigned long);
	unsigned long		data;
	u32			flags;
};

上面是低精度定时器timer_list的数据结构,从名字就可以判断出其基于链表实现,其通过entry挂载到内核的定时器哈希列表中。expires表示定时时间,注意:expires的单位为时钟滴答间隔,比如,你想设置一个定时周期为10ms的定时器,那么expires应该表示为jiffies+msec_to_jiffies(10);function为定时器处理函数,data为function的入参,flags为定时器的一些选项,一般不需要配置。

可以看到,定时器的数据结构十分的简单,基本上没有什么难理解的地方。下面主要看一下,如何使用该定时器。

主要API

要想在内核中使用timer_list定时器,一般需要以下几个步骤:

  1. 声明一个定时器,例如,struct timer_list timer;

  2. 初始化定义定时器,初始化定时器有几种方式:

    • #define TIMER_INITIALIZER(_function, _expires, _data) ,例如,struct timer_list timer = TIMER_INITIALIZER(func, expires, data);
    • #define DEFINE_TIMER(_name, _function, _expires, _data),例如,DEFIME_TIMER(timer,func,expires,data);
    • #define setup_timer(timer, fn, data), 例如,setup_timer(&timer, func, data)
  3. 增加定时器, add_timer用于将定时器添加到内核定时器链表中,至此,定时器开始工作。timer_list,为单次触发的定时器,如果想连续执行定时任务,那么需要再定时处理函数的末尾再次执行add_timer,以便再次激活定时器。add_timer函数原型如下:

    extern void add_timer(struct timer_list *timer);

  4. 删除定时器,del_timer用于将定时器从内核定时器链表中立刻去除,不管是否正在处理该定时器。del_timer_sync(),是del_timer的同步版本,其会等待该定时器被处理完毕,注意,该函数可能会导致发生系统调度,所以其不能用在原子上下文中,比如,中断上下文。del_timer函数原型如下:

    extern int del_timer(struct timer_list * timer);

  5. 修改定时器,mod_timer可以修改定时器的超时时间,其函数原型如下:

    mod_timer(struct timer_list *timer, unsigned long expires)

    • timer,表示当前被修改的定时器
    • expires,基于jiffiesd新的超时时间

对于已经激活的定时器(未激活时,mod_timer会将其激活),修改超时时间,使用mod_timer十分高效的,其相当于下面的操作:

del_timer(timer); timer->expires = expires; add_timer(timer); 

这里需要注意的是,如果系统中有多个用户同步的使用同一个已激活的定时器(未加锁进行串行化),那么使用mod_timer是唯一可以安全的修改定时器的方法,因为mod_timer对于定时器的超时时间的修改是原子性的

定时器使用模板

好了,实际开发时,知道上面的API使用方式,完全可以应付90%的低速定时器使用场景。下面说一下此类定时器的使用时的具体模式,请看代码。

lrtimer.c文件:

    #include <linux/kernel.h>
	#include <linux/module.h>
	#include <linux/time.h>
	 
	static struct timer_list timer;
	static unsigned long data = 10; 	 
	 
	/*
	 * @breif:定时器处理函数
	 *
	 * @func:
	 *
	 * @param: data为setup_timer时传入的data参数
	 *
	 * @return:
	 *
	 */
	static void timer_cb(unsigned long data)
	{
	    printk("data = %lu.\n", data);
	 
	    mod_timer(&timer, jiffies + msecs_to_jiffies(10)); /*step3:重新激活定时器*/
	}
	 
	 
	static int __init module_lrtimer_init( void )
	{
	    printk("low resolution timer init.\n");
	    
	    setup_timer(&timer, timer_cb, data);              /*step1:定义并初始化定时器*/
	 
	    mod_timer(&timer, jiffies + msecs_to_jiffies(10/*ms*/));/*step2:修改定时器超时时间,并激活定时器*/
	 
	    return 0;
	}
	 
	 
	static void __exit module_lrtimer_exit( void )
	{
	    printk("low resolution timer exit.\n");
	    del_timer(&timer);
	}
	 
	module_init(module_lrtimer_init);
	module_exit(module_lrtimer_exit);

Makefile文件:

	KVERS = $(shell uname -r)
	        
	# Kernel modules
	obj-m += lrtimer.o
	        
	# Specify flags for the module compilation.
	EXTRA_CFLAGS=-g -O0 
	        
	build: kernel_modules
	        
	kernel_modules:
	    make -C $(KVERS)/build M=$(CURDIR) modules
	        
	clean:
	    make -C $(KVERS)/build M=$(CURDIR) clean         

高精度定时器

上文看到低精度定时器的分辨率严重依赖内核的时钟中断,如果HZ为1000,那么其分辨率也仅仅为1ms,如果想要使用更高分辨率的定时器,那只能求助于hrtimer了。Linux内核在2005年开始在内核中增加hrtimer的支持,其基本特性如下:

  • 高精度,定时的分辨率为1ns(ps:实际的分辨率依赖于CPU时钟的频率,嵌入式系统的分辨率在us级别),其时钟源来自于CPU的硬件clock。
  • 与timer_list不同,与jiffies没有任何关系,所有内核逻辑都工作在64-bit ns级分辨率之上。
  • 支持多种平台,i386, x86_64, ARM, PPC, PPC64, IA64。

hrtimer的应用也十分的广泛,依赖于posix-timers的用户空间程序,内核中需要高分辨率的驱动程序(多媒体驱动程序)等。

检查是否支持hrtimer

在使用hrtimer之前,需要确认当前内核是否支持,检测是否支持的方式有两种:

  1. 对于自行编译的内核,可以检测内核的配置文件是否打开了CONFIG_HIGH_RES_TIMERS选项。

     #
     # Timers subsystem
     #
     CONFIG_TICK_ONESHOT=y
     CONFIG_NO_HZ_COMMON=y
     # CONFIG_HZ_PERIODIC is not set
     CONFIG_NO_HZ_IDLE=y
     # CONFIG_NO_HZ_FULL is not set
     CONFIG_NO_HZ=y
     CONFIG_HIGH_RES_TIMERS=y
    
  2. 对于现存的内核,可以通过查看/proc/timer_list的信息,来确定是否支持hrtimer。

     root@zpd /proc$ cat timer_list 
     Timer List Version: v0.7
     HRTIMER_MAX_CLOCK_BASES: 4
     now at 1559369165518 nsecs
     
     cpu: 0
      clock 0:
       .base:       8bdc0308
       .index:      0
       .resolution: 1 nsecs
    

注意到,HRTIMER_MAX_CLOCK_BASES:4,.resolution: 1 nsecs表示当前内核是支持hrtimer的。

基本数据结构

  1. ktime:

     union ktime {   
         s64 tv64;
     	#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
     	struct {
     	# ifdef __BIG_ENDIAN
     	s32 sec, nsec;
     	# else
     	s32 nsec, sec;
     	# endif
     	} tv; 
     	#endif	
     };
    

ktime用于保存hrtimer的定时时间,从定义中可以看出其完美高效的支持32bit/64bit系统。

  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;
     };
    

struct hrtimer为hrtimer的基本数据结构,其主要包括如下几部分:

  • node,hrtimer最终通过node挂接到timerqueue中。
  • _softexpires,表示hrtimer的定时时间,_soft表示此定时时间只是软件意义上的。
  • function,表示hrtimer的超时处理函数。

主要API

  1. ktime相关的API

    • ktime_t ktime_set(const long secs, const unsigned long nsecs),通过secs和nsecs生成ktime_t
    • static inline ktime_t ns_to_ktime(u64 ns),通过ns生成ktime_t
    • static inline ktime_t ms_to_ktime(u64 ms),通过ms生成ktime_t
  2. hrtimer_init 用于初始化一个hrtimer

     extern void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);
    
     - timer:表示hrtimer定时器
     - which_clock:表示选择系统的哪种时钟,主要包括两种:CLOCK_REALTIME、CLOCK_MONOTONIC,两种时钟的区别是,CLOCK_REALTIME表示绝对时间,而CLOCK_MONOTONIC表示相对时间。
     - mode:表示hrtimer的类型,主要包括两种:HRTIMER_MODE_ABS、HRTIMER_MODE_REL,前者是绝对模式,对应于CLOCK_REALTIME时钟,后者是相对模式,对应于CLOCK_MONOTONIC。
    
  3. hrtimer_start用于启动一个定时器

     int hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
     
    - timer:表示当前的定时器
    - tim:定时时间
    - mode:与hrtimer_init中的mode一样。
    
  4. hrtimer_cancle应于取消一个定时器, 并等待其执行完毕

     int hrtimer_cancel(struct hrtimer *timer)
    
  5. hrtimer_forward_now,重新设置hrtimer的超时时间,用于实现连续定时。

      u64 hrtimer_forward_now(struct hrtimer *timer, ktime_t interval)                                                                                                                                                  
    

示例

下面是基于hrtimer的简单示例,定时器的超时时间为10ms,并且在定时器超时处理函数中,调用hrtimer_forward_now再次启动定时器。

hrtimer.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
 
MODULE_LICENSE("GPL");
                                                                                                                                                                                                                    
static struct hrtimer hr_timer;
static unsigned long interval= 10; /* unit: ms */
struct timespec uptimeLast;
 
unsigned long long diff_tv(struct timespec start, struct timespec end) 
{
    return (end.tv_sec - start.tv_sec)*1000000000 + (end.tv_nsec-start.tv_nsec);
}
 
enum hrtimer_restart my_hrtimer_callback( struct hrtimer *timer )
{
    struct timespec uptime;
 
    do_posix_clock_monotonic_gettime(&uptime);  
 
    printk(KERN_INFO"hrtimer:%9lu sec, %9lu ns, interval_delay=%lu us\n", 
            (unsigned long) uptime.tv_sec, 
            uptime.tv_nsec,
            (unsigned long)(diff_tv(uptimeLast, uptime))/1000); 
 
    uptimeLast=uptime;
 
    hrtimer_forward_now(timer, ns_to_ktime(interval));
 
    return HRTIMER_RESTART;
}
 
static int __init module_hrtimer_init( void )
{
    struct timespec uptime; 
    static ktime_t ktime;
 
    printk(KERN_INFO"HR Timer module installing\n");
 
    hrtimer_init( &hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL );
 
    ktime = ms_to_ktime(interval);
 
    hr_timer.function = my_hrtimer_callback;
    hrtimer_start(&hr_timer, ktime, HRTIMER_MODE_REL );
 
    do_posix_clock_monotonic_gettime(&uptime);
    uptimeLast = uptime;
 
    printk(KERN_INFO "hrtimer:%9lu sec, %9lu ns\n", 
            (unsigned long) uptime.tv_sec, uptime.tv_nsec ); 
 
    return 0;
}

static void __exit module_hrtimer_exit( void )
{
	int ret;
    ret = hrtimer_cancel( &hr_timer );
    if (ret) 
      printk("The timer was still in use...\n");
    printk("HR Timer module uninstalling.\n");
}

module_init(module_hrtimer_init);
module_exit(module_hrtimer_exit);   

Makefile文件:

#KVERS = $(shell uname -r)                                                                                                                                                                                          

# Kernel modules
obj-m += hrtimer.o

# Specify flags for the module compilation.	
EXTRA_CFLAGS=-g -O0 

build: kernel_modules

kernel_modules:
    make -C $(KVERS)/build M=$(CURDIR) modules

clean:
    make -C $(KVERS)/build M=$(CURDIR) clean
<think>嗯,用户现在想找关于在Linux内核驱动中使用高精度定时器hrtimer实现微秒级延时函数delay_us的代码示例。我需要先回顾之前的对话,确保这次回答符合用户的需求。 用户之前的问题是关于如何在Linux内核中使用_IOW宏定义字节类型的数据,我给出了详细的实现方法代码示例,还附带了相关问题。现在用户的新问题是关于hrtimer实现微秒延时的代码示例,所以需要重点讲解hrtimer的使用步骤注意事项。 首先,用户的需求是微秒级延时,而hrtimer正是高精度定时器,适合这个场景。我需要解释hrtimer的基本用法,比如如何初始化定时器、设置回调函数、启动取消定时器等。同时,用户可能需要一个具体的代码示例,展示如何封装一个delay_us函数。 接下来,考虑用户可能的背景。他们可能正在开发内核驱动,需要精确的延时控制,比如在硬件操作或实时任务中。因此,除了代码示例,还需要强调使用hrtimer的优势,比如相比传统的定时器hrtimer提供更高的精度更低的延迟。 然后,我需要检查之前的引用文献。用户提到了引用[1],其中提到ioctl接口将内核API映射到用户空间,这可能与当前的hrtimer问题没有直接关系,但可能需要考虑用户空间与内核的交互。不过当前问题集中在内核内部的定时器实现,所以重点应放在内核模块的代码结构上。 用户可能还会关心如何确保延时的准确性,以及如何处理定时器回调函数中的上下文。比如,hrtimer的回调函数是在中断上下文中执行的,不能进行可能导致睡眠的操作。这一点需要明确指出,避免用户在使用时犯错。 另外,需要提到hrtimer的不同模式,比如HRTIMER_MODE_REL(相对时间)HRTIMER_MODE_ABS(绝对时间),在示例中使用相对时间模式更符合延时的需求。 在代码示例中,应该包括模块的初始化、定时器的设置、回调函数的实现,以及如何启动停止定时器。同时,需要展示如何将hrtimer封装成delay_us函数,可能需要使用完成量(completion)或者等待队列来让调用线程等待定时器完成。 还要注意内核版本的兼容性,因为hrtimer的API在不同版本中可能有变化。需要建议用户查阅他们使用的内核版本的文档,或者确认示例代码的适用性。 最后,用户可能想知道如何处理多线程环境下的并发问题,或者如何测试这个延时函数的准确性。不过,用户当前的问题主要是代码示例,因此这些可以作为相关问题的补充。 总结下来,回答的结构应包括实现步骤、代码示例、关键注意事项相关问题。需要确保代码正确使用hrtimer的API,并解释每个部分的作用,帮助用户理解如何集成到他们的驱动中。</think>### Linux内核hrtimer实现微秒级延时函数`delay_us()`代码示例 #### 一、核心实现原理 高精度定时器hrtimer)通过`hrtimer_init()`初始化定时器结构体,使用`hrtimer_start()`设置延时时间,通过`wait_for_completion()`实现同步等待[^1]。关键实现步骤如下: ```c #include <linux/hrtimer.h> #include <linux/completion.h> static struct hrtimer delay_timer; static struct completion delay_complete; // 定时器回调函数 static enum hrtimer_restart delay_callback(struct hrtimer *timer) { complete(&delay_completion); // 唤醒等待线程 return HRTIMER_NORESTART; // 单次触发模式 } // 微秒级延时函数 void delay_us(unsigned long microseconds) { ktime_t delay_time = ktime_set(0, microseconds * NSEC_PER_USEC); hrtimer_init(&delay_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); delay_timer.function = delay_callback; init_completion(&delay_completion); hrtimer_start(&delay_timer, delay_time, HRTIMER_MODE_REL); wait_for_completion(&delay_completion); } ``` #### 二、代码实现细节 ##### 1. **定时器初始化** ```c hrtimer_init(&delay_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ``` - `CLOCK_MONOTONIC`:使用单调递增时钟,避免系统时间调整影响 - `HRTIMER_MODE_REL`:相对时间模式 ##### 2. **时间单位转换 ```c ktime_t delay_time = ktime_set(0, microseconds * NSEC_PER_USEC); ``` - 将微秒转换为纳秒(1μs = 1000ns) - `ktime_set(secs, nsecs)`创建时间对象 ##### 3. **同步等待机制 ```c init_completion(&delay_completion); wait_for_completion(&delay_completion); ``` - 使用完成量实现线程阻塞/唤醒 - 避免忙等待(busy-loop)造成的CPU资源浪费 #### 三、优化改进方案 ##### 1. 中断上下文安全版 ```c void delay_us_atomic(unsigned long usec) { ktime_t delay = ktime_set(0, usec * NSEC_PER_USEC); hrtimer_init_on_stack(&delay_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); hrtimer_start(&delay_timer, delay, HRTIMER_MODE_REL); while (hrtimer_active(&delay_timer)) cpu_relax(); // 适用于中断上下文 } ``` - 使用`hrtimer_active()`检测定时器状态 - `cpu_relax()`降低CPU占用(需配合`CONFIG_PREEMPT`配置) ##### 2. 批量延时优化 ```c void delay_us_batch(unsigned long total_us, unsigned int batch_size) { unsigned long cycles = total_us / batch_size; ktime_t batch_delay = ktime_set(0, batch_size * NSEC_PER_USEC); for (int i = 0; i < cycles; i++) { hrtimer_start(&delay_timer, batch_delay, HRTIMER_MODE_REL); wait_for_completion(&delay_completion); reinit_completion(&delay_completion); } } ``` #### 四、关键注意事项 1. **精度范围** hrtimer理论最小精度为1ns,实际受以下因素影响: ```text +------------------------+-----------------+ | 影响因素 | 典型误差范围 | +------------------------+-----------------+ | 硬件时钟源 | ±50ns | | 内核配置(CONFIG_HZ) | 100-1000μs | | 中断延迟 | 1-50μs | +------------------------+-----------------+ ``` 2. **上下文限制** - 在**中断上下文**中禁止使用`wait_for_completion()` - 原子版本需使用`hrtimer_init_on_stack()`避免内存分配 3. **资源释放** ```c void cleanup_delay(void) { hrtimer_cancel(&delay_timer); destroy_completion(&delay_completion); } ``` #### 五、性能测试方法 使用`ktime_get()`测量实际延时: ```c ktime_t start = ktime_get(); delay_us(100); // 测试100μs延时 ktime_t end = ktime_get(); s64 actual_delay = ktime_to_ns(end - start); ``` --- ### 相关问题 1. 如何选择hrtimer传统的timer_list定时器? 2. 在实时内核(PREEMPT_RT)中使用hrtimer有什么特别注意事项? 3. 如何通过ftrace分析hrtimer的实际触发延迟? [^1]: Linux内核文档:`Documentation/timers/hrtimers.txt` [^2]: 《Linux Kernel Development》第11章 "Timers and Time Management"
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值