The Task State Segment
The Task State Segment is a structure protected mode briefly introduces as the source of the stack pointer used on a ring 3 to ring 0 transition, but the TSS is a larger structure than that single field, and one nearly every kernel supporting user-mode code ends up depending on for more than just that pointer. Unlike a GDT or IDT entry, a TSS isn’t consulted by index on every access; instead, exactly one is marked current at a time by loading its selector into the special TR register, and the CPU reads fields out of that current TSS automatically whenever specific hardware events, chiefly privilege-raising interrupts, occur.
Layout
Section titled “Layout”The 64-bit TSS is a fixed 104-byte structure (plus, optionally, an appended I/O permission bitmap): three stack pointers (RSP0 through RSP2, one per privilege level a transition could land in from a lower one), seven Interrupt Stack Table pointers used by the mechanism the IDT article already covers, and a 16-bit offset field pointing to where the I/O permission bitmap begins, relative to the TSS’s own base. The 32-bit-mode TSS predates all of this and is considerably larger, since it also holds a full saved register set (EAX through EDI, segment selectors, EFLAGS) for the hardware task-switching mechanism the structure was originally designed around.
struct tss_64 { uint32_t reserved0; uint64_t rsp0, rsp1, rsp2; uint64_t reserved1; uint64_t ist1, ist2, ist3, ist4, ist5, ist6, ist7; uint64_t reserved2; uint16_t reserved3; uint16_t iopb_offset;} __attribute__((packed));The stack-switch fields and privilege transitions
Section titled “The stack-switch fields and privilege transitions”RSP0 (still commonly called ESP0/SS0, its 32-bit-mode name, in most kernel source) is the field that matters for essentially every kernel: whenever a syscall, interrupt, or exception raises the CPU’s privilege from ring 3 to ring 0, the CPU loads RSP from this field before pushing the interrupt frame, because a ring 3 stack cannot be trusted to have enough room, or even to be mapped at all, for the kernel to safely push data onto. Without a valid RSP0, there is no defined destination for that push at all; a transition attempted with the TSS unset or pointing at unmapped memory faults immediately, before a single instruction of the handler itself runs. RSP1 and RSP2 exist for symmetry with ring 1 and ring 2, but see essentially no practical use, since almost no kernel defines intermediate privilege levels between user and kernel code.
The I/O permission bitmap
Section titled “The I/O permission bitmap”The bitmap iopb_offset points to is what determines whether ring 3 code can execute IN/OUT directly against a given port without faulting and trapping into the kernel first: one bit per I/O port, with a set bit meaning “this port is not accessible,” read at the moment ring 3 code attempts a port access. A kernel that wants user-mode code to have unrestricted port access, the common case, points iopb_offset past the end of the TSS’s actual limit, which the CPU treats as “no bitmap present” and denies all ports; a kernel wanting to grant a specific process access to specific ports (a userspace driver reading one particular device’s ports, for instance) instead allocates the full 8 KB bitmap and clears only the bits for the ports that process is permitted to touch. This is a per-TSS setting, not a per-process one directly, so switching which process’s I/O permissions are active on a single-TSS-per-core design means rewriting the relevant bitmap bits on every context switch between a process with elevated port access and one without, a cost some kernels avoid by keeping port I/O behind system calls entirely rather than ever granting ring 3 direct access.
One TSS per CPU
Section titled “One TSS per CPU”TR is a single register, one per core, so a symmetric multiprocessing kernel cannot share a single TSS across CPUs: each core needs its own TSS, its own GDT entry describing that TSS (or its own dedicated slot in a shared GDT), and its own LTR load performed during that core’s bring-up, before the core can safely take an interrupt at all. Getting this wrong in a way that’s easy to miss during single-core testing shows up only once a second CPU is brought online: two cores sharing one TSS end up racing to overwrite the same RSP0 field, so whichever core’s write happened most recently silently determines where the other core’s next ring 0 transition pushes its interrupt frame, corrupting that core’s kernel stack in a way that looks, from the crash alone, unrelated to the actual cause.
Implementation notes
Section titled “Implementation notes”A GDT TSS descriptor is 16 bytes on x86-64 rather than the usual 8, since it needs the full 64-bit base address a TSS living anywhere in a 64-bit address space requires; a kernel that copies the 32-bit 8-byte descriptor layout by habit ends up corrupting whatever descriptor follows it in the table. LTR also has a one-time restriction worth knowing before it causes a confusing fault: the TSS descriptor’s busy bit must be clear before the load, and LTR itself sets that bit as a side effect, so attempting to LTR the same selector twice without an intervening task switch (which, on the interrupt-based transitions covered above, never happens) faults on the second attempt.
References
Section titled “References”- ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, Chapter 8: the complete TSS layout, the I/O permission bitmap, and the
LTR/STRinstructions.
See also
Section titled “See also”- Protected Mode: the article that first introduces the TSS’s role in a ring 3 → ring 0 transition, expanded on here.
- The Interrupt Descriptor Table: the Interrupt Stack Table mechanism, itself defined inside the current TSS, covered from the IDT gate descriptor’s side.
- Port I/O versus Memory-Mapped I/O: the
IN/OUTmechanism the I/O permission bitmap above gates access to. - Debug Registers and Hardware Breakpoints: a ring-0-only mechanism with no equivalent of the ring-3 access control this article’s bitmap provides.
- Multiprocessor Bring-Up: the per-CPU setup, TSS included, each additional core needs before it can safely take an interrupt.