kernel: reclaim domain resources after lifecycle quiescence
dde478e3e6cbc3a2b9189e616fa46f9e2d3375fcd92c13204421190f93d54b21
Verified: make -C kernel check; lifecycle transitions, terminal events, surviving Page recipients, and native instance reclamation pass.
1 parent
04ed5958
kernel/NOTES.md
+23 -2
| 1 | 1 | # Kernel implementation decisions |
|
| 2 | 2 | ||
| 3 | 3 | The specification at https://radiant.computer/system/kernel takes precedence |
|
| 4 | 4 | for fixed call numbers, handle layout, rights, and object behavior. These notes |
|
| 5 | - | record the contracts established through step 18 of the 22-step plan. |
|
| 5 | + | record the contracts established through step 19 of the 22-step plan. |
|
| 6 | 6 | ||
| 7 | 7 | ## Source and trust boundary |
|
| 8 | 8 | ||
| 9 | 9 | - Kernel mechanisms use freestanding Radiance; RAS owns machine entry, register |
|
| 10 | 10 | state, atomics, and MMIO. Hosted checks exercise the same mechanism modules. |
| 257 | 257 | old incarnation's ancestry bit without erasing surviving ancestors. Private |
|
| 258 | 258 | frame ownership prevents reuse before reclamation. |
|
| 259 | 259 | - The eighth native image checks instance state: initial value 41 becomes 42 |
|
| 260 | 260 | and 44 across two contexts in one domain, while another domain reports 42. |
|
| 261 | 261 | ||
| 262 | + | ## Termination and reclamation |
|
| 263 | + | ||
| 264 | + | - The first accepted Exit or Fault fixes the terminal report. Dying domains |
|
| 265 | + | immediately lose lookup, activation, and dispatch authority. Retain physical |
|
| 266 | + | state until every context has returned its architectural frame and completed |
|
| 267 | + | budget accounting. |
|
| 268 | + | - Destruction may select creation descendants independently of notification |
|
| 269 | + | parents. Reparent requires Destroy on the child and Wake on the new parent; |
|
| 270 | + | reserve the new terminal credit before releasing the old one. Queued terminal |
|
| 271 | + | reports keep their original receiver. |
|
| 272 | + | - Reclamation releases context bindings, private stacks, installed capabilities, |
|
| 273 | + | and lifetime Page pins. Surviving recipients retain Page memory. Domain, |
|
| 274 | + | Events, handle, and context incarnations prevent stale-name reuse. |
|
| 275 | + | - Root termination obeys the same lifecycle rules. Surviving children become |
|
| 276 | + | parentless and retain independent authority. Domain exit is not machine |
|
| 277 | + | shutdown. |
|
| 278 | + | - Checks cover running-context retention, cascades through dead creators, |
|
| 279 | + | exhausted reparenting credits, stale names, and malformed parent progress. |
|
| 280 | + | Destroying an image instance must preserve its sibling and immutable initializer |
|
| 281 | + | so a newly created instance starts from the same value. |
|
| 282 | + | ||
| 262 | 283 | ## Validation |
|
| 263 | 284 | ||
| 264 | 285 | Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass |
|
| 265 | 286 | `EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build |
|
| 266 | 287 | checks compiler dependencies. From the repository root, run: |
|
| 267 | 288 | ||
| 268 | 289 | ```sh |
|
| 269 | 290 | make -C kernel check |
|
| 270 | 291 | ``` |
|
| 271 | 292 | ||
| 272 | - | Run the eight-image native workload and hosted activation checks. Check complete argument/stack coverage, independent private instances, and two contexts sharing one instance with reported values 42 and 44 versus 42 in another. |
|
| 293 | + | Exercise deferred resource retention, creation cascades, reparenting capacity, first terminal reports, surviving Page recipients, and stale identities. Destroy an instance, preserve its sibling, then create and execute a fresh instance from the unchanged initializer. |
|
| 273 | 294 | ||
| 274 | 295 | Run the context reservation probe with an emulator that retains LR/SC |
|
| 275 | 296 | reservations across traps. This checks the kernel's reservation invalidation. |
kernel/check.rad
+2 -0
| 12 | 12 | mod budget_caps; |
|
| 13 | 13 | mod timers; |
|
| 14 | 14 | mod notifications; |
|
| 15 | 15 | mod devices; |
|
| 16 | 16 | mod activation; |
|
| 17 | + | mod lifecycle; |
|
| 17 | 18 | ||
| 18 | 19 | /// Run the available kernel mechanism checks. |
|
| 19 | 20 | @default fn main() -> u32 { |
|
| 20 | 21 | frames::run(); |
|
| 21 | 22 | boot::run(); |
| 28 | 29 | budget_caps::run(); |
|
| 29 | 30 | timers::run(); |
|
| 30 | 31 | notifications::run(); |
|
| 31 | 32 | devices::run(); |
|
| 32 | 33 | activation::run(); |
|
| 34 | + | lifecycle::run(); |
|
| 33 | 35 | return 0; |
|
| 34 | 36 | } |
kernel/check/lifecycle.rad
added
+285 -0
| 1 | + | //! Incarnation invalidation, creation ancestry, credits, and quiescent teardown. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::budget_caps; |
|
| 5 | + | use core::budgets; |
|
| 6 | + | use core::capabilities; |
|
| 7 | + | use core::contexts; |
|
| 8 | + | use core::domains; |
|
| 9 | + | use core::events; |
|
| 10 | + | use core::fdt; |
|
| 11 | + | use core::frames; |
|
| 12 | + | use core::handles; |
|
| 13 | + | use core::lifecycle; |
|
| 14 | + | use core::memory; |
|
| 15 | + | use core::pages; |
|
| 16 | + | use core::platform; |
|
| 17 | + | use core::resources; |
|
| 18 | + | use core::state; |
|
| 19 | + | ||
| 20 | + | /// Simulated physical backing, never dereferenced as a host physical address. |
|
| 21 | + | static RAM: [u8; 64 * frames::PAGE_SIZE] = undefined; |
|
| 22 | + | /// Enough slots for a creator chain, survivors, and an unrelated replacement. |
|
| 23 | + | static DOMAINS: [domains::Domain; 8] = undefined; |
|
| 24 | + | /// Page and retained-budget identities. |
|
| 25 | + | static OBJECTS: [resources::Slot; 8] = undefined; |
|
| 26 | + | /// Both contexts must quiesce before either private stack is reclaimed. |
|
| 27 | + | static CONTEXTS: [contexts::Context; 2] = undefined; |
|
| 28 | + | /// Frame availability and lifetime accounting. |
|
| 29 | + | static POOL: frames::Pool = undefined; |
|
| 30 | + | static PINS: [u16; 64] = undefined; |
|
| 31 | + | static ASSIGNED: [bool; 64] = undefined; |
|
| 32 | + | static GRANTS: [u64; 8] = undefined; |
|
| 33 | + | /// Public mechanism state shared by each isolated scenario. |
|
| 34 | + | static KERNEL: state::State = undefined; |
|
| 35 | + | ||
| 36 | + | /// Clear only the allocator's simulated physical range. |
|
| 37 | + | fn clear(base: u64, size: u32) { |
|
| 38 | + | assert base <= RAM.len as u64 and size as u64 <= RAM.len as u64 - base; |
|
| 39 | + | for i in base as u32..base as u32 + size { set RAM[i] = 0; } |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | /// Reset boot-owned storage between behavior scenarios. |
|
| 43 | + | fn setup() -> abi::Object { |
|
| 44 | + | let mut machine: platform::Platform = undefined; |
|
| 45 | + | set machine.memoryCount = 1; |
|
| 46 | + | set machine.memory[0] = fdt::Range { base: 0, size: RAM.len as u64 }; |
|
| 47 | + | set machine.reservedCount = 0; |
|
| 48 | + | try! frames::init(&mut POOL, &machine); |
|
| 49 | + | let mut ram: memory::Memory = undefined; |
|
| 50 | + | memory::init(&mut ram, &mut POOL, &mut PINS[..], &mut ASSIGNED[..], &mut GRANTS[..], DOMAINS.len); |
|
| 51 | + | domains::init(&mut DOMAINS[..]); |
|
| 52 | + | resources::init(&mut OBJECTS[..]); |
|
| 53 | + | for i in 0..CONTEXTS.len { contexts::init(&mut CONTEXTS[i], i); } |
|
| 54 | + | set KERNEL = state::State { domains: &mut DOMAINS[..], resources: &mut OBJECTS[..], contexts: &mut CONTEXTS[..], memory: ram }; |
|
| 55 | + | let root = domains::root(&mut DOMAINS[..], 0); |
|
| 56 | + | return try! domains::resolve(&DOMAINS[..], 0, root, abi::CREATE); |
|
| 57 | + | } |
|
| 58 | + | ||
| 59 | + | /// Install test-owned authority through the real handle generation mechanism. |
|
| 60 | + | fn handle(caller: u32, object: abi::Object, rights: u16) -> abi::Handle { |
|
| 61 | + | resources::retain(&mut OBJECTS[..], object); |
|
| 62 | + | return handles::install(&mut DOMAINS[caller].handles, try! handles::vacant(&DOMAINS[caller].handles), object, rights); |
|
| 63 | + | } |
|
| 64 | + | ||
| 65 | + | /// A healthy consumer permits one complete bounded reclamation pass. |
|
| 66 | + | fn poll() { |
|
| 67 | + | if let bad = lifecycle::poll(&mut KERNEL) { panic "poll: unexpected invalid consumer"; } |
|
| 68 | + | } |
|
| 69 | + | ||
| 70 | + | /// Reject stale authority through consumer-visible lookup, not table inspection. |
|
| 71 | + | fn badHandle(caller: u32, name: abi::Handle) { |
|
| 72 | + | let _entry = try capabilities::lookup(&KERNEL, caller, name) catch error { |
|
| 73 | + | assert error == abi::Error::BadHandle; |
|
| 74 | + | return; |
|
| 75 | + | }; |
|
| 76 | + | panic "badHandle: stale capability regained authority"; |
|
| 77 | + | } |
|
| 78 | + | ||
| 79 | + | /// Require rejected destroy requests to report an error rather than succeed idle. |
|
| 80 | + | fn badRequest(caller: u32, name: abi::Handle, flags: u64, expected: abi::Error) { |
|
| 81 | + | let _owner = try lifecycle::request(&mut KERNEL, caller, name, flags) catch error { |
|
| 82 | + | assert error == expected; |
|
| 83 | + | return; |
|
| 84 | + | }; |
|
| 85 | + | panic "badRequest: invalid destroy succeeded"; |
|
| 86 | + | } |
|
| 87 | + | ||
| 88 | + | /// Old execution identities remain invalid after their slots are prepared again. |
|
| 89 | + | fn badContext(id: u64) { |
|
| 90 | + | let _index = try contexts::resolve(&CONTEXTS[..], id) catch error { |
|
| 91 | + | assert error == abi::Error::InvalidArg; |
|
| 92 | + | return; |
|
| 93 | + | }; |
|
| 94 | + | panic "badContext: stale context resolved"; |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | /// Preserve physical ownership until all charged execution has returned. |
|
| 98 | + | fn quiescence() { |
|
| 99 | + | let root = setup(); |
|
| 100 | + | let child = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 101 | + | let target = handle(0, child, abi::DOMAIN_RIGHTS); |
|
| 102 | + | let targetSlot = try! handles::slot(&DOMAINS[0].handles, target); |
|
| 103 | + | let self = handle(child.index, child, abi::ROOT_RIGHTS); |
|
| 104 | + | let parent = handle(child.index, root, abi::WAKE); |
|
| 105 | + | let oldEvents = abi::Handle { bits: DOMAINS[child.index].env.eventsHandle }; |
|
| 106 | + | let eventsAlias = try! capabilities::grant(&mut KERNEL, child.index, oldEvents, abi::Handle { bits: 0 }, abi::READ as u64); |
|
| 107 | + | let shared = try! pages::allocate(&mut KERNEL, child.index, self, 2, clear); |
|
| 108 | + | let external = try! capabilities::grant(&mut KERNEL, child.index, shared, parent, abi::PAGE_RIGHTS as u64); |
|
| 109 | + | let dropped = try! pages::allocate(&mut KERNEL, child.index, self, 1, clear); |
|
| 110 | + | try! capabilities::drop(&mut KERNEL, child.index, dropped); |
|
| 111 | + | let _exclusive = try! pages::allocate(&mut KERNEL, child.index, self, 1, clear); |
|
| 112 | + | set DOMAINS[child.index].private = frames::install(try! frames::allocate(&mut POOL, 2, clear)); |
|
| 113 | + | set DOMAINS[child.index].state = domains::Lifecycle::Active; |
|
| 114 | + | for i in 0..CONTEXTS.len { |
|
| 115 | + | try! contexts::prepare(&mut CONTEXTS[i], child, 32, 4096, DOMAINS[child.index].env); |
|
| 116 | + | set CONTEXTS[i].private = frames::install(try! frames::allocate(&mut POOL, 1, clear)); |
|
| 117 | + | } |
|
| 118 | + | let id = contexts::identity(&CONTEXTS[0]); |
|
| 119 | + | let second = contexts::identity(&CONTEXTS[1]); |
|
| 120 | + | let budget = try! resources::create(&mut OBJECTS[..], resources::Value::Budget(try! budgets::init(0, 100))); |
|
| 121 | + | let budgetHandle = handle(0, budget, abi::EXECUTE); |
|
| 122 | + | try! budget_caps::bind(&mut KERNEL, 0, budgetHandle, target, id); |
|
| 123 | + | try! capabilities::drop(&mut KERNEL, 0, budgetHandle); |
|
| 124 | + | let running = try! contexts::begin(&mut CONTEXTS[0], budget_caps::value(&mut KERNEL, budget), 10, 0); |
|
| 125 | + | let owner = try! lifecycle::request(&mut KERNEL, 0, target, 0); |
|
| 126 | + | badHandle(0, target); |
|
| 127 | + | badHandle(child.index, oldEvents); |
|
| 128 | + | badHandle(child.index, eventsAlias); |
|
| 129 | + | badRequest(0, target, 0, abi::Error::BadHandle); |
|
| 130 | + | poll(); |
|
| 131 | + | assert not lifecycle::completed(&DOMAINS[..], owner); |
|
| 132 | + | assert POOL.available == 56; |
|
| 133 | + | assert try! contexts::resolve(&CONTEXTS[..], second) == 1; |
|
| 134 | + | assert DOMAINS[0].events.ring.tail == 0; |
|
| 135 | + | assert budget_caps::value(&mut KERNEL, budget).context == id; |
|
| 136 | + | assert contexts::finish(&mut CONTEXTS[0], budget_caps::value(&mut KERNEL, budget), running, 15) == 95; |
|
| 137 | + | poll(); |
|
| 138 | + | assert lifecycle::completed(&DOMAINS[..], owner); |
|
| 139 | + | badContext(id); |
|
| 140 | + | badContext(second); |
|
| 141 | + | assert POOL.available == 62; |
|
| 142 | + | assert resources::kind(OBJECTS[budget.index].value) == abi::Kind::Empty; |
|
| 143 | + | assert (try! pages::access(&KERNEL, 0, external, abi::READ)).size == 2 * frames::PAGE_SIZE as u64; |
|
| 144 | + | assert DOMAINS[0].events.ring.tail == 1; |
|
| 145 | + | assert DOMAINS[0].events.ring.data[0].kind == 4 and DOMAINS[0].events.ring.data[0].value == child.index as u64; |
|
| 146 | + | poll(); |
|
| 147 | + | assert DOMAINS[0].events.ring.tail == 1; |
|
| 148 | + | let replacement = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 149 | + | assert replacement.index == child.index and replacement.epoch <> child.epoch; |
|
| 150 | + | let fresh = handles::install(&mut DOMAINS[0].handles, targetSlot, replacement, abi::DOMAIN_RIGHTS); |
|
| 151 | + | assert fresh.bits <> target.bits; |
|
| 152 | + | badHandle(0, target); |
|
| 153 | + | badHandle(replacement.index, oldEvents); |
|
| 154 | + | badHandle(replacement.index, eventsAlias); |
|
| 155 | + | try! contexts::prepare(&mut CONTEXTS[0], replacement, 64, 0, DOMAINS[replacement.index].env); |
|
| 156 | + | badContext(id); |
|
| 157 | + | assert contexts::identity(&CONTEXTS[0]) <> id; |
|
| 158 | + | try! capabilities::drop(&mut KERNEL, 0, external); |
|
| 159 | + | assert POOL.available == 62; |
|
| 160 | + | lifecycle::terminate(&mut KERNEL, root, 4, 0); |
|
| 161 | + | poll(); |
|
| 162 | + | assert POOL.available == 64; |
|
| 163 | + | assert domains::live(&DOMAINS[..], replacement); |
|
| 164 | + | assert DOMAINS[replacement.index].parent.kind == abi::Kind::Empty; |
|
| 165 | + | } |
|
| 166 | + | ||
| 167 | + | /// An exited intermediate creator cannot sever a surviving ancestor's cascade. |
|
| 168 | + | fn ancestry() { |
|
| 169 | + | let root = setup(); |
|
| 170 | + | let ancestor = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 171 | + | let middle = try! domains::create(&mut DOMAINS[..], ancestor, 0); |
|
| 172 | + | let leaf = try! domains::create(&mut DOMAINS[..], middle, 0); |
|
| 173 | + | let target = handle(0, ancestor, abi::DESTROY); |
|
| 174 | + | lifecycle::terminate(&mut KERNEL, middle, 4, 17); |
|
| 175 | + | poll(); |
|
| 176 | + | assert domains::live(&DOMAINS[..], leaf); |
|
| 177 | + | assert DOMAINS[leaf.index].parent.kind == abi::Kind::Empty; |
|
| 178 | + | let replacement = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 179 | + | assert replacement.index == middle.index and replacement.epoch <> middle.epoch; |
|
| 180 | + | let unrelated = handle(0, replacement, abi::DESTROY); |
|
| 181 | + | let _replacement = try! lifecycle::request(&mut KERNEL, 0, unrelated, 1); |
|
| 182 | + | assert domains::live(&DOMAINS[..], leaf); |
|
| 183 | + | poll(); |
|
| 184 | + | let _ancestor = try! lifecycle::request(&mut KERNEL, 0, target, 1); |
|
| 185 | + | assert not domains::live(&DOMAINS[..], ancestor) and not domains::live(&DOMAINS[..], leaf); |
|
| 186 | + | poll(); |
|
| 187 | + | assert lifecycle::completed(&DOMAINS[..], ancestor) and lifecycle::completed(&DOMAINS[..], leaf); |
|
| 188 | + | assert domains::live(&DOMAINS[..], root); |
|
| 189 | + | } |
|
| 190 | + | ||
| 191 | + | /// Noncascade destruction leaves creation descendants alive and parentless. |
|
| 192 | + | fn survival() { |
|
| 193 | + | let root = setup(); |
|
| 194 | + | let child = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 195 | + | let leaf = try! domains::create(&mut DOMAINS[..], child, 0); |
|
| 196 | + | let denied = handle(0, child, abi::WAKE); |
|
| 197 | + | badRequest(0, denied, 0, abi::Error::Denied); |
|
| 198 | + | badRequest(child.index, abi::Handle { bits: 0 }, 1, abi::Error::InvalidArg); |
|
| 199 | + | badRequest(0, denied, 2, abi::Error::InvalidArg); |
|
| 200 | + | let _owner = try! lifecycle::request(&mut KERNEL, child.index, abi::Handle { bits: 0 }, 0); |
|
| 201 | + | lifecycle::terminate(&mut KERNEL, child, 3, 99); |
|
| 202 | + | poll(); |
|
| 203 | + | assert domains::live(&DOMAINS[..], leaf); |
|
| 204 | + | assert DOMAINS[leaf.index].parent.kind == abi::Kind::Empty; |
|
| 205 | + | assert DOMAINS[0].events.ring.data[0].kind == 4 and DOMAINS[0].events.ring.data[0].code == 0; |
|
| 206 | + | lifecycle::terminate(&mut KERNEL, leaf, 3, 42); |
|
| 207 | + | poll(); |
|
| 208 | + | assert lifecycle::completed(&DOMAINS[..], leaf); |
|
| 209 | + | assert DOMAINS[0].events.ring.tail == 1; |
|
| 210 | + | } |
|
| 211 | + | ||
| 212 | + | /// Require failed reparent operations to leave their relationship usable. |
|
| 213 | + | fn badReparent(child: abi::Handle, parent: abi::Handle, expected: abi::Error) { |
|
| 214 | + | try lifecycle::reparent(&mut KERNEL, 0, child, parent) catch error { |
|
| 215 | + | assert error == expected; |
|
| 216 | + | return; |
|
| 217 | + | }; |
|
| 218 | + | panic "badReparent: invalid parent accepted"; |
|
| 219 | + | } |
|
| 220 | + | ||
| 221 | + | /// Credit exhaustion is transactional; queued notifications never change owner. |
|
| 222 | + | fn parenting() { |
|
| 223 | + | let root = setup(); |
|
| 224 | + | let child = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 225 | + | let receiver = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 226 | + | let prior = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 227 | + | lifecycle::terminate(&mut KERNEL, prior, 4, 13); |
|
| 228 | + | poll(); |
|
| 229 | + | let target = handle(0, child, abi::DESTROY); |
|
| 230 | + | let parent = handle(0, receiver, abi::WAKE); |
|
| 231 | + | let weakParent = handle(0, receiver, abi::DESTROY); |
|
| 232 | + | badReparent(target, weakParent, abi::Error::Denied); |
|
| 233 | + | badReparent(target, abi::Handle { bits: 0 }, abi::Error::BadHandle); |
|
| 234 | + | badReparent(target, handle(0, child, abi::WAKE), abi::Error::InvalidArg); |
|
| 235 | + | for i in 0..events::CRITICAL { |
|
| 236 | + | try! events::reserve(&mut DOMAINS[receiver.index].events); |
|
| 237 | + | try! events::push(&mut DOMAINS[receiver.index].events, |
|
| 238 | + | events::Event { kind: 4, reserved: 0, code: i, value: 99 }, events::Class::Critical); |
|
| 239 | + | } |
|
| 240 | + | badReparent(target, parent, abi::Error::Exhausted); |
|
| 241 | + | assert DOMAINS[child.index].parent.index == root.index; |
|
| 242 | + | assert DOMAINS[0].events.reserved == 3; |
|
| 243 | + | set DOMAINS[receiver.index].events.ring.head = 1; |
|
| 244 | + | try! lifecycle::reparent(&mut KERNEL, 0, target, parent); |
|
| 245 | + | assert DOMAINS[0].events.reserved == 2; |
|
| 246 | + | // Same-parent no-op must not require a second delivery credit. |
|
| 247 | + | try! lifecycle::reparent(&mut KERNEL, 0, target, parent); |
|
| 248 | + | assert DOMAINS[receiver.index].events.reserved == events::CRITICAL; |
|
| 249 | + | lifecycle::terminate(&mut KERNEL, child, 3, 77); |
|
| 250 | + | lifecycle::terminate(&mut KERNEL, child, 4, 12); |
|
| 251 | + | poll(); |
|
| 252 | + | assert DOMAINS[0].events.ring.tail == 1; |
|
| 253 | + | assert DOMAINS[0].events.ring.data[0].code == 13 and DOMAINS[0].events.ring.data[0].value == prior.index as u64; |
|
| 254 | + | let event = DOMAINS[receiver.index].events.ring.data[events::CRITICAL]; |
|
| 255 | + | assert event.kind == 3 and event.code == 77 and event.value == child.index as u64; |
|
| 256 | + | assert DOMAINS[receiver.index].events.ring.data[1].value == 99; |
|
| 257 | + | set DOMAINS[receiver.index].events.ring.head = events::CRITICAL + 1; |
|
| 258 | + | try! events::refresh(&mut DOMAINS[receiver.index].events); |
|
| 259 | + | assert DOMAINS[receiver.index].events.reserved == 0; |
|
| 260 | + | } |
|
| 261 | + | ||
| 262 | + | /// Malformed parent progress cannot drop a child's reserved terminal report. |
|
| 263 | + | fn invalidParent() { |
|
| 264 | + | let root = setup(); |
|
| 265 | + | let child = try! domains::create(&mut DOMAINS[..], root, 0); |
|
| 266 | + | lifecycle::terminate(&mut KERNEL, child, 4, 5); |
|
| 267 | + | set DOMAINS[0].events.ring.head = 1; |
|
| 268 | + | if let bad = lifecycle::poll(&mut KERNEL) { |
|
| 269 | + | assert bad.index == root.index and bad.epoch == root.epoch; |
|
| 270 | + | assert not lifecycle::completed(&DOMAINS[..], child); |
|
| 271 | + | assert DOMAINS[0].events.reserved == 1; |
|
| 272 | + | lifecycle::terminate(&mut KERNEL, bad, 3, 1); |
|
| 273 | + | } else { panic "invalidParent: invalid consumer not reported"; } |
|
| 274 | + | poll(); |
|
| 275 | + | assert lifecycle::completed(&DOMAINS[..], root) and lifecycle::completed(&DOMAINS[..], child); |
|
| 276 | + | } |
|
| 277 | + | ||
| 278 | + | /// Exercise lifecycle through real authority, accounting, and event mechanisms. |
|
| 279 | + | export fn run() { |
|
| 280 | + | quiescence(); |
|
| 281 | + | ancestry(); |
|
| 282 | + | survival(); |
|
| 283 | + | parenting(); |
|
| 284 | + | invalidParent(); |
|
| 285 | + | } |
kernel/core.rad
+1 -0
| 23 | 23 | export mod devices; |
|
| 24 | 24 | export mod interrupts; |
|
| 25 | 25 | export mod mmio; |
|
| 26 | 26 | export mod physical; |
|
| 27 | 27 | export mod activation; |
|
| 28 | + | export mod lifecycle; |
kernel/core/lifecycle.rad
added
+192 -0
| 1 | + | //! Serialized domain termination, parent credits, and quiescent reclamation. |
|
| 2 | + | ||
| 3 | + | use core::abi; |
|
| 4 | + | use core::budget_caps; |
|
| 5 | + | use core::contexts; |
|
| 6 | + | use core::cpu; |
|
| 7 | + | use core::domains; |
|
| 8 | + | use core::events; |
|
| 9 | + | use core::frames; |
|
| 10 | + | use core::handles; |
|
| 11 | + | use core::memory; |
|
| 12 | + | use core::resources; |
|
| 13 | + | use core::state; |
|
| 14 | + | ||
| 15 | + | /// Compare incarnation-qualified identities, including their object kind. |
|
| 16 | + | fn same(left: abi::Object, right: abi::Object) -> bool { |
|
| 17 | + | return left.kind == right.kind and left.index == right.index and left.epoch == right.epoch; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Record the first trusted Exit (4) or Fault (3), without creation cascade. |
|
| 21 | + | /// Hold the global state lock. Running harts must finish their charged intervals |
|
| 22 | + | /// and relinquish architectural frames before poll can reclaim this owner. |
|
| 23 | + | /// A stale owner or an already terminal incarnation cannot rewrite the cause. |
|
| 24 | + | export fn terminate(kernel: *mut state::State, owner: abi::Object, kind: u16, code: u32) { |
|
| 25 | + | assert kind == 3 or kind == 4; |
|
| 26 | + | if not domains::live(kernel.domains, owner) { return; } |
|
| 27 | + | let domain = &mut kernel.domains[owner.index]; |
|
| 28 | + | set domain.terminalKind = kind; |
|
| 29 | + | set domain.terminalCode = code; |
|
| 30 | + | set domain.state = domains::Lifecycle::Dying; |
|
| 31 | + | } |
|
| 32 | + | ||
| 33 | + | /// Accept teardown under the global lock and return its named incarnation. |
|
| 34 | + | /// Zero names self only with zero flags; explicit handles require Destroy. |
|
| 35 | + | /// Bit zero selects creation descendants, independently of current parents. |
|
| 36 | + | /// Acceptance immediately denies new activation, dispatch, and object lookup; |
|
| 37 | + | /// the caller must drive hart quiescence and poll before reporting completion. |
|
| 38 | + | export fn request(kernel: *mut state::State, caller: u32, handle: abi::Handle, flags: u64) -> abi::Object throws (abi::Error) { |
|
| 39 | + | if flags & ~1 <> 0 or (handle.bits == 0 and flags <> 0) { throw abi::Error::InvalidArg; } |
|
| 40 | + | assert caller < kernel.domains.len; |
|
| 41 | + | let mut owner = abi::Object { kind: abi::Kind::Domain, index: caller, epoch: kernel.domains[caller].epoch }; |
|
| 42 | + | if not domains::live(kernel.domains, owner) { throw abi::Error::BadHandle; } |
|
| 43 | + | if handle.bits <> 0 { set owner = try domains::resolve(kernel.domains, caller, handle, abi::DESTROY); } |
|
| 44 | + | terminate(kernel, owner, 4, 0); |
|
| 45 | + | if flags & 1 <> 0 { |
|
| 46 | + | let word = owner.index / 64; |
|
| 47 | + | let bit = 1 as u64 << (owner.index as u64 & 63); |
|
| 48 | + | for i in 0..kernel.domains.len { |
|
| 49 | + | let domain = &kernel.domains[i]; |
|
| 50 | + | if domain.ancestors[word] & bit <> 0 { |
|
| 51 | + | terminate(kernel, abi::Object { kind: abi::Kind::Domain, index: i, epoch: domain.epoch }, 4, 0); |
|
| 52 | + | } |
|
| 53 | + | } |
|
| 54 | + | } |
|
| 55 | + | return owner; |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | /// Cancel only the unfulfilled reservation owned by a current relationship. |
|
| 59 | + | /// Queued terminal entries no longer have a relationship and are never moved |
|
| 60 | + | /// or canceled here. A matching Dying parent still owns its private queue. |
|
| 61 | + | fn detach(table: *mut [domains::Domain], index: u32) { |
|
| 62 | + | let parent = table[index].parent; |
|
| 63 | + | if parent.kind == abi::Kind::Domain and parent.index < table.len { |
|
| 64 | + | let receiver = &mut table[parent.index]; |
|
| 65 | + | if receiver.epoch == parent.epoch and receiver.state <> domains::Lifecycle::Dead { |
|
| 66 | + | assert receiver.events.reserved > 0; |
|
| 67 | + | set receiver.events.reserved -= 1; |
|
| 68 | + | } |
|
| 69 | + | } |
|
| 70 | + | set table[index].parent = domains::none(); |
|
| 71 | + | } |
|
| 72 | + | ||
| 73 | + | /// Atomically change terminal receiver under the global lock, not ancestry. |
|
| 74 | + | /// Destroy on the child and Wake on the new parent are both required. |
|
| 75 | + | /// Reserve before canceling the old unfulfilled credit; failure keeps the old |
|
| 76 | + | /// relationship intact. Already queued events stay in their original queue. |
|
| 77 | + | export fn reparent(kernel: *mut state::State, caller: u32, childHandle: abi::Handle, parentHandle: abi::Handle) throws (abi::Error) { |
|
| 78 | + | let child = try domains::resolve(kernel.domains, caller, childHandle, abi::DESTROY); |
|
| 79 | + | let parent = try domains::resolve(kernel.domains, caller, parentHandle, abi::WAKE); |
|
| 80 | + | if same(child, parent) { throw abi::Error::InvalidArg; } |
|
| 81 | + | if same(kernel.domains[child.index].parent, parent) { return; } |
|
| 82 | + | try events::reserve(&mut kernel.domains[parent.index].events); |
|
| 83 | + | detach(kernel.domains, child.index); |
|
| 84 | + | set kernel.domains[child.index].parent = parent; |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | /// Test completion of the named incarnation, not merely loss of authority. |
|
| 88 | + | /// Cascade callers must also wait for their selected descendant incarnations; |
|
| 89 | + | /// capture that bounded set under the same lock as request, before slot reuse. |
|
| 90 | + | export fn completed(table: *[domains::Domain], owner: abi::Object) -> bool { |
|
| 91 | + | assert owner.kind == abi::Kind::Domain and owner.epoch <> 0 and owner.index < table.len; |
|
| 92 | + | return table[owner.index].epoch <> owner.epoch or table[owner.index].state == domains::Lifecycle::Dead; |
|
| 93 | + | } |
|
| 94 | + | ||
| 95 | + | /// All physical addresses are discarded only after architectural ownership ends. |
|
| 96 | + | fn emptyEnv() -> domains::Env { |
|
| 97 | + | return domains::Env { |
|
| 98 | + | argsPointer: 0, argsSize: 0, eventsHandle: 0, eventsPointer: 0, |
|
| 99 | + | stateBase: 0, stackBase: 0, stackTop: 0, context: 0, |
|
| 100 | + | privateStackBase: 0, privateStackTop: 0, |
|
| 101 | + | }; |
|
| 102 | + | } |
|
| 103 | + | ||
| 104 | + | /// Remove trusted authority, including installed Events, and release Page claims. |
|
| 105 | + | fn remove(kernel: *mut state::State, owner: u32, slot: u32) { |
|
| 106 | + | let entry = handles::remove(&mut kernel.domains[owner].handles, slot); |
|
| 107 | + | if let run = resources::release(kernel.resources, entry.object) { |
|
| 108 | + | memory::release(&mut kernel.memory, run); |
|
| 109 | + | } |
|
| 110 | + | } |
|
| 111 | + | ||
| 112 | + | /// Reclaim a quiescent owner after terminal publication or loss of its parent. |
|
| 113 | + | /// Retained budget bindings precede private-stack release and slot invalidation. |
|
| 114 | + | fn reclaim(kernel: *mut state::State, owner: abi::Object) { |
|
| 115 | + | for i in 0..kernel.contexts.len { |
|
| 116 | + | let context = &mut kernel.contexts[i]; |
|
| 117 | + | if context.status == contexts::Status::Vacant or not same(context.owner, owner) { continue; } |
|
| 118 | + | assert context.status <> contexts::Status::Running; |
|
| 119 | + | budget_caps::unbind(kernel, context); |
|
| 120 | + | if context.private.count > 0 { frames::reclaim(kernel.memory.pool, context.private); } |
|
| 121 | + | set context.private = frames::Run { first: 0, count: 0 }; |
|
| 122 | + | set context.status = contexts::Status::Vacant; |
|
| 123 | + | set context.owner = domains::none(); |
|
| 124 | + | set context.hart = contexts::NO_HART; |
|
| 125 | + | set context.continuation = 0; |
|
| 126 | + | set context.deadline = 0; |
|
| 127 | + | set context.env = emptyEnv(); |
|
| 128 | + | cpu::init(&mut context.frame, 0, 0, 0, 0); |
|
| 129 | + | } |
|
| 130 | + | for slot in 0..abi::MAX_HANDLES { |
|
| 131 | + | if kernel.domains[owner.index].handles.entries[slot].object.kind <> abi::Kind::Empty { |
|
| 132 | + | remove(kernel, owner.index, slot); |
|
| 133 | + | } |
|
| 134 | + | } |
|
| 135 | + | // Removing dead Domain/Events entries advances packed-name generations. |
|
| 136 | + | // These objects carry no physical-resource reference counts. |
|
| 137 | + | for i in 0..kernel.domains.len { |
|
| 138 | + | let domain = &kernel.domains[i]; |
|
| 139 | + | if domain.state <> domains::Lifecycle::Pending and domain.state <> domains::Lifecycle::Active { continue; } |
|
| 140 | + | for slot in 0..abi::MAX_HANDLES { |
|
| 141 | + | let object = domain.handles.entries[slot].object; |
|
| 142 | + | if (object.kind == abi::Kind::Domain or object.kind == abi::Kind::Events) |
|
| 143 | + | and not resources::live(kernel.resources, kernel.domains, object) { |
|
| 144 | + | remove(kernel, i, slot); |
|
| 145 | + | } |
|
| 146 | + | } |
|
| 147 | + | } |
|
| 148 | + | memory::retire(&mut kernel.memory, owner.index); |
|
| 149 | + | let domain = &mut kernel.domains[owner.index]; |
|
| 150 | + | if domain.private.count > 0 { frames::reclaim(kernel.memory.pool, domain.private); } |
|
| 151 | + | set domain.private = frames::Run { first: 0, count: 0 }; |
|
| 152 | + | set domain.env = emptyEnv(); |
|
| 153 | + | events::init(&mut domain.events); |
|
| 154 | + | set domain.state = domains::Lifecycle::Dead; |
|
| 155 | + | } |
|
| 156 | + | ||
| 157 | + | /// Make one bounded reclamation pass under the global lock; never stop a hart. |
|
| 158 | + | /// Orphan relationships to nonlive parents, including children still Running. |
|
| 159 | + | /// No owner is reclaimed while any of its contexts is Running. An invalid |
|
| 160 | + | /// live parent consumer returns that identity without reclaiming its child: |
|
| 161 | + | /// apply trusted Fault termination to the returned parent, then poll again. |
|
| 162 | + | /// Terminal values use the public domain index; private epochs protect reuse. |
|
| 163 | + | export fn poll(kernel: *mut state::State) -> ?abi::Object { |
|
| 164 | + | for i in 0..kernel.domains.len { |
|
| 165 | + | let domain = &kernel.domains[i]; |
|
| 166 | + | if domain.state == domains::Lifecycle::Dead { continue; } |
|
| 167 | + | if domain.parent.kind == abi::Kind::Domain and not domains::live(kernel.domains, domain.parent) { |
|
| 168 | + | detach(kernel.domains, i); |
|
| 169 | + | } |
|
| 170 | + | } |
|
| 171 | + | for i in 0..kernel.domains.len { |
|
| 172 | + | let domain = &mut kernel.domains[i]; |
|
| 173 | + | if domain.state <> domains::Lifecycle::Dying { continue; } |
|
| 174 | + | let owner = abi::Object { kind: abi::Kind::Domain, index: i, epoch: domain.epoch }; |
|
| 175 | + | let mut running = false; |
|
| 176 | + | for c in 0..kernel.contexts.len { |
|
| 177 | + | let context = &kernel.contexts[c]; |
|
| 178 | + | if context.status == contexts::Status::Running and same(context.owner, owner) { set running = true; } |
|
| 179 | + | } |
|
| 180 | + | if running { continue; } |
|
| 181 | + | let parent = domain.parent; |
|
| 182 | + | if domains::live(kernel.domains, parent) { |
|
| 183 | + | try events::push(&mut kernel.domains[parent.index].events, events::Event { |
|
| 184 | + | kind: domain.terminalKind, reserved: 0, code: domain.terminalCode, value: i as u64, |
|
| 185 | + | }, events::Class::Critical) catch { return parent; }; |
|
| 186 | + | // Publication consumes the relationship, not its queued reservation. |
|
| 187 | + | set domain.parent = domains::none(); |
|
| 188 | + | } |
|
| 189 | + | reclaim(kernel, owner); |
|
| 190 | + | } |
|
| 191 | + | return nil; |
|
| 192 | + | } |
kernel/native/instances.rad
+11 -2
| 6 | 6 | use core::budgets; |
|
| 7 | 7 | use core::capabilities; |
|
| 8 | 8 | use core::clock; |
|
| 9 | 9 | use core::contexts; |
|
| 10 | 10 | use core::cpu; |
|
| 11 | - | ||
| 11 | + | use core::domains; |
|
| 12 | 12 | use core::handles; |
|
| 13 | 13 | use core::pages; |
|
| 14 | - | ||
| 14 | + | use core::lifecycle; |
|
| 15 | 15 | use core::physical; |
|
| 16 | 16 | use core::resources; |
|
| 17 | 17 | use core::state; |
|
| 18 | 18 | ||
| 19 | 19 | /// Give one explicit context its own finite budget and observe its native Exit. |
| 56 | 56 | let extraId = try! activation::context(kernel, 0, first, top, range.base + 4, 4, physical::clear); |
|
| 57 | 57 | let secondId = try! activation::activate(kernel, 0, second, top, range.base, 4, physical::clear); |
|
| 58 | 58 | execute(kernel, first, firstId, clint, 42); |
|
| 59 | 59 | execute(kernel, first, extraId, clint, 44); |
|
| 60 | 60 | execute(kernel, second, secondId, clint, 42); |
|
| 61 | + | let secondOwner = try! domains::resolve(kernel.domains, 0, second, 0); |
|
| 62 | + | let retired = try! lifecycle::request(kernel, 0, first, 0); |
|
| 63 | + | if let _bad = lifecycle::poll(kernel) { panic "run: invalid lifecycle consumer"; } |
|
| 64 | + | assert lifecycle::completed(kernel.domains, retired); |
|
| 65 | + | assert domains::live(kernel.domains, secondOwner); |
|
| 66 | + | let fresh = try! activation::create(kernel, 0, own, image, physical::clear, physical::copy); |
|
| 67 | + | let _page = try! capabilities::grant(kernel, 0, page, fresh, (abi::READ | abi::WRITE) as u64); |
|
| 68 | + | let freshId = try! activation::activate(kernel, 0, fresh, top, range.base, 4, physical::clear); |
|
| 69 | + | execute(kernel, fresh, freshId, clint, 42); |
|
| 61 | 70 | } |