2009年(49)
分类: LINUX
2009-05-25 12:17:42
CHAPTER 6 TIMING MEASUREMENT
学习这部分之前觉得这个部分应该跟uCOSii里面关于定时中断的内容很像,应该比较简单易懂,但是学了之后才知道linux这部分相当的复杂。。。设计很多的数据结构和特定的算法,很多东西都没有弄懂,只有今后慢慢消化,感觉这个部分还是相当的重要。
Linux内核需要两种方面的定时测量。1.保持当前的时间和日期,这样可以被用户程序获得。2.维持定时器。这样可以通知内核或者用户进程一个特定的时间间隔到了。
定时测量的硬件支持设备有固定频率振荡器和计数器两个部分。
一 时钟和定时器电路
时钟电路用来跟踪当前的时间和日期,可以用来描述精确的时间测量。
定时器是在内核中定制的。他们用来固定的产生中断。这种周期性的中断在用作为内核和用户进程执行软件定时器上面是十分重要和必要的。
1 real time clock RTC 实时时钟
独立于CPU和其他的芯片
RTC可以在IRQ8上面产生周期性的中断。
Linux只从RTC上面得到时间和日期。内核可以在0x70和0x71端口上访问RTC。
2 time stamp counter TSC 时间戳计数器
80x86可以通过clk input pin来获得外部时钟信号。有64bit的寄存器。用这个寄存器的时候内核需要考虑时钟频率。比如1GHZ的CPU,TSC就似乎每一纳秒加一。
3 programmable interval timer PIT
跟微波炉的定时器很像。这个设备哦你过来产生一个特殊的中断叫做timer interrupt,定时器中断。
Linux将IRQ0分给这个中断。大概是1000HZ,也就是一毫秒,ms。这个时间间隔叫做tick,就是一个滴答。
并不是ticks的间隔越小越好,虽然越小的tick间隔会使有些应用反应越快速。但是小的tick会使CPU更多的运行在kernel mode下,所以就会导致用户程序运行的更慢。
慢的机子tick一般为10ms,快的机子一般是1ms。
8254用来产生定时中断。具体的工作图可以参考陈莉君的《深入分析linux内核源代码》。
4 high precision event timer HPET
微软和intel提出的一个东西,比PIC有更高的精度。好像要取代PIC,不知道。。。
5 ACPI power management timer
这个可以在CPU进入省电模式或者频率经常变化的时候,TSC变化的时候使用。
二 linux的时间维护体系
内核需要周期的进行一下几个工作:
1从系统启动以后更新时间
2更新时间和日期
3决定当前进程需要被运行多久
4更新资源变量
5检查软件定时器的时间是不是已到
内核使用了两个时间维护的功能:1保持当前的时间。2另一个计算当前一秒里面过了多少纳秒。
里面包含的数据结构:
l 定时器对象timer object:timer_opts (这个里面可选择hpet,或者tsc,或者pit 等)
{ name, mark_offset, get_offset, monotonic_clock, delay }
l Cur_timer 变量用来存储一个定时器对象timer object的地址。
在内核初始化的时候,select_timer()用来为cur_timer选择一个最合适的定时器对象。(具体选择情况参看p234。)
l Jiffies用来存储自从系统启动后有多少个tick产生了。
Jiffies有32个bit,和它对应的有一个64bit的jiffies_64,为什么开始不用64bit是因为64bit的操作在32位的主机上不是一个原子性的操作。
l Xtime 变量 xtime用来存储当前的时间和日期。它是一个timesepc的结构体
Timespec有两个域:tv_sec,tv_nsec
xtime变量通常在每个tick的时候更新。也就是一秒钟更新1000次,就是1ms更新一次。
三 初始化
time_init()用来初始化timekeeping architecture。
1 初始化xtime变量。就是从1970.1.1开始到现在有多少时间过去了。
2 初始化wall_to_monotonic变量,这个变量跟xtime一样也是一个timespec的结构体(具体的功能没有看懂。。。)
3 调用select_timer()来选择一个最好的时间源来做为cur_timer变量。
4 调用 setup_irq(0,&irq0);这样,timer_interrupt()就会在每个tick被调用。
四 timer_interrupt()
1用write_seqlock()作用于xtime_lock seqlock来保护时间相关的内核变量。
2执行cur_timer 时间对象上的mark_offset方法。
3调用do_timer_interrupt()。
a. 给jiffies_64加一。
b. 调用update_times(),用来更新时间和日期。
c. 调用update_process_times(),用来更新本地CPU的一些跟时间相关的统计量。
d. 调用profile_tick(),来统计内核中使用的hot spots。
e. adjtimex()用来用外部时钟更新。如果需要
4释放xtime_lock seqlock。用write_sequnlock()。
5返回1来通知中断被有效的执行了。
五 更新时间和日期
用户进程从xtime变量里面获得当前的时间和日期。
update_times()被定时中断调用,用来更新时间和日期。具体代码参看p240
其中wall_jiffies存储的是上次更新的xtime变量的值。
Update_wall_time(),每次给xtime.tv_nsec的值加1000000,(也就是每次update_wall_time()的调用间隔是1微妙),如果nsec的值大于999999999,那么就给tv_sec的值加一。
六 更新系统统计值
1 检查当前进程对CPU的使用限制。
2更新本地CPU参数
3计算CPU的平局负载
4 profiling the kernel code
更新本地CPU的信息
update_process_times():
1 检查当前进程已经运行多久了。
调用account_usr_time()当进程在USER MODE运行。调用account_system_time()当进程在kernel mode。
更新utime,stime。
并且检查CPU time limit是不是到了,如果到了的话,向当前进程发送SIGXCUP和SIGKILL信号。
调用account_it_virt()和account_it_prof()来检查进程的定时器。
2调用raise_softirq()来激活TIMER_SOFTIRQ的tasklet
3调用scheduler_tick(),用来给当前进程的时间片(time slice)减一。
七 软件定时器和延时函数
每个定时器都有一个域来表示需要多久这个timer才被expire。这个域是jiffies再加上一个特定的值(ticks),(这样不用每次去改这个值,只需要每次去对比一项当前的jiffies和这个值以不一样。)
Linux有两种类型的定时器。一个是dynamic timers动态定时器,这个定时器被内核使用。另一个是interval timer间隔定时器,这个定时器可以在进程的USER MODE被创建。
检查定时器的工作每次都是在defferable function上面被执行的。所以定时器不一定是在设定的时候就立刻执行。所以这个定时器不能在real-time的情况下应用。(真正的RTOS是怎么实现这个功能的呢?)
除了软件定时器,内核还应用delay,这个就是执行一个tight的指令,来消耗给定的时间。
八 动态定时器
动态定时器可以被动态的创建和销毁。动态定时器的数据结构是timer_list给出的。
里面的结构参看p245
其中有expires,就是需要消耗的时间。还有function,就是到了这个时间需要执行的函数。
Data就是需要给这个函数的参数。比如一个设备的ID号。
创建一个动态定时器,内核必须:
1 如果需要,创建一个新的timer_list 对象。t
2 初始化这个对象,init_timer(&t)
3 加载function的地址。
4 如果动态定时器没有在list中,(这个时候list已经创建了),那么就add_timer(&t)。
5 如果动态定时器已经在list中,并且需要修改这个timer,那么就mod_timer()。
当定时器过期了,那么内核会自动将t从list中移除。
动态定时器里面的队列的排序方法十分的复杂。就是为了达到一个好的效率。
其中每个CPU里面有一个tvec_base_t的数据结构,里面分了1个tvec_root_t和4个tvec_t(tv1-tv4)来将定时器队列(一个队列就是一个time_list)分开。这样通过算法就可以很容易的找到当前时刻需要处理的动态定时器。具体的算法参看p248。
处理软件定时器是非常耗时间的。所以在linux2.6中,他是被放到deferrable function中处理的,叫做TIMER_SOFTIRQ的softirq。(这个里面其实是tasklet。)
run_timer_softirq()就是这个deferralbe function。这个里面就会处理tvec_base_t这个数据机构。。。有时间就好好看看。
定时器的一个具体应用就是nanosleep()这个系统调用,具体的情况参看p250
九 delay function
软件定时器在延时很短的情况下是没有作用的(因为是在defferable function里面实现的)所以需要delay function,内核使用udelay()和ndelay(),这个里面使用了cur_timer里面的delay方法。具体函数参看p251
十 interval timers
Linux允许user mode下面也使用特殊的一种定时器,这种定时器会发送信号个进程。
有setitimer()和alarm()
Setitimer():有三个参数
第一个参数是使用的politics,ITIMER_REAL,ITIMER_VIRTUAL,ITIMER_PROF。
REAL参数是真实的时间。这时是用来动态定时器的。因为这个时候就算进程不在CPU,也需要给它发送信号。
第二个参数是时间的数据结构。itimerval。这个是一个timespec的结构体