【嵌入式环境下linux内核及驱动学习笔记-(9-内核定时器)】

2023-05-16

目录

  • 1、时钟tick中断等概念
  • 2、延时机制
    • 2.1 短延时(忙等待类--非阻塞害)
      • 2.1.1 ndelay 忙等待延迟多少纳秒
      • 2.1.2 udelay 忙等待延迟多少微秒
      • 2.1.3 mdelay 忙等待延迟多少毫秒
    • 2.2 长延迟:忙等待 (非阻塞类)
      • 2.2.1 time_after 忙等待延迟到
      • 2.2.2 time_before 忙等待延迟到
    • 2.3 阻塞类睡眠延迟
      • 2.3.1 msleep 深睡
      • 2.3.2 msleep_interruptible 浅睡
  • 3、定时器
    • 3.1 定义定时器结构体
    • 3.2 init_timer 初始化定时器
    • 3.3 add_timer 加入链表
    • 3.4 del_timer 删除定时器
    • 3.5 mod_timer 修改定时器
    • 3.6 定时器的使用框架
  • 4 、实例
    • 4.1 计数器驱动代码
    • 4.2 以下是应用层程序
    • 4.3 测试过程

内核在运行中,需要处理两种时间应用,一个是延时,一个是定时。这些都要依赖于精确的tick中断来实现。

内核定时器机制主要包含两部分:

1、 定时器列表:内核维护一个定时器列表,用于保存所有激活的定时器。每个定时器都关联一个过期时间,定时器按过期时间排序在列表中。

2、定时器处理线程:内核有一个独立的线程运行定时器处理函数timer_expire_work_func()。这个函数不断从定时器列表头部取出已过期的定时器,调用定时器关联的回调函数。

1、时钟tick中断等概念

硬件有一个时钟装置,该装置每隔一定时间发出一个时钟中断(称为一次时钟嘀嗒-tick),对应的中断处理程序就将全局变量jiffies_64加1。

jiffies_64 是一个全局64位整型, jiffies全局变量为其低32位的全局变量,程序中一般用jiffies。

所以可以把jiffies看成是一个时间戳计数器

HZ:可配置的宏,表示1秒钟产生的时钟中断次数,一般设为100或200

2、延时机制

按时间工短分为:短延时与长延时

按延时的方式分为:阻塞类延时和非阻塞类延时

延时机制的选择原则:

  1. 异常上下文中只能采用忙等待类
  2. 任务上下文短延迟采用忙等待类,长延迟采用阻塞类

2.1 短延时(忙等待类–非阻塞害)

2.1.1 ndelay 忙等待延迟多少纳秒

void ndelay(unsigned long nsecs) 延迟多少纳秒

2.1.2 udelay 忙等待延迟多少微秒

void udelay(unsigned long usecs) 延迟多少微秒

2.1.3 mdelay 忙等待延迟多少毫秒

void mdelay(unsigned long msecs) 延迟多少毫秒

2.2 长延迟:忙等待 (非阻塞类)

使用jiffies比较宏来实现

2.2.1 time_after 忙等待延迟到

time_after(a,b) //a > b

2.2.2 time_before 忙等待延迟到

time_before(a,b) //a < b 意为从a时间延迟到b时间

//延迟100个jiffies
unsigned long delay = jiffies + 100;
while(time_before(jiffies,delay))
{
;
}

//延迟2s
unsigned long delay = jiffies + 2*HZ;
while(time_before(jiffies,delay))
{
;
}

2.3 阻塞类睡眠延迟

2.3.1 msleep 深睡

void msleep(unsigned int msecs);

2.3.2 msleep_interruptible 浅睡

unsigned long msleep_interruptible(unsigned int msecs);

3、定时器

3.1 定义定时器结构体

linux 3.14
#include <linux/timer.h>  

struct timer_list 
{
 struct list_head entry;
 unsigned long expires;           // 期望的时间值   当前jiffies + x * HZ  做为超时时间
 void (*function)(unsigned long); // 时间到达后,执行的回调函数,该函数处于软中断异常上下文,因此该回调函数内不允许有阻塞操作
 unsigned long data;      //传给回调的参数
};
linux 4.15
struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct hlist_node	entry;
	unsigned long		expires;
	void			(*function)(struct timer_list *);
	u32			flags;

#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
};

这个函数完成了定时器结构体的初始化工作:

  • 将 entry.pprev 字段置为空,以标识定时器未插入任何链表。
  • 设置 function 字段为传入的回调函数 callback。
  • 设置 flags 字段为传入的定时器标志 flags。它会检查 flags 值的合法性。
  • 可选地设置 lockdep_map 字段用于锁验证,这需要 CONFIG_LOCKDEP 选项开启。
  • 不会设置 expires 字段,这需要在添加定时器时或手动设置。
    所以,这个函数将在静态初始化时为一个定时器结构体配置好回调函数和标志,以备随后添加使用。调用者只需要设置 expires 字段,就可以使用该定时器了。

3.2 init_timer 初始化定时器

init_timer(struct timer_list *)

注意:如果是linux4.15版以后,用的是timer_setup函数源码

static inline void timer_setup(struct timer_list *timer, 
               					void (*callback)(struct timer_list *), 
               					unsigned int flags)
{
    /* 
     * check for invalid bits, the rest is stored as-is 
     * Note that TIMER_DUMMY is silently ignored, so there is no
     * way to select it here.
     */
    BUILD_BUG_ON(flags & ~TIMER_ADVANCED_BITS|TIMER_DEFERRED);

    timer->entry.pprev = NULL;
    timer->function = callback;
    timer->flags = flags;
#ifdef CONFIG_LOCKDEP
    timer->lockdep_map = 0;
#endif
}

3.3 add_timer 加入链表

增加定时器到链表中------从而使该 定时器开始计时
void add_timer(struct timer_list *timer);

3.4 del_timer 删除定时器

删除定时器 -------定时器停止工作
int del_timer(struct timer_list * timer);

3.5 mod_timer 修改定时器

int mod_timer(struct timer_list *timer, unsigned long expires);
需要每次定时器到时后,调用这个函数重新设置定时器的expires参数。使定时器再次定时

3.6 定时器的使用框架

1、定义struct timer_list tl类型的变量
2、定义定时器回调函数
void xxx_func(unsigned long arg)
{

mod_timer(…);//如需要定时器继续隔指定时间再次调用本函数
}

3、在模块入口函数处初始化定时器
init_timer(…);//模块入口函数
如果是linux4.15以上版本,则用timer_setup()函数初始化

4、在模块入口函数或open或希望定时器开始工作的地方写如下代码
tl.expires = jiffies + n * HZ //n秒
tl.function = xxx_func;
tl.data = …;

add_timer(…);

5、不想让定时器继续工作时
del_timer(…);

4 、实例

这里做一个定时器驱动。该驱动会启动一个定时器,每一秒计一次数。另外,从应用层有一个程序,间隔去读这个驱动的计数器并显示出来:

4.1 计数器驱动代码

/*************************************************************************
	> File Name: timer-second.c   
 ************************************************************************/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/timer.h>

/*1、定义重要的变量及结构体*/
struct x_dev_t {
    struct cdev  my_dev;  //cdev设备描述结构体变量
    atomic_t have_open;   //记录驱动是否被打开的原子变量
    struct timer_list tlist;  //定时器结构体
    unsigned int second;    //计数器变量
};

struct x_dev_t *pcdev;

/*所有驱动函数声明*/
ssize_t read (struct file *, char __user *, size_t, loff_t *);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
    .owner = THIS_MODULE,
    .open = open,
    .release = release,
    .read = read,
};

//定时器的回调函数linux4.15版
void timer_back(struct timer_list *data){
    pcdev->second ++;
    //printk("timer_back second %d\n",pcdev->second);
    data->expires = jiffies + HZ;
     mod_timer(data , jiffies+HZ);
    //add_timer(data);
}

static int __init my_init(void){
    int unsucc =0;
    dev_t devno;
    int major,minor;
    pcdev = kzalloc(sizeof(struct x_dev_t), GFP_KERNEL);
    /*2、创建 devno */
    unsucc = alloc_chrdev_region(&devno , 0 , 1 , "timer-second");
    if (unsucc){
        printk(" creating devno  faild\n");
        return -1;
    }
    major = MAJOR(devno);
    minor = MINOR(devno);
    printk("devno major = %d ; minor = %d;\n",major , minor);

    /*3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来*/
    /*这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体*/
    cdev_init(&pcdev->my_dev , &fops);
    pcdev->my_dev.owner = THIS_MODULE;
    /*4、注册cdev结构体到内核链表中*/
    unsucc = cdev_add(&pcdev->my_dev,devno,1);
    if (unsucc){
        printk("cdev add faild \n");
        return 1;
    }
    //初始化原子量have_open为1
    atomic_set(&pcdev->have_open,1);
    printk("the driver timer-second initalization completed\n");
    return 0;
}
static void  __exit my_exit(void)
{
    cdev_del(&pcdev->my_dev);
    unregister_chrdev_region(pcdev->my_dev.dev , 1);
    printk("***************the driver timer-second  exit************\n");
}

/*5、驱动函数的实现*/
/*file_operations结构全成员函数.open的具体实现*/
int open(struct inode *pnode , struct file *pf){
    struct x_dev_t *p = container_of(pnode->i_cdev,struct x_dev_t , my_dev);
    pf->private_data = (void *)p;
    //在open函数中对原子量have_open进行减1并检测。=0,允许打开文件,<0则不允许打开
    if (atomic_dec_and_test(&p->have_open)){
        printk("timer-second is opened\n");
        //初始化定时器并启动
        p->second = 0;
        timer_setup(&p->tlist , timer_back , 0);
        p->tlist.expires = jiffies + HZ;
        add_timer(&p->tlist);
        return 0;
    }else{
        printk("device timer-second can't be opened again\n");
        atomic_inc(&p->have_open);//原子量=-1,记得这里要把原子量加回到0
        return -1;
    }   
}
/*file_operations结构全成员函数.release的具体实现*/
int release(struct inode *pnode , struct file *pf){
    struct x_dev_t *p = (struct x_dev_t *)pf->private_data;
    printk("timer-second is closed \n");
    atomic_set(&p->have_open,1);
    del_timer(&p->tlist);
    return 0;
}
ssize_t  read (struct file * pf,  char __user * buf, size_t size ,  loff_t * pops){
    struct x_dev_t *p = pf->private_data;
    printk("driver: now timer is %d 's\n",p->second);
    return p->second;
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");


4.2 以下是应用层程序

/*************************************************************************
	> File Name: test.c
 ************************************************************************/

#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main (int argc , char **argv){
    int fd0,fd1;

    if (argc <2){
        printf("argument is too less\n");
        return -1;
    }else{
        fd0 = open(argv[1] , O_RDONLY );
        while (fd0){

            printf("read timer-second is %d\n",read(fd0 , NULL,0));
            sleep(2);
        }
        
    }
    close(fd0);
    return 0;


}

4.3 测试过程

sudo insmod timer-second.ko

用dmesg显示,设备号为244 0

sudo mknod /dev/timer-second c 244 0
sudo chmod 777 /dev/timer-second
执行测试程序: ./test.elf /dev/timer-second

结果:每2秒读一次设备,显示驱动的计数值。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【嵌入式环境下linux内核及驱动学习笔记-(9-内核定时器)】 的相关文章

随机推荐