内核定时器时钟源有硬件定时器提供,定时器频率可以设置,设置好之后,周期性产生定时器中断,系统利用定时器中断计时。中断频率即为系统频率,也叫作节拍率。单位Hz。
HZ
表示。$ make menuconfig
->Kernel Features
->Timer frequency ( [=y] )
定时器节拍率设置之后,内核周期性进入中断处理,累计节拍数。内核中使用全局变量记录从系统启动以来的系统节拍数。
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
其中jiffies_64
用在64位系统,jiffies
用于32位系统。在内核启动时初始化为0,每此进入硬件定时中断累加1,因此64位系统在节拍率为1000时,5.8亿年计数溢出,可忽略不计。但是32位系统只需要49.7天机会溢出,因此需要考虑绕回处理。
函数 | 描述 |
---|---|
time_after(unkown, known) | unkown 通常为 jiffies, known 通常是需要对比的值。 |
time_before(unkown, known) | unkown 通常为 jiffies, known 通常是需要对比的值。 |
time_after_eq(unkown, known) | unkown 通常为 jiffies, known 通常是需要对比的值。 |
time_before_eq(unkown, known) | unkown 通常为 jiffies, known 通常是需要对比的值。 |
如果unknown超过known,time_after返回真,否则返回假。time_before相反。
unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 */
/*************************************
具体的代码
************************************/
/* 判断有没有超时 */
if(time_before(jiffies, timeout))
{
/* 超时未发生 */
}
else
{
/* 超时发生 */
}
函数 | 描述 |
---|---|
int jiffies_to_msecs(const unsigned long j) | 将 jiffies 类型的参数 j 分别转换为对应的毫秒 |
int jiffies_to_usecs(const unsigned long j) | 将 jiffies 类型的参数 j 分别转换为对应的微秒 |
u64 jiffies_to_nsecs(const unsigned long j) | 将 jiffies 类型的参数 j 分别转换为对应的纳秒 |
long msecs_to_jiffies(const unsigned int m) | 将毫秒转换成jiffies类型 |
long usecs_to_jiffies(const unsigned int u) | 将微秒转换成jiffies类型 |
unsigned long nsecs_to_jiffies(u64 n) | 将纳秒转换成jiffies类型 |
Linux定时器使用很简单,只需要提供超时时间和定时器处理函数即可。但是内核定时器并不是周期性运行,超时后会自动关闭,因此如果需要周期性定时,需要在定时器处理函数中重新启动定时器。
struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};
struct timer_list timer; //定时器
void init_timer(struct timer_list *timer)
....demo....
init_timer(&timerdev.timer);
void add_timer(struct timer_list *timer)
int del_timer(struct timer_list * timer)
int del_timer_sync(struct timer_list *timer)
同步删除定时器,会等待其他处理器用完定时器再删除。
int mod_timer(struct timer_list *timer, unsigned long expires)
函数 | 描述 |
---|---|
void ndelay(unsigned long nsecs) | 纳秒延时 |
void udelay(unsigned long usecs) | 微秒延时 |
void mdelay(unsigned long mseces) | 毫秒延时 |
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define TIMER_CNT 1
#define TIMER_NAME "timer"
#define CLOSE_CMD (_IO(0xEF,0x1)) //关闭定时器
#define OPEN_CMD (_IO(0xEF,0x2)) //打开定时器
#define SETPERIOD_CMD (_IO(0xEF,0x3)) //设置定时器周期命令
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
//gpioled设备结构体
struct timer_dev
{
/* data */
dev_t devid; //设备号
struct cdev cdev; //cdev
struct class *class; //类
struct device *device; //设备
int major; //主设备号
int minor; //次设备号
struct device_node *nd; //设备节点
int led_gpio; //使用的GPIO编号
int timeperiod; //定时器周期,单位ms
struct timer_list timer; //定义定时器
spinlock_t lock; //定义自旋锁
};
struct timer_dev timerdev;//timer设备
/*
初始化led IO,open函数打开驱动时初始化GPIO
*/
static int led_init(void)
{
timerdev.nd = of_find_node_by_path("/light");//获取设备节点
if(timerdev.nd == NULL)
{
return -EINVAL;
}
timerdev.led_gpio = of_get_named_gpio(timerdev.nd,"light-gpio",0);//获取GPIO编号
if(timerdev.led_gpio < 0)
{
printk("can't find light-gpio!\r\n");
}
return 0;
}
/*
@description : 打开设备
@param - inode : 传递给驱动的inode
@param - filp : 设备文件
@return : 0 成功; 其他 失败
*/
static int timer_open(struct inode *inode, struct file *file)
{
int ret = 0;
file->private_data = &timerdev; //设置私有数据
timerdev.timeperiod = 1000;//默认定时器为1s
ret = led_init();
if(ret < 0)
{
return ret;
}
return 0;
}
/*
ioctl函数
*/
static long timer_unlocked_ioctl(struct file *filp,unsigned int cmd, unsigned long arg)
{
struct timer_dev *dev = (struct timer_dev *)filp->private_data;
int timerperiod;
unsigned long flags;
switch (cmd)
{
case CLOSE_CMD:
del_timer_sync(&dev->timer);
break;
case OPEN_CMD:
spin_lock_irqsave(&dev->lock,flags);
timerperiod = dev->timeperiod;
spin_unlock_irqrestore(&dev->lock,flags);
mod_timer(&dev->timer,jiffies + msecs_to_jiffies(timerperiod));
break;
case SETPERIOD_CMD:
spin_lock_irqsave(&dev->lock,flags);
dev->timeperiod = arg;
spin_unlock_irqrestore(&dev->lock,flags);
mod_timer(&dev->timer,jiffies + msecs_to_jiffies(arg));
break;
default:
break;
}
return 0;
}
/* 设备操作函数集合 */
static struct file_operations timer_fops = {
.owner = THIS_MODULE,
.open = timer_open,
.unlocked_ioctl = timer_unlocked_ioctl,
};
void timer_function(unsigned long arg)
{
struct timer_dev *dev = (struct timer_dev*)arg;
static int sta = 1;
int timerperiod;
unsigned long flags;
sta = !sta;//每次状态取反,翻转LED
gpio_set_value(dev->led_gpio,sta);
//重启定时器
spin_lock_irqsave(&dev->lock,flags);
timerperiod = dev->timeperiod;
spin_unlock_irqrestore(&dev->lock,flags);
mod_timer(&dev->timer,jiffies + msecs_to_jiffies(dev->timeperiod));
}
/*
@description : 驱动入口函数
@param - : 无
@return : 无
*/
static int __init timer_init(void)
{
//初始化自旋锁
spin_lock_init(&timerdev.lock);
//注册字符设备驱动
//1.创建设备号
if(timerdev.major)
{
timerdev.devid = MKDEV(timerdev.major,0);
register_chrdev_region(timerdev.devid,TIMER_CNT,TIMER_NAME);
}
else
{
alloc_chrdev_region(&timerdev.devid,0,TIMER_CNT,TIMER_NAME);//申请设备号
timerdev.major = MAJOR(timerdev.devid);//获取主设备号
timerdev.minor = MINOR(timerdev.devid);//获取次设备号
}
printk("timerdev major=%d,minor=%d\r\n",timerdev.major,timerdev.minor);
//2.初始化cdev
timerdev.cdev.owner = THIS_MODULE;
cdev_init(&timerdev.cdev,&timer_fops);
//3.添加cdev
cdev_add(&timerdev.cdev,timerdev.devid,TIMER_CNT);
//4.创建类
timerdev.class = class_create(THIS_MODULE,TIMER_NAME);
if(IS_ERR(timerdev.class))
{
return PTR_ERR(timerdev.class);
}
//5.创建设备
timerdev.device = device_create(timerdev.class,NULL,timerdev.devid,NULL,TIMER_NAME);
if(IS_ERR(timerdev.device))
{
return PTR_ERR(timerdev.device);
}
//6.初始化timer,设置定时器回调函数,未设置周期,因此不会激活定时器
init_timer(&timerdev.timer);
timerdev.timer.function = timer_function;
timerdev.timer.data = (unsigned long)&timerdev;
return 0;
}
static void __exit timer_exit(void)
{
gpio_set_value(timerdev.led_gpio,1);
del_timer_sync(&timerdev.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_LICENSE("GPL");
MODULE_AUTHOR("Turing");
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#define CLOSE_CMD (_IO(0xEF,0x1)) //关闭定时器
#define OPEN_CMD (_IO(0xEF,0x2)) //打开定时器
#define SETPERIOD_CMD (_IO(0xEF,0x3)) //设置定时器周期命令
int main(int argc, char *argv[])
{
int fd,ret;
char *filename;
unsigned int cmd;
unsigned int arg;
unsigned char str[100];
if(argc != 2)
{
printf("error usage!\r\n");
return -1;
}
filename = argv[1];
//打开LED驱动
fd = open(filename,O_RDWR);
if(fd<0)
{
printf("file %s open failed! \r\n",argv[1]);
return -1;
}
while (1)
{
printf("Input CMD:");
ret = scanf("%d",&cmd);
if(ret != 1)//参数输入错误
{
gets(str);//防止卡死
}
if(cmd == 1)
cmd = CLOSE_CMD;
else if (cmd == 2)
{
cmd = OPEN_CMD;
}
else if (cmd == 3)
{
cmd = SETPERIOD_CMD;//设置周期
printf("Input Timer Period:");
ret = scanf("%d",&arg);
if(ret != 1)//参数输入错误
{
gets(str);//防止卡死
}
}
ioctl(fd,cmd,arg);//控制定时器打开关闭
}
close(fd);
}
KERNELDIR := /home/book/arm/imx6ull/ebf_6ull_linux
CURRENT_PATH := $(shell pwd)
obj-m := timer.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
#!/bin/bash
sudo make -j12 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
arm-linux-gnueabihf-gcc timerApp.c -o timerApp
sudo rm -r .*.cmd *.mod.* *.o *.symvers *.order
sudo cp *.ko /home/book/arm/imx6ull/eth_file/modules
sudo cp *App /home/book/arm/imx6ull/eth_file/modules