Messages in this thread Patch in this message | | | From | "Ulrich Windl" <> | Date | Tue, 16 Sep 1997 08:47:18 +0200 | Subject | 2.0 (i386) patch: "completely mad time" |
| |
(Out of despair and lack of other good ideas I invented the "Complete Mad Time (TM)":)
There's a difference with the timeoffset taken from the timer chip and the fasttimeoffset taken from the Pentium cycle counter:
If a time interrupt is delayed with the Pentium stuff, the error accumulates (e.g. when the first is 500µs late and the next is 200µs late, the total delay is 700µs). Due to the way the fasttimeoffset is reset in the timer interrupt routine, these delays are lost forever. I call this badness "1"
If a timer interrupt is delayed significantly (for more than one tick), the current timeoffset is clamped at one tick. Thus the time would no longer increase until the interrupt is processed. I call that badness "2".
If you remove the clamping, the timeoffset can grow beyond one tick, but on the next timer interrupt only one tick will be added and the offset will be reset to zero, efectively makeing the time run backwards. I call this badness "3".
I tried to fix these problems with my CMT (Completely Mad Time) patch on top of 2.0.31-pre9 (The kernel should be tested anyway): The clamping in the fast timeoffset has been removed, and the "cycles per microsecond" is available more globally.
In the timer interrupt the delay is computed from the cycle counter and added to the system clock. If the amount is larger than one tick (very bad), only one tick is corrected and the remainder is accumulated for later correction. If the delay is smaller, any remaining correction from previous runs is subtracted from the system time, but only up to one tick (because the time between the last to invokations of the time routine was shorter than one tick then).
I haven't dealt with the APM case yet, and maybe some constants need tuning (border cases). After all it looks not much worse than the previous code. Is anyone out there that wants to test the patch? Ingo Molnar still around?
There are two patches on top of 2.0.31-pre9 and against arch/i386/kernel/time.c. Finally I have included a histogram of measured time differences in a tight loop. Time doesn't look too bad. The Program was taken from the xntp distribution (util/hist.c).
Have a nice time, have a mad time, have CMT
Ulrich
--- time.c 1997/09/12 19:35:53 1.2 +++ time.c 1997/09/15 19:19:36 @@ -52,7 +52,7 @@ { register unsigned long eax asm("ax"); register unsigned long edx asm("dx"); - unsigned long tmp, quotient, low_timer, missing_time; + unsigned long tmp, quotient;
/* Last jiffy when do_fast_gettimeoffset() was called.. */ static unsigned long last_jiffies=0; @@ -63,21 +63,11 @@ /* The "clocks per usec" value is calculated once each jiffy */ tmp = jiffies; quotient = cached_quotient; - low_timer = last_timer_cc.low; - missing_time = 0; if (last_jiffies != tmp) { last_jiffies = tmp; - /* - * test for hanging bottom handler (this means xtime is not - * updated yet) - */ - if (test_bit(TIMER_BH, &bh_active) ) - { - missing_time = 1000020/HZ; - }
/* Get last timer tick in absolute kernel time */ - eax = low_timer; + eax = last_timer_cc.low; edx = last_timer_cc.high; __asm__("subl "SYMBOL_NAME_STR(init_timer_cc)",%0\n\t" "sbbl "SYMBOL_NAME_STR(init_timer_cc)"+4,%1" @@ -95,7 +85,7 @@ :"r" (tmp), "0" (eax), "1" (edx));
- edx = 1000020/HZ; + edx = tick; tmp = eax; eax = 0;
@@ -112,28 +102,20 @@ :"=a" (eax), "=d" (edx));
/* .. relative to previous jiffy (32 bits is enough) */ - edx = 0; - eax -= low_timer; + __asm__("subl "SYMBOL_NAME_STR(last_timer_cc)",%0\n\t" + "sbbl "SYMBOL_NAME_STR(last_timer_cc)"+4,%1" + :"=a" (eax), "=d" (edx) + :"0" (eax), "1" (edx));
/* - * Time offset = (1000020/HZ * time_low) / quotient. + * Convert to microseconds */ - __asm__("mull %2" :"=a" (eax), "=d" (edx) :"r" (quotient), "0" (eax), "1" (edx));
- /* - * Due to rounding errors (and jiffies inconsistencies), - * we need to check the result so that we'll get a timer - * that is monotonic. - */ - if (edx >= 1000020/HZ) - edx = 1000020/HZ-1; - - eax = edx + missing_time; - return eax; + return edx; } #endif
--- arch/i386/kernel/time.c 1997/09/15 19:19:36 1.3 +++ arch/i386/kernel/time.c 1997/09/15 20:35:20 @@ -43,6 +43,9 @@ unsigned long high; } init_timer_cc, last_timer_cc;
+/* Cached "clocks per usec" value.. */ +static unsigned long cached_quotient=0; + /* * This is more assembly than C, but it's also rather * timing-critical and we have to use assembler to get @@ -57,12 +60,8 @@ /* Last jiffy when do_fast_gettimeoffset() was called.. */ static unsigned long last_jiffies=0;
- /* Cached "clocks per usec" value.. */ - static unsigned long cached_quotient=0; - /* The "clocks per usec" value is calculated once each jiffy */ tmp = jiffies; - quotient = cached_quotient; if (last_jiffies != tmp) { last_jiffies = tmp;
@@ -94,8 +93,8 @@ :"r" (tmp), "0" (eax), "1" (edx)); cached_quotient = eax; - quotient = eax; } + quotient = cached_quotient;
/* Read the time counter */ __asm__(".byte 0x0f,0x31" @@ -360,6 +359,58 @@ }
#ifndef CONFIG_APM /* cycle counter may be unreliable */ +static void do_clock_correction(void) +{ + static long last_delay = 0; + register unsigned long eax asm("ax"); + register unsigned long edx asm("dx"); + unsigned long quotient = cached_quotient; + unsigned long usecs; + + /* Read the time counter.. */ + __asm__(".byte 0x0f,0x31" + :"=a" (eax), "=d" (edx)); + + /* ..relative to previous interrupt */ + __asm__("subl "SYMBOL_NAME_STR(last_timer_cc)",%0\n\t" + "sbbl "SYMBOL_NAME_STR(last_timer_cc)"+4,%1" + :"=a" (eax), "=d" (edx) + :"0" (eax), "1" (edx)); + + /* + * Check if we are late + */ + __asm__("mull %2" + :"=a" (eax), "=d" (edx) + :"r" (quotient), + "0" (eax), "1" (edx)); + usecs = edx; + if (usecs > tick) { + /* add the amount that we are late */ + if ((xtime.tv_usec += usecs - tick) > 1000000) + ++xtime.tv_sec, xtime.tv_usec -= 1000000; + last_delay += usecs - tick; + } + else if ( last_delay > 0) { + /* we are on schedule, but were late previously */ + /* as we should reduce tick now, we just reduce the time to + make it correct again */ + if ( last_delay > tick ) { + /* we have a slight problem here */ + xtime.tv_usec -= tick; + last_delay -= tick; + } + else { + xtime.tv_usec -= last_delay; + last_delay = 0; + } + if (xtime.tv_usec < 0) + --xtime.tv_sec, xtime.tv_usec += 1000000; + /* NOTE: Time won't go backwards (we'll add another tick) */ + + } +} + /* * This is the same as the above, except we _also_ save the current * cycle counter value at the time of the timer interrupt, so that @@ -367,10 +418,14 @@ */ static void pentium_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) { - /* read Pentium cycle counter */ + do_clock_correction(); + /* read Pentium cycle counter and let's forget that we were late + * (that's stored in last_delay + */ __asm__(".byte 0x0f,0x31" :"=a" (last_timer_cc.low), "=d" (last_timer_cc.high)); + timer_interrupt(irq, NULL, regs); } #endif # Histogram: time between calls, number of occurrencies 4 11690501 5 110328542 6 36456 7 330 8 18 9 1 15 3188 16 29589 17 21253 18 4513 19 883 20 379 21 306 22 276 23 302 24 279 25 119 26 48 27 73 28 650 29 563 30 143 31 24 32 3 ... 396 1 401 1 427 1 438 1 439 1 443 1 456 1 496 1 524 1 525 1 606 1 755 1 962 2 1301 1 1428 1 1669 1 1836 1 1865 1 1870 1 1889 1 2400 1 6301 1 6309 1 12876 1
| |