一、一些概念
-
定时器外设
通过一个硬件定时器
来提供时钟源(频率可调)
,由此产生周期性的定时中断
,系统使用定时中断
来计时
中断产生的频率就是系统频率
,即节拍率
节拍率
是可以设置的(通过图形化界面设置),在内核源码目录下.config
文件中
HZ
表示系统节拍率
,HZ=100
表示系统每秒产生中断100次
-
节拍率
越高,时间精度
就越高,但是中断产生更加频繁,加重系统的负担 -
全局变量 HZ
表示一秒的节拍数,每秒产生的定时中断的次数
#undef HZ
#define HZ CONFIG_HZ
#define USER_HZ 100
#define CLOCKS_PER_SEC (USER_HZ)
-
全局变量 jiffies
include/linux/jiffies.h
用于记录系统从启动以来的系统节拍数(一共产生了多少次定时中断)
系统每一次启动时会将 jiffies 初始化为 0
jiffies / HZ
就是系统当前的运行时间
,单位是秒
jiffies 有溢出
的风险,溢出后会从开始计数,此为绕回
。32位的jiffies 50天产生绕回,64位的jiffies 绕回需要 5.8 亿年
板子是32位的系统,处理 jiffies 的绕回是十分必要的 -
内核定时器
1、软件定时器不同于硬件定时器那样直接给周期值。软件定时器设置的是期满以后的时间点(jiffies + 你需要定时的时间长短)
2、定时处理函数(超时时间到了以后设置的定时处理函数就会执行)
3、内核定时器不是周期性的,一次超时时间到了就会关闭,若希望实现周期定时,那么就要在定时处理函数中重新开启定时器
4、内核中使用timer_list 结构体
来表示内核定时器
,成员
有超时值、超时处理函数、传递给超时处理函数的参数 等
struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};
二、kernel api
- init_timer 函数
init_timer 函数负责初始化 timer_list 类型变量,当我们定义了一个 timer_list 变量以后一定要先用 init_timer 初始化一下
void init_timer(struct timer_list *timer)
timer:要初始化的定时器结构体指针
- add_timer 函数
用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行
void add_timer(struct timer_list *timer)
timer:要注册的定时器结构体指针
- del_timer 函数
del_timer 函数用于删除一个定时器
不管定时器有没有被激活,都可以使用此函数删除。
在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出
int del_timer(struct timer_list * timer)
timer:要删除的定时器结构体指针
返回值: 0,定时器还没被激活; 1,定时器已经激活
- del_timer_sync 函数
del_timer 函数的同步版
,会等待其他处理器使用完定时器再删除,
del_timer_sync 不能使用在中断上下文中
int del_timer_sync(struct timer_list *timer)
timer:要删除的定时器结构体指针。
返回值: 0,定时器还没被激活; 1,定时器已经激活。
- mod_timer 函数
mod_timer 函数用于修改定时值
如果定时器还没有激活的话, mod_timer 函数会激活定时器
int mod_timer(struct timer_list *timer, unsigned long expires)
timer:要修改超时时间(定时值)的定时器结构体指针。
expires:修改后的超时时间。
返回值: 0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活。
- 内核定时器一般的使用流程如下所示:
struct timer_list timer; /* 定义定时器 */
/* 定时器回调函数 */
void function(unsigned long arg)
{
/*
* 定时器处理代码
*/
/* 如果需要定时器周期性运行的话就使用 mod_timer
* 函数重新设置超时值并且启动定时器。
*/
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(2000));
}
/* 初始化函数 */
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);
}
- 几个 jiffies 和 ms、μs、ns之间的转换函数
函数 | 描述 |
---|---|
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 kernel 短延时函数(用于内核和驱动中)
函数 | 描述 |
---|---|
void mdelay(unsigned long msecs) | 毫秒延时函数 |
void udelay(unsigned long usecs) | 微秒延时函数 |
void ndelay(unsigned long nsecs) | 纳秒延时函数 |
-
ioctl 函数详解
1、dir
(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
2、type
(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如
‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
3、nr
(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
4、size
,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数; -
应用程序里面调用 ioctl,对应到驱动结构体ops里面的
// struct file_operations 结构体,fs.h
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
//在 64 位系统上,32 位的应用程序调用将会使用函数 compat_ioctl。
//在 32 位的系统上运行 32 位的应用程序调用的是 unlocked_ioctl。
//ioctl的命令是自己定义的但要符合相关规则
// include/uapi/asm-generic/ioctl.h
/* used to create numbers */
// type是幻数 8bit;nr是序数 8bit;数据传输方向 2bit;size是数据大小 14bit
#define _IO(type,nr) // 没有参数的命令
#define _IOR(type,nr,size) // 该命令从驱动里面读数据
#define _IOW(type,nr,size) // 该命令向驱动里面写数据
#define _IOWR(type,nr,size) // 双向数据传输
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
/* used to decode ioctl numbers.. */
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
/* ...and for the drivers/sound files... */
#define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT (_IOC_SIZESHIFT)
三、驱动源码
- timer驱动
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/of.h>
#include<linux/of_address.h>
#include<linux/of_irq.h>
#include<linux/gpio.h>
#include<linux/of_gpio.h>
#include<linux/atomic.h>
#include<linux/timer.h>
#include<linux/jiffies.h>
#define TIMER_CNT 1
#define TIMER_NAME "timer"
#define CLOSE_CMD _IO(0xef, 1)
#define OPEN_CMD _IO(0xef, 2)
#define SETPERIOD_CMD _IOW(0xef, 3, int)
static long timer_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static int timer_open(struct inode *inode, struct file *filp);
static int timer_release(struct inode *inode, struct file *filp);
struct timer_dev
{
dev_t devid; //设备号
u32 major; //主设备号
u32 minor; //次设备号
struct cdev cdev; //用于注册字符设备
struct class *class; //用于自动创建设备节点
struct device *device; //用于自动创建设备节点
struct device_node *nd; //指向设备数节点
int led_gpio; //gpio标号
struct timer_list timer;//表示一个定时器
int timer_period; //定时器超时值
};
static const struct file_operations timerdev_fops =
{
.owner = THIS_MODULE,
.open = timer_open,
.unlocked_ioctl = timer_ioctl, // 成员名为 unlocked_ioctl
.release =timer_release,
};
struct timer_dev timerdev;
static int timer_open(struct inode *inode, struct file *filp)
{
filp->private_data = &timerdev;
return 0;
}
static int timer_release(struct inode *inode, struct file *filp)
{
return 0;
}
static long timer_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct timer_dev *dev = file->private_data;
int val = 0;
int ret = 0;
switch (cmd) {
case CLOSE_CMD:
printk("%s(%d)\n", __FILE__, __LINE__);
// 删除一个定时器
del_timer_sync(&dev->timer);
break;
case OPEN_CMD:
// 设置一个定时器的超时值,激活定时器
del_timer(&dev->timer);
init_timer(&dev->timer);
dev->timer.function = timer_func;
dev->timer.expires = jiffies + \
msecs_to_jiffies(dev->timer_period);
dev->timer.data = (unsigned long)dev;
add_timer(&dev->timer);
break;
case SETPERIOD_CMD:
// 从 app 向 驱动 设置定时周期
// 第二个参数 arg 对应 app中 ioctl(fd, SETPERIOD_CMD, &arg) arg的地址
ret = copy_from_user(&val, (void *)arg, sizeof(int));
printk("%s(%d) val = %d arg = %d\n", __FILE__, __LINE__, val, *(int *)arg);
if(ret < 0)
{
printk("%s(%d):fail\n", __FILE__, __LINE__);
return -1;
}
dev->timer_period = val;
// 设置定时器的超时值,激活定时器
mod_timer(&dev->timer, jiffies+msecs_to_jiffies(dev->timer_period));
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}
// 超时处理函数
static void timer_func(unsigned long arg)
{
struct timer_dev *dev = (struct timer_dev* )arg;
static u8 status = 1;
status = !status ;
gpio_set_value(dev->led_gpio, status);
mod_timer(&dev->timer, jiffies+msecs_to_jiffies(dev->timer_period));
}
// 初始化led对应的gpio
int led_init(struct timer_dev *dev)
{
int ret = 0;
// 寻找设备树中的gpio对应节点
dev->nd = of_find_node_by_path("/gpioled");
if(dev->nd == NULL)
{
ret = -1;
printk("%s(%d)\n", __FILE__, __LINE__);
goto fail_fd;
}
// 获取gpio标号
dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpios", 0);
if(dev->led_gpio < 0)
{
ret = -1;
printk("%s(%d)\n", __FILE__, __LINE__);
goto fail_gpio;
}
//向内核申请gpio标号
ret = gpio_request(dev->led_gpio, "led");
if(ret)
{
ret = -1;
printk("%s(%d)\n", __FILE__, __LINE__);
goto fail_request;
}
// 设置gpio为输出
ret = gpio_direction_output(dev->led_gpio, 1);
if(ret < 0)
{
ret = -1;
printk("%s(%d)\n", __FILE__, __LINE__);
goto fail_setdir;
}
return 0;
fail_setdir:
// gpio_request、使用完之后要gpio_free
gpio_free(dev->led_gpio);
fail_request:
fail_gpio:
fail_fd:
return ret;
}
static int __init timer_init(void)
{
int ret = 0;
// 1、获取设备号
timerdev.major = 0;
if(timerdev.major)
{
// 自己指定一个主设备号,并向内核注册
timerdev.devid = MKDEV(timerdev.major, 0);
ret = register_chrdev_region(timerdev.devid, 1, TIMER_NAME);
}
else
{
// 由内核分配一个可用的设备号
ret = alloc_chrdev_region(&timerdev.devid, 0, 1, TIMER_NAME);
timerdev.major = MAJOR(timerdev.devid);
timerdev.minor = MINOR(timerdev.devid);
}
if(ret < 0)
{
printk("%d:Register timer dev error.\r\n", __LINE__);
goto fail_devid;
}
// 2、初始化cdev结构体,并用它来向内核注册字符设备
timerdev.cdev.owner = THIS_MODULE;
cdev_init(&timerdev.cdev, &timerdev_fops);
ret = cdev_add(&timerdev.cdev, timerdev.devid, 1);
if(ret < 0)
{
goto fail_cdev;
}
// 3、创建 class、device结构体,用于自动创建设备节点
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;
}
// 4、初始化 led
ret = led_init(&timerdev);
if(ret < 0)
{
printk("%s(%d)\n", __FILE__, __LINE__);
goto fail_ledinit;
}
// 5、初始化一个定时器相关 / 一个 struct timer_list 结构体
timerdev.timer_period = 500;
init_timer(&timerdev.timer);
// 设置周期
// 将周期值转换成定时器的超时值
timerdev.timer.expires = jiffies + msecs_to_jiffies(timerdev.timer_period);
// 定时器的超时处理函数
timerdev.timer.function = timer_func;
/* 指定超时处理函数的参数 */
timerdev.timer.data = (u32)&timerdev;
add_timer(&timerdev.timer);
printk("%d:---------------\r\n", __LINE__);
return 0;
fail_ledinit:
device_destroy(timerdev.class, timerdev.devid);
fail_device:
class_destroy(timerdev.class);
fail_class:
cdev_del(&timerdev.cdev);
fail_cdev:
unregister_chrdev_region(timerdev.devid, 1);
fail_devid:
printk("%d:---------------\r\n", __LINE__);
return ret;
}
static void __exit timer_exit(void)
{
gpio_set_value(timerdev.led_gpio, 1);
del_timer(&timerdev.timer);
gpio_free(timerdev.led_gpio);
cdev_del(&timer.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");
- 测试APP
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/ioctl.h>
#define CLOSE_CMD _IO(0xef, 1)
#define OPEN_CMD _IO(0xef, 2)
#define SETPERIOD_CMD _IOW(0xef, 3, int) // write period to driver
int main(int argc, char **argv)
{
int ret = 0;
int fd = 0;
char *filename;
unsigned char databuf[1];
unsigned int cmd;
unsigned int arg;
unsigned char str[100];
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("Can't open file \"%s\"\r\n", filename);
return -1;
}
while(1)
{
printf("Plz input cmd:");
ret = scanf("%d", &cmd);
getchar();
if(1 == cmd) //close
{
ioctl(fd, CLOSE_CMD, &arg);
printf("%s(%d)\n", __FILE__, __LINE__);
}
else if(2 == cmd) //open
{
ioctl(fd, OPEN_CMD, &arg);
}
else if(3 == cmd) //set period
{
printf("Plz input the period of timer:");
ret = scanf("%d", &arg);
printf("%s(%d)arg = %d\n", __FILE__, __LINE__, arg);
getchar();
ioctl(fd, SETPERIOD_CMD, &arg); // 注意此处传入的是地址,驱动接收端是 unsigned long arg
}
}
return 0;
}