MP157 linux内核定时器

MP157 linux内核定时器

一、Linux 系统节拍数

1.1 简介

Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会
将 jiffies 初始化为 0,jiffies 定义在文件 include/linux/jiffies.h 中

//定义了一个 64 位的 jiffies_64
extern u64 __cacheline_aligned_in_smp jiffies_64; 
//定义了一个 unsigned long 类型的 32 位的 jiffies
extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies; 

jiffies_64 和 jiffies 其实是同一个东西,jiffies_64 用于 64 位系统,而 jiffies 用于 32 位系统。
jiffies 其实就是 jiffies_64 的低 32 位

1.2 API函数

将 jiffies 类型的参数 j 分别转换为对应的毫秒、微秒、纳秒

int jiffies_to_msecs(const unsigned long j) 
int jiffies_to_usecs(const unsigned long j)
u64 jiffies_to_nsecs(const unsigned long j) 

将毫秒、微秒、纳秒转换为 jiffies 类型

long msecs_to_jiffies(const unsigned int m) 
long usecs_to_jiffies(const unsigned int u) 
unsigned long nsecs_to_jiffies(u64 n) 

二、内核定时器

2.1 简介

Linux 内核定时器使用很简单,只需要提供超时时间(相当于定时值)和定时处理函数即可。

注意:内核定时器并不是周期性运行的,超时以后就会自动关闭,那么就需要在定时处理函数中重新开启定时器

Linux 内核使用 timer_list 结构体表示内核定时器,timer_list 定义在文件 include/linux/timer.h 中

struct timer_list {
		struct hlist_node entry; 
		unsigned long expires; /* 定时器超时时间,单位是节拍数 */
		void (*function)(struct timer_list *);/* 定时处理函数*/
		u32 flags; /* 标志位 */
		
#ifdef CONFIG_LOCKDEP 
	struct lockdep_map lockdep_map; 
	//锁依赖性检测	这是一种用于帮助检测锁相关问题的调试工具,特别是在多线程和并发环境中。
#endif
};	

2.2 API函数

2.2.1 timer_setup 函数
/**
*	功能:	
*		负责初始化 timer_list 类型变量
*	参数:	
*		timer:要初始化定时器
*		func:定时器的回调函数,此函数的形参是当前定时器的变量
*		flags: 标志位,直接给 0 就行
*	返回值:
*		没有返回值
*/
void timer_setup(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags)
2.2.2 add_timer 函数
/**
*	功能:	
*		向 Linux 内核注册定时器
*	参数:	
*		timer:要注册的定时器
*	返回值:
*		没有返回值
*/
void add_timer(struct timer_list *timer) 
2.2.3 删除一个定时器
/**
*	功能:	
*		用于删除一个定时器
*	参数:	
*		timer:要删除的定时器
*	返回值:
*		0,定时器还没被激活;1,定时器已经激活
*	注意:
*		不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,
*		因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出
*/
int del_timer(struct timer_list * timer) 
/**
*	功能:	
*		用于删除一个定时器
*	参数:	
*		timer:要删除的定时器
*	返回值:
*		0,定时器还没被激活;1,定时器已经激活
*	注意:
*		会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中
*/
int del_timer_sync(struct timer_list *timer)
2.2.4 mod_timer 函数
/**
*	功能:	
*		mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器
*	参数:	
*		要修改超时时间(定时值)的定时器
*		expires:修改后的超时时间
*	返回值:
*		0,调用 mod_timer 函数前定时器未被激活;1,调用 mod_timer 函数前定时器已被激活	
*/
int mod_timer(struct timer_list *timer, unsigned long expires) 

2.3 Linux 内核短延时函数

//纳秒、微秒和毫秒延时函数
void ndelay(unsigned long nsecs)
void udelay(unsigned long usecs) 
void mdelay(unsigned long mseces) 

三、简单使用方法

struct timer_list timer; /* 定义定时器 */ 

/* 定时器回调函数 */
void timeout(struct timer_list *arg)
{
	/*	用户执行代码段*/
	
	/*	函数重新设置超时值并且启动定时器	*/
	mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000)); 
	
}

void init(void)
{
	timer_setup(&timer, timeout, 0);	//初始化定时器,并设置回调函数
	timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
	add_timer(&timer);
}

void exit(void)
{
	del_timer(&timer); /* 删除定时器 */
}

四、使用实例(定时LED翻转)

4.1 设备树

gpioi0 低电平有效

gpioled {
		compatible = "dzl,led";
		status = "okay";
		led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;
	};

4.2 驱动程序

mod_timer(&timerdev.timer, jiffies + msecs_to_jiffies(timerdev.timeperiod));
解释
jiffies 为当前系统节拍数
timerdev.timeperiod 前文定义为 2000(2秒)
假设 jiffies 当前为4555,所以当下次jiffies 为(4555+2000)两秒后发生定时器中断

#include <linux/types.h> 
#include <linux/kernel.h> 
#include <linux/delay.h> 
#include <linux/ide.h> 
#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/cdev.h> 
#include <linux/device.h> 
#include <linux/of.h> 
#include <linux/of_address.h> 
#include <linux/of_gpio.h> 
#include <linux/semaphore.h> 
#include <linux/timer.h> 
#include <asm/mach/map.h> 
#include <asm/uaccess.h> 
#include <asm/io.h> 

#define TIMER_CNT           1                   /* 设备号个数 */ 
#define TIMER_NAME          "timer"             /* 名字 */

/*  timer 设备结构体*/
struct timer_dev {
    dev_t devid;               /*  设备号      */
    int major;                  /*  主设备号    */
    int minor;                  /*  次设备号    */
    struct cdev cdev;           /*  cdev        */
    struct class *class;        /*  类          */
    struct device *device;      /*  设备        */
    struct device_node *nd;     /*  设备节点    */
    int led_gpio;                /* led 所使用的 GPIO 编号 */ 
    int timeperiod;             /* 定时周期,单位为 ms */
    struct timer_list timer; /* 定义一个定时器 */
};


struct timer_dev timerdev; /* timer 设备 */

static int led_init(void)
{
    int ret;
    const char *str;
    /*  设置 LED 所使用的 GPIO    */
    /*  获取设备节点:gpioled  */
    timerdev.nd = of_find_node_by_path("/gpioled");
    if(timerdev.nd == NULL) {
        printk("gpioled node nost find!\r\n"); 
        return -EINVAL;
    }else {
        printk("gpioled node find!\r\n");
    }

    /*  获取 compatible 属性内容  */
    ret = of_property_read_string(timerdev.nd,"compatible",&str);
    if(ret < 0) {
        printk("compatible read failed !\r\n");
    }else {
        printk("compatible = %s !\r\n", str);
        if(strcmp(str,"dzl,led")) {
            return -EINVAL;
        }
    }
    ret = of_property_read_string(timerdev.nd,"status",&str);
    if(ret < 0) {
        printk("status read failed !\r\n");
    }else {
        printk("status = %s !\r\n", str);
        if(strcmp(str,"okay")) {
            return -EINVAL;
        }
    }

    /* 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    timerdev.led_gpio = of_get_named_gpio(timerdev.nd, "led-gpio", 0);
    if(timerdev.led_gpio < 0) {
        printk("can't get led-gpio\r\n");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", timerdev.led_gpio);

    /* 向 gpio 子系统申请使用 GPIO */
    ret = gpio_request(timerdev.led_gpio, "LED-GPIO");
    if (ret) {
        printk(KERN_ERR "gpioled: Failed to request led-gpio\n");
        return ret;
    }

    /* 设置 PI0 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(timerdev.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!\r\n");
        return ret;
    }
    printk("LED 配置成功\r\n");
    return 0;
}

static int timer_open(struct inode *inode, struct file *filp) 
{
    int ret = 0;
    filp->private_data = &timerdev;     /* 设置私有数据 */ 

    timerdev.timeperiod = 2000; /* 默认周期为 2s 定时2秒*/
    mod_timer(&timerdev.timer, jiffies + msecs_to_jiffies(timerdev.timeperiod)); 

    ret = led_init();                   /* 初始化 LED IO */

    if(ret < 0)
        return ret;

    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    struct timer_dev *dev = filp->private_data;
   	
   	/**
   	*	APP	close()关闭文件时拉高电平	灯灭
   	*/
    gpio_set_value(dev->led_gpio, 1);
    gpio_free(dev->led_gpio); 

    return 0;
}


/* 设备操作函数 */ 
static struct file_operations timer_fops = { 
    .owner = THIS_MODULE, 
    .open = timer_open, 
    // .unlocked_ioctl = timer_unlocked_ioctl, 
    .release = led_release, 
};

void  timer_function(struct timer_list *arg)
{
    static int sta = 1;
    sta = !sta; /* 每次都取反,实现 LED 灯反转 */
    gpio_set_value(timerdev.led_gpio, sta);

	/**
	*	内核定时器并不是周期性运行的,超时以后就会自动关闭,
	*	那么就需要在定时处理函数中重新开启定时器
	*/
    mod_timer(&timerdev.timer, jiffies + msecs_to_jiffies(timerdev.timeperiod)); //重新启动定时器
}

/**
 *  驱动函数入口
 */
static int __init timer_init(void)
{
    int ret;

	/*创建设备号 */ 
    if(timerdev.major) {
        timerdev.devid = MKDEV(timerdev.major, 0);
        ret = register_chrdev_region(timerdev.devid, TIMER_CNT,TIMER_NAME); 
        if(ret < 0) 
            return -EIO;
    }else {
        ret = alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT,TIMER_NAME); 
        if(ret < 0) 
            return -EIO;
        timerdev.major = MAJOR(timerdev.devid); /* 获取主设备号 */ 
        timerdev.minor = MINOR(timerdev.devid); /* 获取次设备号 */ 
    }
    printk("timerdev major=%d,minor=%d\r\n",timerdev.major,timerdev.minor); 
    
    /* 初始化 cdev */
    timerdev.cdev.owner = THIS_MODULE;
    cdev_init(&timerdev.cdev, &timer_fops); 

    /* 添加一个 cdev */ 
    ret = cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
    if(ret < 0) 
        goto del_unregister;

    /* 创建类 */
    timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
    if (IS_ERR(timerdev.class))
        goto del_cdev;

    /* 创建设备 */
    timerdev.device = device_create(timerdev.class, NULL,timerdev.devid, NULL, TIMER_NAME);
    if (IS_ERR(timerdev.device))
        goto destroy_class;


    timer_setup(&timerdev.timer, timer_function, 0);	//初始化定时器,并设置回调函数
    return 0;
    
destroy_class: 
    class_destroy(timerdev.class);      
del_cdev: 
    cdev_del(&timerdev.cdev);
del_unregister:
    unregister_chrdev_region(timerdev.devid, TIMER_CNT);
    return -EIO;
}

/**
 *  驱动函数出口
 */
static void __exit timer_exit(void)
{

    del_timer_sync(&timerdev.timer); /* 删除 timer */ 


    /* 注销字符设备驱动 */ 
    cdev_del(&timerdev.cdev); /* 删除 cdev */
    unregister_chrdev_region(timerdev.devid, TIMER_CNT);

    device_destroy(timerdev.class, timerdev.devid); 
    class_destroy(timerdev.class);
}


module_init(timer_init); 
module_exit(timer_exit);
MODULE_AUTHOR("dzl");
MODULE_LICENSE("GPL");
MODULE_INFO(intree, "Y");

4.3 应用程序(APP)

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


int main(int argc,char *argv[])
{
    int fd;
    int ret;
    char *filename;
    int i = 5;

    filename = argv[1];
    fd = open(filename,O_RDWR);
    if(fd < 0) {
        perror("open error\r\n");
        return -1;
    }

    while(i--) {
    	/**
    	*	循环15秒
    	*/
        sleep(3);
    }

    ret= close(fd);
    if(ret < 0){ 
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

4.4 结果

insmod xxx.ko	#加载驱动模块
./APP /dev/timer

程序等待15秒,期间LED两秒翻转一次,while()循环结束后,关闭文件(close(fd)),拉高电平灯灭。

rmmod	xxx.ko	#卸载驱动模块
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值