IMX6ULL_LINUX内核定时器学习笔记
我们这里学习的是硬件定时器提供时钟源,时钟源的频率可以设置,设置好以后就能周期性的产生定时中断,系统使用定时中断来计时。
中断周期产生的频率就是系统频率,也叫节拍率
1> 设置系统时钟的系统频率
输入make menuconfig
==> Kernel Features
==> Timer frequency
Kconfig中描述如下
choice
depends on HZ_FIXED = 0
prompt "Timer frequency"
config HZ_100
bool "100 Hz"
config HZ_200
bool "200 Hz"
config HZ_250
bool "250 Hz"
config HZ_300
bool "300 Hz"
config HZ_500
bool "500 Hz"
config HZ_1000
bool "1000 Hz"
endchoice
==> 6选1 我们这里选择 100HZ (X) 100HZ
在.config文件中查看配置是否生效
CONFIG_HZ_FIXED=0 // ==> 有这个才可以能6选1
CONFIG_HZ_100=y
# CONFIG_HZ_200 is not set
# CONFIG_HZ_250 is not set
# CONFIG_HZ_300 is not set
# CONFIG_HZ_500 is not set
# CONFIG_HZ_1000 is not set
CONFIG_HZ=100 //系统时钟频率为100HZ
我们这里选择的系统频率最终会在哪里被使用?
/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek$ grep -rn "CONFIG_HZ" ./
./arch/alpha/include/asm/param.h:7:# define HZ CONFIG_HZ
所以以后看见宏HZ,代表的就是一秒的节拍数(即系统频率)
2> 系统时钟频率高的优缺点 (例如1000与100的区别)
优点: 高系统频率 1000 高系统频率会提高系统的时间精度 100hz,相当于1个周期==10ms,而1000hz是一个周期=1ms,1000HZ的时间精度相比于100HZ提高了10倍
缺点: 因为频率变高,所以中断的产生更加频繁,处理中断是需要消耗cpu的,故系统消耗的cpu也会增加,现在处理器性能都很强大,放在现在1000HZ也不会增加太大的负担
3> 系统时间的概念
linux内核使用全局变量jiffies来记录系统从启动以来的系统节拍数(HZ=1000,就相当于1s中有1000个节拍),系统启动时会将jiffies初始化为0
在linux内核根目录下的 include/linux/jiffies.h中有关于jiffies变量的定义
/*
* The 64-bit value is not atomic - you MUST NOT read it
* without sampling the sequence number in jiffies_lock.
* get_jiffies_64() will do this for you as appropriate.
64位的值不是原子的——您不能读取它,不采样jiffies_lock中的序列号,get_jiffies_64()将为您做这个适当的。
*/
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
为什么有两个jiffies变量?
==> 为了代码的兼容性,为了适配32位和64位这两种系统
jiffies_64适用于64位系统,jiffies适用于32位系统,用get_jiffies_64获取jiffiers_64的值(不能直接读取,因为jiffiers_64不是一个原子,用函数读取就不会出现问题)
在32位系统,用jiffies表示系统节拍数,在这里jiffies就是jiffies_64的低32位的值
在64位系统,jiffies_64和jiffies表示同一个变量
==> 故其实不管在32/64位系统上都可以用jiffies来表示系统节拍数
系统运行时间(单位:s) : jiffies/HZ (系统总节拍数/一秒对应的节拍数)
因为jiffies表示系统节拍数,jiffies是一个变量,变量的取值就会有范围,即有一个能表示的最大值,如果系统节拍数超过了这个值会怎么呢?
==> 会发生绕回现象 ,jiffies_64(2的64次方后才绕回,所以基本可以忽略不计),只有jiffies才有可能产生绕回现象,当HZ=1000时,大概49.7天就会发生绕回现象
4> 内核定时器的操作
linux内核定时器的使用非常简单,只需要提供超时时间(定时值)和超时处理函数即可,当超时时间到了以后就会执行对应的超时处理函数
注意点: 内核定时器并不是周期性运行的,超时以后就会自动关闭,因此你想要实现周期性定时就需要在超时函数中重新打开定时器
5> 内核定时器使用的demo
/*
1. 定义一个定时器变量
linux内核中使用timer_list结构体来表示内核定时器
struct timer_list {
unsigned long expires; //定时值(单位:节拍数)
void (*function)(unsigned long); //超时函数
unsigned long data; //传递给超时函数的参数
... ...
};
*/
struct timer_list timer;
void function(unsigned long arg)
{
/*
超时后的操作
*/
/*
重新打开定时器,并重新设置定时值,实现定时器的周期性运行
mod_timer(重新打开的定时器,重新设置的定时值);
msecs_to_jiffies(); 将要设置的超时值(单位ms)转换为节拍数
jiffies + msecs_to_jiffies(dev->timeperiod): 当前节拍数 + 要定时的节拍数 ==> 当节拍数等于该值时再次执行该超时函数
*/
mod_timer(&(dev->timer),jiffies + msecs_to_jiffies(dev->timeperiod));
}
/*
定时器的初始化 --前期配置定时器的操作
*/
void init(void)
{
/* 初始化定时器*/
init_timer();
timer.function = function; //指定该定时器对应的超时函数
timer.expires = jiffies + msecs_to_jifffies(2000); //设置超时值 2s
timer.data = (unsigned long)&dev; //设置将dev作为参数传递给超时函数
add_timer(); //向内核注册定时器,注册完成定时器就会开始运行
}
void exit(void)
{
/*
删除定时器
会等待其他处理器使用完定时器在删除
*/
del_timer_sync();
}
6> linux内核短延时函数
void ndelay(unsigned long nsecs); //ns延时
void udelay(unsigned long usecs); //us延时
void mdelay(unsigned long msecs); //ms延时
7> 使用定时器实现对led等的定时开关控制
/*
timer驱动文件
注意点:
在使用系统定时器时,需要提高make menuconfig操作来对系统1s钟的频率进行设置
通过app中的ioctl()来实现对驱动中定时器的控制
*/
#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_ATOMIC_FLAG 1
#define timer_CNT 1
#define timer_DEVID_NAME “timer_devid”
#define timer_CLASS_NAME “timer_class”
#define timer_DEVICE_NAME “timer_device”
#define timer_LABEL_NAME “timer_label”
/*学习网站 xxx
_IO(type,nr) 这是最简单的交互协议,不带arg
type: 设备类型,占8bit char类型 被称为“魔数” 作用为使同一个设备的具有唯一且夏天的设备标识
nr: 命令编号 占8bit unsigned char 类型 如果该设备定义多个命令通常命令编号从0开始向上递增
*/
#define CLOSE_CMD (_IO(0XEF,0x1)) //关闭定时器
#define OPEN_CMD (_IO(0XEF,0x2)) //关闭定时器
#define SETPERIOD_CMD (_IO(0XEF,0x3)) //关闭定时器
#define LEDON 1
#define LEDOFF 0
struct timer_dev{
struct cdev cdev;
dev_t devid;
int major;
int minor;
struct class *class;
struct device *device;
struct device_node *node;
int led_gpio;
struct timer_list timer; //定义一个定时器对象
int timeperiod; //定义一个定时数,定时多少ms
spinlock_t lock;
};
struct timer_dev timerdev;
static int timer_open(struct inode inode, struct file filp)
{
printk("**** _ %s ******* \n",FUNCTION);
//1. 将该设备结构体保存中filp的私有数据中
filp->private_data = &timerdev;
return 0;
}
static ssize_t timer_read(struct file filp, char __user buf, size_t cnt, loff_t offt)
{
printk("*** _ %s ******* \n",FUNCTION);
return 0;
}
static ssize_t timer_write(struct file filp, const char __user buf, size_t cnt, loff_t lofft)
{
printk("*** _ %s ******* \n",FUNCTION);
return 0;
}
/*
命令控制
cmd: 用户层传递过来的命令
arg:用户空间传递给内核空间的参数
/
static long timer_ioctl(struct file filp, unsigned int cmd, unsigned long arg)
{
int timeperiod;
unsigned long flags;
//1. 从filp中取出保存的驱动信息结构体
struct timer_dev dev = (struct timer_dev )filp->private_data;
printk("** _ %s ******* \n",FUNCTION);
//2. 根据用户层传递过来的命令,来完成对应的操作
switch(cmd)
{
case CLOSE_CMD:
//关定时器
/*
int del_timer_sync(struct timer_list *timer);
fun: del_time()的进价函数,会在等待私有处理器的定时器使用结束在删除定时器timer
return: 0=在删除timer定时器对象时,该定时器还没被激活,1=删除它时,它已被激活
*/
del_timer_sync(&(dev->timer));
break;
case OPEN_CMD:
//开定时器
//这里使用自旋锁来包含我们设置的定时器的定时值
spin_lock_irqsave(&(dev->lock),flags);
timeperiod = dev->timeperiod;
spin_unlock_irqrestore(&(dev->lock),flags);
//设置超时值,并激活定时器开始记时
/*
int mod_timer(struct timer_list *timer,unsigned long expires)
fun: 修改定时器timer的定时值为expires,如果定时器还没被激活就同时激活定时器
return: 0 = 在操作这个定时器之前这个定时器还没被激活,1=之前就激活了
*/
/*
long msecs_to_jiffies(comst unsigned int m);
fun: 获取m个ms时间对应的节拍数(jiffies)
return: 转换到的jiffies值
当jiffies的值达到jiffies + msecs_to_jiffies(timeperiod)时系统会调用该定时器的超时函数
*/
mod_timer(&(dev->timer),jiffies + msecs_to_jiffies(timeperiod));
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 int timer_release(struct inode inode, struct file filp)
{
//1. 从filp的私有数据中取出该设备的信息结构体
struct timer_dev dev = filp->private_data;
printk("*** _ %s ******* \n",FUNCTION);
//2. 释放申请到的gpio编号
gpio_free(dev->led_gpio);
return 0;
}
static struct file_operations timer_fops = {
.owner = THIS_MODULE,
.open = timer_open,
.read = timer_read,
.write = timer_write,
.unlocked_ioctl = timer_ioctl,
.release = timer_release,
};
//定义定时器的超时函数
static void timer_function(unsigned long arg)
{
static int status = 1; //这里非常关键 必须是要定义成静态变量,不然值不会实时更新
int timeperiod;
unsigned long flags;
//1. 通过定时器传递的参数来获取驱动信息结构体(初始化时使用定时器的data成员变量保存了信息结构体)
struct timer_dev *dev = (struct timer_dev *)arg;
//2. 每次超时,都实现LED状态的反转
printk("****** ^_^ %s ******* \n",__FUNCTION__);
status = !status;
gpio_set_value(dev->led_gpio,status);
//重新启动定时器,这样形成一直定时的效果
spin_lock_irqsave(&(dev->lock),flags);
timeperiod = dev->timeperiod;
spin_unlock_irqrestore(&(dev->lock),flags);
mod_timer(&(dev->timer),jiffies + msecs_to_jiffies(dev->timeperiod));
}
static int __init wjctimer_init(void)
{
int ret;
struct property *property;
const char *status_str;
printk("****** ^_^ %s ******* \n",__FUNCTION__);
//1. 创建一个保存该设备信息的全局设备结构体变量
//2. 初始化自旋锁对象
spin_lock_init(&(timerdev.lock));
//3. 动态注册设备号
ret = alloc_chrdev_region(&(timerdev.devid),0,timer_CNT,timer_DEVID_NAME);
timerdev.major = MAJOR(timerdev.devid);
timerdev.minor = MINOR(timerdev.devid);
//4. cdev操作
timerdev.cdev.owner = THIS_MODULE;
cdev_init(&(timerdev.cdev),&timer_fops);
cdev_add(&(timerdev.cdev),timerdev.devid,timer_CNT);
//5. 创建设备类
timerdev.class = class_create(THIS_MODULE,timer_CLASS_NAME);
if(IS_ERR(timerdev.class))
{
return PTR_ERR(timerdev.class);
}
//6. 创建设备文件
timerdev.device = device_create(timerdev.class,NULL,timerdev.devid,NULL,timer_DEVICE_NAME);
if(IS_ERR(timerdev.device))
{
return PTR_ERR(timerdev.device);
}
//7. 硬件初始化
//a. 先通过路径获取设备节点
timerdev.node = of_find_node_by_path("/wjcgpioled");
if(timerdev.node == NULL)
{
printk("of_find_node_by_path fun error!\n");
return -EINVAL;
}
//b. 获取compatible属性的值
property = of_find_property(timerdev.node,"compatible",NULL);
if(property == NULL)
{
printk("of_find_property fun error!\n");
return -EINVAL;
}
printk("the property compatible = %s \n",(char *)(property->value));
//c. 获取status属性的值
ret = of_property_read_string(timerdev.node,"status",&status_str);
if(ret < 0)
{
printk("of_property_read_string fun error!\n");
return -EINVAL;
}
//d. 获取gpio编号
timerdev.led_gpio = of_get_named_gpio(timerdev.node,"wjcled-gpio",0);
if(timerdev.led_gpio < 0)
{
printk("of_get_named_gpio fun error!\n");
return -EINVAL;
}
else
{
printk("this led_gpio is %d !\n",timerdev.led_gpio);
}
//e. gpioAPI函数 申请 -- 操作 --释放
gpio_request(timerdev.led_gpio,timer_LABEL_NAME);
gpio_direction_output(timerdev.led_gpio,1);
//f. 初始化定时器对象(这里只是设置定时器超时函数和定时值,并不激活)
/*
void init_timer(struct time_list *timer);
fun: 初始化定时器timer
*/
init_timer(&(timerdev.timer));
timerdev.timer.function = timer_function; //定义超时处理函数
timerdev.timer.data = (unsigned long)&timerdev; //将该驱动的设备信息结构体作为参数记录在定时器中,这个值会在该定时器发生超时操作调用超时函数时讲该值作为参数传递进去
timerdev.timeperiod = 1000; //定时1s
//8. 实现fops
//9. 错误处理 ---这里省略
return 0;
}
static void __exit wjctimer_exit(void)
{
printk("****** _ %s ******* \n",FUNCTION);
//1. 注销设备文件
device_destroy(timerdev.class,timerdev.devid);
//2. 注销设备类
class_destroy(timerdev.class);
//3. 注销cdev
cdev_del(&(timerdev.cdev));
//4. 注销设备号
unregister_chrdev_region(timerdev.devid,timer_CNT);
}
module_init(wjctimer_init);
module_exit(wjctimer_exit);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“wujc”);
8> 测试文件demo
/*
timer定时器测试文件
./timerApp /dev/timer_device
输入1 关闭定时器 现象:LED状态不在发生改变
输入2 打开定时器 现象:LED状态一直发生改变
输入3 重新设置定时器的定时值 (用户输入新的值 代表新的定时值ms)
*/
#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,retvalue,ret;
unsigned int cmd;
unsigned int arg;
unsigned char str[100];
if(argc != 2)
{
printf("uasge input error !\n");
return -1;
}
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("open file %s error !\n",argv[1]);
return -1;
}
printf("请输入你对定时器的操作命令: \n");
printf("1.关定时器 \n");
printf("2.开定时器 \n");
printf("1.设置定时器的定时值 \n");
while(1)
{
printf("you select ? \n");
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("请输入你要设置多少ms的定时:");
ret = scanf("%d",&arg);
if(ret != 1)
{
gets(str);
}
}
//传递命令和参数到内核空间
ioctl(fd,cmd,arg);
}
retvalue = close(fd);
if(retvalue < 0)
{
printf("close file %s error!\n",argv[1]);
return -1;
}
return 0;
}