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

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

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;


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


  • #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)


  1. 增加定时器, add_timer用于将定时器添加到内核定时器链表中,至此,定时器开始工作。timer_list,为单次触发的定时器,如果想连续执行定时任务,那么需要再定时处理函数的末尾再次执行add_timer,以便再次激活定时器。add_timer函数原型如下:

 extern void add_timer(struct timer_list *timer);


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


extern int del_timer(struct timer_list * timer);


  1. 修改定时器,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


对于现存的内核,可以通过查看/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


  1. 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。


hrtimer_start用于启动一个定时器


int hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
- timer:表示当前的定时器
- tim:定时时间
- mode:与hrtimer_init中的mode一样。


hrtimer_cancle应于取消一个定时器, 并等待其执行完毕


int hrtimer_cancel(struct hrtimer *timer)


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


相关文章
|
1月前
|
缓存 Linux 开发者
Linux内核中的并发控制机制
本文深入探讨了Linux操作系统中用于管理多线程和进程的并发控制的关键技术,包括原子操作、锁机制、自旋锁、互斥量以及信号量。通过详细分析这些技术的原理和应用,旨在为读者提供一个关于如何有效利用Linux内核提供的并发控制工具以优化系统性能和稳定性的综合视角。
|
1月前
|
缓存 负载均衡 算法
深入探索Linux内核的调度机制
本文旨在揭示Linux操作系统核心的心脏——进程调度机制。我们将从Linux内核的架构出发,深入剖析其调度策略、算法以及它们如何共同作用于系统性能优化和资源管理。不同于常规摘要提供文章概览的方式,本摘要将直接带领读者进入Linux调度机制的世界,通过对其工作原理的解析,展现这一复杂系统的精妙设计与实现。
75 8
|
1月前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
69 4
|
16天前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
16天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
17天前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
17天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
19天前
|
负载均衡 算法 Linux
深入探索Linux内核调度机制:公平与效率的平衡####
本文旨在剖析Linux操作系统内核中的进程调度机制,特别是其如何通过CFS(完全公平调度器)算法实现多任务环境下资源分配的公平性与系统响应速度之间的微妙平衡。不同于传统摘要的概览性质,本文摘要将直接聚焦于CFS的核心原理、设计目标及面临的挑战,为读者揭开Linux高效调度的秘密。 ####
32 3
|
22天前
|
负载均衡 算法 Linux
深入探索Linux内核调度器:公平与效率的平衡####
本文通过剖析Linux内核调度器的工作机制,揭示了其在多任务处理环境中如何实现时间片轮转、优先级调整及完全公平调度算法(CFS),以达到既公平又高效地分配CPU资源的目标。通过对比FIFO和RR等传统调度策略,本文展示了Linux调度器如何在复杂的计算场景下优化性能,为系统设计师和开发者提供了宝贵的设计思路。 ####
33 6
|
21天前
|
消息中间件 安全 Linux
深入探索Linux操作系统的内核机制
本文旨在为读者提供一个关于Linux操作系统内核机制的全面解析。通过探讨Linux内核的设计哲学、核心组件、以及其如何高效地管理硬件资源和系统操作,本文揭示了Linux之所以成为众多开发者和组织首选操作系统的原因。不同于常规摘要,此处我们不涉及具体代码或技术细节,而是从宏观的角度审视Linux内核的架构和功能,为对Linux感兴趣的读者提供一个高层次的理解框架。
下一篇
DataWorks