Support accurate short delays in the timer code, to speed up disk
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 13 Dec 2004 22:42:01 +0000 (22:42 +0000)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 13 Dec 2004 22:42:01 +0000 (22:42 +0000)
access.  (Before, a "400 ns" sleep took up to 1/TIMER_FREQ seconds.
Now it should be an accurate delays.)

Update projects, tour.

doc/threads.texi
doc/tour.texi
src/devices/disk.c
src/devices/timer.c
src/devices/timer.h
src/threads/init.c

index 2e0032657560c8be1799fcb5b8906e1beb8ce077..49183be9c6027536815b01b835a5bb700bb23083 100644 (file)
@@ -387,11 +387,17 @@ advanced far enough.  This is undesirable because it wastes time that
 could potentially be used more profitably by another thread.  Your
 solution should not busy wait.
 
-The argument to @func{timer_sleep} is expressed in timer ticks, not
-in milliseconds or another unit.  There are @code{TIMER_FREQ} timer
+The argument to @func{timer_sleep} is expressed in timer ticks, not in
+milliseconds or any another unit.  There are @code{TIMER_FREQ} timer
 ticks per second, where @code{TIMER_FREQ} is a macro defined in
 @code{devices/timer.h}.
 
+Separate functions @func{timer_msleep}, @func{timer_usleep}, and
+@func{timer_nsleep} do exist for sleeping a specific number of
+milliseconds, microseconds, or nanoseconds, respectively, but these will
+call @func{timer_sleep} automatically when necessary.  You do not need
+to modify them.
+
 If your delays seem too short or too long, reread the explanation of the
 @option{-r} option to @command{pintos} (@pxref{Debugging versus
 Testing}).
index ee90cca0f55d22b0896bcc144a35416624655a8f..e9ba008f89ef9a15843dac2875e423b3f1ceefb7 100644 (file)
@@ -173,7 +173,8 @@ and @func{syscall_init}.
 Now that interrupts are set up, we can start preemptively scheduling
 threads with @func{thread_start}, which also enables interrupts.
 Interrupt-driven serial port I/O is also possible now, so we use
-@func{serial_init_queue} to switch to that mode.
+@func{serial_init_queue} to switch to that mode.  Finally,
+@func{timer_calibrate} calibrates the timer for accurate short delays.
 
 If the filesystem is compiled in, as it will be in project 2 and
 later, we now initialize the disks with @func{disk_init}, then the
index 206f69c91feceed3f6a5eef2776f1c8e34a8521b..971f3af537288f38523d92e0807e2489870e33f6 100644 (file)
@@ -285,12 +285,12 @@ reset_channel (struct channel *c)
   /* Issue soft reset sequence, which selects device 0 as a side effect.
      Also enable interrupts. */
   outb (reg_ctl (c), 0);
-  timer_sleep (timer_us2ticks (10));
+  timer_usleep (10);
   outb (reg_ctl (c), CTL_SRST);
-  timer_sleep (timer_us2ticks (10));
+  timer_usleep (10);
   outb (reg_ctl (c), 0);
 
-  timer_sleep (timer_ms2ticks (150));
+  timer_msleep (150);
 
   /* Wait for device 0 to clear BSY. */
   if (present[0]) 
@@ -309,7 +309,7 @@ reset_channel (struct channel *c)
         {
           if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1)
             break;
-          timer_sleep (timer_ms2ticks (10));
+          timer_msleep (10);
         }
       wait_while_busy (&c->devices[1]);
     }
@@ -475,7 +475,7 @@ wait_until_idle (const struct disk *d)
     {
       if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0)
         return;
-      timer_sleep (timer_us2ticks (10));
+      timer_usleep (10);
     }
 
   printf ("%s: idle timeout\n", d->name);
@@ -501,7 +501,7 @@ wait_while_busy (const struct disk *d)
             printf ("ok\n");
           return (inb (reg_alt_status (c)) & STA_DRQ) != 0;
         }
-      timer_sleep (timer_ms2ticks (10));
+      timer_msleep (10);
     }
 
   printf ("failed\n");
@@ -518,7 +518,7 @@ select_device (const struct disk *d)
     dev |= DEV_DEV;
   outb (reg_device (c), dev);
   inb (reg_alt_status (c));
-  timer_sleep (timer_ns2ticks (400));
+  timer_nsleep (400);
 }
 
 /* Select disk D in its channel, as select_device(), but wait for
index ecf6c5aacdfee97c6577c8b60912475ef99e4e77..3724abf944ab4fbf7d53936cdbe822a2a7b9a5a5 100644 (file)
 /* Number of timer ticks since OS booted. */
 static volatile int64_t ticks;
 
+/* Number of loops per timer tick.
+   Initialized by timer_calibrate(). */
+static unsigned loops_per_tick;
+
 static intr_handler_func timer_interrupt;
+static bool too_many_loops (unsigned loops);
+static void busy_wait (int64_t loops);
+static void real_time_sleep (int64_t num, int32_t denom);
 
 /* Sets up the 8254 Programmable Interval Timer (PIT) to
    interrupt PIT_FREQ times per second, and registers the
@@ -42,6 +49,30 @@ timer_init (void)
   intr_register (0x20, 0, INTR_OFF, timer_interrupt, "8254 Timer");
 }
 
+/* Calibrates loops_per_tick, used to implement brief delays. */
+void
+timer_calibrate (void) 
+{
+  unsigned high_bit, test_bit;
+
+  ASSERT (intr_get_level () == INTR_ON);
+  printf ("Calibrating timer...  ");
+
+  /* Approximate loops_per_tick as the largest power-of-two
+     still less than one timer tick. */
+  loops_per_tick = 1u << 10;
+  while (!too_many_loops (loops_per_tick << 1))
+    loops_per_tick <<= 1;
+
+  /* Refine the next 8 bits of loops_per_tick. */
+  high_bit = loops_per_tick;
+  for (test_bit = high_bit >> 1; test_bit != high_bit >> 10; test_bit >>= 1)
+    if (!too_many_loops (high_bit | test_bit))
+      loops_per_tick |= test_bit;
+
+  printf ("%'"PRIu64" loops/s.\n", (uint64_t) loops_per_tick * TIMER_FREQ);
+}
+
 /* Returns the number of timer ticks since the OS booted. */
 int64_t
 timer_ticks (void) 
@@ -71,29 +102,25 @@ timer_sleep (int64_t ticks)
     thread_yield ();
 }
 
-/* Returns MS milliseconds in timer ticks, rounding up. */
-int64_t
-timer_ms2ticks (int64_t ms) 
+/* Suspends execution for approximately MS milliseconds. */
+void
+timer_msleep (int64_t ms) 
 {
-  /*       MS / 1000 s          
-     ------------------------ = MS * TIMER_FREQ / 1000 ticks. 
-     (1 / TIMER_FREQ) ticks/s
-  */
-  return DIV_ROUND_UP (ms * TIMER_FREQ, 1000);
+  real_time_sleep (ms, 1000);
 }
 
-/* Returns US microseconds in timer ticks, rounding up. */
-int64_t
-timer_us2ticks (int64_t us) 
+/* Suspends execution for approximately US microseconds. */
+void
+timer_usleep (int64_t us) 
 {
-  return DIV_ROUND_UP (us * TIMER_FREQ, 1000000);
+  real_time_sleep (us, 1000 * 1000);
 }
 
-/* Returns NS nanoseconds in timer ticks, rounding up. */
-int64_t
-timer_ns2ticks (int64_t ns) 
+/* Suspends execution for approximately NS nanoseconds. */
+void
+timer_nsleep (int64_t ns) 
 {
-  return DIV_ROUND_UP (ns * TIMER_FREQ, 1000000000);
+  real_time_sleep (ns, 1000 * 1000 * 1000);
 }
 
 /* Prints timer statistics. */
@@ -112,3 +139,68 @@ timer_interrupt (struct intr_frame *args UNUSED)
   if (ticks % TIME_SLICE == 0)
     intr_yield_on_return ();
 }
+
+/* Returns true if LOOPS iterations waits for more than one timer
+   tick, otherwise false. */
+static bool
+too_many_loops (unsigned loops) 
+{
+  int64_t start;
+
+  /* Wait for a timer tick. */
+  start = ticks;
+  while (ticks == start)
+    continue;
+
+  /* Run LOOPS loops. */
+  start = ticks;
+  busy_wait (loops);
+
+  /* If the tick count changed, we iterated too long. */
+  return start != ticks;
+}
+
+/* Iterates through a simple loop LOOPS times, for implementing
+   brief delays.
+
+   Marked NO_INLINE because code alignment can significantly
+   affect timings, so that if this function was inlined
+   differently in different places the results would be difficult
+   to predict. */
+static void NO_INLINE
+busy_wait (int64_t loops) 
+{
+  while (loops-- > 0)
+    continue;
+}
+
+/* Sleep for approximately NUM/DENOM seconds. */
+static void
+real_time_sleep (int64_t num, int32_t denom) 
+{
+  /* Convert NUM/DENOM seconds into timer ticks, rounding down.
+          
+        (NUM / DENOM) s          
+     ---------------------- = NUM * TIMER_FREQ / DENOM ticks. 
+     1 s / TIMER_FREQ ticks
+  */
+  int64_t ticks = num * TIMER_FREQ / denom;
+
+  ASSERT (intr_get_level () == INTR_ON);
+  if (ticks > 0)
+    {
+      /* We're waiting for at least one full timer tick.  Use
+         timer_sleep() because it will yield the CPU to other
+         processes. */                
+      timer_sleep (ticks); 
+    }
+  else 
+    {
+      /* Otherwise, use a busy-wait loop for more accurate
+         sub-tick timing.  We scale the numerator and denominator
+         down by 1000 to avoid the possibility of overflow. */
+      ASSERT (denom % 1000 == 0);
+      busy_wait (loops_per_tick * num / 1000 * TIMER_FREQ / (denom / 1000)); 
+    }
+}
+
index 59f7c9a57835dc6fbc523b47c4cdd3d086119a17..45a3f72e1127cd02f97b79d995277bec67a0812f 100644 (file)
@@ -8,14 +8,15 @@
 #define TIMER_FREQ 100
 
 void timer_init (void);
+void timer_calibrate (void);
+
 int64_t timer_ticks (void);
 int64_t timer_elapsed (int64_t);
 
 void timer_sleep (int64_t ticks);
-
-int64_t timer_ms2ticks (int64_t ms);
-int64_t timer_us2ticks (int64_t us);
-int64_t timer_ns2ticks (int64_t ns);
+void timer_msleep (int64_t milliseconds);
+void timer_usleep (int64_t microseconds);
+void timer_nsleep (int64_t nanoseconds);
 
 void timer_print_stats (void);
 
index 741f0e42e4339b8824f16646aae237de0f365dc0..1135ae7ccecc79ad1593671c7e3bbd906c31caa1 100644 (file)
@@ -105,6 +105,7 @@ main (void)
   /* Start thread scheduler and enable interrupts. */
   thread_start ();
   serial_init_queue ();
+  timer_calibrate ();
 
 #ifdef FILESYS
   /* Initialize filesystem. */