问题描述

如何使用内核定时器?

内核定时器

Linux内核定时器是timer_list,下面我们详细介绍定时器的使用。

1. 简介

内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于 <Linux/timer.h> 和 kernel/timer.c 文件中。

被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:

    1. 没有 current 指针、不允许访问用户空间。因为没有进程上下文,相关代码和被中断的进程没有任何联系。
    1. 不能执行休眠(或可能引起休眠的函数)和调度。
    1. 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。

内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。

在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。

2. 数据结构

(1) 内核定时器的数据结构

    struct timer_list {
      struct list_head entry, /*定时器列表*/
      unsigned long expires, /*定时器到期时间*/
      void (*function) (unsigned long), /*定时器处理函数*/
      unsigned long data,/*作为参数被传入定时器处理函数*/
      struct timer_base_s *base,
      ...
    };

其中 expires 字段表示期望定时器执行的 jiffies 值,到达该 jiffies 值时,将调用 function 函数,并传递 data 作为参数。

jiffies

当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。

base 字段是内核内部实现所用的。

需要注意的是 expires 的值是32位的,因为内核定时器并不适用于长的未来时间点。

(2) 初始化定时器

方法一:

DEFINE_TIMER(timer_name, function_name, expires_value, data);

该宏会定义一个名叫 timer_name 内核定时器,并初始化其 function, expires, name 和 base 字段。

方法二:

struct timer_list mytimer;
void init_timer(struct timer_list *timer);

上述init_timer函数将初始化struct timer_list的 entry的next 为 NULL ,并为base指针赋值

(3) 增加定时器

定时器要生效,还必须被连接到内核专门的链表中,这可以通过 add_timer(struct timer_list *timer) 来实现。

void add_timer (struct timer_list *timer);

(4) 删除定时器

注销一个定时器,可以通过 del_timer(struct timer_list *timer) 或 del_timer_sync(struct timer_list *timer) 。

int del_timer (struct timer_list *timer);
int del_timer_sync(struct timer_list *timer)

其中 del_timer_sync 是用在 SMP 系统上的(在非SMP系统上,它等于del_timer),当要被注销的定时器函数正在另一个 cpu 上运行时,del_timer_sync() 会等待其运行完,所以这个函数会休眠。

另外还应避免它和被调度的函数中用同一个锁。对于一个已经被运行过且没有重新注册自己的定时器而言,注销函数其实也没什么事可做。

int timer_pending(const struct timer_list *timer);

这个函数用来判断一个定时器是否被添加到了内核链表中以等待被调度运行。注意,当一个定时器函数即将要被运行前,内核会把相应的定时器从内核链表中删除(相当于注销)。

(5) 修改定时器的expire

要修改一个定时器的调度时间,可以通过调用 mod_timer(struct timer_list *timer, unsigned long expires) 。

mod_timer() 会重新注册定时器到内核,而不管定时器函数是否被运行过。

int mod_timer (struct timer_list *timer, unsigned long expires);

(6) 对于周期性的任务,linux内核还提供了一种delayed_work机制来完成,本质上用工作队列和定时器实现。

3. 举例

例1:实现每隔一秒向内核log中打印一条信息

/* 实现每隔一秒向内核log中打印一条信息 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/time.h>
#include <linux/timer.h> static struct timer_list tm;
struct timeval oldtv; void callback(unsigned long arg)
{
struct timeval tv;
char *strp = (char*)arg; printk("%s: %lu, %s\n", __func__, jiffies, strp); do_gettimeofday(&tv);
printk("%s: %ld, %ld\n", __func__,
tv.tv_sec - oldtv.tv_sec, //与上次中断间隔 s
tv.tv_usec- oldtv.tv_usec); //与上次中断间隔 ms oldtv = tv;
tm.expires = jiffies+1*HZ;
add_timer(&tm); //重新开始计时
} static int __init demo_init(void)
{
printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__); init_timer(&tm); //初始化内核定时器 do_gettimeofday(&oldtv); //获取当前时间
tm.function= callback; //指定定时时间到后的回调函数
tm.data = (unsigned long)"hello world"; //回调函数的参数
tm.expires = jiffies+1*HZ; //定时时间
add_timer(&tm); //注册定时器 return 0;
} static void __exit demo_exit(void)
{
printk(KERN_INFO "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__);
del_timer(&tm); //注销定时器
} module_init(demo_init);
module_exit(demo_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("yikoupeng");
MODULE_DESCRIPTION("timerlist");

例2:秒字符设备

second_drv.c

1 #include <linux/module.h>
2 #include <linux/types.h>
3 #include <linux/fs.h>
4 #include <linux/errno.h>
5 #include <linux/mm.h>
6 #include <linux/sched.h>
7 #include <linux/init.h>
8 #include <linux/cdev.h>
9 #include <asm/io.h>
10 #include <asm/system.h>
11 #include <asm/uaccess.h>
12 #include <linux/slab.h>
13
14 #define SECOND_MAJOR 248
15
16 static int second_major = SECOND_MAJOR;
17
18 struct second_dev {
19 struct cdev cdev;
20 atomic_t counter;
21 struct timer_list s_timer;
22 };
23
24 struct second_dev *second_devp;
25 static void second_timer_handle (unsigned long arg)
26 {
27 mod_timer (&second_devp->s_timer, jiffies + HZ);
28 atomic_inc (&second_devp->counter);
29 printk (KERN_NOTICE "current jiffies is %ld\n", jiffies);
30 }
31 int second_open (struct inode *inode, struct file *filp)
32 {
33 init_timer (&second_devp->s_timer);
34 second_devp->s_timer.function = &second_timer_handle;
35 second_devp->s_timer.expires = jiffies + HZ;
36 add_timer (&second_devp->s_timer);
37 atomic_set (&second_devp->counter, 0);
38 return 0;
39 }
40 int second_release (struct inode *inode, struct file *filp)
41 {
42 del_timer (&second_devp->s_timer);
43 return 0;
44 }
45 static ssize_t second_read (struct file *filp, char __user *buf,
46 size_t count, loff_t *ppos)
47 {
48 int counter;
49 counter = atomic_read (&second_devp->counter);
50 if (put_user (counter, (int *)buf))
51 return -EFAULT;
52 else
53 return sizeof (unsigned int);
54 }
55 static const struct file_operations second_fops = {
56 .owner = THIS_MODULE,
57 .open = second_open,
58 .release = second_release,
59 .read = second_read,
60 };
61 static void second_setup_cdev (struct second_dev *dev, int index)
62 {
63 int err, devno = MKDEV (second_major, index);
64 cdev_init (&dev->cdev, &second_fops);
65 dev->cdev.owner = THIS_MODULE;
66 err = cdev_add (&dev->cdev, devno, 1);
67 if (err)
68 printk (KERN_NOTICE "Error %d adding CDEV %d", err, index);
69 }
70 int second_init (void)
71 {
72 int ret;
73 dev_t devno = MKDEV (second_major, 0);
74 if (second_major)
75 ret = register_chrdev_region (devno, 1, "second");
76 else {
77 return alloc_chrdev_region (&devno, 0, 1, "second");
78 second_major = MAJOR (devno);
79 }
80 if (ret < 0)
81 return ret;
82 second_devp = kmalloc (sizeof (struct second_dev), GFP_KERNEL);
83 if (!second_devp) {
84 ret = -ENOMEM;
85 goto fail_malloc;
86 }
87 memset (second_devp, 0, sizeof (struct second_dev));
88 second_setup_cdev (second_devp, 0);
89 return 0;
90 fail_malloc:
91 unregister_chrdev_region (devno, 1);
92 return ret;
93 }
94 void second_exit (void)
95 {
96 cdev_del (&second_devp->cdev);
97 kfree (second_devp);
98 unregister_chrdev_region (MKDEV (second_major, 0), 1);
99 }
100 MODULE_AUTHOR ("yikoupeng");
101 MODULE_LICENSE ("Dual BSD/GPL");
102 module_param (second_major, int, S_IRUGO);
103 module_init (second_init);
104 module_exit (second_exit);

second_test.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h> int main (void)
{
int fd;
int counter = 0;
int old_counter = 0; fd = open ("/dev/second", O_RDONLY);
if (fd != -1) {
while (1) {
read (fd, &counter, sizeof (unsigned int));
if (counter != old_counter) {
printf ("seconds after open /dev/second: %d\n",
counter);
old_counter = counter;
}
}
} else {
printf ("Device open failure\n");
}
return 0;
}

4. 其他时间函数、宏介绍

1)节拍率

系统定时器的频率;通过静态预处理定义的——HZ;系统启动按照HZ值对硬件进行设置。体系结构不同,HZ值也不同;HZ可变的。

//内核时间频率

#define HZ 1000

提高节拍率中断产生更加频繁带来的好处:

  • 提高时间驱动事件的解析度;
  • 提高时间驱动事件的准确度;
  • 内核定时器以更高的频度和准确度;
  • 依赖顶上执行的系统调用poll()和select()能更高的精度运行;
  • 系统时间测量更精细;
  • 提高进程抢占的准确度;

提高节拍率带来的副作用:

  • 中断频率增高系统负担增加;
  • 中断处理程序占用处理器时间增多;
  • 频繁打断处理器高速缓存;

所以:节拍率HZ值需要在其中进行平衡。

2) jiffies

  1. 概念

    jiffies:全局变量,用来记录自系统启动以来产生的节拍总数。启动时内核将该变量初始化为0;

此后每次时钟中断处理程序增加该变量的值。

每一秒钟中断次数HZ,jiffies一秒内增加HZ。

系统运行时间 = jiffie/HZ.

jiffies用途:计算流逝时间和时间管理

  1. 头文件:
linux/jiffies.h
  1. 定义:
extern u64 jiffies_64;
extern unsigned long volatile jiffies; //位长更系统有关32/64

可以计算一下:

32位:497天后溢出

64位:天文数字

  1. 举例:0.5秒后超时
//0.5秒后超时
unsigned long timeout = jiffies + HZ/2; //注意jiffies值溢出回绕用宏time_before 而非 直timeout > jiffies
if(time_before(jiffies,timeout)){
//没有超时
}else{
//超时
}

3) 时间函数do_gettimeofday

  1. 简介:

    在Linux中可以使用函数do_gettimeofday()函数来得到精确时间。它的精度可以达到微妙,是与C标准库中gettimeofday()用法相同的函数。在Linux内核中获得时间的函数。

  2. 函数原型:

linux/time.h
void do_gettimeofday(struct timeval *tv)
  1. 说明:

    do_gettimeofday()会把目前的时间用tv 结构体返回,当地时区的信息则放到tz所指的结构中
  2. 结构体:

    timeval 结构体定义:
struct timeval {
  time_t tv_sec; /* seconds */
  suseconds_t tv_usec; /* microseconds */
};
struct  timeval   tv_begin,tv_end;
do_gettimeofday(&tv_begin,NULL);
…………
do_gettimeofday(&tv_end,NULL); printk(“tv_begin_sec:%d\n”,tv_begin.tv_sec);
printk(“tv_begin_usec:%d\n”,tv_begin.tv_usec);
printk(“tv_end_sec:%d\n”,tv_end.tv_sec);
printk(“tv_end_usec:%d\n”,tv_end.tv_usec);

【粉丝问答20】Linux内核定时器使用及其他时间操作的更多相关文章

  1. 芯灵思Sinlinx A64开发板Linux内核定时器编程

    开发平台 芯灵思Sinlinx A64 内存: 1GB 存储: 4GB 开发板详细参数 https://m.tb.cn/h.3wMaSKm 开发板交流群 641395230 Linux 内核定时器是内 ...

  2. 全志A33开发板Linux内核定时器编程

    开发平台 * 芯灵思SinlinxA33开发板 淘宝店铺: https://sinlinx.taobao.com/ 嵌入式linux 开发板交流 QQ:641395230 Linux 内核定时器是内核 ...

  3. 芯灵思SinlinxA33开发板Linux内核定时器编程

    开发平台 * 芯灵思SinlinxA33开发板 淘宝店铺: https://sinlinx.taobao.com/ 嵌入式linux 开发板交流 QQ:641395230 Linux 内核定时器是内核 ...

  4. 模仿linux内核定时器代码,用python语言实现定时器

    大学无聊的时候看过linux内核的定时器,如今已经想不起来了,也不知道当时有没有看懂,如今想要模仿linux内核的定时器.用python写一个定时器,已经想不起来它的设计原理了.找了一篇blog,li ...

  5. (笔记)Linux内核中内存相关的操作函数

    linux内核中内存相关的操作函数 1.kmalloc()/kfree() static __always_inline void *kmalloc(size_t size, gfp_t flags) ...

  6. Linux内核——定时器和时间管理

    定时器和时间管理 系统定时器是一种可编程硬件芯片.它能以固定频率产生中断.该中断就是所谓的定时器中断.它所相应的中断处理程序负责更新系统时间,还负责执行须要周期性执行的任务. 系统定时器和时钟中断处理 ...

  7. Linux内核 - 定时器

    #include <linux/timer.h> //头文件 struct timer_list mytimer; //定义变量 static void my_timer(unsigned ...

  8. Linux内核定时器struct timer_list

    1.前言 Linux内核中的定时器是一个很常用的功能,某些需要周期性处理的工作都需要用到定时器.在Linux内核中,使用定时器功能比较简单,需要提供定时器的超时时间和超时后需要执行的处理函数. 2.常 ...

  9. linux 内核定时器的实现

    为了使用它们, 尽管你不会需要知道内核定时器如何实现, 这个实现是有趣的, 并且值得 看一下它们的内部. 定时器的实现被设计来符合下列要求和假设: 定时器管理必须尽可能简化. 设计应当随着激活的定时器 ...

  10. linux 内核定时器

    无论何时你需要调度一个动作以后发生, 而不阻塞当前进程直到到时, 内核定时器是给你 的工具. 这些定时器用来调度一个函数在将来一个特定的时间执行, 基于时钟嘀哒, 并且 可用作各类任务; 例如, 当硬 ...

随机推荐

  1. Shell读取整行

    像C/C++,JAVA,Python等语言中,可以轻松地对文件进行按行读取. 那么,Shell中怎么实现对行读取呢? #!/bin/bash while read i do echo $i done ...

  2. 2019 香港区域赛 BDEG 题解

    B.Binary Tree 题意:给你一棵二叉树.有两个游戏者,回合制,他们每次可以删去这棵二叉树中的一棵满二叉树.求最后谁赢. 解法:每一棵满二叉树有奇数个节点,那么每次游戏者只能删去奇数个节点,所 ...

  3. OpenSSL静态库交叉编译

    一.编译前环境准备 使用的内核:4.15.0-118-generic(命令:uname -r可以查看) 交叉编译器:aarch64-linux-gnu-gcc openssl源码:openssl-1. ...

  4. 攻防世界——CRYPTO新手练习区解题总结<1>(1-4题)

    第一题base64: 下载附件,得到一个txt文件,打开 得到一串乱码,由题目可知,是base64,解码得到flag 第二题Caesar: 下载附件得到乱码 oknqdbqmoq{kag_tmhq_x ...

  5. 三屏异显案例分享,基于全国产RK3568J工业平台!

    在工业领域中,能否更灵活.更高效地在主屏幕进行主要任务,并在其他副屏幕上进行其他次要任务(例如查看参考资料.监控其他应用程序),一直都是许多工业领域客户面临的刚需,而"多屏异显"功 ...

  6. python3 模型日记

    说明 作为一种 python 框架模型的记录吧,用于个人总结,不定时更新. 正文 1. 主进程退出后,子进程也跟着退出 之前遇到过一种情况,用 flet 写了一个页面,然后又同时开了一个 tcp se ...

  7. Linux 提权-SUID/SGID_1

    本文通过 Google 翻译 SUID | SGID Part-1 – Linux Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释 ...

  8. DDP:微软提出动态detection head选择,适配计算资源有限场景 | CVPR 2022

    DPP能够对目标检测proposal进行非统一处理,根据proposal选择不同复杂度的算子,加速整体推理过程.从实验结果来看,效果非常不错 来源:晓飞的算法工程笔记 公众号 论文: Should A ...

  9. 使用FastReport报表动态更新人员签名图片

    在一些报表模块中,需要我们根据用户操作的名称,来动态根据人员姓名,更新报表的签名图片,也就是电子手写签名效果,本篇随笔介绍一下使用FastReport报表动态更新人员签名图片. 1.设计FastRep ...

  10. javascript深入参数传递

    我们都知道javascript的基础数据类型有: Undefined . Null . Boolean . Number . String . 如果从一个变量向另一个变量复制基本类型的值,会在变量对象 ...