zoukankan      html  css  js  c++  java
  • 在Linux驱动中使用timer定时器

    在Linux驱动中使用timer定时器

    原文(有删改): https://www.cnblogs.com/chen-farsight/p/6226562.html

    介绍

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

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

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

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

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

    原型定义

    struct timer_list {
        /*
         * All fields that change during normal runtime grouped to the
         * same cacheline
         */
        struct list_head entry;
        unsigned long expires;
        struct tvec_base *base;
    
        void (*function)(unsigned long);
        unsigned long data;
    
        int slack;
    
    #ifdef CONFIG_TIMER_STATS
        int start_pid;
        void *start_site;
        char start_comm[16];
    #endif
    #ifdef CONFIG_LOCKDEP
        struct lockdep_map lockdep_map;
    #endif
    };
    

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

    当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。base 字段是内核内部实现所用的。

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

    初始化

    在使用 struct timer_list 之前,需要初始化该数据结构,确保所有的字段都被正确地设置。初始化有两种方法。

    方法一:基于宏定义DEFINE_

    DEFINE_TIMER(timer_name, function_name, expires_value, data);
    

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

    方法二:调用接口

    void init_timer(struct timer_list *timer); // 再加上赋值
    void setup_timer(struct timer_list *timer, (*function)(unsigned long), unsigned long data);
    

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

    // timer function
    void timer_function(unsigned long arg)
    {
        struct gpio_led_data *led_dat = (struct gpio_led_data *)arg;
        INIT_WORK(&led_dat->delay_work, turn_off_led_work);
        schedule_work(&led_dat->delay_work);
        printk(KERN_ERR "turn_off_led!
    ");
    }
    
    void mytimer_init(void)
    {
        // 声明
        struct timer_list mytimer;
        int dat = 1;
    
        // 初始化、赋值
    #if 0 // 下面两种等价
        init_timer(&(mytimer));
        mytimer.function = timer_function;
        mytimer.data     = (unsigned long)dat;
    #else
        // setup_timer 方法也可以用于初始化定时器并赋值其成员
        setup_timer(&mytimer, timer_function, (unsigned long)dat); 
    #endif
        mytimer.expires  = jiffies +  (秒数)*HZ;
    }
    

    注意,无论用哪种方法初始化,其本质都只是给字段赋值,所以只要在运行 add_timer() 之前,expires, function 和 data 字段都可以直接再修改。

    注册/开启计时器

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

    注意,每次add_timer只会执行function一次。如果需要执行的话,还需要重新调用add_timer

    重新注册(修改)

    要修改一个定时器的调度时间,可以通过调用 mod_timer(struct timer_list *timer, unsigned long expires)mod_timer() 会重新注册定时器到内核,而不管定时器函数是否被运行过。

    注销

    注销一个定时器,可以通过 del_timer(struct timer_list *timer)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);
    

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

    例子

    实现每隔一秒向内核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
    ", __func__, jiffies, strp);
    
        do_gettimeofday(&tv);
        printk("%s: %ld, %ld
    ", __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.
    ", __FILE__, __func__, __LINE__);
    
        init_timer(&tm);    //初始化内核定时器
    
        do_gettimeofday(&oldtv);        //获取当前时间
        tm.function= callback;            //指定定时时间到后的回调函数
        tm.data    = (unsigned long)"hello world";        //回调函数的参数
        tm.expires = jiffies+1*HZ;        //定时时间, 1*HZ ,这里的 1 代表秒数
        add_timer(&tm);        //注册定时器
    
        return 0;
    }
    
    static void __exit demo_exit(void)
    {
        printk(KERN_INFO "%s : %s : %d - ok.
    ", __FILE__, __func__, __LINE__);
        del_timer(&tm);        //注销定时器
    }
    
    module_init(demo_init);
    module_exit(demo_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Farsight");
    MODULE_DESCRIPTION("Demo for kernel module");
    

    附录A:和时间相关的函数与变量

    ----------------------------------------------
    linux/jiffies.h
    jiffies 计数值:可用 函数 `u64 get_jiffies_64(void)` 获取
    ----------------------------------------------
    asm/param.h
    
    HZ : 每秒触发中断的次数
    ----------------------------------------------
    时间值
    秒数=(jiffies(new) - jiffies(old))/HZ
    jiffies(new) = jiffies(old) + 秒*HZ
    
    ----------------------------------------------
    linux/delay.h
    延时函数
    void ssleep(unsigned int seconds);
    void msleep(unsigned int msecs);
    ----------------------------------------------
    时间函数
    linux/time.h
    void do_gettimeofday(struct timeval *tv)
    

    附录B:本文提到的定义原型

    关于上面这些宏和函数的定义,具体参见 include/linux/timer.h。

    #define init_timer(timer)                       
        __init_timer((timer), 0)
    
    #define __init_timer(_timer, _flags)                    
        do {                                
            static struct lock_class_key __key;         
            init_timer_key((_timer), (_flags), #_timer, &__key);    
        } while (0)
    
    #define __setup_timer(_timer, _fn, _data, _flags)           
        do {                                
            __init_timer((_timer), (_flags));           
            (_timer)->function = (_fn);             
            (_timer)->data = (_data);               
        } while (0)
    
    #define __setup_timer_on_stack(_timer, _fn, _data, _flags)      
        do {                                
            __init_timer_on_stack((_timer), (_flags));      
            (_timer)->function = (_fn);             
            (_timer)->data = (_data);               
        } while (0)
    
    #define setup_timer(timer, fn, data)                    
        __setup_timer((timer), (fn), (data), 0)
    
    /**
     * init_timer_key - initialize a timer
     * @timer: the timer to be initialized
     * @flags: timer flags
     * @name: name of the timer
     * @key: lockdep class key of the fake lock used for tracking timer
     *       sync lock dependencies
     *
     * init_timer_key() must be done to a timer prior calling *any* of the
     * other timer functions.
     */
    void init_timer_key(struct timer_list *timer, unsigned int flags,
                const char *name, struct lock_class_key *key)
    {
        debug_init(timer);
        do_init_timer(timer, flags, name, key);
    }
    
    static void do_init_timer(struct timer_list *timer, unsigned int flags,
                  const char *name, struct lock_class_key *key)
    {
        struct tvec_base *base;
    
    #ifdef CONFIG_SMP
        if (flags & TIMER_DEFERRABLE)
            base = tvec_base_deferral;
        else
    #endif
        base = __raw_get_cpu_var(tvec_bases);
    
        timer->entry.next = NULL;
        timer->base = (void *)((unsigned long)base | flags);
        timer->slack = -1;
    #ifdef CONFIG_TIMER_STATS
        timer->start_site = NULL;
        timer->start_pid = -1;
        memset(timer->start_comm, 0, TASK_COMM_LEN);
    #endif
        lockdep_init_map(&timer->lockdep_map, name, key, 0);
    }
    
    
    #define __TIMER_INITIALIZER(_function, _expires, _data, _flags) { 
            .entry = { .prev = TIMER_ENTRY_STATIC },    
            .function = (_function),            
            .expires = (_expires),              
            .data = (_data),                
            .base = (void *)((unsigned long)&boot_tvec_bases + (_flags)), 
            .slack = -1,                    
            __TIMER_LOCKDEP_MAP_INITIALIZER(        
                __FILE__ ":" __stringify(__LINE__)) 
        }
    
    #define TIMER_INITIALIZER(_function, _expires, _data)       
        __TIMER_INITIALIZER((_function), (_expires), (_data), 0)
    
    #define TIMER_DEFERRED_INITIALIZER(_function, _expires, _data)  
        __TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_DEFERRABLE)
    
    #define DEFINE_TIMER(_name, _function, _expires, _data)     
        struct timer_list _name =               
            TIMER_INITIALIZER(_function, _expires, _data)
    
    如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
    若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
    博客地址:https://www.cnblogs.com/schips/
  • 相关阅读:
    前端 JS,localStorage/sessionStorage、cookie 及 url 等实现前台数据共享、传输
    webpack 利用Code Splitting 分批打包、按需下载
    React项目之BrowserRouter路由方式之-------生产环境404问题
    React生产环境打包&&后台环境运行(有跨域+无跨域)
    React前台改用HashRouter并解决两个问题
    React路由基础
    React前台404组件页面+路由控制重定向
    react调用方法
    JavaScript 数组遍历方法的对比
    数据可视化相关库说明
  • 原文地址:https://www.cnblogs.com/schips/p/using_kernel_timer_in_linux_driver.html
Copyright © 2011-2022 走看看