linux内核定时器

来源:互联网 发布:为什么淘宝评论删不掉 编辑:程序博客网 时间:2024/05/10 23:28

Linux内核定时器,一个硬件逻辑单元,系统的定时器硬件,定时一一定周期频率输出方波,给CPU产生定时器中断。以某种频率自行触发时钟中断,其频率可通过变成预定。

在内核中,有一个对应的时钟中断的处理程序,这个中断处理程序一般会做下面的事情:
1. 更新系统的运行时间(jiffies);
2. 更新实际时间(年月日时分秒);
3. 检查进程的时间片,如果时间片用尽了,将重新调度;
4. 检查有没有超时的定时器,更新资源消耗和CPU时间的统计值。

我们系统的时间没有百分之百精准的,因为我们的进程是放在一个链表里面等待执行的,因此系统要遍历链表,而这点时间就产生了误差,我们只能尽量减少这种误差。还记得我们的开发板的时钟每次掉电后时间就不对了,这是因为掉电以后有一个纽扣电池给RTC(硬件)供电,但是我们的纽扣电池显然已经没电了。。。RTC是用来维护时间的硬件

下面是几个重要的概念:
1. 节拍率HZ,内核的一个常数,系统的定时器频率,与体系结构相关,系统启动根据HZ设置定时器硬件,描述一秒钟硬件定时器发生了多少次中断,这个值最终会写入硬件定时器中,HZ=100,表示1s(ARM平台)产生100次时钟中断,在32位系统中为1000次。
2. 节拍tick:HZ的倒数,每发生一次硬件定时器中断的时间间隔,1tick = 1/HZ = 10ms(HZ=100)。
3. jiffies:内核的全局变量(32位,unsigned long),被用来记录开机以来,发生了多少次时钟中断,每发生一次时钟中断(tick数),jiffies加1,一般使用它来描述当前时间。如:unsigned long timeout = jiffies + 3 * HZ,timeout表示3s以后的时间。jiffies的定义为:extern unsigned long volatile jiffies; 其中volatile关键字是为了防止系统对此数据进行优化,即每次访问jiffies都从内存去访问,而不是访问寄存器。顺便提一下,访问的速度由高到底为:寄存器 > cache > 内存 > 外存

jiffies回绕问题

我们知道,jiffies是内核定义的全局变量,它的值会一直自增,它在内核中定义为无符号长整型,因此在32位系统中最大值为32^2-1,在64位系统中为64^2-1,所以,当jiffies增加到最大值时,又会回到0,这时候和系统运行的时间当然就不对了,这就是回绕问题,

这里有个例子说明一下:

unsigned long timeout = jiffies + HZ / 2;/*do something*/..................../*check timeout*/if(timeout > jiffies){  /*not timeout*/}else{  /*it is timeout!*/}

这个程序用timeout来检测程序的执行是否超时,timeout设为jiffies加上0.5秒,如果jiffies回环了,那么程序就会出错。

因此,linux定义了下面四个比较函数

#define time_after(jiffies, timeout)  ((long)(known) - (long)(unknown) < 0)#define time_before(jiffies, timeout)  ((long)(known) - (long)(unknown) < 0)#define time_after_eq(jiffies, timeout)  ((long)(known) - (long)(unknown) >= 0)#define time_before_eq(jiffies, timeout)  ((long)(known) - (long)(unknown) >= 0)

可以看出这里是将无符号的类型换成了有符号的类型,因此这样就可以降到负数,那么负数从补码变为原码,这时再比较原码就能得出正确结果,下面举两个例子:

  1. 无符号数据jiffies为250,补码为11111010,timeout为252,补码为11111100,过一会儿jiffies变为1,即00000001,这时候明显两次的jiffies数据都比timeout小,结果与实际不符合

  2. 有符号数据jiffies为250,补码转为原码(符号位不变,其余取反,最后加1,要进位):10000110,十进制为-6;有符号数据timeout为252,补码转为原码:10000100,十进制为-4,过一会儿jiffies变为1,补码转为原码:00000001,即为1,此时timeout的值刚好夹在中间,那么就巧妙地解决了回绕问题。

内核定时器能指定某个函数(定时器函数)在特定的未来某个时刻执行,且内核定时器注册的处理函数只执行一次,不是循环的。

内核定时器定义:

<linux/timer.h>struct timer_list{  struct list_head entry;  //链表头,内核维护  unsigned long expires;   //超时时候jiffies的值,jiffies + 5*HZ  void(*function)(unsigned long); //超时处理函数  unsigned long data; //内核调用超时处理函数时传递给他的参数,一般为指针  struct tvec_base *base;  //内核维护}

分配一个定时器:

struct timer_list timer;

初始化定时器:

init_timer(&timer);  //内核只初始化自己关心的字段time.expires = jiffies + 5 * HZ; //设置超时时间为5s以后timer.function = mytimer_function;  //设置超时处理函数timer.data = (unsigned long)&mydata;  //给函数传递的参数

启动定时器:

add_time(&timer);  //一旦定时器到期,内核会自动删除定时器,处理函数只能被执行一次

删除定时器:

del_timer(&timer);

修改定时器:

mod_timer(&timer, 新的超时时候的jiffies值);mod_timer(&timer, jiffies + 10 * HZ);mod_timer = del_timer + expires = 新值 + add_timer;

如果想重复循环执行定时器的处理函数,只需在超时处理函数中重新启动定时器即可。

下面是一些关于定时器的代码例程:

定时器控制led闪烁间隔:

/********************************************************************************* *      Copyright:  (C) 2017 tangyanjun<519656780@qq.com> *                  All rights reserved. * *       Filename:  timer.c *    Description:  This file * *        Version:  1.0.0(08/21/2017) *         Author:  tangyanjun <519656780@qq.com> *      ChangeLog:  1, Release initial version on "08/21/2017 08:38:55 PM" * ********************************************************************************/#include <linux/init.h>#include <linux/module.h>#include <linux/timer.h>#include <asm/gpio.h>#include <plat/gpio-cfg.h>static struct timer_list mytimer;static int mydata = 0x5555;static void mytimer_function(unsigned long data){    static int i = 0;    if(i == 100)    {        i = 0;    }    else if((i % 2) == 0)    {        gpio_direction_output(S3C2410_GPB(5), 1);    }    else    {        gpio_direction_output(S3C2410_GPB(5), 0);    }    i++;    //重复循环执行超时处理函数    mod_timer(&mytimer, jiffies + 2 * HZ);    //此时定时器的超时时间为0    //add_timer(&mytimer);    //mytimer.expires = jiffies + 2 * HZ;    //add_timer(&mytimer); //因为此执行有可能被别的任务所打断,比如中断,如果在中断处理函数中对mytimer进行操作,最后引起出错,这个执行路劲不是原子的}static int mytimer_init(void){    //初始化定时器    init_timer(&mytimer);    //指定定时器的超时时间    mytimer.expires = jiffies + 2 * HZ;    //指定定时器的超时处理函数    mytimer.function = mytimer_function;    //给超时处理函数传递的参数    mytimer.data = (unsigned long)&mydata;    gpio_request(S3C2410_GPB(5), "LED1");    gpio_direction_output(S3C2410_GPB(5), 0); //   gpio_set_value(S3C2410_GPB(5), 0);    //启动定时器    add_timer(&mytimer);    printk("Start Timer!\n");    printk("Led on\n");    return 0;}static void mytimer_exit(void){    gpio_set_value(S3C2410_GPB(5), 1);    gpio_free(S3C2410_GPB(5));    //删除定时器    del_timer(&mytimer);}module_init(mytimer_init);module_exit(mytimer_exit);MODULE_LICENSE("GPL");
原创粉丝点击