这章学习Linux内核定时器,介绍其API函数和用法。
一、时间管理
系统节拍默认100hz,即每秒100次,定义一个HZ=100
Linux内核使用全局变量jiffies来记录系统从启动以来的系统节拍数,系统启动时将jiffies初始化为0,定义在include/linux/jiffies.h中。
所以jiffies/HZ就是系统运行时间。
函数 | 描述 |
---|---|
time_after(unknow,know) | unknown超过known返回真,否则返回假 |
time_before(unknow,known) | 没有超过返回真,否则返回假 |
time_after_eq(unknow,known) | 大于等于返回真,否则返回假 |
time_before_eq(unknown,known) | 小于等于返回真,否则返回假 |
unknown一般为jiffies,known是对比的值。
下面是jiffies和ms、us、ns之间的转换函数
函数 | 描述 |
---|---|
int jiffies_to_msecs(const unsigned long j) | jiffies转换成毫秒 |
int jiffies_to_usecs(const unsigned long j) | jiffies转换成微秒 |
u64 jiffies_to_nsecs(const unsigned long j) | jiffies转换成纳秒 |
long msecs_to_jiffies(const unsigned int m) | 毫秒转换成jiffies |
long usecs_to_jiffies(const unsigned int u) | 微妙转换成jiffes |
unsigned long nsecs_to_jiffies(u64 n) | 纳秒转换成jiffies |
二、内核定时器
提供超时时间和需要处理的函数即可。
struct timer_list {
struct list_head entry;
unsigned long expires; /*定时器超时时间,节拍数*/
struct tvec_base *base;
void (*function) (unsigned long); /*处理函数*/
unsigned long data; /*传给function的参数*/
int slack;
};
比如定义一个周期2s的定时器,超时时间定义为expires = jiffies+(2*HZ)。
下面是定时器的API函数
/*初始化*/
void init_timer (struct timer_list *timer)
/*向内核注册定时器*/
void add_timer (struct timer_list *timer)
/*删除定时器,成功返回1*/
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)
Linux内核短延迟函数
函数 | 描述 |
---|---|
void ndelay(unsigned long nsecs) | 纳秒延迟 |
void udelay(unsignd long usces) | 微秒延迟 |
void mdelay(unsigned long msecs) | 毫秒延迟 |
三、驱动程序
使用定时器让led闪,输入时间控制闪烁频率。
#include <linux/timer.h>
#define TIMER_CNT 1
#define KEY_NAME "timer"
struct timer_device {
struct device_node *nd;
int timer_gpio;
struct timer_list timer;
int period;
}
struct timer_device timer;
static int timer_open(struct inode *inode, struct file *filp)
{
filp->private = &timer;
timer.period = 1000;
}
/*
filp:要打开的设备文件
cmd:应用程序发送的命令
arg:参数
*/
static long timer_unlocked_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
struct timer_device *dev = filp->private;
int ret;
switch(cmd) {
case CLOSE:
ret = del_timer_sync(&dev->timer);
break;
case OPEN:
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->period));
break;
case SET:
dev->period = arg;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->period));
break;
default:
break;
}
return 0;
}
static struct file_operations timer_fops = {
.owner = THIS_MODULE,
.open = timer_open,
.unlocked_ioctl = timer_unlocked_ioctl,
};
void function(unsigned long arg)
{
struct timer_device *t = (struct timer_device *)arg;
static int temp = 0;
if(temp % 2)
gpio_set_value(t->timer_gpio, 1);
else
gpio_set_value(t->timer_gpio, 0);
temp++;
mod_timer (&t->limer, jiffies + msecs_to_jiffies(t->period));
}
static int __init timer_init(void)
{
init_timer(&timer.timer);
timer.timer.function = function;
timer.timer.expires = jiffies + msecs_to_jiffies(1000);
timer.timer.data = (unsigned long) &timer;
return 0;
}
static void __exit timer_exit(void)
{
del_timer_sync(&timer.timer);
cdev_del(&timer.cdev);
unreginser_chrdev_region(timer.devid, TIMER_CNT);
device_destroy(timer.class, timer.devid);
class_destroy(timer.class);
}
重复代码略写
这fops函数加了unlocked_ioctl函数,用于传递参数。
在del定时器后,可以不必初始化-注册定时器,因为只是从内核删除,本身还存在,所以再激活,只要add或者mod函数激活既可以使用。
定时器fun函数输入参数是dev结构体的地址(unsigned long),因此可以在fun函数继续使用结构体内的变量。
最后exit函数,记得删除定时器,删除建议使用sync函数。
四、测试
1.测试App
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
unsigned int cmd;
unsigned int arg;
unsigned char str[100];
filename = argv[1];
fd = open(filename, O_RDWR);
while (1) {
printf("Input CMD:");
ret = scanf("%d", &cmd);
if (ret != 1) { /* 参数输入错误 */
gets(str); /* 防止卡死 */
}
if(cmd == 1) /* 关闭LED灯 */
cmd = CLOSE;
else if(cmd == 2) /* 打开LED灯 */
cmd = OPEN;
else if(cmd == 3) {
cmd = SET; /* 设置周期值 */
printf("Input Timer Period:");
ret = scanf("%d", &arg);
if (ret != 1) { /* 参数输入错误 */
gets(str); /* 防止卡死 */
}
}
ioctl(fd, cmd, arg); /* 控制定时器的打开和关闭 */
}
close(fd);
}
2.编译
和前面一样
3.测试
参考App代码吧。