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}).
/* 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])
{
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]);
}
{
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);
printf ("ok\n");
return (inb (reg_alt_status (c)) & STA_DRQ) != 0;
}
- timer_sleep (timer_ms2ticks (10));
+ timer_msleep (10);
}
printf ("failed\n");
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
/* 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
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)
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. */
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));
+ }
+}
+