第六章 定时测量

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:监管内核代码

      

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值