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");
Linux 定时器 `timer_list` 是内核中用于管理定时器的数据结构之一。它是一个双向链表,包含了所有当前运行的定时器。 在 Linux 内核中,定时器是通过 `struct timer_list` 结构体来表示的。它包含了定时器的各种属性,如定时器的超时时间、回调函数、私有数据等。 `timer_list` 数据结构被定义在 `<linux/timer.h>` 头文件中,其定义如下: ```c struct timer_list { struct list_head entry; unsigned long expires; void (*function)(unsigned long); unsigned long data; #ifdef CONFIG_TIMER_STATS void *start_site; char start_comm[16]; int start_pid; #endif }; ``` 其中,`entry` 是双向链表的节点,用于将定时器添加到 `timer_list` 中。`expires` 字段记录了定时器的超时时间,以 jiffies 单位表示。`function` 是定时器超时时要执行的回调函数,`data` 则是传递给回调函数的参数。 要使用 `timer_list`,需要先声明一个 `struct timer_list` 类型的变量,并进行初始化。然后可以使用 `init_timer()` 函数来初始化定时器,并使用 `add_timer()` 函数将定时器添加到 `timer_list` 中。当定时器超时时,内核会自动调用指定的回调函数。 需要注意的是,在使用定时器后,应在不需要时及时删除或停止它们,以避免资源浪费或不正确的行为。 这就是关于 Linux 定时器 `timer_list` 的简要介绍,希望能对你有所帮助!如果还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值