gdt[SEL_UDSEG / sizeof *gdt] = make_data_desc (3);
gdt[SEL_TSS / sizeof *gdt] = make_tss_desc (tss_get ());
- /* Load GDTR, TR. */
+ /* Load GDTR, TR. See [IA32-v3] 2.4.1, 2.4.4, 6.2.3. */
gdtr_operand = make_gdtr_operand (sizeof gdt - 1, gdt);
asm volatile ("lgdt %0" :: "m" (gdtr_operand));
asm volatile ("ltr %w0" :: "r" (SEL_TSS));
/* Returns a descriptor for an "available" 32-bit Task-State
Segment with its base at the given linear address, a limit of
- 0x67 bytes (the size of a 32-bit TSS), and a DPL of 0. */
+ 0x67 bytes (the size of a 32-bit TSS), and a DPL of 0.
+ See [IA32-v3] 6.2.2. */
static uint64_t
make_tss_desc (void *laddr)
{
#include "mmu.h"
#include "palloc.h"
+/* The Task-State Segment (TSS).
+
+ Instances of the TSS, an x86-specific structure, are used to
+ define "tasks", a form of support for multitasking built right
+ into the processor. However, for various reasons including
+ portability, speed, and flexibility, most x86 OSes almost
+ completely ignore the TSS. We are no exception.
+
+ Unfortunately, there is one thing that can only be done using
+ a TSS: stack switching for interrupts that occur in user mode.
+ When an interrupt occurs in user mode (ring 3), the processor
+ consults the ss0 and esp0 members of the current TSS to
+ determine the stack to use for handling the interrupt. Thus,
+ we must create a TSS and initialize at least these fields, and
+ this is precisely what this file does.
+
+ When an interrupt is handled by an interrupt or trap gate
+ (which applies to all interrupts we handle), an x86 processor
+ works like this:
+
+ - If the code interrupted by the interrupt is in the same
+ ring as the interrupt handler, then no stack switch takes
+ place. This is the case for interrupts that happen when
+ we're running in the kernel. The contents of the TSS are
+ irrelevant for this case.
+
+ - If the interrupted code is in a different ring from the
+ handler, then the processor switches to the stack
+ specified in the TSS for the new ring. This is the case
+ for interrupts that happen when we're in user space. It's
+ important that we switch to a stack that's not already in
+ use, to avoid corruption. Because we're running in user
+ space, we know that the current process's kernel stack is
+ not in use, so we can always use that. Thus, when the
+ scheduler switches threads, it also changes the TSS's
+ stack pointer to point to the new thread's kernel stack.
+ (The call is in schedule_tail() in thread.c.)
+
+ See [IA32-v3] 6.2.1 for a description of the TSS and 5.12.1
+ for a description of when and how stack switching occurs
+ during an interrupt. */
struct tss
{
uint16_t back_link, :16;
- void *esp0;
- uint16_t ss0, :16;
+ void *esp0; /* Ring 0 stack virtual address. */
+ uint16_t ss0, :16; /* Ring 0 stack segment selector. */
void *esp1;
uint16_t ss1, :16;
void *esp2;
uint16_t trace, bitmap;
};
+/* Kernel TSS. */
static struct tss *tss;
+/* Initializes the kernel TSS. */
void
tss_init (void)
{
tss->bitmap = 0xdfff;
}
+/* Returns the kernel TSS. */
struct tss *
tss_get (void)
{
return tss;
}
+/* Sets the ring 0 stack pointer in the TSS to ESP0. */
void
tss_set_esp0 (uint8_t *esp0)
{