Linux设备驱动开发基础--内核定时器

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

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

(1) 没有 current 指针、不允许访问用户空间。因为没有进程上下文,相关代码和被中断的进程没有任何联系。

(2) 不能执行休眠(或可能引起休眠的函数)和调度。

(3) 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。 

3. Linux内核使用struct timer_list来描述一个定时器

struct timer_list {
    struct list_head entry;
    unsigned long expires;

    void (*function)(unsigned long);
    unsigned long data;

    struct tvec_base *base;
};

(1)expires:表示期望定时器执行的 jiffies 值,到达该 jiffies 值时,将调用 function 函数,并传递 data 作为参数

(2)entry:连接该定时器到一个内核链表中

(3)base:内核内部使用

4. 定时器使用流程

(1)定义和初始化定时器

  ① 方法一:

DEFINE_TIMER(timer_name, function_name, expires_value, data);

  DEFINE_TIMER为宏定义,源码如下

#define DEFINE_TIMER(_name, _function, _expires, _data)        
    struct timer_list _name =                
        TIMER_INITIALIZER(_function, _expires, _data)

  ② 方法二

struct timer_list my_timer;
init_timer(&my_timer);
my_timer.expires = jiffies + delay; 
my_timer.data = 0;     
my_timer.function = my_function; 


  注:以上宏和函数定义于include/linux/timer.h。

(3)注册定时器:add_timer

add_timer(&my_timer);

(4)启动定时器:mod_timer

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

  注:该函数负责修改内核定时器timer的超时字段expires。该函数可以修改激活和没有激活的内核定时器的超时时间,并把它们都设置为激活状态;返回值为0表示修改的内核定时器在修改之前处于未激活状态,返回值为1表示修改的内核定时器在修改之前处于已激活状态。

(5)删除定时器:一但定时到期,内核会自动删除定时器节点。但有时需要显示的调用函数del_timer()来删除定时器节点

int del_timer(struct timer_list *timer)

5. Linux内核定时器组织架构

(1)与softirq、工作队列两种中断下半部的处理方法类似,每一个内核定时器节点与系统中的处理器通过一个每处理器变量联系起来。内核在文件 kernel/timer.c中使用下面的语句分配了一个名称为tvec_bases、类型为tvec_base_t的每处理器变量。

static DEFINE_PER_CPU(struct tvec_base *, tvec_bases) = &boot_tvec_bases;

(2)数据结结struct tevc_base用来记录系统中每一个处理器上待处理内核定时器节点的相关信息

struct tvec_base {
    spinlock_t lock;
    struct timer_list *running_timer;
    unsigned long timer_jiffies;
    struct tvec_root tv1;
    struct tvec tv2;
    struct tvec tv3;
    struct tvec tv4;
    struct tvec tv5;
} ____cacheline_aligned;

①lock:用于保护每处理器变量tvec_bases的本地拷贝

running_timer:记录正在本地处理器上进行超时处理的内核定时器

③timer_jiffies:记录该数据结构中所包含的定时器中最早超时时间,根据该变量可以计算出超时定时器节点所在链表的表头。

④tv1,tv2,tv3,tv4,tv5:

#define TVN_BITS (CONFIG_BASE_SMALL ? 4 : 6)
#define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)

struct tvec {
    struct list_head vec[TVN_SIZE];
};

struct tvec_root {
    struct list_head vec[TVR_SIZE];
};

(3)内核定时器架构示意图:

5. 简单示例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/atomic.h>

MODULE_LICENSE("GPL");

static struct timer_list timer;

static void do_work(struct work_struct* work);
static DECLARE_WORK(test_work, do_work);
static DECLARE_WORK(test_work1, do_work);
static struct workqueue_struct *test_workqueue;

// 定时器超时函数
void test_timer_fn(unsigned long arg)
{
    if (queue_work(test_workqueue, &test_work)== 0) {
        printk("Timer (0) add work queue failed
");
    }
}

// work func
static void do_work(struct work_struct* work)
{    
    printk("====do_work start====
");
    printk("====do_work end====
");
}

// init
int wq_init(void)
{
    // work queue        
    test_workqueue = create_singlethread_workqueue("test-wq");

    // timer1
    init_timer(&timer);
    timer.function= test_timer_fn;
    add_timer(&timer);
    
    //设置超时时间,启动定时器
    mod_timer(&timer, jiffies + HZ / 100); // 设置超时时间 1100s 
    
    return 0;
}

void wq_exit(void)
{
    del_timer(&timer);
    
    destroy_workqueue(test_workqueue);
    
    printk("wq exit success
");
}

module_init(wq_init);
module_exit(wq_exit);
原文地址:https://www.cnblogs.com/wulei0630/p/9510759.html