⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 linux_clock

📁 基于LINUX操作系统下的各种详细配置(如FTP
💻
📖 第 1 页 / 共 5 页
字号:
* timestamp, the maximum error is < 1 jiffy. But timestamps are* still perfectly ordered.* Note that the TSC counter will be reset if APM suspends* to disk; this won't break the kernel, though, 'cuz we're* smart. See arch/i386/kernel/apm.c.*//** Firstly we have to do a CPU check for chips with* a potentially buggy TSC. At this point we haven't run* the ident/bugs checks so we must run this hook as it* may turn off the TSC flag.** NOTE: this doesnt yet handle SMP 486 machines where only* some CPU's have a TSC. Thats never worked and nobody has* moaned if you have the only one in the world - you fix it!*/dodgy_tsc();if (cpu_has_tsc) {unsigned long tsc_quotient = calibrate_tsc();if (tsc_quotient) {fast_gettimeoffset_quotient = tsc_quotient;use_tsc = 1;/** We could be more selective here I suspect* and just enable this for the next intel chips ?*/x86_udelay_tsc = 1;#ifndef do_gettimeoffsetdo_gettimeoffset = do_fast_gettimeoffset;#endifdo_get_fast_time = do_gettimeofday;/* report CPU clock rate in Hz.* The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) =* clock/second. Our precision is about 100 ppm.*/{ unsigned long eax=0, edx=1000;__asm__("divl %2":"=a" (cpu_khz), "=d" (edx):"r" (tsc_quotient),"0" (eax), "1" (edx));printk("Detected %lu.%03lu MHz processor.\n", cpu_khz / 1000, cpu_khz % 1000);}}}#ifdef CONFIG_VISWSprintk("Starting Cobalt Timer system clock\n");/* Set the countdown value */co_cpu_write(CO_CPU_TIMEVAL, CO_TIME_HZ/HZ);/* Start the timer */co_cpu_write(CO_CPU_CTRL, co_cpu_read(CO_CPU_CTRL) | CO_CTRL_TIMERUN);/* Enable (unmask) the timer interrupt */co_cpu_write(CO_CPU_CTRL, co_cpu_read(CO_CPU_CTRL) & ~CO_CTRL_TIMEMASK);/* Wire cpu IDT entry to s/w handler (and Cobalt APIC to IDT) */setup_irq(CO_IRQ_TIMER, &irq0);#elsesetup_irq(0, &irq0);#endif}对该函数的注解如下:(1)调用函数get_cmos_time()从RTC中得到系统启动时的时间与日期,它返回的是当前时间相对于1970-01-01 00:00:00这个UNIX时间基准的秒数值。因此这个秒数值就被保存在系统全局变量xtime的tv_sec成员中。而xtime的另一个成员 tv_usec则被初始化为0。(2)通过dodgy_tsc()函数检测CPU是否存在时间戳记数器BUG(I know nothing about it:-)(3)通过宏cpu_has_tsc来确定系统中CPU是否存在TSC计数器。如果存在TSC,那么内核就可以用TSC来获得更为精确的时间。为了能够用TSC来修正内核时间。这里必须作一些初始化工作:①调用calibrate_tsc()来确定TSC的每一次计数真正代表多长的时间间隔(单位为us),也即一个时钟周期的真正时间间隔长度。②将calibrate_tsc()函数所返回的值保存在全局变量 fast_gettimeoffset_quotient中,该变量被用来快速地计算时间偏差;同时还将另一个全局变量use_tsc设置为1,表示内核可以使用TSC。这两个变量都定义在arch/i386/kernel/time.c文件中,如下:/* Cached *multiplier* to convert TSC counts to microseconds.* (see the equation below).* Equal to 2^32 * (1 / (clocks per usec) ).* Initialized in time_init.*/unsigned long fast_gettimeoffset_quotient;……static int use_tsc;③接下来,将系统全局变量x86_udelay_tsc设置为1,表示可以通过TSC来实现微妙级的精确延时。该变量定义在 arch/i386/lib/delay.c文件中。④将函数指针do_gettimeoffset强制性地指向函数 do_fast_gettimeoffset()(与之对应的是do_slow_gettimeoffset()函数),从而使内核在计算时间偏差时可以用TSC这种快速的方法来进行。⑤将函数指针do_get_fast_time指向函数do_gettimeofday(),从而可以让其他内核模块通过 do_gettimeofday()函数来获得更精准的当前时间。⑥计算并报告根据TSC所算得的CPU时钟频率。(4)不考虑CONFIG_VISWS的情况,因此time_init()的最后一个步骤就是调用setup_irq()函数来为IRQ0挂接具体的中断服务描述符irq0。全局变量irq0是时钟中断请求的中断服务描述符,其定义如下(arch/i386/kernel/time.c):static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};显然,函数timer_interrupt()将成为时钟中断的服务程序(ISR),而SA_INTERRUPT标志也指定了 timer_interrupt()函数将是在CPU关中断的条件下执行的。结构irq0中的next指针被设置为NULL,因此IRQ0所对应的中断服务队列中只有irq0这唯一的一个元素,且IRQ0不允许中断共享。7.4.2 时钟中断服务例程timer_interrupt()中断服务描述符irq0一旦被钩挂到IRQ0的中断服务队列中去后,Linux内核就可以通过irq0->handler函数指针所指向的 timer_interrupt()函数对时钟中断请求进行真正的服务,而不是向前面所说的那样只是让CPU“空跑”一趟。此时,Linux内核可以说是真正的“跳动”起来了。在本节一开始所述的对时钟中断驱动的5项要求中,通常只有第一项(即timekeeping)是最为迫切的,因此必须在时钟中断服务例程中完成。而其余的几个要求可以稍缓,因此可以放在时钟中断的Bottom Half中去执行。这样,Linux内核就是timer_interrupt()函数的执行时间尽可能的短,因为它是在CPU关中断的条件下执行的。函数timer_interrupt()的源码如下(arch/i386/kernel/time.c):/** This is the same as the above, except we _also_ save the current* Time Stamp Counter value at the time of the timer interrupt, so that* we later on can estimate the time of day more exactly.*/static void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs){int count;/** Here we are in the timer irq handler. We just have irqs locally* disabled but we don't know if the timer_bh is running on the other* CPU. We need to avoid to SMP race with it. NOTE: we don' t need* the irq version of write_lock because as just said we have irq* locally disabled. -arca*/write_lock(&xtime_lock);if (use_tsc){/** It is important that these two operations happen almost at* the same time. We do the RDTSC stuff first, since it's* faster. To avoid any inconsistencies, we need interrupts* disabled locally.*//** Interrupts are just disabled locally since the timer irq* has the SA_INTERRUPT flag set. -arca*//* read Pentium cycle counter */rdtscl(last_tsc_low);spin_lock(&i8253_lock);outb_p(0x00, 0x43); /* latch the count ASAP */count = inb_p(0x40); /* read the latched count */count |= inb(0x40) << 8;spin_unlock(&i8253_lock);count = ((LATCH-1) - count) * TICK_SIZE;delay_at_last_interrupt = (count + LATCH/2) / LATCH;}do_timer_interrupt(irq, NULL, regs);write_unlock(&xtime_lock);}对该函数的注释如下:(1)由于函数执行期间要访问全局时间变量xtime,因此一开就对自旋锁xtime_lock进行加锁。(2)如果内核使用CPU的TSC寄存器(use_tsc变量非0),那么通过TSC寄存器来计算从时间中断的产生到timer_interrupt()函数真正在CPU上执行这之间的时间延迟:l 调用宏rdtscl()将64位的TSC寄存器值中的低32位(LSB)读到变量last_tsc_low中,以供 do_fast_gettimeoffset()函数计算时间偏差之用。这一步的实质就是将CPU TSC寄存器的值更新到内核对TSC的缓存变量last_tsc_low中。l 通过读8254 PIT的通道0的计数器的当前值来计算时间延迟,为此:首先,对自旋锁i8253_lock进行加锁。自旋锁i8253_lock的作用就是用来串行化对 8254 PIT的读写访问。其次,向8254的控制寄存器(端口0x43)中写入值0x00,以便对通道0的计数器进行锁存。最后,通过端口0x40将通道0的计数器的当前值读到局部变量count中,并解锁i8253_lock。l 显然,从时间中断的产生到timer_interrupt()函数真正执行这段时间内,以一共流逝了((LATCH-1)-count)个时钟周期,因此这个延时长度可以用如下公式计算:delay_at_last_interrupt=(((LATCH-1)-count)÷LATCH)﹡TICK_SIZE显然,上述公式的结果是个小数,应对其进行四舍五入,为此,Linux用下述表达式来计算delay_at_last_interrupt变量的值:(((LATCH-1)-count)*TICK_SIZE+LATCH/2)/LATCH上述被除数表达式中的LATCH/2就是用来将结果向上圆整成整数的。(3)在计算出时间延迟后,最后调用函数do_timer_interrupt()执行真正的时钟服务。函数do_timer_interrupt()的源码如下(arch/i386/kernel/time.c):/** timer_interrupt() needs to keep up the real-time clock,* as well as call the "do_timer()" routine every clocktick*/static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs){。。。。。。do_timer(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_usec >= 500000 - ((unsigned) tick) / 2 &&xtime.tv_usec <= 500000 + ((unsigned) tick) / 2) {if (set_rtc_mmss(xtime.tv_sec) == 0)last_rtc_update = xtime.tv_sec;elselast_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */}……}上述代码中省略了许多与SMP相关的代码,因为我们不关心SMP。从上述代码我们可以看出,do_timer_interrupt()函数主要作两件事:(1)调用do_timer()函数。(2)判断是否需要更新CMOS时钟(即RTC)中的时间。Linux仅在下列三个条件同时成立时才更新CMOS时钟:①系统全局时间状态变量 time_status中没有设置STA_UNSYNC标志,也即说明Linux有一个外部同步时钟。实际上全局时间状态变量time_status仅在一种情况下会被清除STA_SYNC标志,那就是执行adjtimex()系统调用时(这个syscall与NTP有关)。②自从上次CMOS时钟更新已经过去了11分钟。全局变量last_rtc_update保存着上次更新CMOS时钟的时间。③由于RTC存在Update Cycle,因此最好在一秒时间间隔的中间位置500ms左右调用set_rtc_mmss()函数来更新CMOS时钟。因此Linux规定仅当全局变量 xtime的微秒数tv_usec在500000±(tick/2)微秒范围范围之内时,才调用set_rtc_mmss()函数。如果上述条件均成立,那就调用s

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -