《linux定时器详解》由会员分享,可在线阅读,更多相关《linux定时器详解(24页珍藏版)》请在金锄头文库上搜索。
1、Linux 内核定时器详解80X86 体系结构上,常用的定时器电路实时时钟(RTC)RTC内核通过IRQ8上发出周期性的中断,频率在2-8192HZ之间,掉电后依 然工作,内核通过访问0x70和0x71 I/O端口访问RTC。时间戳计时器( TSC)利用CLK输入引线,接收外部振荡器的时钟信号,该计算器是利用64位的 时间戳计时器寄存器来实现额,与可编程间隔定时器传递来的时间测量相比,更 为精确。可编程间隔定时器(PIT)PIT的作用类似于微波炉的闹钟,PIT永远以内核确定的固定频率发出中断, 但频率不算高。CPU 本地定时器利用PIC或者APIC总线的时钟计算。高精度时间定时器( HPET)
2、功能比较强大,家机很少用,也不去记了。ACPI 电源管理定时器它的时钟信号拥有大约为3.58MHZ的固定频率,该设备实际上是一个简单的 计数器,为了读取计算器的值,内核需要访问某个I/O端口,需要初始化定时器的数据结构利用 timer_opts 描述定时器Timer_opts 的数据结构Name :标志定时器员的一个字符串Mark_offset :记录上一个节拍开始所经过的时间,由时钟中断处理程序调 用Get_offset 返回自上一个节拍开始所经过的时间Monotonic_clock :返回自内核初始化开始所经过的纳秒数Delay:等待制定数目的“循环”定时插补就好像我们要为 1 小时 35
3、分 34 秒进行定时,我们不可能用秒表去统计,肯 定先使用计算时的表,再用计算分的,最后才用秒表,在80x86架构的定时器也 会使用各种定时器去进行定时插补,我们可以通过 cur_timer 指针来实现。单处理器系统上的计时体系结构所有与定时有关的活动都是由IRQ线0上的可编程间隔定时器的中断触发。初始化阶段1. 初始化间,time_init()函数被调用来建立计时体系结构2. 初始化xtime变量(xtime变量存放当前时间和日期,它是一个timespec 类型的数据结构)3. 初始化 wall_to_monotonic 变量,它跟 xtime 是同一类型的,但它存放 将加在xtime上的描
4、述和纳秒数,这样即使突发改变xtime也不会受到影响。4. 看是否支持高精度计时器 HPET5调用select_timer()挑选系统中可利用的最好的定时资源,并让 cur_t imer变量指向该定时器6.调用setup_irq(0, &irq0)来创建与IRQ相应的中断门。时钟中断处理程序1. 在xtime_lock顺序锁产生一个write_seqlock()来保护与定时相关的内 核变量,这样防止中断让该进程被阻止。2. 执行cur_timer定时器对象的mark_offset方法(记录上一个节拍开始 所经过的时间,由时钟中断处理程序调用)3. 调用 do_timer_interrupt 函
5、数,步骤为a) 使 jiffies_64 值增 1b) 调用updata_times()函数来更新系统日期和时间。c) 调用updata_process_times ()函数为本地CPU执行几个与定时相关的计 数器作用。d) 调用 profile.tick()函数e) 如果利用外部时钟来同步系统时钟,则每隔 660 秒,调用一次st_rtc_mmss()函数来调整实时时钟。f) 调用 write_sequnlokc()释放 xtime_lock 顺序锁。4. 返回值 1,报告中断已经有效地处理了。 这个还算简单,接下来是多处理器系统上的计时体系设计。 多处理器系统上的计时体系初始化阶段通过ca
6、librate_APIC_clock()计算本地APIC多久才产生一次中断。全局时钟中断处理程序SMP版本的timer_interrupt()处理程序与UP版本的处理程序在几个地方有 差异。Timer_interrupt()调用函数 do_timer_interrupt()向 I/O APIC 芯片的一 个端口写入,以应答定时器的中断要求。Updata_process_times()函数不被调用,因为该函数执行与特定CPU相关的 操作Profile_tick()不被调用,因为该函数同样执行与特定CPU相关的操作。 动态定时器这部分应用很容易,但要理解动态定时器的机理,真的囧,就说说用的部分 吧
7、。动态定时器存放在 timer_list 结构中Struct time_listStruct list_head entry;Spinlock_t lock;Unsigned long magic;Void (*function)(unsigned long);Unsigned long data;Tvec_base_t *base;Entry 字段用于将软定时器插入双向循环链表队列中,其值该链表根据定时 器 expires 字段的值将他们分组放开(如果对动态定时器实现原理没兴趣的,可 以无视,不需要要设置的项目)Expries 字段给出定时器到期时间,时间用拍子数表示,一般都是 unsign
8、ed long expire=timeout+jiffiesLock 自旋锁Function 定时产生中断后,执行得函数Da ta,可以定义一个单独的通用函数来处理多个设备驱动程序超时的问题关于间隔定时器所谓间隔定时器(Interval Timer,简称itimer)就是指定时器采用间隔”值(interval)来作为计时方式,当定时器启动后,间隔值interval将不断减小。当interval值减到0时,我们就说 该间隔定时器到期。与上一节所说的内核动态定时器相比,二者最大的区别在于定时器的计时方式 不同。内核定时器是通过它的到期时刻expires值来计时的,当全局变量jiffies值大于或等
9、于内核 动态定时器的expires值时,我们说内核内核定时器到期。而间隔定时器则实际上是通过一个不断 减小的计数器来计时的。虽然这两种定时器并不相同,但却也是相互联系的。假如我们每个时钟节 拍都使间隔定时器的间隔计数器减 1,那么在这种情形下间隔定时器实际上就是内核动态定时器(下面我们会看到进程的真实间隔定时器就是这样通过内核定时器来实现的)。间隔定时器主要被应用在用户进程上。每个Linux进程都有三个相互关联的间隔定时器。其各自的间隔计数器都定义在进程的task_struct结构中,如下所示(include/linux/sched.h):struct task_struct unsigned
10、 long it_real_value, it_prof_value, it_virt_value;unsigned long it_real_incr, it_prof_incr, it_virt_incr;struct timer_list real_timer;(1)真实间隔定时器(ITIMER_REAL):这种间隔定时器在启动后,不管进程是否运行,每 个时钟滴答都将其间隔计数器减1。当减到0值时,内核向进程发送SIGALRM信号。结构类型t ask_struct 中的成员 it_real_incr 则表示真实间隔定时器的间隔计数器的初始值,而成员 it_rea l_value 则表示真
11、实间隔定时器的间隔计数器的当前值。由于这种间隔定时器本质上与上一节的内 核定时器时一样的,因此Linux实际上是通过real_timer这个内嵌在task_struct结构中的内核 动态定时器来实现真实间隔定时器ITIMER_REAL的。2)虚拟间隔定时器ITIMER_VIRT:也称为进程的用户态间隔定时器。结构类型task_strue t中成员it_virt_incr和it_virt_value分别表示虚拟间隔定时器的间隔计数器的初始值和当前值, 二者均以时钟滴答次数位计数单位。当虚拟间隔定时器启动后,只有当进程在用户态下运行时,一 次时钟滴答才能使间隔计数器当前值it_virt_value
12、减1。当减到0值时,内核向进程发送SIGVT ALRM信号(虚拟闹钟信号),并将it_virt_value重置为初值it_virt_incr。具体请见7.4.3节 中的do_it_virt()函数的实现。(3)PROF 间隔定时器 ITIMER_PROF:进程的 task_struct 结构中的 it_prof_value 和 it _prof_incr成员分别表示PROF间隔定时器的间隔计数器的当前值和初始值(均以时钟滴答为单 位)。当一个进程的PROF间隔定时器启动后,则只要该进程处于运行中,而不管是在用户态或核 心态下执行,每个时钟滴答都使间隔计数器it_prof_value值减1。当减
13、到0值时,内核向进程发 送 SIGPROF 信号,并将 it_prof_value 重置为初值 it_prof_incr 。具体请见 7.4.3 节的 do_it_pr of()函数。Linux 在 include/linux/time.h 头文件中为上述三种进程间隔定时器定义了索引标识,如下 所示:#define ITIMER_REAL 0#define ITIMER_VIRTUAL 1#define ITIMER_PROF 2771 数据结构 itimerval 虽然,在内核中间隔定时器的间隔计数器是以时钟滴答次数为单位,但是让用户以时钟滴答为单位来指定间隔定时器的间隔计数器的初值显然是不
14、太方便的,因为用户习惯的时间单位是秒、毫秒或微秒等。所以Linux定义了数据结构itimerval来让用户以秒或微秒为单位指定间隔定时器的时间间隔值。其定义如下(in clude/li nu x/time.h):struct itimerval struct timeval it_interval; /* timer interval */struct timeval it_value; /* current value */;其中,it_interval成员表示间隔计数器的初始值,而it_value成员表示间隔计数器的当前值。这两个成员都是timeval结构类型的变量,因此其精度可以达到微秒
15、级。timeval与jiffies之间的相互转换由于间隔定时器的间隔计数器的内部表示方式与外部表现方式互不相同,因此有必要实现以微秒为单位的timeval结构和为时钟滴答次数单位的jiffies之间的相互转换。为此,Linux在kernel/itimer.c中实现了两个函数实现二者的互相转换tvtojiffies()函数和jiffiestotv()函数。它们的源码如下:static unsigned long tvtojiffies(struct timeval *value)unsigned long sec = (unsigned) value-tv_sec;unsigned long usec = (unsigned) value-tv_usec;if (sec (ULONG_MAX / HZ)return ULONG_MAX;usec += 1000000 / HZ - 1;usec /= 1000000 / HZ;return HZ*sec+usec;static void jiffiestotv(unsigned long jiffies, struct timeval *value)value-tv_usec = (jiffies % HZ) * (1000000 / H