6.1:时钟和定时器电路
所有架构对时间的处理都类似。首先有cycle值,表示一秒钟有多少个晶振。Linux中这个值用宏表示:
|
#ifdef CONFIG_X86_ELAN # define CLOCK_TICK_RATE 1189200 /* AMD Elan has different frequency! */ #else # define CLOCK_TICK_RATE 1193182 /* Underlying HZ */ #endif |
然后有tick值,这就是软件时间,一个tick就是一个时钟中断。使用宏来标识一秒钟有多少个tick
|
# define HZ 1000 /* Internal kernel timer frequency.每秒的tick数量*/ |
要能够产生时钟中断,所有架构中都有一个寄存器(DEC或者PIT)。当这个寄存器中的值减到0的时候,就产生一个时钟中断,也就是一个tick。因此,要将寄存器中的值设置为cycle和tick的比例LATCH。如下所示:
|
void setup_pit_timer(void) { extern spinlock_t i8253_lock; unsigned long flags; spin_lock_irqsave(&i8253_lock, flags); outb_p(0x34,PIT_MODE); /* binary, mode 2, LSB/MSB, ch 0 */ udelay(10); outb_p(LATCH & 0xff , PIT_CH0); /* LSB */ udelay(10); outb(LATCH >> 8 , PIT_CH0); /* MSB */ spin_unlock_irqrestore(&i8253_lock, flags); } |
6.1.1:实时时钟(RTC)
6.1.2:时间戳计数器(TSC)
6.1.3:可编程间隔定时器(PIT)
6.1.4:CPU本地定时器
6.1.5:高精度时间定时器(HPET)
6.1.6:ACPI电源管理定时器
6.2:Linux计时体系结构
内核周期性的:
1:更新自系统启动以来经过的时间;
2:更新时间和日期
3:确定进程在每个CPU上运行了多少时间。如果已经超过了分配给他的时间份额,那么就抢占这个程序。
4:更新资源使用统计数。
5:检查每个软定时器的时间间隔是否已经达到。
6.2.1:计时体系机构的数据结构
6.2.1.1:定时器对象
使用定时器对象描述定时器资源,包含定时器名字和四个标准方法。
|
/** * struct timer_ops - used to define a timer source * * @name: name of the timer. * @mark_offset: called by the timer interrupt. //记录上一个节拍的准确时间,由中断处理程序调用 * @get_offset: called by gettimeofday(). Returns the number of microseconds * since the last timer interupt. //记录自上一个节拍开始经过的时间 * @monotonic_clock: returns the number of nanoseconds since the init of the * timer. //记录自内核初始化开始经过的纳秒数 * @delay: delays this many clock cycles. //等待指定数目的cycle */ struct timer_opts { char* name; void (*mark_offset)(void); unsigned long (*get_offset)(void); unsigned long long (*monotonic_clock)(void); void (*delay)(unsigned long); }; |
6.2.1.2:jiffies变量
jiffies变量用于记录自系统启动以来产生的tick总数。每次时钟中断,他的值加1(每次时钟中断大概是1ms)。
为了保证变量不溢出,设计了一个64位的,描述系统启动以来产生的节拍数的变量,jiffies_64。这个变量是通过jiffies计算获得的。为了保证读这个变量的时候,这个变量不会被更新(读64位数不是原子操作),需要使用下面的接口:
|
u64 get_jiffies_64(void) { unsigned long seq; u64 ret; do { seq = read_seqbegin(&xtime_lock); ret = jiffies_64; } while (read_seqretry(&xtime_lock, seq)); //使用顺序锁进行同步 return ret; } |
6.2.1.3:xtime变量
使用xtime变量存放当前时间和日期。
|
struct timespec xtime; struct timespec { long ts_sec; //存放自1970年1月1日午夜以来经过的秒数 long ts_nsec; //存放自上一秒开始经过的纳秒数 }; |
6.2.2:单处理器系统上的计时体系结构
单核中,所有活动都是由IRQ线0上产生的时钟中断触发的。
6.2.2.1:初始化阶段
使用函数time_init初始化时间模块:
|
void __init time_init(void) { #ifdef CONFIG_HPET_TIMER if (is_hpet_capable()) { //HPET芯片初始化必须在mem_init执行后完成。但是time_init在mem_init之前。因此,注册一个回调函数。 late_time_init = hpet_time_init; return; } #endif xtime.tv_sec = get_cmos_time(); //从实时时钟上获得从1970年1月1日午夜开始的秒数 xtime.tv_nsec = (INITIAL_JIFFIES % HZ) * (NSEC_PER_SEC / HZ); set_normalized_timespec(&wall_to_monotonic, -xtime.tv_sec, -xtime.tv_nsec); //初始化wall_to_monotonic变量。这个变量存放系统开始时,经过的秒数 cur_timer = select_timer(); printk(KERN_INFO "Using %s for high-res timesource\n",cur_timer->name); time_init_hook(); //设置中断门,这里定义时钟中断的处理函数 } |
中断门的设置使用的函数如下:
|
static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL}; //注意SA_INTERRUPT的作用。在处理中断的时候,如果设置了这个标签。那么在执行中断处理函数的时候不要开中断。 void __init time_init_hook(void) { setup_irq(0, &irq0); //注册时钟中断处理函数为timer_interrupt函数 } |
分析一下函数setup_irq的作用:
|
int setup_irq(unsigned int irq, struct irqaction * new) { struct irq_desc *desc = irq_desc + irq; //这里是因为时钟中断都是IRQ线0产生的,所以直接使用全局数组irq_desc_t irq_desc [NR_IRQS]的首地址 struct irqaction *old, **p; unsigned long flags; int shared = 0; if (desc->handler == &no_irq_type) return -ENOSYS; /* * The following block of code has to be executed atomically //下面的代码是临界区 */ spin_lock_irqsave(&desc->lock,flags); p = &desc->action; //action是一个链表的指针。如果发生这个中断,会执行这个链表上的所有中断处理函数。 if ((old = *p) != NULL) { /* Can't share interrupts unless both agree to */ if (!(old->flags & new->flags & SA_SHIRQ)) { spin_unlock_irqrestore(&desc->lock,flags); return -EBUSY; } /* add new interrupt at end of irq queue */ do { p = &old->next; old = *p; } while (old); shared = 1; } *p = new; //这里将新的中断处理函数添加到desc中 if (!shared) { desc->depth = 0; desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING | IRQ_INPROGRESS); if (desc->handler->startup) desc->handler->startup(irq); else desc->handler->enable(irq); } spin_unlock_irqrestore(&desc->lock,flags); new->irq = irq; register_irq_proc(irq); new->dir = NULL; register_handler_proc(irq, new); return 0; } |
因此,在这里总结一下中断的初始化全过程(注意这里只说明中断,不说明异常)。
1:在最开始的时候,通过setup_idt,将所有的中断门,异常门的跳转函数设置为ignore_int函数。
2:在函数init_IRQ中,将所有的中断门的跳转函数设置为irq_entries_start。通过这个函数跳转到common_interrupt。
3:common_interrupt会跳转到每个中断描述符中,注册的处理函数。setup_irq的作用就是给中段描述符注册处理函数。
6.2.2.2:时钟中断处理程序
当发生一个时钟中断的时候,硬件自动跳转到中断门处的函数执行,也就是common_interrupt。然后common_interrupt跳转到do_IRQ中,执行中断处理函数timer_interrupt。
|
irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) { write_seqlock(&xtime_lock); //此时处于关中断状态。但是还要防止其他CPU对这个全局变量的修改,因此需要使用顺序锁。(自旋锁,一直自旋直到拿到锁) cur_timer->mark_offset(); //和产生时钟中断的来源有关。如果是通过PIT产生的时钟中断,那么这个函数什么都不做。HPET等定时器会做额外操作 do_timer_interrupt(irq, NULL, regs); write_sequnlock(&xtime_lock); return IRQ_HANDLED; } |
其中最重要的是do_timer_interrupt函数。
|
static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) { do_timer_interrupt_hook(regs); /* * If we have an externally synchronized Linux clock, then update * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be * called as close as possible to 500 ms before the new second starts. */ if ((time_status & STA_UNSYNC) == 0 && xtime.tv_sec > last_rtc_update + 660 && (xtime.tv_nsec / 1000) >= USEC_AFTER - ((unsigned) TICK_SIZE) / 2 && (xtime.tv_nsec / 1000) <= USEC_BEFORE + ((unsigned) TICK_SIZE) / 2) { /* horrible...FIXME */ if (efi_enabled) { if (efi_set_rtc_mmss(xtime.tv_sec) == 0) last_rtc_update = xtime.tv_sec; else last_rtc_update = xtime.tv_sec - 600; } else if (set_rtc_mmss(xtime.tv_sec) == 0) last_rtc_update = xtime.tv_sec; else last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */ } } |
do_timer_interrupt_hook函数实现如下:
|
static inline void do_timer_interrupt_hook(struct pt_regs *regs) { do_timer(regs); //jiffies_64的值加1,并且更新系统时间和日期。具体内容参考后面的“更新时间和日期”,“更新系统统计数” #ifndef CONFIG_SMP //单核环境 update_process_times(user_mode(regs)); //参考更新“CPU统计数” #endif /* * In the SMP case we use the local APIC timer interrupt to do the * profiling, except when we simulate SMP mode on a uniprocessor * system, in that case we have to call the local interrupt handler. */ #ifndef CONFIG_X86_LOCAL_APIC profile_tick(CPU_PROFILING, regs); //参考“监管内核代码”一节 #else if (!using_apic_timer) smp_local_timer_interrupt(regs); #endif } |
6.3:更新时间和日期
用户态程序要从xtime变量中获得当前的时间和日期。因此,内核调用函数更新这个全局变量。
|
static inline void update_times(void) { unsigned long ticks; ticks = jiffies - wall_jiffies; //wall_jiffies是上一次记录的tick数 if (ticks) { wall_jiffies += ticks; update_wall_time(ticks); //更新全局变量xtime } calc_load(ticks); } |
6.4:更新系统统计数
6.4.1:更新本地CPU统计数
|
void update_process_times(int user_tick) // user_tick:根据被中断的时候,进程是在用户态还是内核态,取不同的值 { struct task_struct *p = current; int cpu = smp_processor_id(); /* Note: this timer irq context must be accounted for as well. */ if (user_tick) //被中断前处于用户态 account_user_time(p, jiffies_to_cputime(1)); else account_system_time(p, HARDIRQ_OFFSET, jiffies_to_cputime(1)); run_local_timers(); if (rcu_pending(cpu)) rcu_check_callbacks(cpu, user_tick); scheduler_tick(); //将在调度的章节中介绍 } |
6.4.2:记录系统负载
6.4.3:监管内核代码
1141

被折叠的 条评论
为什么被折叠?



