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 #卸载驱动模块