1)linux内核给用户很多选择选项,为了兼容不同的硬件提供了多种选项,硬件定时器(HPET、TSC),软件定时器。
2)注意处理竞争条件(在多核情况下,会引入在单核情况下不存在的竞争条件,例如,定时器函数在【被删除函数】终止时可能在其他CPU上运行【和删除函数不在一个CPU上】,del_timer_sync()删除指定的定时器后,需要检查定时函数是否还在其他CPU上运行,如果是,则需要等到定时器函数结束)。
3)在体系结构中分为 全局时钟和 CPU本地时钟
4) 监管内核代码,依赖于eip寄存器的值,来揭示中断发生前内核在做什么。
5)看门狗的实现依赖于 非屏蔽中断(NMI),它们在每个CPU上产生周期性的NMI中断。
用tv1,tv2,tv3,tv4,tv5将list_head元素分为在将来2^8-1,2^14-1, 2^20-1, 2^26-1和2^32-1时钟节拍内将要到期的所有动态定时器。(分而治之的思想)
一、定时器
RTC:振源内部提供,自己有电源,电脑关机后也能自己位置时间,Linux只用RTC来获取时间和日期,可以通过/dev/rtc对设备文件进行操作,可以对RTC编程。
TSC:振源外部提供,注意它的振源频率可能是变幻的(有些电脑可以节约功耗,变频工作),所以即使他的频率高,但是不一定选取。
TSC(64位)每个时钟信号到来时加1,其单位与时钟节拍频率有关,如果时钟节拍频率是1GHz,则单位是ns(即每1na增加1),是计数器(是在上电后工作,RTC可以在断电后工作,RTC有自己的电源)。calibrate_tsc()通过计算一个大约5ms(这个时间是有PIT提供的)的时间间隔内所产生的时钟信号的 个数 来算出 CPU实际频率。
PIT :振源内波提供,他是可以编写的。。你可以规定他的频率,所以你可以在电脑启动时候得知一个固定的时间,但是频率不高。大约能提供1ms一次中断。通常使用0X40 ~ 0x43 I/O端口的一个8254CMOS芯片。
HPET、ACPIPMT:振源外部提供,他们两个都是高精度,但是明显HPET精度更高(听名字都知道哈。。==!)
二、常用数据结构
1)定时器对象:timer_opt
{ name,
mark_offset, 上一个节拍的准确时间
get_offset, 上一个节拍开始经过的时间 (这个主要是拿来获取字节拍,假如你的时钟中断1ms一次,那你要知道100ns这个时间段的时候就会用到这个)
monotonic_clock, 纳秒数
delay
}
2)jiffies (32位)变量
系统启动后每次时钟节拍加1(大约50天会回绕wraparound到 0)。
初始化为0xfffb6c20,(-300 000)。大约5分钟内能益出,目的是检查有缺陷的(不能对jiffies作溢出检测)内核代码。
unsigned long longget_jiffies_64(void)
{
unsigned long seq;
unsigned long long ret;
do{
seq=read_seqbegin(&xtime_lock);
ret=jiffies_64;
}while(read_seqretry(&xtime_lock,seq)); (注意这里是检查是否读取的和上次一样,如果不一样就说明中途被改过了。。就要重新读取)
return ret;
}
voidupdate_times(void)
{
unsigned long ticks;
ticks= jiffies –wall_jiffies; //wall_jiffies变量存储的xtime变量最后更新时间
if(ticks){
wall_jiffies +=ticks;
update_wall_time
}
calc_load(ticks);
}
3)xtime 变量
保存当前时间和日期,timespec类型的数据结构{tv_sec,tv_nsec}。
三、记录系统负载
1)监管内核代码(readprofiler)
为了确定内核的“热点”hotspot。,内核从堆栈取回中断发生前的eip寄存器的值,用这个值揭示中断发生前内核正在做什么。
内核启动时“profile=N”,2^N表示要监管代码的大小。
/proc/profile中保存采集的数据。
2)检查NMI监视器:这个是一个看门狗,他每次中断时不能屏蔽的,每次中断检查一个变量是否加1,正常情况下这个变量每次时钟中断(定时器的中断)都加1,如果出现中断没有发生,或者没有捕获,则说明该cup肯定有什么问题。
四、动态定时器和间隔定时器
两种类型定时器:动态定时器(只能由内核使用),间隔定时器(可以由进程在用户态创建)
1)动态定时器
struct timer_list{
struct list_head entry;
unsignedlong expires; 定时器到期时间
spinlock_t lock;
unsigned long magic;
void (*function) (unsigned long); 定时器到期执行函数的地址
unsigned long data; 传递给定时器函数的参数(可以存放设备ID,实现处理多个设备驱动程序的超时问题)
tvec_base_t * base;
}
del_singleshot_timer_sync()函数不能重新激活
typedef struct tvec_t_base_s {
spinlock_t lock;
unsigned longtimer_jiffies;
struct timer_list*running_timer;
tvec_root_t tv1; 将来2^8-1 节拍
tvec_ttv2; 2^14-1
tvec_ttv3; 2^20-1
tvec_ttv4; 2^26-1
tvec_ttv5; 2^32 -1
}tvec_base_t;
定时器管理思想:这里我们主要是将某个时间点,后面的256个时间中断将到时的定时器(这里可以看出定时器的精度问题)放到tv1,在256后64的的时钟中断放在tv2,以此类推。每次中断都来,我们都检查tv1上是否为空,为空我们才把tv2移动到tv1,在检查tv2上是否为空,为空我们在吧tv3移动过来,依次类推。这样我们可以节约很多处理时间。。
if(!index && (!cascade(base,&base->tv2,(base->timer_jiffies>>8)&63)) &&
//将tv2中的所有动态定时器移到base->tv1适当的链表上。
(!cascade(base, &base->tv3,(base->timer_jiffies>>14)&63)) &&
(!cascade(base,&base->tv4,(base->timer_jiffies>>20)&63)) )
cascade(base,&base->tv5,(base->timer_jiffies>>26)&63);
2)延迟函数:延迟函数用到的就是定时器方法中的delay;根据精度不同提供udelay(单位us),ndelay(单位ns);
总体总结:我们电脑会选取一个定时器,一般有多个,我们选取其中一个,然后这个定时器为我们输送一个定时的中断(如1ms一次中断),然后每次中断来到,我们就要更新系统时间,然后是触发软中断,去检查哪些挂着的软定器是否到时。(当然还有其他的一些,如记录负载等等),但是我们主要是这个,所以软定器不一定准,第一是由于给cpu的中断精度不高。第二是中断来的时候,依次执行,假如tv1数组上tv1[0]上面挂有很多个软定时器,则大家可以想象肯定后面的要慢点。。。所以这个定时器是不绝对准的。