深入理解Linux内核 学习笔记(5)

第五章  定时测量

内核必须显式地与三种时钟打交道:实时时钟(Real Time Clock, RTC)、时间标记计数器(Time Stamp Counter, TSC)及可编程间隔定时器( ProgrammableIntervalTimer,PIT)。前两种硬件设备允许内核跟踪当前的时间;后一种设备由内核编程,以使它能以固定的、预先定义的频率发出中断。对于内核和用户程序使用的定时器来说,这样的周期性巾断是至关重要的。

 

实时时钟:

所有的PC都包含了一个叫实时时钟(RTC)的时钟.它是独立于CPU和所有其他芯片的。即使当PC关掉电源,RTC还继续走,因为它靠个小电池或蓄电池供电。RTC能发出周期性的中断,可作为一个闹钟来工作。

Linux只用RTC来获得时间和日期,然而,通过作用于/dev/rtc设备文件,也允许进程对RTC编程。内核通过0x70和0x71 1/O端口存取RTC。通过执行/sbin/clock系统程序(它直接作用于这两个I/O端口),系统管理员可以配置时钟。

 

时间标记计数器

一个64位的时间标记计数器(TSC)的寄存器,可以通过汇编语言指令rdtsc读这个寄存器。这个寄存器是一个计数器,它在每个时钟信号到来时加1,例如,如果时钟节拍的频率是400 MHz,那么,时间标记计数器每2.5纳秒增加一次。Linux 利用这个寄存器获得精准的时间测量

 

可编程间隔定时器

时间测量设备,到了发定时中断,PLT以某一固定的频率(由内核决定)不停地发出中断,定时中断的间隔叫做节拍,

 

时间计数器TSC :

如果CPU有TSC寄存器,用do_ gett imeofday()计算当前时间,否则,用

do_ normal_ gettime()计算。 在do_ _get_ fast_ time变量存放的指针指向合适的函数。

当TSC寄存器可用时,用do_ fast_ gett imecffset ()计算微秒数,否则,用do_ slow_ gect imeof fset ()计算。这个函数的地址存放在do_ gett imeoffset变量中。

在内核启动时运行的time_ init() 函数能将这些变最指向正确的函数。

 

进程描述符的counter域表示进程在CPU上运行剩余节拍数。(counter由一个下半部分以延缓的方式更新,一次递减可能大于一个节拍),变得小于0时,把need_resched置为1,恢复用户态程序执行时掉schedule,换程序在CPU执行

系统调用函数adjtimex(),每660s调用set_rtc_mmss调整一次实时时钟。

 

定时器:

一种软件工具,每个定时器有一个域,表示需要多长时间到期,jiffies+正确节拍数,每次内核检查定时器,就用jiffies和这个比较。由于使用下半部分,所以不能严格遵守时间(延迟几百ms)

Linux有三种类型的定时器,静态定时器,动态定时器和间隔定时器,前两者有内核使用,第三种可以由进程在用户态创建。

静态定时器

存在timer_table数组,每项一个结构体,

struct t imer_ struct (
unsigned long expires;

void (*fr.) lvoid) ;
);

 

expires域指定定时器到期时间。这个时间被表示成自系统启动以来的时钟节拍数。expires值小于或等于jiffies值的所有定时器就认为是到期了或停止了。fn域包含定时器到期时被执行函数的地址。

静态定时器检测工作也是由TIMER_BH下半部分调用函数检查。在内核激活一个定时器时,要在timer_active设置合适的 标志,这里会先清0然后执行fn。

 

动态定时器

struct timer_ list {

struct timer_ list *noxt;

struct tiner_ list *prev;

unsigned long expires;

uns igned long data ;

void (* funct ion) (uns-gned 1ong) ;

 

};.

 

 

function域包含定时器到期时要执行函数的地址。data域指定传递给这个定时器函数的参数。

data域使得定义一个通用闲数处理几个设备驱动程序的定时问题成为可能,可以在data域存放设备ID,或其他有意义的数据,这些数据可被用来区分不同设备的函数使用。

expires域的含义与静态定时器相应域的含义相同。,

next和prev域实现双向循环链表的连接。事实上,每个活动动态定时器根据expires的值被精确地插人到512个双向循环链表的其中-一个中。在本章稍后将描述使用这个链表的算法。

 

动态定时器的遍历不像静态的是单循环,使用了一个聪明的数据结构。这个数据结构叫tvecs的数组,数组的元素指向tv1-5结构标识的链表。tv1是struct timer_vec_root,

struct timer_vec_root (
int index;
Struct timer_list *vec[TVR_SIZE] ;
);

其TVR_SIZE为256,index指定当前扫描的链表,每个节拍时增1,模256为0时,由tv2-tv5顺序补充。每个vec指向一个timer_ list链表。

tv2-tv4这个TVR_SIZE为64,炎型为struct timer_vec的tv2、tv3及tv4結枸分別包含了在緊接着到来的214-1、220-1及226-1个节拍内將要到期的所有动态定吋器。

tv5的结构与前面的几个结构相同,除了vec数组的最后一项包含的动态定时器expires域的值可以任意大,它再也不需要从另一个数组进行补充了。

动态定时器的应用:在一些情况下,例如,当内核不能提供一个给定的服务时,就可以把当前进程挂起一个固定的时间。这通常是通过执行一个进程延时完成的。

 

与定时测量相关的系统调用:

Gettimeofday():该函数实现了time()和ftime(),root用户可以调用stime或settimeofday来修改当前日期和时间,不过请注意当这两个系统调用修改xtime的值时都没有修改RTC寄存器,因此当系统关机时新的时间会丢失,除非用户执行/sbin/clock这个程序来改变RTC的值。

Settimer和alarm:间隔定时器,

通过POSIX setit imer ()系统调用可以激活间隔定时器。第一个参数指定应当采取下面的哪一个策略:

ITIMER_REAL:真正过去的时间;进程接受SIGALRM信号
ITIMER_VIRTUAL:进程在用户念下花费的时间;进程接受SIGVTALRM信号
ITIMER_PROF:进程既在用户态下又在内核态下所花费的时间;进程接受SIGPROF信号
为了能分别实现前述每种策略的间隔定时器,进程描述符要包含三对域:
it_real_incr 和it_real_value
it_virt_incr 和it_virt_value
it_prof_incr 和it_prof_value

每对中的第一个域存放着两个信号之间以节拍为单位的问隔;另一个城存放着定时器的当前值。

ITIMER_REAL间隔定时器是利用动态定时器实现的,因为即使进程不在CPU上运行时,内核也必须向进程发送信号。因此,每个进程描述符包含一个叫real_ timer的动态定时器对象。setitimer(}系统调用初始化rea1_timer域,然后调用add_timer()把动态定时器插人到合适的链表中。当定时器到期时,内核执行it_rea1_fn()定时函数。依次类推,it_real_in() 函数向进程发送一个SIGALRM信号。如果it_real_incr不为空,那么它会再次设置expires城,重新激活定时器。

ITIMER_VIRTUAL和ITIMER_PROF间隔定时器不需要动态定时器,因为只有当进程运行时,它们才能被更新: do_it_virt()和dc_it_prof () 由update_ore_process ()调用,当执行TIMER_BH下半部分时update_one_process()才运行。因此,每个节拍中,这两个间隔定时器通常都被更新--次,并且如果它们到期,就给当前进程发送一个合适的信号。

a1arm()系统调用会在一个指定的时间间隔用完时向调用的进程发送一个SIGALRM信号。当以ITIMER_ REAL为参数调用时,它非常类似于setitimer(),因为它利用了包含在进程描述符中的real_timer动态定时器。因此,不能同时使用以ITIMER_REAL为参数的alarm()和setitimer()。

 

posted @ 2019-04-16 19:56  61355ing  阅读(307)  评论(0编辑  收藏  举报