1.内核定时器
定时器是一个很常用的功能,需要周期性处理的工作都要用到定时器。 Linux 内核定时器
采用系统时钟来实现,并不是6ull里面的硬件定时器。 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 list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
=========================
2.内核定时器API函数
要使用内核定时器首先要先定义一个 timer_list 变量,表示定时器, tiemr_list 结构体的
expires 成员变量表示超时时间,单位为节拍数。比如我们现在需要定义一个周期为 2 秒的定时
器,那么这个定时器的超时时间就是 jiffies+msecs_to_jiffies(2000),function 就是定时器超时以后的定时处理函数,当定时时间到了以后,就会跳转到function执行。
定义好定时器后,还需要API函数(定义在linux/timer.h)来初始化定时器:
①、init_timer函数
init_timer 函数负责初始化 timer_list 类型变量,函数原型:
#define init_timer(timer) \
__init_timer((timer), 0)
#define __init_timer(_timer, _flags) \
init_timer_key((_timer), (_flags), NULL, NULL)
void init_timer_key(struct timer_list *timer, unsigned int flags,
const char *name, struct lock_class_key *key)
timer:要初始化的定时器。
②、add_timer函数
用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行,函数原型如下:
extern void add_timer(struct timer_list *timer);
timer:要初始化的定时器。
③、del_timer函数
用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出,函数原型:
extern int del_timer(struct timer_list * timer);
timer:要初始化的定时器。
返回值:0,定时器没被激活,1,定时已经激活。
④、del_timer_sync函数
函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中。函数原型:
extern int try_to_del_timer_sync(struct timer_list *timer);
timer:要初始化的定时器。
返回值:0,定时器没被激活,1,定时已经激活。
⑤、mod_timer函数
用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!函数原型如下:
extern int mod_timer(struct timer_list *timer, unsigned long expires);
timer:要修改超时时间的定时器。
expires:修改后的超时时间。
返回值:0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活。
===============================
3.内核定时器的使用流程
struct timer_list timer; /* 定义定时器 */
/* 定时器回调函数 */
void function(unsigned long arg)
{
/*
* 定时器处理代码
*/
/* 如果需要定时器周期性运行的话就使用 mod_timer
* 函数重新设置超时值并且启动定时器。
*/
mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000)); /* 修改超时时间为2s */
}
/* 初始化函数 */
void init(void)
{
init_timer(&timer); /* 初始化定时器 */
timer.function = function; /* 设置定时处理函数 */
timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
add_timer(&timer); /* 启动定时器 */
}
/* 退出函数 */
void exit(void)
{
del_timer(&timer); /* 删除定时器 */
/* 或者使用 */
del_timer_sync(&timer);
}
4.Linux内核短延时函数
有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。 Linux 内核提供了毫秒、微秒和纳秒延时函数,如表:
函数描述
void ndelay(unsigned long nsecs)
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)
// ms、us、ns延时函数
unlocked_ioctl和compat_ioctl
函数简介:
unlocked_ioctl\compat_ioctl是file_operation 结构体中的两个函数
unlocked_ioctl函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。简单点来说,当用户空间应用程序调用 ioctl函数向驱动发送控制信息,驱动程序会执行unlocked_ioctl这个函数
long (*unlocked_ioctl) (struct file *filep, unsigned int cmd, unsigned long arg);
filep:设备文件名。
cmd:应用程序发送过来的命令信息。后面我们会仔细说一下这个CMD命令如何创建。
arg:应用程序发过来的参数。
compat_ioctl函数的功能与unlocked_ioctl函数一样,区别在于64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。
long (*compat_ioctl) (struct file *filep, unsigned int cmd, unsigned long arg);
===========================
5.ioctl函数CMD命令
在linux内核中有帮助手册:linux/Documentation/ioctl/ioctl-decoding.txt这个文档中有介绍CMD这个命令:
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \ //dir(读写)方向左移30位
((type) << _IOC_TYPESHIFT) | \ //type类型左移8位
((nr) << _IOC_NRSHIFT) | \ //nr功能左移0位
((size) << _IOC_SIZESHIFT)) // size传递数据大小左移16位
/**通过分析是dir、type、nr、size几个数都左移了一个不知道的宏的位数,通过查找发现这些宏如下,
所以得到上边每行注释左移位数**/
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
# define _IOC_SIZEBITS 14
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) // 0+8 = 8
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) //8+8 = 16
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) //16+14 = 30
总结:我们通过分析_IOC这个宏可以发现他做了这么一件事,将一个32位的数拆成了四个部分,分别是dir、type、nr、size,分别如下解释和图示:
bit31~bit30:"区别读写" 区,作用是区分是读取命令还是写入命令;
bit29~bit16:"数据大小" 区,表示 ioctl() 中的 arg 变量传送的内存大小。
bit15~bit8 : " 魔数" (也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit7~bit0 :"区别序号"区,是区分命令的命令顺序序号
这个CMD是一个32位的。31~30位是方向位,_IOR是向驱序读,_IOW是向驱动写。29~16位是用户空间向内核空间传输控制信息的数据大小,15~8位表示类型,驱动的标识位,一个特殊字符(ASCII)代表不同的一个驱动。7~0位就是不同的控制功能。
===========================
内核代码:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/errno.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/semaphore.h>
#include <asm/ioctls.h>
/* 设备名称和个数 */
#define TIMER_CNT 1
#define TIMER_NAME "timer"
/* 命令宏 */
#define OPEN_CMD _IO('E', 1)
#define CLOSE_CMD _IO('E', 2)
#define SET_PERIOD_CMD _IOW('E', 3, int)
/* timer结构体 */
typedef struct timer_dev {
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev dev; /* 设备 */
struct class *class; /* 类 */
struct device *device; /* 类的设备 */
struct device_node *nd; /* 设备树节点 */
int led_gpio; /* LED的GPIO编号 */
struct timer_list timer; /* 定时器 */
int timerperiod; /* 定时器周期 */
spinlock_t lock; /* 自旋锁 */
}timer_dev;
timer_dev timer;
static int timer_open (struct inode *inode, struct file *filep)
{
filep->private_data = &timer; /* 设置私有数据 */
timer.timerperiod = 500; /* 设置定时时间为1s */
return 0;
}
static long timer_unlocked_ioctlioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
int ret = 0;
timer_dev *dev = filep->private_data; /* 获取私有数据 */
unsigned int timerperod = 0;
unsigned long flag = 0;
unsigned long value = 0;
switch (cmd) {
case OPEN_CMD: /* 打开定时器 */
spin_lock_irqsave(&dev->lock,flag); /* 自锁 */
timerperod = dev->timerperiod;
spin_unlock_irqrestore(&dev->lock,flag); /* 解锁 */
mod_timer(&dev->timer,jiffies + msecs_to_jiffies(timerperod));
break;
case CLOSE_CMD: /* 关闭定时器 */
del_timer(&dev->timer);
break;
case SET_PERIOD_CMD: /* 修改定时器的周期 */
ret = copy_from_user(&value, (int *)arg, sizeof(int));
if(ret < 0) {
return -EFAULT;
}
spin_lock_irqsave(&dev->lock,flag);
dev->timerperiod = value;
spin_unlock_irqrestore(&dev->lock,flag);
mod_timer(&dev->timer,jiffies + msecs_to_jiffies(value));
break;
}
return 0;
}
/* 设备文件操作集合 */
const struct file_operations timer_opts = {
.owner = THIS_MODULE,
.open = timer_open,
.unlocked_ioctl = timer_unlocked_ioctlioctl,
};
/* LED灯初始化 */
int led_init(timer_dev * ptimer)
{
int ret = 0;
timer_dev *dev = ptimer;
/* 获取LED节点和信息 */
dev->nd = of_find_node_by_path("/gpioled");
/* 得到GPIO的编号 */
dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpio", 0);
if(dev->led_gpio < 0) {
ret = -EINVAL;
printk("fail get gpio\r\n");
goto fail_getgpio;
}
/* 申请GPIO */
ret = gpio_request(dev->led_gpio, "led_gpio");
if(ret) {
printk("fail gpio request\r\n");
ret = -EBUSY;
goto fail_request;
}
/* 设置GPIO输入输出 */
ret = gpio_direction_output(dev->led_gpio, 1); /* 输出模式 给1关灯,默认关灯 */
if(ret){
printk("fail gpio set output\r\n");
ret = -EBUSY;
goto fail_setout;
}
return 0;
fail_setout:
gpio_free(dev->led_gpio);
fail_request:
fail_getgpio:
return ret;
}
/* 定时器定时时间到回调函数 */
void timer_timerout (unsigned long arg)
{
static int status = 1;
unsigned long flags;
int timerperiod = 0;
timer_dev *dev = (timer_dev *)arg;
/* 设置LED灯电平 */
status = !status;
gpio_set_value(dev->led_gpio,status);
spin_lock_irqsave(&dev->lock,flags); /* 自锁 */
timerperiod = dev->timerperiod;
spin_unlock_irqrestore(&dev->lock, flags); /* 解锁 */
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));
}
/* 入口函数 */
static int __init timer_init(void)
{
int ret = 0;
/* 初始化自旋锁 */
spin_lock_init(&timer.lock);
timer.timerperiod = 500;
/* 注册设备号 */
timer.major = 0;
if(timer.major) { /* 指定设备号 */
timer.devid = MKDEV(timer.major,0);
ret = register_chrdev_region(timer.devid, TIMER_CNT, TIMER_NAME);
}else { /* 没有指定设备号 */
ret = alloc_chrdev_region(&timer.devid, 0, TIMER_CNT, TIMER_NAME);
timer.major = MAJOR(timer.devid);
timer.minor = MINOR(timer.devid);
}
if(ret < 0) {
printk("fail devid\r\n");
goto fail_devid;
}
printk("major = %d,minor = %d\r\n",timer.major,timer.minor); /* 打印设备号 */
/* 注册设备 */
timer.dev.owner = THIS_MODULE;
cdev_init(&timer.dev, &timer_opts);
ret = cdev_add(&timer.dev, timer.devid, TIMER_CNT);
if(ret < 0) {
printk("fail dev\r\n");
goto fail_dev;
}
/* 自动创建节点信息 */
timer.class = class_create(THIS_MODULE, TIMER_NAME);
if(IS_ERR(timer.class)) {
ret = PTR_ERR(timer.class);
printk("fail class\r\n");
goto fail_class;
}
timer.device = device_create(timer.class, NULL, timer.devid, NULL, TIMER_NAME);
if(IS_ERR(timer.device)) {
ret = PTR_ERR(timer.device);
printk("fail device\r\n");
goto fail_device;
}
/* 初始化LED灯 */
ret = led_init(&timer);
if(ret < 0) {
printk("fail led init\r\n");
goto fail_led_init;
}
/* 初始化定时器 */
init_timer(&timer.timer);
timer.timer.data = (unsigned long) &timer;
timer.timer.function = timer_timerout;
mod_timer(&timer.timer, jiffies + msecs_to_jiffies(timer.timerperiod));
return 0;
fail_led_init:
fail_device:
class_destroy(timer.class);
fail_class:
cdev_del(&timer.dev);
fail_dev:
unregister_chrdev_region(timer.devid, TIMER_CNT);
fail_devid:
return ret;
}
/* 出口函数 */
static void __exit timer_exit(void)
{
/* 关灯 */
gpio_set_value(timer.led_gpio, 1);
/* 删除定时器 */
del_timer_sync(&timer.timer);
/* 注销GPIO */
gpio_free(timer.led_gpio);
/* 删除类的设备 */
device_destroy(timer.class, timer.devid);
/* 删除类 */
class_destroy(timer.class);
/* 删除设备 */
cdev_del(&timer.dev);
/* 删除设备号 */
unregister_chrdev_region(timer.devid, TIMER_CNT);
printk("timer exit\r\n");
}
/* 注册入口和出口函数 */
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZhangXueGuo");
上层代码编写
============
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
/* 命令宏 */
#define OPEN_CMD _IO('E', 1)
#define CLOSE_CMD _IO('E', 2)
#define SET_PERIOD_CMD _IOW('E', 3, int)
/*
* main主程序
* argc:argv数字个数,一般指传递给函数的参数数量
* argv:具体的参数内容,一般都是字符串格式
* return:0表示成功
*
*/
int main(int argc, char *argv[])
{
int fd,ret;
char *FileName;
int cmd,arg;
unsigned char str[100];
/* 判断使用命令参数是否正确 */
if(argc != 2){
printf("命令使用错误!\r\n");
ret = -1;
goto fail_open;
}
/* 打开程序 */
FileName = argv[1];
fd = open(FileName,O_RDWR);
if(fd < 0){
printf("应用程序打开设备文件失败!\r\n");
ret = fd;
goto fail_open;
}
while(1) {
printf("Please input CMD:");
ret = scanf("%d",&cmd);
if(ret != 1) {
gets(str);
}
if(cmd == 1) {
ret = ioctl(fd, OPEN_CMD, &arg);
} else if(cmd == 2) {
ret = ioctl(fd, CLOSE_CMD, &arg);
} else if(cmd == 3) {
printf("Input period:");
ret = scanf("%d",&arg);
if(ret != 1) {
gets(str);
}
ret = ioctl(fd, SET_PERIOD_CMD, &arg);
}
}
/* 关闭文件 */
close(fd);
return 0;
fail_open:
return ret;
}