Linux驱动 | Linux高分辨率定时器hrtimer(实操代码编写)

ktime_t结构

相对timer定时器,hrtimer是以ktime_t来定义时间的。ktime_t的结构如下所示:

  union ktime {
  	s64 tv64;
  };
  typedef union ktime ktime_t;        /* Kill this */

ktime_t是一个64位的数,针对ktime_t有如下的一些函数(更多的代码请查看include/linux/ktime.h)

ktime_t ktime_set(secs, nsecs) : 初始化一个ktime_t对象
ktime_t ktime_get() : 获取系统自启动以来的相对时间
ktime_t ktime_get_real() : 获取系统真实时间
struct timeval ktime_to_timeval(ktime_t kt) 
struct timespec ktime_to_timespec(ktime_t kt) 
ktime_equal(const ktime_t cmp1, const ktime_t cmp2)
ktime_compare(const ktime_t cmp1, const ktime_t cmp2)
ktime_after(const ktime_t cmp1, const ktime_t cmp2)
ktime_before(const ktime_t cmp1, const ktime_t cmp2)

hrtimer结构体

  struct hrtimer {
      ....
      enum hrtimer_restart        (*function)(struct hrtimer *);
      ....
  };

hrtimer结构体中对驱动开发者有用的成员为function,通过function成员指定hrtimer定时器函数,其返回为枚举值hrtimer_restart,如果在定时器函数中重新设置定时器,实现延时或者循环,可以返回HRTIMER_RESTART告诉内核重启定时器。

  /*  
   * Return values for the callback function
   */
  enum hrtimer_restart {
      HRTIMER_NORESTART,  /* Timer is not restarted */
      HRTIMER_RESTART,    /* Timer must be restarted */
  };

hrtimer定时器常用函数

//hrtimer_init初始化一个hrtimer,clock_id是时钟类型,CLOCK_MONOTONIC表示自系统开机以来的单调递增时间,
//mode是时间模式,HRTIMER_MODE_ABS表绝对时间,HRTIMER_MODE_REL表相对时间
hrtimer_init(struct hrtimer *timer, clockid_t clock_id , enum hrtimer_mode mode)
//初始化之后就可以调用以下两个函数开始定时器,
//hrtimer_forward_now设置的时间为距离当前的interval个时间后
hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
hrtimer_forward_now(struct hrtimer *timer,ktime_t interval)
//取消函数
hrtimer_cancel(struct hrtimer *timer)

hrtimer实践

纸上谈兵没用,本萌觉得学习驱动,可能最好的方法是多写,多搞点乱七八糟的需求让自己去学着实现,将这些文绉绉的知识变为实实在在的代码,在不知道怎么写的时候,在内核源代码中都有相应的实现。好记性不如烂笔头嘛,自己安慰自己,哈哈哈。

需求

①设置定时器时间为10ms
②驱动每10ms生成一个uuid,并保存定时器函数被调用的ktime_t时间,一共生成5个uuid,即要设置5次定时器。
③通过cat /proc/test_proc可以查看到相应的uuid和ktime_t时间,生成ktime_t时间是为了通过实验查看定时器调用的精度。
④通过seq_file接口,实现其中的start,next,show,stop方法,使用链表组织以上的uuid数据和ktime_t时间。
最终效果如下图(可以看到该高精度计时器有一定误差)
在这里插入图片描述

代码说明

可以跳到最后看代码,本萌希望求得各位大佬的批评意见,这里写代码说明,一是为了防止遗忘,一是为了讲述代码的实现过程。
其实,之所以要刻意用链表把数据组织起来,是因为我的上一篇文章写timer定时器的时候,将所有数据直接扔到内存页中,简单粗暴但空间浪费得有点多。所以这里我想总结下链表的用法(毕竟这篇文章是写给我自己看的嘛)。

//链表初始化:
#define LIST_HEAD(name) \
      struct list_head name = LIST_HEAD_INIT(name)
      
//链表的插入(有两个函数list_add和list_add_tail),本质上都是调用_list_add方法
//__list_add(new, A, B);
//查看代码发现_list_add就是将新的节点插入到A和B节点之间
//list_add将new插入到head和head->next之间,即新插入的节点插入到head节点的右边
list_add(struct list_head *new, struct list_head *head)
//list_add_tail将new插入到head->prev和head之间,即新插入的节点插入到head的左边
list_add_tail(struct list_head *new, struct list_head *head)

//链表的遍历,遍历链表的每个节点,遍历到head节点时停下
list_for_each(pos, head)

接着,还必须说明一下seq_file的实现,seq_file需要实现4个迭代器对象,分别为show,start,next,stop,被迭代的数据可能是数组或者链表等,若是数组,迭代器的游标就是数组元素的地址,若是链表,则是链表节点的地址。第一次调用,则调用start将迭代数据的第一个游标地址返回,然后,调用show方法取得当前游标,将数据seq_printf出去。以后,则是调用next方法获取下一个游标地址,同样调用show方法获取游标数据,接着再调用next,再调用show,循环往复,直到数据的尾部,调用stop方法,做一些清除工作(为了完成互斥操作,比如可以在start方法加一把锁,在stop方法解锁。)
以上过程,可以通过printk跟踪到。
在这里插入图片描述
在本需求中,所谓的被迭代的数据,就是我们的uuid和ktime_t数据,我们可以将其设计为一个tproc_device_entry结构体,在里面包含一个链表元素。

  struct tproc_device_entry {
      char *uuid_buf;
	  ktime_t t;
	  struct list_head node;
  };

tproc_init
tproc_init完成一些初始化工作,创建proc的入口文件,初始化并添加了一个10ms的定时器hrtimer,HRTIMER_MODE_REL表示相对时间,设置定时器函数为tproc_timer_fn。

tproc_timer_fn
定时时间到达,该函数被调用,首先初始化一个struct tproc_device_entry对象,分配空间,填充里面的uuid数组,使用ktime_get获取相对时间,赋值给对象的ktime_t成员,接着,调用tproc_list_add()将对象链接到我们一开始定义的tproc_list_head头节点。最后,重新设置下时间为10ms后,返回HRTIMER_RESTART通知内核重启定时器。

tproc_seq_start
使用seq_list_start返回我们的链表第一个数据节点。

tproc_seq_next
该函数的参数中有个v地址,表示当前游标地址,使用seq_list_next返回我们的链表下一个节点地址。

tproc_seq_stop
什么都不做,无需释放什么东西。

tproc_seq_show
v表示当前游标地址,即我们链表节点的地址,根据该信息,获取数据节点的地址,获取其中的uuid数据和ktime_t数据,通过seq_printf将当前节点的这些数据打印出去。

  static int tproc_seq_show(struct seq_file *m, void *v)
  {
      struct tproc_device_entry *dev = container_of(v, struct tproc_device_entry, node);
      struct timeval t = ktime_to_timeval(dev->t);
      seq_printf(m,"uuid : %s\t second : %ld msecond : %ld\n",
                 dev->uuid_buf,(long)t.tv_sec,(long)t.tv_usec);
      return 0;
  }

tproc_exit
驱动被卸载调用,做后续工作:释放定时器,释放链表内存,移除proc入口。

到此,实现逻辑已经介绍完了,代码看后面吧,再见。

代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/seq_file.h>
#include <linux/uuid.h>
#include <linux/types.h>
#include <linux/ktime.h>
#include <linux/hrtimer.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>

#define PROC_NAME "test_proc"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Javier Huang");
//需求:将所有uuid用链表的方式链接在一起,并记录其中的生成时间,使用proc读取链表生成proc文件
//proc文件使用seq_open函数和seq_release函数
//驱动主名: tproc_

struct tproc_device_entry {
    char *uuid_buf;
    ktime_t t;
    struct list_head node;
};

static LIST_HEAD(tproc_list_head);
static struct hrtimer timer;
static struct proc_dir_entry *entry = NULL;
static int loops = 5;

static int tproc_open(struct inode *inode, struct file *filp);
static void * tproc_seq_start(struct seq_file *m, loff_t *pos);
static void tproc_seq_stop(struct seq_file *m, void *v);
static void * tproc_seq_next(struct seq_file *m, void *v, loff_t *pos);
static int tproc_seq_show(struct seq_file *m, void *v);
static void tproc_list_add(struct tproc_device_entry *entry);
static void tproc_list_free_all(void);

static const struct seq_operations tproc_seq_ops = {
    .start = tproc_seq_start,
    .next = tproc_seq_next,
    .stop = tproc_seq_stop,
    .show = tproc_seq_show
};

static const struct file_operations tproc_fops = {
    .owner = THIS_MODULE,
    .open = tproc_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = seq_release
};

static enum hrtimer_restart tproc_timer_fn(struct hrtimer *timer)
{
    struct tproc_device_entry *item = NULL;
    int uuid_size , i;
    char *buf2;
    uuid_le u;
    ktime_t now = ktime_get();

    //分配节点内存
    item = kmalloc(sizeof(struct tproc_device_entry),GFP_KERNEL);
    if(!item){
        printk(KERN_ERR "no enough memory left");
        return HRTIMER_NORESTART;
    }
    memset(item,0,sizeof(*item));

    //设置uuid_buf
    uuid_size = ARRAY_SIZE(u.b) * 2 + 1;
    item->uuid_buf = kmalloc(uuid_size,GFP_KERNEL);
    if(!item->uuid_buf){
        printk(KERN_ERR "no enough memory left");
        return HRTIMER_NORESTART;
    }
    memset(item->uuid_buf,0,uuid_size);
    buf2 = item->uuid_buf;
    uuid_le_gen(&u);
    for(i=0;i<ARRAY_SIZE(u.b);i++){
        if(u.b[i]<=0x0f)
            buf2 += sprintf(buf2,"0%x",u.b[i]);
        else
            buf2 += sprintf(buf2,"%x",u.b[i]);
    }
    
    //设置调用的时间
    item->t = now;

    //将节点插入链表
    tproc_list_add(item);

    if(--loops){
        //重新设置定时器,10ms后
        hrtimer_forward_now(timer,ktime_set(0,10000));
        return HRTIMER_RESTART;
    }else
        return HRTIMER_NORESTART;
}

static int tproc_open(struct inode *inode, struct file *filp)
{
    return seq_open(filp,&tproc_seq_ops);
}

static void * tproc_seq_start(struct seq_file *m, loff_t *pos)
{
    return seq_list_start(&tproc_list_head,*pos);
}

static void tproc_seq_stop(struct seq_file *m, void *v)
{
    //nothing
}

static void * tproc_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
    return seq_list_next(v,&tproc_list_head,pos);
}

static void tproc_list_add(struct tproc_device_entry *entry)
{
    list_add_tail(&entry->node,&tproc_list_head);
}

static void tproc_list_free_all(void)
{
    struct list_head *pos = NULL;
    struct tproc_device_entry *entry;
    list_for_each(pos,&tproc_list_head){
        entry = container_of(pos,struct tproc_device_entry,node);
        kfree(entry->uuid_buf);
        kfree(entry);
    }
}

static int tproc_seq_show(struct seq_file *m, void *v)
{
    struct tproc_device_entry *dev = container_of(v, struct tproc_device_entry, node);
    struct timeval t = ktime_to_timeval(dev->t);
    seq_printf(m,"uuid : %s\t second : %ld msecond : %ld\n",
               dev->uuid_buf,(long)t.tv_sec,(long)t.tv_usec);
    return 0;
}

static int __init tproc_init(void) 
{
    ktime_t tim;
    entry = proc_create(PROC_NAME,0,NULL,&tproc_fops);    
    if(!entry){
        printk(KERN_ERR "proc_create failed.\n");
        return -EIO;
    }
    
    hrtimer_init(&timer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);
    timer.function = tproc_timer_fn;
    tim = ktime_set(0,10000); //10ms
    hrtimer_start(&timer,tim,HRTIMER_MODE_REL);
    return 0;
}

static void __exit tproc_exit(void)
{
    hrtimer_cancel(&timer);
    tproc_list_free_all();
    remove_proc_entry(PROC_NAME,NULL); 
}

module_init(tproc_init);
module_exit(tproc_exit);
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值