linux内核延时机制 (linux内核定时器唤醒线程实例)

内核定时器

Linux内核定时器是timer_list,下面我们详细介绍定时器的使用。

1. 简介

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

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

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

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

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

2. 数据结构

(1) 内核定时器的数据结构

structtimer_list{
structlist_headentry,/*定时器列表*/
unsignedlongexpires,/*定时器到期时间*/
void(*function)(unsignedlong),/*定时器处理函数*/
unsignedlongdata,/*作为参数被传入定时器处理函数*/
structtimer_base_s*base,
...
};

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

jiffies 当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。

base 字段是内核内部实现所用的。

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

(2) 初始化定时器

方法一:

DEFINE_TIMER(timer_name,function_name,expires_value,data);

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

方法二:

structtimer_listmytimer;
voidinit_timer(structtimer_list*timer);

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

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

voidadd_timer(structtimer_list*timer);

(4) 删除定时器 注销一个定时器,可以通过 del_timer(struct timer_list *timer) 或 del_timer_sync(struct timer_list *timer) 。

intdel_timer(structtimer_list*timer);
intdel_timer_sync(structtimer_list*timer)

其中 del_timer_sync 是用在 SMP 系统上的(在非SMP系统上,它等于del_timer),当要被注销的定时器函数正在另一个 cpu 上运行时,del_timer_sync() 会等待其运行完,所以这个函数会休眠。

另外还应避免它和被调度的函数中用同一个锁。对于一个已经被运行过且没有重新注册自己的定时器而言,注销函数其实也没什么事可做。

inttimer_pending(conststructtimer_list*timer);

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

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

intmod_timer(structtimer_list*timer,unsignedlongexpires);

(6) 对于周期性的任务,linux内核还提供了一种delayed_work机制来完成,本质上用工作队列和定时器实现。

3. 举例

例1:实现每隔一秒向内核log中打印一条信息

/*实现每隔一秒向内核log中打印一条信息*/
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/time.h>
#include<linux/timer.h>

staticstructtimer_listtm;
structtimevaloldtv;

voidcallback(unsignedlongarg)
{
structtimevaltv;
char*strp=(char*)arg;

printk("%s:%lu,%s\n",__func__,jiffies,strp);

do_gettimeofday(&tv);
printk("%s:%ld,%ld\n",__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);//重新开始计时
}

staticint__initdemo_init(void)
{
printk(KERN_INFO"%s:%s:%d-ok.\n",__FILE__,__func__,__LINE__);

init_timer(&tm);//初始化内核定时器

do_gettimeofday(&oldtv);//获取当前时间
tm.function=callback;//指定定时时间到后的回调函数
tm.data=(unsignedlong)"helloworld";//回调函数的参数
tm.expires=jiffies+1*HZ;//定时时间
add_timer(&tm);//注册定时器

return0;
}

staticvoid__exitdemo_exit(void)
{
printk(KERN_INFO"%s:%s:%d-ok.\n",__FILE__,__func__,__LINE__);
del_timer(&tm);//注销定时器
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("yikoupeng");
MODULE_DESCRIPTION("timerlist");

例2:秒字符设备

second_drv.c

1#include<linux/module.h>
2#include<linux/types.h>
3#include<linux/fs.h>
4#include<linux/errno.h>
5#include<linux/mm.h>
6#include<linux/sched.h>
7#include<linux/init.h>
8#include<linux/cdev.h>
9#include<asm/io.h>
10#include<asm/system.h>
11#include<asm/uaccess.h>
12#include<linux/slab.h>
13
14#defineSECOND_MAJOR248
15
16staticintsecond_major=SECOND_MAJOR;
17
18structsecond_dev{
19structcdevcdev;
20atomic_tcounter;
21structtimer_lists_timer;
22};
23
24structsecond_dev*second_devp;
25staticvoidsecond_timer_handle(unsignedlongarg)
26{
27mod_timer(&second_devp->s_timer,jiffies+HZ);
28atomic_inc(&second_devp->counter);
29printk(KERN_NOTICE"currentjiffiesis%ld\n",jiffies);
30}
31intsecond_open(structinode*inode,structfile*filp)
32{
33init_timer(&second_devp->s_timer);
34second_devp->s_timer.function=&second_timer_handle;
35second_devp->s_timer.expires=jiffies+HZ;
36add_timer(&second_devp->s_timer);
37atomic_set(&second_devp->counter,0);
38return0;
39}
40intsecond_release(structinode*inode,structfile*filp)
41{
42del_timer(&second_devp->s_timer);
43return0;
44}
45staticssize_tsecond_read(structfile*filp,char__user*buf,
46size_tcount,loff_t*ppos)
47{
48intcounter;
49counter=atomic_read(&second_devp->counter);
50if(put_user(counter,(int*)buf))
51return-EFAULT;
52else
53returnsizeof(unsignedint);
54}
55staticconststructfile_operationssecond_fops={
56.owner=THIS_MODULE,
57.open=second_open,
58.release=second_release,
59.read=second_read,
60};
61staticvoidsecond_setup_cdev(structsecond_dev*dev,intindex)
62{
63interr,devno=MKDEV(second_major,index);
64cdev_init(&dev->cdev,&second_fops);
65dev->cdev.owner=THIS_MODULE;
66err=cdev_add(&dev->cdev,devno,1);
67if(err)
68printk(KERN_NOTICE"Error%daddingCDEV%d",err,index);
69}
70intsecond_init(void)
71{
72intret;
73dev_tdevno=MKDEV(second_major,0);
74if(second_major)
75ret=register_chrdev_region(devno,1,"second");
76else{
77returnalloc_chrdev_region(&devno,0,1,"second");
78second_major=MAJOR(devno);
79}
80if(ret<0)
81returnret;
82second_devp=kmalloc(sizeof(structsecond_dev),GFP_KERNEL);
83if(!second_devp){
84ret=-ENOMEM;
85gotofail_malloc;
86}
87memset(second_devp,0,sizeof(structsecond_dev));
88second_setup_cdev(second_devp,0);
89return0;
90fail_malloc:
91unregister_chrdev_region(devno,1);
92returnret;
93}
94voidsecond_exit(void)
95{
96cdev_del(&second_devp->cdev);
97kfree(second_devp);
98unregister_chrdev_region(MKDEV(second_major,0),1);
99}
100MODULE_AUTHOR("yikoupeng");
101MODULE_LICENSE("DualBSD/GPL");
102module_param(second_major,int,S_IRUGO);
103module_init(second_init);
104module_exit(second_exit);

second_test.c

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>

intmain(void)
{
intfd;
intcounter=0;
intold_counter=0;

fd=open("/dev/second",O_RDONLY);
if(fd!=-1){
while(1){
read(fd,&counter,sizeof(unsignedint));
if(counter!=old_counter){
printf("secondsafteropen/dev/second:%d\n",
counter);
old_counter=counter;
}
}
}else{
printf("Deviceopenfailure\n");
}
return0;
}

4. 其他时间函数、宏介绍

1)节拍率

系统定时器的频率;通过静态预处理定义的——HZ;系统启动按照HZ值对硬件进行设置。体系结构不同,HZ值也不同;HZ可变的。

//内核时间频率

#defineHZ1000

提高节拍率中断产生更加频繁带来的好处:

  • 提高时间驱动事件的解析度;
  • 提高时间驱动事件的准确度;
  • 内核定时器以更高的频度和准确度;
  • 依赖顶上执行的系统调用poll()和select()能更高的精度运行;
  • 系统时间测量更精细;
  • 提高进程抢占的准确度;

提高节拍率带来的副作用:

  • 中断频率增高系统负担增加;
  • 中断处理程序占用处理器时间增多;
  • 频繁打断处理器高速缓存;

所以:节拍率HZ值需要在其中进行平衡。

2) jiffies

  1. 概念 jiffies:全局变量,用来记录自系统启动以来产生的节拍总数。启动时内核将该变量初始化为0;

此后每次时钟中断处理程序增加该变量的值。

每一秒钟中断次数HZ,jiffies一秒内增加HZ。

系统运行时间 = jiffie/HZ.

jiffies用途:计算流逝时间和时间管理

  1. 头文件:
linux/jiffies.h

  1. 定义:
externu64jiffies_64;
externunsignedlongvolatilejiffies;//位长更系统有关32/64

可以计算一下:32位:497天后溢出

64位:天文数字

  1. 举例 :0.5秒后超时
//0.5秒后超时
unsignedlongtimeout=jiffies+HZ/2;

//注意jiffies值溢出回绕用宏time_before 而非直timeout > jiffies
if(time_before(jiffies,timeout)){
//没有超时
}else{
//超时
}

3) 时间函数do_gettimeofday

  1. 简介:在Linux中可以使用函数do_gettimeofday()函数来得到精确时间。它的精度可以达到微妙,是与C标准库中gettimeofday()用法相同的函数。在Linux内核中获得时间的函数。
  2. 函数原型:
linux/time.h
voiddo_gettimeofday(structtimeval*tv)

  1. 说明:do_gettimeofday()会把目前的时间用tv 结构体返回,当地时区的信息则放到tz所指的结构中
  2. 结构体:timeval 结构体定义:
structtimeval{
time_ttv_sec;/*seconds*/
suseconds_ttv_usec;/*microseconds*/
};

structtimevaltv_begin,tv_end;
do_gettimeofday(&tv_begin,NULL);
…………
do_gettimeofday(&tv_end,NULL);

printk(“tv_begin_sec:%d\n”,tv_begin.tv_sec);
printk(“tv_begin_usec:%d\n”,tv_begin.tv_usec);
printk(“tv_end_sec:%d\n”,tv_end.tv_sec);
printk(“tv_end_usec:%d\n”,tv_end.tv_usec);