大家好,欢迎来到IT知识分享网。
目录
一、内核时间管理简介
硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率,也叫节拍率(tick rate)(有的叫系统率),比如 1000Hz, 100Hz 等等说的就是系统节拍率。系统节拍率是可以通过Linux 内核设置的
Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0,jiffies 定义在文件 include/linux/jiffies.h 中,如下
访问 jiffies 的时候其实访问的是 jiffies_64 的低 32 位,在 32 位的系统上读取 jiffies 的值,在 64 位的系统上 jiffes 和 jiffies_64表示同一个变量,因此也可以直接读取 jiffies 的值。所以不管是 32 位的系统还是 64 位系统,都可以使用 jiffies
HZ 表示每秒的节拍数, jiffies 表示系统运行的 jiffies 节拍数,所以 jiffies/HZ 就是系统运行时间,单位为秒。不管是 32 位还是 64 位的 jiffies,都有溢出的风险,溢出以后会重新从 0 开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。处理 32 位 jiffies 的绕回显得尤为重要
相关API使用到的时候介绍
二、内核定时器简介
Linux 内核定时器使用很简单,只需要提供超时时间(相当于定时值)和定时处理函数即可,当超时时间到了以后设置的定时处理函数就会执行,和使用硬件定时器的套路一样,只是使用内核定时器不需要做一大堆的寄存器初始化工作。Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数
unsigned long data; /* 要传递给 function 函数的参数 */
unsigned long expires; /* 定时器超时时间,单位是节拍数*/
int slack;}
在使用内核定时器的时候要注意一点,内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。
要使用内核定时器首先要先定义一个 timer_list 变量,表示定时器,比如需要定义一个周期为 2 秒的定时器,那么这个定时器的超时时间就是 jiffies+(2*HZ),相关API使用到的时候介绍
三、驱动编写
这里的led节点在之前篇章已经添加到设备树中,可以直接获取使用
自行编写好基本的字符设备驱动
1、修改makefile
2、添加定义
3、初始化led函数
在驱动入口函数前面添加
77行,通过路径查找获取led设备节点
82行,通过名字获取“led-gpios”属性的第0个索引
87行,申请一个叫“led”的 GPIO 管脚
93行,设置gpio为输出,默认输出值为1
这段代码主要是通过设备树获取属性,并设置GPIO的输出
4、添加调用
在驱动入口函数里面添加调用led_init()函数
5、初始化定时器与定时器处理函数
在驱动入口里面调用初始化led后面添加
152行,初始化 timer_list 变量,init_timer 函数原型如下
void init_timer(struct timer_list *timer)
timer:要初始化定时器。返回值: 没有返回值
154行,这里编写一个函数为timer_func,把它赋给 function,这个是定时处理函数
timer_func函数如下,在驱动入口函数之前编写
68行,获取私有数据
71行,设置gpio的电平为0,也就是低
72行,mod_timer 函数用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器,原型如下:
int mod_timer(struct timer_list *timer, unsigned long expires)
timer:要修改超时时间(定时值)的定时器。expires:修改后的超时时间。
返回值: 0,调用 mod_timer 函数前定时器未被激活;1,调用 mod_timer 函数前定时器已被激活
这里定时器的超时时间为jiffies+msecs_to_jiffies(500),msecs_to_jiffies函数原型如下
long msecs_to_jiffies(const unsigned int m)
将毫秒转换为 jiffies 类型,这里是500毫秒
msecs_to_jiffies函数主要就是设置周期运行,周期为500毫秒
回到155行,expires为定时器超时时间,和timer_func函数里一样500毫秒
156行,通过data传递timer数据给timer_func函数
157行,add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行,函数原型如下:
void add_timer(struct timer_list *timer)
timer:要注册的定时器; 没有返回值
这段代码主要是,在初始化led后,使用定时器,在执行代码就会以500毫秒为一个周期,使led闪烁
这部分代码如下
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.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/slab.h> #include <linux/of_address.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" /*timer设备结构体*/ struct timer_dev{ dev_t devid;/*设备号*/ int major;/*主设备号*/ int minor;/*次设备号*/ struct cdev cdev;/*cdev表示一个字符设备*/ struct class *class;/*类*/ struct device *device;/*设备*/ struct device_node *nd;/* 设备节点 */ struct timer_list timer;/*定时器*/ int ledgpio;/* key所使用的GPIO编号 */ }timer; static int timer_open(struct inode *inode, struct file *filp) { filp->private_data = &timer;/* 设置私有数据 */ return 0; } static ssize_t timer_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } static ssize_t timer_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *ppos) { int ret = 0; return ret; } static int timer_release(struct inode *inode, struct file *filp) { return 0; } /* 设备操作集 */ static const struct file_operations timer_fops = { .owner = THIS_MODULE, .open = timer_open, .read = timer_read, .write = timer_write, .release = timer_release, }; /*定时器处理函数*/ static void timer_func(unsigned long arg){ struct timer_dev *dev = (struct timer_dev*)arg; static int sta =1; sta =!sta;/* 每次都取反,实现LED灯反转 */ gpio_set_value(dev->ledgpio,sta); mod_timer(&dev->timer,jiffies + msecs_to_jiffies(500)); } /*初始化led*/ int led_init(struct timer_dev *dev){ int ret = 0; dev->nd = of_find_node_by_path("/gpioled"); if(dev->nd == NULL){ ret = -EINVAL; goto fail_fd; } dev->ledgpio = of_get_named_gpio(dev->nd,"led-gpios",0); if(dev->ledgpio<0){ ret = -EINVAL; goto fail_gpio; } ret = gpio_request(dev->ledgpio,"led"); if(ret){ ret = -EBUSY; printk("IO %d can't request\r\n",dev->ledgpio); goto fail_request; } ret= gpio_direction_output(dev->ledgpio,1);// if(ret < 0){ ret = -EINVAL; goto fail_gpioset; } return 0; fail_gpioset: gpio_free(dev->ledgpio); fail_request: fail_gpio: fail_fd: return ret; } /*驱动入口函数*/ static int __init timer_init(void){ int ret =0; /*注册字符设备驱动*/ 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){ goto fail_devid; } printk("timer.major = %d,timer.minor = %d\r\n", timer.major,timer.minor); /*初始化cdev*/ timer.cdev.owner = THIS_MODULE; cdev_init(&timer.cdev,&timer_fops); /*向 Linux 系统添加这个字符设备*/ ret = cdev_add(&timer.cdev,timer.devid,TIMER_CNT); if(ret < 0){ goto fail_cdev; } /*设备文件节点的自动创建与删除*/ /*创建类*/ timer.class = class_create(THIS_MODULE,TIMER_NAME); if(IS_ERR(timer.class)){ ret = PTR_ERR(timer.class); goto fail_class; } /*在类下创建设备,生成/dev/TIMER_NAME这个设备文件*/ timer.device = device_create(timer.class,NULL,timer.devid,NULL,TIMER_NAME); if(IS_ERR(timer.device)){ ret = PTR_ERR(timer.device); goto fail_device; } /*初始化led*/ ret = led_init(&timer); if(ret < 0){ goto fail_ledinit; } /*初始化定时器*/ init_timer(&timer.timer);/* 初始化定时器 */ timer.timer.function = timer_func;/* 设置定时处理函数 */ timer.timer.expires = jiffies + msecs_to_jiffies(500);/* 超时时间 500毫秒 */ timer.timer.data = (unsigned long)&timer;/* 将设备结构体作为参数 */ add_timer(&timer.timer);/* 启动定时器 */ return 0; fail_ledinit: fail_device: class_destroy(timer.class); fail_class: cdev_del(&timer.cdev); fail_cdev: unregister_chrdev_region(timer.devid,TIMER_CNT); fail_devid: return ret; } /*驱动出口函数*/ static void __exit timer_exit(void){ gpio_set_value(timer.ledgpio,1);/*关灯*/ del_timer(&timer.timer);/*删除定时器*/ gpio_free(timer.ledgpio);/*释放io*/ del_timer(&timer.timer);/*删除定时器*/ device_destroy(timer.class,timer.devid);/*删除设备*/ class_destroy(timer.class);/*删除类*/ cdev_del(&timer.cdev); /*Linux 内核中删除相应的字符设备*/ unregister_chrdev_region(timer.devid,TIMER_CNT);/*释放设备号*/ } /*注册和卸载驱动*/ module_init(timer_init); module_exit(timer_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ba che kai qi lai");
编译验证——加载驱动之后就会以500毫秒进行闪烁
四、ioctl函数
在应用层有ioctl 函数,在内核上对应就会调用unlocked_ioctl 函数或者compat_ioctl 函数,这两个功能一样,前者用在32为操作系统上,后者用在64位操作系统上
作用:设备在运行的时候可能要求数据的写入是连续的,如果这个时候用write函数去写指令的话,就有可能导致数据的不连续,比如声卡放音乐卡顿,电影播放不流畅等情况,为了解决这种情况,就有了ioctl函数,此函数专门向驱动层发送或者接收指令
五、内核添加unlocked_ioctl 函数
1、添加设备操作集unlocked_ioctl成员
2、添加timepriod变量
用于后续接收设置的毫秒
3、初始化timepriod
在驱动入口函数的初始化定时器里面添加,timepriod设置为500 ,182行,用变量替换500毫秒
同理、把下面99行的500毫秒改为用变量timepriod
4、添加unlocked_ioctl对应的函数
在添加之前先定义三个宏
解释: _IO(type,nr)
type:是个0-0xff的数或者一个字符,占8bit。这个数是用来区分不同的驱动的,像设备号一样
nr:命令编号/序数,8 bit,取值范围 0~255
这里分别用1-2-3来实现关闭、打开和设置周期的操作
下面在设备操作集之前添加函数
68行,删除定时器。del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中。 del_timer_sync 函数原型如下所示:
int del_timer_sync(struct timer_list *timer)
timer:要删除的定时器。
返回值: 0,定时器还没被激活; 1,定时器已经激活。
70行, mod_timer 函数会激活定时器
74-80行,获取应用层数据,根据获取到的数据来设置周期值,并开始按数据进行周期运行
六、编写应用测试
需要头文件#include <sys/ioctl.h>
1、添加宏定义
这个和内核驱动的一样
2、使用ioctl函数
ioctl函数原型如下
int ioctl(int fd, int cmd, …) ;
fd:文件描述符;cmd:交互命令,设备驱动将根据 cmd 执行对应操作;
…:可变参数 arg,一些情况下应用程序需要向驱动程序传参,参数就通过arg来传递
执行成功时返回 0,失败则返回 -1 并设置全局变量 errorno 值
通过应用层ioctl函数获取数据之后,内核就会根据数据执行对应的操作
七、编译测试
通过1和2命令实现关闭和开启定时器
通过3命令设置时间(毫秒)之后就会按照设置的时间来周期运行
总体代码如下
驱动
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.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/slab.h> #include <linux/of_address.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 _IO(0XEF,3) /*设置周期*/ /*timer设备结构体*/ struct timer_dev{ dev_t devid;/*设备号*/ int major;/*主设备号*/ int minor;/*次设备号*/ struct cdev cdev;/*cdev表示一个字符设备*/ struct class *class;/*类*/ struct device *device;/*设备*/ struct device_node *nd;/* 设备节点 */ struct timer_list timer;/*定时器*/ int ledgpio;/* key所使用的GPIO编号 */ int timepriod;/*定时器周期ms*/ }timer; static int timer_open(struct inode *inode, struct file *filp) { filp->private_data = &timer;/* 设置私有数据 */ return 0; } static ssize_t timer_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; } static ssize_t timer_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *ppos) { int ret = 0; return ret; } static int timer_release(struct inode *inode, struct file *filp) { return 0; } static long timer_ioctl(struct file *filp,unsigned int cmd, unsigned long arg){ struct timer_dev *dev = filp->private_data; int ret = 0; int value=0; switch (cmd){ case CLOSE_CMD: del_timer_sync(&dev->timer); break; case OPEN_CMD: mod_timer(&dev->timer,jiffies + msecs_to_jiffies(dev->timepriod)); break; case SETPERIOD_CMD: ret = copy_from_user(&value,(int *)arg,sizeof(int)); if(ret < 0){ return -EFAULT; } dev->timepriod = value; mod_timer(&dev->timer,jiffies + msecs_to_jiffies(dev->timepriod)); break; } return ret; } /* 设备操作集 */ static const struct file_operations timer_fops = { .owner = THIS_MODULE, .open = timer_open, .read = timer_read, .write = timer_write, .release = timer_release, .unlocked_ioctl = timer_ioctl, }; /*定时器处理函数*/ static void timer_func(unsigned long arg){ struct timer_dev *dev = (struct timer_dev*)arg; static int sta =1; sta =!sta;/* 每次都取反,实现LED灯反转 */ gpio_set_value(dev->ledgpio,sta); mod_timer(&dev->timer,jiffies + msecs_to_jiffies(dev->timepriod)); } /*初始化led*/ int led_init(struct timer_dev *dev){ int ret = 0; dev->nd = of_find_node_by_path("/gpioled"); if(dev->nd == NULL){ ret = -EINVAL; goto fail_fd; } dev->ledgpio = of_get_named_gpio(dev->nd,"led-gpios",0); if(dev->ledgpio<0){ ret = -EINVAL; goto fail_gpio; } ret = gpio_request(dev->ledgpio,"led"); if(ret){ ret = -EBUSY; printk("IO %d can't request\r\n",dev->ledgpio); goto fail_request; } ret= gpio_direction_output(dev->ledgpio,1);// if(ret < 0){ ret = -EINVAL; goto fail_gpioset; } return 0; fail_gpioset: gpio_free(dev->ledgpio); fail_request: fail_gpio: fail_fd: return ret; } /*驱动入口函数*/ static int __init timer_init(void){ int ret =0; /*注册字符设备驱动*/ 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){ goto fail_devid; } printk("timer.major = %d,timer.minor = %d\r\n", timer.major,timer.minor); /*初始化cdev*/ timer.cdev.owner = THIS_MODULE; cdev_init(&timer.cdev,&timer_fops); /*向 Linux 系统添加这个字符设备*/ ret = cdev_add(&timer.cdev,timer.devid,TIMER_CNT); if(ret < 0){ goto fail_cdev; } /*设备文件节点的自动创建与删除*/ /*创建类*/ timer.class = class_create(THIS_MODULE,TIMER_NAME); if(IS_ERR(timer.class)){ ret = PTR_ERR(timer.class); goto fail_class; } /*在类下创建设备,生成/dev/TIMER_NAME这个设备文件*/ timer.device = device_create(timer.class,NULL,timer.devid,NULL,TIMER_NAME); if(IS_ERR(timer.device)){ ret = PTR_ERR(timer.device); goto fail_device; } /*初始化led*/ ret = led_init(&timer); if(ret < 0){ goto fail_ledinit; } /*初始化定时器*/ init_timer(&timer.timer);/* 初始化定时器 */ timer.timepriod = 500; timer.timer.function = timer_func;/* 设置定时处理函数 */ timer.timer.expires = jiffies + msecs_to_jiffies(timer.timepriod);/* 超时时间 500毫秒 */ timer.timer.data = (unsigned long)&timer;/* 将设备结构体作为参数 */ add_timer(&timer.timer);/* 启动定时器 */ return 0; fail_ledinit: fail_device: class_destroy(timer.class); fail_class: cdev_del(&timer.cdev); fail_cdev: unregister_chrdev_region(timer.devid,TIMER_CNT); fail_devid: return ret; } /*驱动出口函数*/ static void __exit timer_exit(void){ gpio_set_value(timer.ledgpio,1);/*关灯*/ del_timer(&timer.timer);/*删除定时器*/ gpio_free(timer.ledgpio);/*释放io*/ del_timer(&timer.timer);/*删除定时器*/ device_destroy(timer.class,timer.devid);/*删除设备*/ class_destroy(timer.class);/*删除类*/ cdev_del(&timer.cdev); /*Linux 内核中删除相应的字符设备*/ unregister_chrdev_region(timer.devid,TIMER_CNT);/*释放设备号*/ } /*注册和卸载驱动*/ module_init(timer_init); module_exit(timer_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ba che kai qi lai");
应用
#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 _IO(0XEF,3) /*设置周期*/ /* argc:应用程序参数个数(argv数组元素个数) argv:具体参数,也可以写作char argv ./timerAPP <filename> ./timerAPP /dev/timer */ int main(int argc, char *argv[]) { int fd,ret; char *filename; unsigned char databuf[1]; unsigned int cmd,arg; /*判断命令行输入参数是否正确*/ if(argc != 2){ printf("error usage!\r\n"); return -1; } /*用指针指向文件*/ filename = argv[1]; /*打开文件*/ fd = open(filename , O_RDWR); if(fd < 0){ printf("file open failed\r\n",filename); return -1; } /*循环读取*/ while(1){ printf("input cmd:"); ret = scanf("%d",&cmd); getchar();/*使用getchar()清理回车\n*/ if(cmd == 1){ ioctl(fd,CLOSE_CMD,&arg);/*关闭*/ }else if (cmd == 2){ ioctl(fd,OPEN_CMD,&arg);/*打开*/ }else if (cmd == 3){ printf("input TImer period:"); ret = scanf("%d",&arg); getchar();/*使用getchar()清理回车\n*/ ioctl(fd,SETPERIOD_CMD,&arg);/*设置周期*/ }else{ printf("input error\r\n"); } } /*关闭文件*/ close(fd); return 0; }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/125974.html