Linux内核定时器使用及其他时间操作

转载:Linux内核定时器使用及其他时间操作 - 知乎 (zhihu.com)

问题描述

如何使用内核定时器?

内核定时器

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);
posted @ 2023-08-14 14:07  burlingame  阅读(390)  评论(0编辑  收藏  举报