linux定时器.docx
《linux定时器.docx》由会员分享,可在线阅读,更多相关《linux定时器.docx(12页珍藏版)》请在冰豆网上搜索。
![linux定时器.docx](https://file1.bdocx.com/fileroot1/2022-10/27/59ab3261-2e3c-44de-81a7-391655fd0aaa/59ab3261-2e3c-44de-81a7-391655fd0aaa1.gif)
linux定时器
目录
一、设计题目与要求1
二、实验思想1
2.1Linux内核对定时器的描述1
2.2Linux内核定时器3
2.3Linux信号signal处理机制6
三、内核定时器机制的实现6
3.1动态定时器机制的初始化6
3.2将定时器插入到链表中7
四、数据结构与模块说明9
4.1定时器使用9
4.2程序流程图10
4.3源程序10
4.4运行结果与运行情况11
五、实验心得13
六、参考文献14
Linux定时器实验
一、设计题目与要求
1.1设计题目:
Linux定时器
1.2设计要求:
通过研究Linux内核的时间函数,学习内核源代码;然后应用这些知识并且使用“信号”机制建立一种定时器实验,每隔多长时间打印出当前系统的时间,并且计算出每次调度的时间。
二、实验思想
2.1Linux内核对定时器的描述
Linux在include/linux/timer.h头文件中定义了数据结构timer_list来描述一个内核定时器:
structtimer_list{
structlist_headlist;
unsignedlongexpires;
unsignedlongdata;
void(*function)(unsignedlong);
};
各数据成员的含义如下:
(1)双向链表元素list:
用来将多个定时器连接成一条双向循环队列。
(2)expires:
指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计数(也即时钟节拍数)。
当一个定时器的expires值小于或等于jiffies变量时,我们就说这个定时器已经超时或到期了。
在初始化一个定时器后,通常把它的expires域设置成当前expires变量的当前值加上某个时间间隔值(以时钟滴答次数计)。
(3)函数指针function:
指向一个可执行函数。
当定时器到期时,内核就执行function所指定的函数。
而data域则被内核用作function函数的调用参数。
内核函数init_timer()用来初始化一个定时器。
实际上,这个初始化函数仅仅将结构中的list成员初始化为空。
如下所示(include/linux/timer.h):
staticinlinevoidinit_timer(structtimer_list*timer)
{
timer->list.next=timer->list.prev=NULL;
}
由于定时器通常被连接在一个双向循环队列中等待执行(此时我们说定时器处于pending状态)。
因此函数time_pending()就可以用list成员是否为空来判断一个定时器是否处于pending状态。
如下所示(include/linux/timer.h):
staticinlineinttimer_pending(conststructtimer_list*timer)
{returntimer->list.next!
=NULL;}
时间比较操作在定时器应用中经常需要比较两个时间值,以确定timer是否超时,所以Linux内核在timer.h头文件中定义了4个时间关系比较操作宏。
这里我们说时刻a在时刻b之后,就意味着时间值a≥b。
Linux强烈推荐用户使用它所定义的下列4个时间比较操作宏(include/linux/timer.h):
#definetime_after(a,b)((long)(b)-(long)(a)<0)
#definetime_before(a,b)time_after(b,a)
#definetime_after_eq(a,b)((long)(a)-(long)(b)>=0)
#definetime_before_eq(a,b)time_after_eq(b,a)
2.2Linux内核定时器
定时器是管理内核时间的基础,用来计算流逝的时间,它以某种频率(节拍率)自行触发时钟中断,当时钟中断发生时,内核就通过一种特殊中断处理程序对其进行处理。
但是原来的实现只能是time_tmytime形式的,经过简单的localtime(mytime)和ctime(&mytime)处理.精度是不够的,为了返回高精度的时间,这里使用了gettimeofday函数。
这个syscall用来供用户获取timeval格式的当前时间信息(精确度为微秒级),以及系统的当前时区信息(timezone)。
结构类型timeval的指针参数tv指向接受时间信息的用户空间缓冲区,参数tz是一个timezone结构类型的指针,指向接收时区信息的用户空间缓冲区。
这两个参数均为输出参数,返回值0表示成功,返回负值表示出错。
函数sys_gettimeofday()的源码如下(kernel/time.c):
asmlinkagelongsys_gettimeofday(structtimeval*tv,structtimezone*tz)
{
if(tv){
structtimevalktv;
do_gettimeofday(&ktv);
if(copy_to_user(tv,&ktv,sizeof(ktv)))
return-EFAULT;
}
if(tz){
if(copy_to_user(tz,&sys_tz,sizeof(sys_tz)))
return-EFAULT;
}
return0;
}
显然,函数的实现主要分成两个大的方面:
(1)如果tv指针有效,则说明用户要以timeval格式来检索系统当前时间。
为此,先调用do_gettimeofday()函数来检索系统当前时间并保存到局部变量ktv中。
然后再调用copy_to_user()宏将保存在内核空间中的当前时间信息拷贝到由参数指针tv所指向的用户空间缓冲区中。
(2)如果tz指针有效,则说明用户要检索当前时区信息,因此调用copy_to_user()宏将全局变量sys_tz中的时区信息拷贝到参数指针tz所指向的用户空间缓冲区中。
(3)最后,返回0表示成功。
函数do_gettimeofday()的源码如下(arch/i386/kernel/time.c):
voiddo_gettimeofday(structtimeval*tv)
{
unsignedlongflags;
unsignedlongusec,sec;
read_lock_irqsave(&xtime_lock,flags);
usec=do_gettimeoffset();
{
unsignedlonglost=jiffies-wall_jiffies;
if(lost)
usec+=lost*(1000000/HZ);
}
sec=xtime.tv_sec;
usec+=xtime.tv_usec;
read_unlock_irqrestore(&xtime_lock,flags);
while(usec>=1000000){
usec-=1000000;
sec++;
}
tv->tv_sec=sec;
tv->tv_usec=usec;
}
该函数的完成实际的当前时间检索工作。
由于gettimeofday()系统调用要求时间精度要达到微秒级,因此do_gettimeofday()函数不能简单地返回xtime中的值即可,而必须精确地确定自从时钟驱动的BottomHalf上一次更新xtime的那个时刻到do_gettimeofday()函数的当前执行时刻之间的具体时间间隔长度,以便精确地修正xtime的值。
假定被do_gettimeofday()用来修正xtime的时间间隔为fixed_usec,而从wall_jiffies到jiffies之间的时间间隔是lost_usec,而从jiffies到do_gettimeofday()函数的执行时刻的时间间隔是offset_usec。
则下列三个等式成立:
fixed_usec=(lost_usec+offset_usec)lost_usec=(jiffies-wall_jiffies)*TICK_SIZE=(jiffies-wall_jiffies)*(1000000/HZ)
由于全局变量last_tsc_low表示上一次时钟中断服务函数timer_interrupt()执行时刻的CPUTSC寄存器的值,因此我们可以用X86CPU的TSC寄存器来计算offset_usec的值。
也即:
offset_usec=delay_at_last_interrupt+(current_tsc_low-last_tsc_low)*fast_gettimeoffset_quotient
其中,delay_at_last_interrupt是从上一次发生时钟中断到timer_interrupt()服务函数真正执行时刻之间的时间延迟间隔。
每一次timer_interrupt()被执行时都会计算这一间隔,并利用TSC的当前值更新last_tsc_low变量。
假定current_tsc_low是do_gettimeofday()函数执行时刻TSC的当前值,全局变量fast_gettimeoffset_quotient则表示TSC寄存器每增加1所代表的时间间隔值,它是由time_init()函数所计算的。
2.3Linux信号signal处理机制
信号signal机制是进程之间相互传递消息的一种方法,全称为软中断信号。
系统调用signal用来设定某个信号的处理方法,其调用声明的格式如下:
void(*signal(intsignum,void(*handler)(int)))(int);成功则返回该信号以前的处理配置,出错则返回SIG_ERR。
在使用该调用的进程中加入以下头文件:
几个常见信号:
SIGINT:
当用户按某些终端键时,引发终端产生的信号.如Ctrl+C键,这将产生中断信号(SIGINT),它将停止一个已失去控制的程序。
SIGSEGV:
由硬件异常(除数为0,无效的内存引用等等)产生的信号。
这些条件通常由硬件检测到,并将其通知内核,然后内核为该条件发生时正在运行的进程产生该信号。
SIGALRM:
进程所设置的闹钟时钟超时的时候产生。
SIGVTALRM:
虚拟时钟超时时产生该信号。
类似于SIGALRM,但是它只计算该进程占用的CPU时间。
SIGPROF:
类似于SIGVTALRM,它不仅包括该进程占用的CPU时间还包括执行系统调用的时间。
三、内核定时器机制的实现
3.1动态定时器机制的初始化
函数init_timervecs()实现对动态定时器机制的初始化。
该函数仅被sched_init()初始化例程所调用。
动态定时器机制初始化过程的主要任务就是将tv1、tv2、…、tv5这5个结构变量中的定时器向量指针数组vec[]初始化为NULL。
如下所示(kernel/timer.c):
voidinit_timervecs(void)
{
inti;
for(i=0;iINIT_LIST_HEAD(tv5.vec+i);
INIT_LIST_HEAD(tv4.vec+i);
INIT_LIST_HEAD(tv3.ve