(七)Linux 内核定时器

定时器是我们最常用到的功能,一般用来完成定时功能,本章我们就来学习一下 Linux 内核提供的定时器 API 函数,通过这些定时器 API 函数我们可以完成很多要求定时的应用。Linux内核也提供了短延时函数,比如微秒、纳秒、毫秒延时函数。

节拍率

Linux 内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于我们驱动编写者来说最常用的定时器。硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率,也叫做节拍率(tick rate)(有的资料也叫系统频率),比如 100Hz、1000Hz 等等说的就是系统节拍率。

系统节拍率是可以设置的,单位是 Hz,我们在编译 Linux 内核的时候可以通过图形化界面设置系统节拍率。可选的系统节拍率为 100Hz、200Hz、250Hz、300Hz、500Hz 和1000Hz,默认情况下选择 100Hz。

设置好以后打开 Linux 内核源码根目录下的.config 文件
在这里插入图片描述
高节拍率和低节拍率的优缺点:
①、高节拍率会提高系统时间精度,如果采用 100Hz 的节拍率,时间精度就是 10ms,采用1000Hz 的话时间精度就是 1ms,精度提高了 10 倍。高精度时钟的好处有很多,对于那些对时间要求严格的函数来说,能够以更高的精度运行,时间测量也更加准确。
②、高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担,1000Hz 和 100Hz的系统节拍率相比,系统要花费 10 倍的“精力”去处理中断。中断服务函数占用处理器的时间增加,但是现在的处理器性能都很强大,所以采用 1000Hz 的系统节拍率并不会增加太大的负载压力。根据自己的实际情况,选择合适的系统节拍率,本教程我们全部采用默认的 100Hz 系统节拍率。

Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0。HZ 表示每秒的节拍数,jiffies 表示系统运行的 jiffies 节拍数,所以 jiffies/HZ 就是系统运行时间,单位为秒。不管是 32 位还是 64 位的 jiffies,都有溢出的风险,溢出以后会重新从 0 开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。假如 HZ 为最大值 1000 的时候,32 位的 jiffies 只需要 49.7 天就发生了绕回,对于 64 位的 jiffies 来说大概需要5.8 亿年才能绕回,因此 jiffies_64 的绕回忽略不计。
在这里插入图片描述如果 unkown 超过 known 的话,time_after 函数返回真,否则返回假。如果 unkown 没有超过 known 的话 time_before 函数返回真,否则返回假。time_after_eq 函数和 time_after 函数类似,只是多了判断等于这个条件。同理,time_before_eq 函数和 time_before 函数也类似。

使用 jiffies 判断超时

1 unsigned long timeout;
2 timeout = jiffies + (2 * HZ); /* 超时的时间点 */
3 
4 /*************************************
5 具体的代码
6 ************************************/
7 
8 /* 判断有没有超时 */
9 if(time_before(jiffies, timeout)) {
10 /* 超时未发生 */
11 } else {
12 /* 超时发生 */
13 }

为了方便开发,Linux 内核提供了几个 jiffies 和 ms、us、ns 之间的转换函数。
在这里插入图片描述

内核定时器

定时器是一个很常用的功能,需要周期性处理的工作都要用到定时器,Linux 内核定时器采用系统时钟来实现。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 hlist_node entry;
 unsigned long expires; /* 定时器超时时间,单位是节拍数 */
 void (*function)(struct timer_list *);/* 定时处理函数*/
 u32 flags; /* 标志位 */

 #ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
 #endif
 };

要使用内核定时器首先要先定义一个 timer_list 变量,表示定时器,tiemr_list 结构体的expires 成员变量表示超时时间,单位为节拍数。比如我们现在需要定义一个周期为 2 秒的定时器,那么这个定时器的超时时间就是 jiffies+(2HZ),因此 expires=jiffies+(2HZ)。function 就是定时器超时以后的定时处理函数,我们要做的工作就放到这个函数里面,需要我们编写这个定时处理函数,function 函数的形参就是我们定义的 timer_list 变量。

定义好定时器以后还需要通过一系列的 API 函数来初始化此定时器

void timer_setup(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags)
void add_timer(struct timer_list *timer)
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 内核定时器函数的描述,以下是这些函数的生成表格,包括每个函数的名称、作用以及相关说明:

函数名称作用相关说明
timer_setup初始化 timer_list 类型变量- 用于设置和初始化一个定时器。
- 需要提供定时器回调函数、定时器到期时间等参数。
- 通常在定时器使用前调用,以确保定时器正确配置。
add_timer向 Linux 内核注册定时器- 注册后,定时器开始运行。
- 定时器到期时,会执行预先设置的回调函数。
- 可以用于单次或周期性定时任务。
- 在多处理器系统上,定时器可能在任何可用的处理器上运行。
del_timer删除一个定时器- 无论定时器是否已经激活,都可以删除。
- 在多处理器系统上,定时器可能在其他处理器上运行。
- 调用此函数后,系统会尝试停止定时器,但不会等待其他处理器上的定时器处理函数退出。
- 适用于非关键场景。
del_timer_sync删除定时器的同步版本- 等待其他处理器使用完定时器后再删除。
- 确保定时器在所有处理器上都已停止。
- 不能在中断上下文中使用。
- 适用于需要确保定时器完全停止的关键场景。
mod_timer修改定时器的到期时间- 如果定时器尚未激活,调用此函数会激活定时器。
- 如果定时器已经激活,修改其到期时间。
- 可以用于动态调整定时器的运行时间。
- 适用于需要灵活调整定时器时间的场景。
 struct timer_list timer; /* 定义定时器 */
 
 /* 定时器回调函数 */
 void function(struct timer_list *arg)
 { 
 /* 
 * 定时器处理代码
 */
 
 /* 如果需要定时器周期性运行的话就使用 mod_timer
 * 函数重新设置超时值并且启动定时器。
 */
 mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
 }
 
 /* 初始化函数 */
 void init(void) 
 {
 timer_setup(&timerdev.timer, timer_function, 0); /* 初始化定时器 */
 timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
 add_timer(&timer); /* 启动定时器 */
 }

 /* 退出函数 */
 void exit(void)
 {
 del_timer(&timer); /* 删除定时器 */
 /* 或者使用 */
 del_timer_sync(&timer);
 }

Linux 内核短延时函数
在这里插入图片描述

LED 灯的闪烁实验

内核定时器周期性的点亮和熄灭开发板上的 LED 灯,LED 灯的闪烁期由内核定时器来设置。

timer.c

1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 #include <linux/semaphore.h>
15 #include <linux/timer.h>
16 #include <asm/mach/map.h>
17 #include <asm/uaccess.h>
18 #include <asm/io.h>
19 /***************************************************************
20 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
21 文件名 : timer.c
22 作者 : 正点原子 Linux 团队
23 版本 : V1.0
24 描述 : Linux 内核定时器实验
25 其他 : 无
26 论坛 : www.openedv.com
27 日志 : 初版 V1.0 2021/01/5 正点原子 Linux 团队创建
28 ***************************************************************/
29 #define TIMER_CNT 1 /* 设备号个数 */
30 #define TIMER_NAME "timer" /* 名字 */
31 #define CLOSE_CMD (_IO(0XEF, 0x1)) /* 关闭定时器 */
32 #define OPEN_CMD (_IO(0XEF, 0x2)) /* 打开定时器 */
33 #define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 设置定时器周期命令*/
34 #define LEDON 1 /* 开灯 */
35 #define LEDOFF 0 /* 关灯 */
36
37 /* timer 设备结构体 */
38 struct timer_dev{
39 dev_t devid; /* 设备号 */
40 struct cdev cdev; /* cdev */
41 struct class *class; /* 类 */
42 struct device *device; /* 设备 */
43 int major; /* 主设备号 */
44 int minor; /* 次设备号 */
45 struct device_node *nd; /* 设备节点 */
46 int led_gpio; /* key 所使用的 GPIO 编号 */
47 int timeperiod; /* 定时周期,单位为 ms */
48 struct timer_list timer; /* 定义一个定时器 */
49 spinlock_t lock; /* 定义自旋锁 */
50 };
51
52 struct timer_dev timerdev; /* timer 设备 */
53
54 /*
55 * @description : 初始化 LED 灯 IO,open 函数打开驱动的时候
56 * 初始化 LED 灯所使用的 GPIO 引脚。
57 * @param : 无
58 * @return : 无
59 */
60 static int led_init(void)
61 {
62 int ret;
63 const char *str;
64 
65 /* 设置 LED 所使用的 GPIO */
66 /* 1、获取设备节点:timerdev */
67 timerdev.nd = of_find_node_by_path("/gpioled");
68 if(timerdev.nd == NULL) {
69 printk("timerdev node not find!\r\n");
70 return -EINVAL;
71 }
72
73 /* 2.读取 status 属性 */
74 ret = of_property_read_string(timerdev.nd, "status", &str);
75 if(ret < 0)
76 return -EINVAL;
77
78 if (strcmp(str, "okay"))
79 return -EINVAL;
80 
81 /* 3、获取 compatible 属性值并进行匹配 */
82 ret = of_property_read_string(timerdev.nd, "compatible", &str);
83 if(ret < 0) {
84 printk("timerdev: Failed to get compatible property\n");
85 return -EINVAL;
86 }
87
88 if (strcmp(str, "alientek,led")) {
89 printk("timerdev: Compatible match failed\n");
90 return -EINVAL;
91 }
92
93 /* 4、 获取设备树中的 gpio 属性,得到 led-gpio 所使用的 led 编号 */
94 timerdev.led_gpio = of_get_named_gpio(timerdev.nd,
"led-gpio", 0);
95 if(timerdev.led_gpio < 0) {
96 printk("can't get led-gpio");
97 return -EINVAL;
98 }
99 printk("led-gpio num = %d\r\n", timerdev.led_gpio);
100
101 /* 5.向 gpio 子系统申请使用 GPIO */
102 ret = gpio_request(timerdev.led_gpio, "led");
103 if (ret) {
104 printk(KERN_ERR "timerdev: Failed to request led-gpio\n");
105 return ret;
106 }
107
108 /* 6、设置 PI0 为输出,并且输出高电平,默认关闭 LED 灯 */
109 ret = gpio_direction_output(timerdev.led_gpio, 1);
110 if(ret < 0) {
111 printk("can't set gpio!\r\n");
112 return ret;
113 }
114 return 0;
115 }
116
117 /*
118 * @description : 打开设备
119 * @param – inode : 传递给驱动的 inode
120 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
121 * 一般在 open 的时候将 private_data 指向设备结构体。
122 * @return : 0 成功;其他 失败
123 */
124 static int timer_open(struct inode *inode, struct file *filp)
125 {
126 int ret = 0;
127 filp->private_data = &timerdev; /* 设置私有数据 */
128
129 timerdev.timeperiod = 1000; /* 默认周期为 1s */
130 ret = led_init(); /* 初始化 LED IO */
131 if (ret < 0) {
132 return ret;
133 }
134
135 return 0;
136 }
137
138 /*
139 * @description : ioctl 函数,
140 * @param - filp : 要打开的设备文件(文件描述符)
141 * @param - cmd : 应用程序发送过来的命令
142 * @param - arg : 参数
143 * @return : 0 成功;其他 失败
144 */
145 static long timer_unlocked_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg)
146 {
147 struct timer_dev *dev = (struct timer_dev *)filp->private_data;
148 int timerperiod;
149 unsigned long flags;
150 
151 switch (cmd) {
152 case CLOSE_CMD: /* 关闭定时器 */
153 del_timer_sync(&dev->timer);
154 break;
155 case OPEN_CMD: /* 打开定时器 */
156 spin_lock_irqsave(&dev->lock, flags);
157 timerperiod = dev->timeperiod;
158 spin_unlock_irqrestore(&dev->lock, flags);
159 mod_timer(&dev->timer, jiffies +
msecs_to_jiffies(timerperiod));
160 break;
161 case SETPERIOD_CMD: /* 设置定时器周期 */
162 spin_lock_irqsave(&dev->lock, flags);
163 dev->timeperiod = arg;
164 spin_unlock_irqrestore(&dev->lock, flags);
165 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));
166 break;
167 default:
168 break;
169 }
170 return 0;
171 }
172
173 /*
174 * @description : 关闭/释放设备
175 * @param - filp : 要关闭的设备文件(文件描述符)
176 * @return : 0 成功;其他 失败
177 */
178 static int led_release(struct inode *inode, struct file *filp)
179 {
180 struct timer_dev *dev = filp->private_data;
181 gpio_set_value(dev->led_gpio, 1); /* APP 结束的时候关闭 LED */
182 gpio_free(dev->led_gpio); /* 释放 LED */
183 del_timer_sync(&dev->timer); /* 关闭定时器 */
184
185 return 0;
186 }
187
188 /* 设备操作函数 */
189 static struct file_operations timer_fops = {
190 .owner = THIS_MODULE,
191 .open = timer_open,
192 .unlocked_ioctl = timer_unlocked_ioctl,
193 .release = led_release,
194 };
195
196 /* 定时器回调函数 */
197 void timer_function(struct timer_list *arg)
198 {
199 /* from_timer 是个宏,可以根据结构体的成员地址,获取到这个结构体的首地址。
200 第一个参数表示结构体,第二个参数表示第一个参数里的一个成员,第三个参数表
示第二个参数的类型,得到第一个参数的首地址。
201 */
202 struct timer_dev *dev = from_timer(dev, arg, timer);
203 static int sta = 1;
204 int timerperiod;
205 unsigned long flags;
206
207 sta = !sta; /* 每次都取反,实现 LED 灯反转 */
208 gpio_set_value(dev->led_gpio, sta);
209 
210 /* 重启定时器 */
211 spin_lock_irqsave(&dev->lock, flags);
212 timerperiod = dev->timeperiod;
213 spin_unlock_irqrestore(&dev->lock, flags);
214 mod_timer(&dev->timer, jiffies +
msecs_to_jiffies(dev->timeperiod));
215 }
216
217 /*
218 * @description : 驱动入口函数
219 * @param : 无
220 * @return : 无
221 */
222 static int __init timer_init(void)
223 {
224 int ret;
225 
226 /* 初始化自旋锁 */
227 spin_lock_init(&timerdev.lock);
228
229 /* 注册字符设备驱动 */
230 /* 1、创建设备号 */
231 if (timerdev.major) { /* 定义了设备号 */
232 timerdev.devid = MKDEV(timerdev.major, 0);
233 ret = register_chrdev_region(timerdev.devid, TIMER_CNT,
TIMER_NAME);
234 if(ret < 0) {
235 pr_err("cannot register %s char driver [ret=%d]\n",
TIMER_NAME, TIMER_CNT);
236 return -EIO;
237 }
238 } else { /* 没有定义设备号 */
239 ret = alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT,
TIMER_NAME); /* 申请设备号 */
240 if(ret < 0) {
241 pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
TIMER_NAME, ret);
242 return -EIO;
243 }
244 timerdev.major = MAJOR(timerdev.devid); /* 获取主设备号 */
245 timerdev.minor = MINOR(timerdev.devid); /* 获取次设备号 */
246 }
247 printk("timerdev major=%d,minor=%d\r\n",timerdev.major,
timerdev.minor); 
248 
249 /* 2、初始化 cdev */
250 timerdev.cdev.owner = THIS_MODULE;
251 cdev_init(&timerdev.cdev, &timer_fops);
252 
253 /* 3、添加一个 cdev */
254 cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
255 if(ret < 0)
256 goto del_unregister;
257 
258 /* 4、创建类 */
259 timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
260 if (IS_ERR(timerdev.class)) {
261 goto del_cdev;
262 }
263
264 /* 5、创建设备 */
265 timerdev.device = device_create(timerdev.class, NULL,
timerdev.devid, NULL, TIMER_NAME);
266 if (IS_ERR(timerdev.device)) {
267 goto destroy_class;
268 }
269 
270 /* 6、初始化 timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */
271 timer_setup(&timerdev.timer, timer_function, 0);
272
273 return 0;
274
275 destroy_class:
276 device_destroy(timerdev.class, timerdev.devid);
277 del_cdev:
278 cdev_del(&timerdev.cdev);
279 del_unregister:
280 unregister_chrdev_region(timerdev.devid, TIMER_CNT);
281 return -EIO;
282 }
283
284 /*
285 * @description : 驱动出口函数
286 * @param : 无
287 * @return : 无
288 */
289 static void __exit timer_exit(void)
290 {
291 del_timer_sync(&timerdev.timer); /* 删除 timer */
292 #if 0
293 del_timer(&timerdev.tiemr);
294 #endif
295
296 /* 注销字符设备驱动 */
297 cdev_del(&timerdev.cdev); /* 删除 cdev */
298 unregister_chrdev_region(timerdev.devid, TIMER_CNT);
299
300 device_destroy(timerdev.class, timerdev.devid);
301 class_destroy(timerdev.class);
302 }
303
304 module_init(timer_init);
305 module_exit(timer_exit);
306 MODULE_LICENSE("GPL");
307 MODULE_AUTHOR("ALIENTEK");
308 MODULE_INFO(intree, "Y");

timerApp.c

1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8 #include <sys/ioctl.h>
9 /***************************************************************
10 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
11 文件名 : timerApp.c
12 作者 : 正点原子 Linux 团队
13 版本 : V1.0
14 描述 : 定时器测试应用程序
15 其他 : 无
16 使用方法 :./timertest /dev/timer 打开测试 App
17 论坛 : www.openedv.com
18 日志 : 初版 V1.0 2021/01/5 正点原子 Linux 团队创建
19 ***************************************************************/
20
21 /* 命令值 */
22 #define CLOSE_CMD (_IO(0XEF, 0x1)) /* 关闭定时器 */
23 #define OPEN_CMD (_IO(0XEF, 0x2)) /* 打开定时器 */
24 #define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 设置定时器周期命令 */
25
26 /*
27 * @description : main 主程序
28 * @param - argc : argv 数组元素个数
29 * @param - argv : 具体参数
30 * @return : 0 成功;其他 失败
31 */
32 int main(int argc, char *argv[])
33 {
34 int fd, ret;
35 char *filename;
36 unsigned int cmd;
37 unsigned int arg;
38 unsigned char str[100];
39
40 if (argc != 2) {
41 printf("Error Usage!\r\n");
42 return -1;
43 }
44
45 filename = argv[1];
46
47 fd = open(filename, O_RDWR);
48 if (fd < 0) {
49 printf("Can't open file %s\r\n", filename);
50 return -1;
51 }
52
53 while (1) {
54 printf("Input CMD:");
55 ret = scanf("%d", &cmd);
56 if (ret != 1) { /* 参数输入错误 */
57 fgets(str, sizeof(str), stdin); /* 防止卡死 */
58 }
59 if(4 == cmd) /* 退出 APP */
60 goto out;
61 if(cmd == 1) /* 关闭 LED 灯 */
62 cmd = CLOSE_CMD;
63 else if(cmd == 2) /* 打开 LED 灯 */
64 cmd = OPEN_CMD;
65 else if(cmd == 3) {
66 cmd = SETPERIOD_CMD; /* 设置周期值 */
67 printf("Input Timer Period:");
68 ret = scanf("%d", &arg);
69 if (ret != 1) { /* 参数输入错误 */
70 fgets(str, sizeof(str), stdin); /* 防止卡死 */
71 }
72 }
73 ioctl(fd, cmd, arg); /* 控制定时器的打开和关闭 */
74 }
75
76 out:
77 close(fd);
78 }

①、运行 APP 以后提示我们输入要测试的命令,输入 1 表示关闭定时器、输入 2 表示打开
定时器,输入 3 设置定时器周期。
②、如果要设置定时器周期的话,需要让用户输入要设置的周期值,单位为毫秒。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值