文章目录
- 前言
- 通用定时器
- 系统节拍
- 节拍数与时间转换
- 基本框架
- 定时器使用
- 代码展示
- 通用定时器特点
- 高精度定时器
前言
LInux内核定时器是一种基于未来时间点的计时方式,以当前时刻来启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分,我要定时5分钟,那么定时就是10点5分+5分钟=10点10分。这个和手机闹钟很相似。比如你要定一个第二天早晨8点的闹钟,就是当前时间定时到第二天早晨8点。
通用定时器
系统节拍
在了解定时器之前,先来了解一下内核系统节拍的设置。硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后 就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率, 也叫做节拍率(tick rate)(有的资料也叫系统频率),比如 1000Hz,100Hz 等等说的就是系统节拍 率。系统节拍率是可以设置的,单位是 Hz,我们在编译 Linux 内核的时候可以通过图形化界面 设置系统节拍率,按照如下路径打开配置界面:
-> Kernel Features -> Timer frequency ( [=y])
默认情况下选择 100Hz。设置好以后打开 Linux 内核源码根目录下的.config 文件中CONFIG_HZ会等于设置好的值,Linux 内核会使用 CONFIG_HZ 来设置自己的系统时 钟。打开文件 include/asm-generic/param.h,有如下内容:
# undef HZ
# define HZ CONFIG_HZ
# define USER_HZ 100
# define CLOCKS_PER_SEC (USER_HZ)
这里需要注意的是,并不是节拍率越高越好,高节拍率会提高系统时间精度,但是高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担。
Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会 将 jiffies 初始化为 0,jiffies 定义在文件 include/linux/jiffies.h 中,如下图所示,jiffies_64 和 jiffies 其实是同一个东西,jiffies_64 用于 64 位系统,而 jiffies 用于 32 位系统。
节拍数与时间转换
为了方便开发,Linux 内核提供了几个 jiffies 和 ms、us、ns 之间的转换函数,如表所示:
有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。Linux 内核提供了毫秒、微 秒和纳秒延时函数:
基本框架
Linux内核使用timer_list结构体表示内核定时器,timer_list定义在文件include/linux/timer.h中,定义如下:
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry;
unsigned long expires; /*定时器超时时间,单位节拍数*/
void (*function)(unsigned long);/*定时处理函数*/
unsigned long data;/*要传递function函数的参数*/
u32 flags;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
在上面这个结构体中,有几个参数需要重点关注一下。一个是expires到期时间,单位是节拍数。等于定时的当前的始终节拍计数(存储在系统的全局变量和jiffies)+定时时长对应的时钟节拍数量。
那么如何把时间 转换成节拍数量呢?示例:假如从现在开始定时1秒,转换成节拍数量是多少呢? 内核中有一个宏HZ,表示一秒对应的时钟节拍数,那么我们就可以通过这个宏来把时间转换成节拍数。所以,定时1秒就是expires = jiffies + 1*HZ。前面已经说明了HZ是怎么设置的。
定时器使用
- 当我们定义了一个 timer_list 变量以后一定 要先用 init_timer 初始化一下
void init_timer(struct timer_list *timer)
- add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后, 定时器就会开始运行
void add_timer(struct timer_list *timer)
- del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。
int del_timer(struct timer_list * timer)
- del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,
int del_timer_sync(struct timer_list *timer)
- mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器!
int mod_timer(struct timer_list *timer, unsigned long expires)
其中timer:要修改超时时间(定时值)的定时器。 expires:修改后的超时时间。
代码展示
下面这个代码实现了内核定时器周期性的点亮和熄灭开发板上的 LED 灯,当CMD = 3 是LED 灯的闪烁周期可以由内核定时器来设置,这里用到了ioctrl的相关知识,linux内核给用户提供了两类系统调用函数:一类是数据操作函数,比如read、write…。 另外一类函数是非数据操作函数,比如ioctl…,用户程序可以用ioctl给底层设备发送指令。 如果不是很熟悉这一块内容的话可以参考我的下一篇帖子:嵌入式驱动开发详解5(ioctl的使用)
应用层代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include "string.h"
#include <stdlib.h>
#include <sys/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];
fd = open(filename ,O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n",filename);
return -1;
}
while(1){
printf("Input CMD:");
ret = scanf("%d",&cmd);
if(ret != 1){ /* 参数输入错误 */
fgets(str,100,stdin); /* 防止卡死 */
}
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){
fgets(str,100,stdin);
}
}
ioctl(fd,cmd,arg);
}
ret = close(fd);
if(ret < 0){
printf("file %s close failed! \r\n",filename);
return -1;
}
return 0;
}
内核层代码如下:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h> //copy
#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"
#define CLOSE_CMD _IO(0XEF, 0X1) /* 鍏抽棴瀹氭椂鍣� */
#define OPEN_CMD _IO(0XEF, 0X2) /* 鎵撳紑瀹氭椂鍣� */
#define SETPERIOD_CMD _IO(0XEF, 0X3) /* 璁剧疆瀹氭椂鍣ㄥ懆鏈熷懡浠� */ //铏界劧姝ゅ娌℃湁鍐檃rg锛屼絾鏄渶鍚庡鏋滄湁arg鍙傛暟杩樻槸鍙互浼犺繘鏉�
#define LED_ON 1
#define LED_OFF 0
struct timer_dev{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int led_gpio;
int timeperiod;
struct timer_list timer;
spinlock_t lock;
};
struct timer_dev timerdev;
static int led_init(void)
{
int ret;
timerdev.nd = of_find_node_by_path("/gpioled");
if(timerdev.nd == NULL){
printk("timerdev node cant not find!!\r\n");
ret = -1;
goto fail_node;
}else{
printk("timerdev node found!!\r\n");
}
timerdev.led_gpio = of_get_named_gpio(timerdev.nd,"led-gpio",0);
if(timerdev.led_gpio < 0){
printk("cant not get led-gpio\r\n");
ret = -1;
goto fail_node;
}
printk("led-gpio-num=%d\r\n",timerdev.led_gpio);
gpio_request(timerdev.led_gpio,"led");
ret = gpio_direction_output(timerdev.led_gpio,1);
if(ret < 0){
printk("can`t set gpio!!!\r\n");
}
return 0;
fail_node:
return ret;
}
static int timer_open(struct inode *inode, struct file *file)
{
int ret = 0;
file->private_data = &timerdev;
timerdev.timeperiod = 1000;
ret = led_init();
if(ret < 0){
return ret;
}
return 0;
}
static long timer_unlocked_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
unsigned long flags;
int timerperiod;
struct timer_dev *dev = filep->private_data;
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(dev->timeperiod));
break;
default:
break;
}
return 0;
}
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;
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(timerperiod));
}
static const struct file_operations timer_fops = {
.owner = THIS_MODULE,
.open = timer_open,
.unlocked_ioctl = timer_unlocked_ioctl,
};
static int __init timer_init(void)
{
int ret;
/* 鍒濆鍖栧師瀛愬彉閲� */
spin_lock_init(&timerdev.lock);
if(timerdev.major){
timerdev.devid = MKDEV(timerdev.major,timerdev.minor);
ret = register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);
}else{
ret = alloc_chrdev_region(&timerdev.devid,0,TIMER_CNT,TIMER_NAME);
timerdev.major = MAJOR(timerdev.devid);
timerdev.minor = MINOR(timerdev.devid);
printk("alloc_chrdev_region major=%d minor=%d\r\n",timerdev.major, timerdev.minor);
}
if (ret < 0) {
printk("Could not register\r\n");
goto fail_devid;
}
timerdev.cdev.owner = THIS_MODULE;
cdev_init(&timerdev.cdev, &timer_fops);
ret = cdev_add(&timerdev.cdev,timerdev.devid,TIMER_CNT);
if(ret < 0){
printk("Could not cdev\r\n");
goto fail_cdev;
}
timerdev.class = class_create(THIS_MODULE,TIMER_NAME);
if(IS_ERR(timerdev.class)){
ret = PTR_ERR(timerdev.class);
goto fail_class;
}
timerdev.device = device_create(timerdev.class,NULL,timerdev.devid,NULL,TIMER_NAME);
if(IS_ERR(timerdev.device)){
ret = PTR_ERR(timerdev.device);
goto fail_device;
}
//鍒濆鍖杢imer锛岃缃畾鏃跺櫒澶勭悊鍑芥暟锛岃繕鏈缃懆鏈燂紝鎵€浠ヤ笉浼氭縺娲诲畾鏃跺櫒
init_timer(&timerdev.timer);
timerdev.timer.function = timer_function;
timerdev.timer.data = (unsigned long)&timerdev; //璁剧疆瑕佷紶閫掔粰 timer_function 鍑芥暟鐨勫弬鏁颁负 timerdev 鐨勫湴鍧€
return 0;
fail_device:
class_destroy(timerdev.class);
fail_class:
cdev_del(&timerdev.cdev);
fail_cdev:
unregister_chrdev_region(timerdev.devid,TIMER_CNT);
fail_devid:
return ret;
}
static void __exit timer_exit(void)
{
gpio_set_value(timerdev.led_gpio,1);
del_timer_sync(&timerdev.timer);
printk("timer_exit\r\n");
cdev_del(&timerdev.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("hbb");
通用定时器特点
内核定时器定时精度不高,不能作为高精度定时器使用。并且内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。
高精度定时器
Linux内核提供了高精度定时器,能够实现纳秒(ns)级别的定时精度。这主要得益于内核中的hrtimer框架,它允许开发者创建和运行高精度的定时器。
关于详细的hrtimer结构体的实现可以参考下面这篇文章linux内核定时器
到这里对通用定时器大致说明就结束了,后面有新的相关的重要的内容会继续进行更新。
对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev