rust-kernel
Rustbelt: a hobby x86_64 kernel written in Rust
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | //! The vDSO and its vvar page (stage 19).
//!
//! Programs ask for the time *a lot* (every log line, every timeout, every
//! frame of a game). A system call for each is wasteful: entering the
//! kernel and coming back costs far more than reading a clock. Linux's
//! answer, copied here, is the **vDSO**: a tiny shared library (source in
//! vdso/vdso.c, built by build.rs, embedded in the kernel below) that is
//! mapped into every process. Its `__vdso_clock_gettime` reads the CPU's
//! time stamp counter and turns it into nanoseconds itself.
//!
//! To do that it needs the same numbers the kernel uses (time.rs): the TSC
//! value at boot, the nanoseconds-per-tick multiplier and the wall-clock
//! time at boot. They live in the **vvar page**: one physical page, mapped
//! *read-only* into every process right in front of the vDSO's code. Only
//! the kernel writes it (here, in `publish`), under a seqlock: `seq` is odd
//! while a write is in progress, and readers retry if they saw an odd or
//! changed `seq`.
//!
//! How a program finds it: the auxiliary vector on its initial stack gets
//! `AT_SYSINFO_EHDR` = the address of the vDSO's ELF header (process.rs).
//! The C library walks that ELF image's dynamic symbol table to find
//! `__vdso_clock_gettime` (version "LINUX_2.6") and calls it.
//!
//! Layout in every address space (fixed addresses, just below the stack's
//! guard zone, reserved so mmap never hands them out):
//!
//! VVAR_ADDR the vvar page (read-only)
//! VDSO_ADDR = +4 KiB the vDSO image (read + execute)
//!
//! The pages are the same physical frames everywhere: allocated once at
//! boot (`init`), and mapped on first touch by the page fault handler
//! (mm.rs), which counts each mapping like any shared frame.
//!
//! When the fast path can't be used -- the TSC isn't calibrated yet, or it
//! isn't *invariant* (on some old CPUs it changes speed with the CPU's
//! clock; see `tsc_trusted` for virtual machines) -- `flags` says so and
//! the vDSO just makes the system call.
use cratememory;
use crate;
use Vec;
use ;
use PhysFrame;
/// Where the vDSO's ELF header is mapped (AT_SYSINFO_EHDR).
pub const VDSO_ADDR: u64 = VVAR_ADDR + PAGE_SIZE;
/// The vDSO itself, compiled by build.rs from vdso/vdso.c.
pub static IMAGE: & = include_bytes!;
/// Pages the image needs.
/// The vvar page's contents. Must match `struct vvar` in vdso/vdso.c.
/// The vDSO may compute the time itself.
pub const VVAR_TSC_OK: u32 = 1;
/// `rdtscp` returns the CPU number (we put it in TSC_AUX, see `init_cpu`).
pub const VVAR_RDTSCP_CPU: u32 = 2;
static PAGES: Once = new;
/// Only one writer of the vvar page at a time.
static WRITER: Mutex = new;
/// Allocate the vvar page and copy the vDSO into frames (once, at boot:
/// memory::init_frame_allocator calls this), then fill in the vvar page.
/// The frames are never freed: we keep our own reference to each, so
/// processes mapping and unmapping them never drop the count to zero.
/// The frame behind user address `page` in the vvar/vDSO area.
/// Add the two regions to a fresh address space (execve). Their pages
/// appear on first touch. Returns the AT_SYSINFO_EHDR value (0 = none).
/// Does the TSC tick at a constant rate, whatever the CPU's speed and
/// power state? (CPUID 0x8000_0007, EDX bit 8: "invariant TSC".)
/// Are we a virtual machine? (CPUID 1, ECX bit 31, "hypervisor present".)
/// May user space trust the TSC? If it's invariant, yes. A virtual CPU's
/// TSC also ticks at one fixed rate (QEMU without KVM computes it from its
/// own clock), even though QEMU can't *advertise* "invariant TSC" there --
/// so under a hypervisor we trust it too. (The kernel itself uses the TSC
/// either way; this only decides whether the vDSO may.)
/// Tests: turn the fast path off (true) to exercise the syscall fallback.
static FORCE_SYSCALL: AtomicBool = new;
/// Does the CPU have `rdtscp`? (CPUID 0x8000_0001, EDX bit 27.)
/// Per-CPU setup (every CPU, at start): put the CPU number into the
/// TSC_AUX register, which `rdtscp` returns -- the vDSO's getcpu().
/// The flags we'd publish right now (for tests and the boot log).
/// Copy the kernel's current clock numbers into the vvar page (after TSC
/// calibration, and at boot). The seqlock write: make `seq` odd, write the
/// fields, make `seq` even again. x86 never reorders stores with other
/// stores, so the compiler fences (which stop the *compiler* reordering)
/// are all we need for readers on other CPUs to see them in this order.
/// The vvar page's sequence number (tests: it must be even when idle).
|