36 linux内核里的HZ定时器与timer_list定时器

在linux内核里的设备驱动常常用到定时器定时器功能,而硬件上的定时器通常只有几个,如果每个设备驱动使用一个硬件定时器,那肯定是不够用,所以在linux内核里会把一个硬件定时器的功能扩展成多个软件定时器.
例如: 硬件定时器每秒钟触发检查一次,那么就可以设置如3秒,4秒,10秒的软件定时器.

内核里的HZ定时器就是基于一个硬件的定时器来实现的.
它与内核源码目录的”.config”里的”CONFIG_HZ”的值有关, 通常值为200, 服务器的值为1000.
在h3里它的值为”CONFIG_HZ=100”, 表示此HZ定时器每秒钟触发100次,每次间隔10ms.
HZ定时器每次触发都会把全局变量jiffies的值加一, 意味着我们可通过jiffies的值的变化可判断出经过多久时间.

在linux内核里,宏HZ就是CONFIG_HZ

#define HZ     CONFIG_HZ

// 以jiffies时间为单位的处理函数
#include <linux/jiffies.h>

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

time_after(a,b) //如果时间a是在时间b之后,则返回真.而且此函数已处理好jiffies溢出的问题
#define time_before(a,b)    time_after(b,a)

//如延时3秒
    unsigned long time = jiffies;
    while (!(time_after(jiffies, time+3*HZ)))
        ;


//如延时30ms
    unsigned long time = jiffies
    while (!(time_after(jiffies, time+HZ*30/1000)))
        ;  

//
内核里基于HZ定时器扩展的软件定时器timer_list.

struct timer_list {
    struct list_head entry;  //指向下一个定时器对象
    unsigned long expires;   //表示定时器的到期时间,也就是当jiffies的值与expires的值相等时,触发调用function函数.

    void (*function)(unsigned long); //定时器超时调用的函数
    unsigned long data; // function函数的参数
    ...
};

用法:
    1). 初始化timer_list对象的成员
        init_timer(&timer);
        timer.function = func;
        timer.data = data;

    2). 增加到内核里的定时器链表里.
        add_timer(&timer);

        或者用mod_timer(&timer, expires) //当定时器对象还没加入定时链表里会自动加入,并且可修改定时的到期时间.

    3). 定时器对象使用完后,需要从定时器链表里移除.
        del_timer(&timer);

/
测试代码:
test.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/jiffies.h>
#include <linux/timer.h>


struct timer_list mytimer;

void timer_func(unsigned long data)
{
    printk("timer time out ...%ld\n", data);

    //除了开始时3秒钟触发,后面每5秒钟触发

    mytimer.data += 1;
    mod_timer(&mytimer, jiffies+5*HZ);
}

static int __init test_init(void)
{
    init_timer(&mytimer);

    mytimer.function = timer_func;
    mytimer.data = 0;

    //开始时3秒钟后,定时
    mod_timer(&mytimer, jiffies + 3*HZ);
    return 0;
}

static void __exit test_exit(void)
{
    del_timer(&mytimer);
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

//
timer_list定时器可用于按键,红外接收头等设备的去抖动处理。
如红外遥控器在按一下一个按键时,在红外接收头这端有可能接收到两个同样的键数据帧,这种场合就可用timer_list定时器来处理.
处理方式:当红外接收头接收到一个键的数据帧后,暂时关闭数据引脚的中断功能,直到两秒钟后再重新打开中断功能。在关闭中断的两秒钟内,不处理接收到键数据帧.

测试代码:
test.c:


#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <mach/gpio.h>
#include <linux/gpio.h>
#include <linux/ktime.h>
#include <linux/timer.h>

// 红外接收头的数据脚接的是PL11
#define IR_IO  GPIOL(11)

int flag = 0; //表示数据帧的开始
int num = 0; //表示数据帧里的第几位数据
static long long prev = 0; //记录上次的时间
unsigned int times[40]; //记录每位数据的时间

struct timer_list mytimer;

void timer_func(unsigned long data)
{
    enable_irq(gpio_to_irq(IR_IO)); //恢复中断
}

irqreturn_t irq_func(int irqno, void *arg)
{
    long long now = ktime_to_us(ktime_get());
    unsigned int offset;
    int i, j, tmp;

    if (!flag) //数据开始
    {
        flag = 1;
        prev = now;
        num = 0;
        return IRQ_HANDLED;
    }

    offset = now - prev;
    prev = now;
    if ((offset > 13000) && (offset < 14000)) //判断是否收到引导码,引导码13.5ms
    {
        num = 0;
        return IRQ_HANDLED;
    }   


    //不是引导码时间,数据位时间
    if (num < 32)
        times[num++] = offset;

    if (num >= 32)
    {
        for (i = 0; i < 4; i++) //共4个字节
        {
            tmp = 0;
            for (j = 0; j < 8; j++) //每字节8位
            {
                if (times[i*8+j] > 2000) //如果数据位的信号周期大于2ms, 则是二进制数据1
                    tmp |= 1<<j;
            }
            printk("%02x ", tmp);
        }
        printk("\n");
        flag = 0; //重新开始帧

        disable_irq_nosync(gpio_to_irq(IR_IO)); //关闭中断
        mod_timer(&mytimer, jiffies+2*HZ); //定时器两秒钟后超时,也就是两秒钟后才重新打开中断功能.
    }
    return IRQ_HANDLED;
}

static int __init test_init(void)
{
    int ret;

    ret = request_irq(gpio_to_irq(IR_IO), irq_func, IRQF_TRIGGER_FALLING, "myir", NULL);
    if (ret < 0)
        goto err0;

    init_timer(&mytimer);
    mytimer.function = timer_func;

    return 0;
err0:
    return ret;

}


static void __exit test_exit(void)
{
    free_irq(gpio_to_irq(IR_IO), NULL);
    del_timer(&mytimer);
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值