diff options
Diffstat (limited to '')
| -rw-r--r-- | rsnix/src/sched.rs | 199 |
1 files changed, 195 insertions, 4 deletions
diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs index 6ffa745..dbe3647 100644 --- a/rsnix/src/sched.rs +++ b/rsnix/src/sched.rs | |||
| @@ -42,6 +42,63 @@ const STACK_PAGES: usize = 3; // 1 guard page + 8 KiB stack | |||
| 42 | const NAME_MAX: usize = 24; | 42 | const NAME_MAX: usize = 24; |
| 43 | const PANIC_MAX: usize = 64; | 43 | const PANIC_MAX: usize = 64; |
| 44 | 44 | ||
| 45 | // --------------------------------------------------------------------------- | ||
| 46 | // Inter-task message queues | ||
| 47 | // --------------------------------------------------------------------------- | ||
| 48 | |||
| 49 | /// Capacity of each task's message ring (power of two; full sends drop | ||
| 50 | /// with a "queue full" error). Sized so input bursts (a 100 Hz mouse, | ||
| 51 | /// key repeat) far outrun a blocked consumer: 16 × ~1.6 s at worst. | ||
| 52 | const MSG_CAP: usize = 16; | ||
| 53 | |||
| 54 | /// One inter-task message. Input events use the same decoded types the | ||
| 55 | /// PS/2 drivers produce, so a window's input routing is "post what the | ||
| 56 | /// driver would have delivered to the console". `User(u64)` is the | ||
| 57 | /// generic escape hatch (window commands, app signals, …). | ||
| 58 | #[derive(Clone, Copy, Debug)] | ||
| 59 | pub enum TaskMsg { | ||
| 60 | Key(crate::ps2::KeyEvent), | ||
| 61 | Mouse(crate::ps2::MouseEvent), | ||
| 62 | Timer, | ||
| 63 | User(u64), | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Decode a message for logging/display (no Debug dep in the shell task). | ||
| 67 | pub fn msg_describe(msg: &TaskMsg) -> alloc::string::String { | ||
| 68 | use crate::ps2::KeyEvent; | ||
| 69 | match msg { | ||
| 70 | TaskMsg::Key(k) => match k { | ||
| 71 | KeyEvent::Char(c) => alloc::format!("Key('{}')", c), | ||
| 72 | KeyEvent::Enter => "Key(Enter)".into(), | ||
| 73 | KeyEvent::Backspace => "Key(Backspace)".into(), | ||
| 74 | KeyEvent::Tab => "Key(Tab)".into(), | ||
| 75 | KeyEvent::Escape => "Key(Escape)".into(), | ||
| 76 | KeyEvent::Ctrl(c) => alloc::format!("Key(Ctrl-{})", c), | ||
| 77 | KeyEvent::CtrlArrow(d) => alloc::format!("Key(CtrlArrow{})", d), | ||
| 78 | KeyEvent::ArrowUp => "Key(Up)".into(), | ||
| 79 | KeyEvent::ArrowDown => "Key(Down)".into(), | ||
| 80 | KeyEvent::ArrowLeft => "Key(Left)".into(), | ||
| 81 | KeyEvent::ArrowRight => "Key(Right)".into(), | ||
| 82 | KeyEvent::Home => "Key(Home)".into(), | ||
| 83 | KeyEvent::End => "Key(End)".into(), | ||
| 84 | KeyEvent::PageUp => "Key(PgUp)".into(), | ||
| 85 | KeyEvent::PageDown => "Key(PgDn)".into(), | ||
| 86 | KeyEvent::Delete => "Key(Delete)".into(), | ||
| 87 | KeyEvent::F(n) => alloc::format!("Key(F{})", n), | ||
| 88 | KeyEvent::Unknown(b) => alloc::format!("Key(0x{:02x})", b), | ||
| 89 | }, | ||
| 90 | TaskMsg::Mouse(m) => alloc::format!( | ||
| 91 | "Mouse({:+},{:+} {} {})", | ||
| 92 | m.dx, | ||
| 93 | m.dy, | ||
| 94 | if m.left { "L" } else { "-" }, | ||
| 95 | if m.right { "R" } else { "-" } | ||
| 96 | ), | ||
| 97 | TaskMsg::Timer => "Timer".into(), | ||
| 98 | TaskMsg::User(n) => alloc::format!("User({})", n), | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 45 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] | 102 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
| 46 | pub enum TaskState { | 103 | pub enum TaskState { |
| 47 | Ready, | 104 | Ready, |
| @@ -65,6 +122,13 @@ struct Task { | |||
| 65 | ticks: u64, | 122 | ticks: u64, |
| 66 | panic: [u8; PANIC_MAX], | 123 | panic: [u8; PANIC_MAX], |
| 67 | panic_len: u8, | 124 | panic_len: u8, |
| 125 | /// Per-task message ring (see `TaskMsg`). Accessed by the owning | ||
| 126 | /// task (pop) and by any poster (push) — both under the scheduler | ||
| 127 | /// lock, so `send` is callable from ISR or task context. | ||
| 128 | msg: [TaskMsg; MSG_CAP], | ||
| 129 | msg_head: u8, | ||
| 130 | msg_tail: u8, | ||
| 131 | msg_count: u8, | ||
| 68 | } | 132 | } |
| 69 | 133 | ||
| 70 | const TASK_NONE: Task = Task { | 134 | const TASK_NONE: Task = Task { |
| @@ -78,6 +142,10 @@ const TASK_NONE: Task = Task { | |||
| 78 | ticks: 0, | 142 | ticks: 0, |
| 79 | panic: [0; PANIC_MAX], | 143 | panic: [0; PANIC_MAX], |
| 80 | panic_len: 0, | 144 | panic_len: 0, |
| 145 | msg: [TaskMsg::User(0); MSG_CAP], | ||
| 146 | msg_head: 0, | ||
| 147 | msg_tail: 0, | ||
| 148 | msg_count: 0, | ||
| 81 | }; | 149 | }; |
| 82 | 150 | ||
| 83 | static mut TASKS: [Task; MAX_TASKS] = [TASK_NONE; MAX_TASKS]; | 151 | static mut TASKS: [Task; MAX_TASKS] = [TASK_NONE; MAX_TASKS]; |
| @@ -133,8 +201,18 @@ pub fn on_tick(cur: usize) -> usize { | |||
| 133 | TASKS[CURRENT].ticks += 1; | 201 | TASKS[CURRENT].ticks += 1; |
| 134 | let now = crate::interrupts::uptime_ms(); | 202 | let now = crate::interrupts::uptime_ms(); |
| 135 | for t in 0..MAX_TASKS { | 203 | for t in 0..MAX_TASKS { |
| 136 | if TASKS[t].state == TaskState::Sleeping && TASKS[t].wake_at <= now { | 204 | match TASKS[t].state { |
| 137 | TASKS[t].state = TaskState::Ready; | 205 | TaskState::Sleeping if TASKS[t].wake_at <= now => { |
| 206 | TASKS[t].state = TaskState::Ready; | ||
| 207 | } | ||
| 208 | // Safety net for the recv race: a task that blocked | ||
| 209 | // *after* its message was queued (or whose poster never | ||
| 210 | // got to wake it) wakes on the next tick as long as its | ||
| 211 | // queue is non-empty. | ||
| 212 | TaskState::Blocked if TASKS[t].msg_count > 0 => { | ||
| 213 | TASKS[t].state = TaskState::Ready; | ||
| 214 | } | ||
| 215 | _ => {} | ||
| 138 | } | 216 | } |
| 139 | } | 217 | } |
| 140 | pick_next(cur) | 218 | pick_next(cur) |
| @@ -279,6 +357,10 @@ fn spawn_at(id: usize, entry: fn(), name: &str) -> bool { | |||
| 279 | ticks: 0, | 357 | ticks: 0, |
| 280 | panic: [0; PANIC_MAX], | 358 | panic: [0; PANIC_MAX], |
| 281 | panic_len: 0, | 359 | panic_len: 0, |
| 360 | msg: [TaskMsg::User(0); MSG_CAP], | ||
| 361 | msg_head: 0, | ||
| 362 | msg_tail: 0, | ||
| 363 | msg_count: 0, | ||
| 282 | }; | 364 | }; |
| 283 | } | 365 | } |
| 284 | true | 366 | true |
| @@ -319,6 +401,110 @@ pub fn yield_now() { | |||
| 319 | yield_int(); | 401 | yield_int(); |
| 320 | } | 402 | } |
| 321 | 403 | ||
| 404 | // --------------------------------------------------------------------------- | ||
| 405 | // Message passing | ||
| 406 | // --------------------------------------------------------------------------- | ||
| 407 | |||
| 408 | /// Post a message to a task's queue and wake it. Safe from any context: | ||
| 409 | /// a task (e.g. a window manager routing input), the shell, or an ISR — | ||
| 410 | /// the scheduler lock makes the queue mutation atomic w.r.t. switching. | ||
| 411 | /// Returns an error when the task is gone or its queue is full. | ||
| 412 | pub fn send(id: usize, msg: TaskMsg) -> Result<(), &'static str> { | ||
| 413 | if id >= MAX_TASKS { | ||
| 414 | return Err("no such task"); | ||
| 415 | } | ||
| 416 | lock(); | ||
| 417 | let r = unsafe { | ||
| 418 | let t = &TASKS[id]; | ||
| 419 | if t.name_len == 0 || t.state == TaskState::Done { | ||
| 420 | Err("no such task") | ||
| 421 | } else if push_msg(id, msg) { | ||
| 422 | if TASKS[id].state == TaskState::Blocked { | ||
| 423 | TASKS[id].state = TaskState::Ready; | ||
| 424 | } | ||
| 425 | Ok(()) | ||
| 426 | } else { | ||
| 427 | Err("queue full") | ||
| 428 | } | ||
| 429 | }; | ||
| 430 | unlock(); | ||
| 431 | r | ||
| 432 | } | ||
| 433 | |||
| 434 | /// Pop the oldest pending message of the current task, if any. | ||
| 435 | pub fn recv() -> Option<TaskMsg> { | ||
| 436 | lock(); | ||
| 437 | let m = unsafe { pop_msg(CURRENT) }; | ||
| 438 | unlock(); | ||
| 439 | m | ||
| 440 | } | ||
| 441 | |||
| 442 | /// Block until a message arrives, then return it. Wake is two-fold: | ||
| 443 | /// `send` flips a Blocked task straight to Ready, and the tick scan | ||
| 444 | /// (see `on_tick`) catches the race where the task blocked after the | ||
| 445 | /// message was already queued. | ||
| 446 | pub fn recv_blocking() -> TaskMsg { | ||
| 447 | loop { | ||
| 448 | if let Some(m) = recv() { | ||
| 449 | return m; | ||
| 450 | } | ||
| 451 | lock(); | ||
| 452 | unsafe { | ||
| 453 | TASKS[CURRENT].state = TaskState::Blocked; | ||
| 454 | } | ||
| 455 | unlock(); | ||
| 456 | yield_int(); | ||
| 457 | } | ||
| 458 | } | ||
| 459 | |||
| 460 | /// Push onto the task's ring (caller holds the scheduler lock). | ||
| 461 | unsafe fn push_msg(id: usize, msg: TaskMsg) -> bool { | ||
| 462 | unsafe { | ||
| 463 | let t = &mut TASKS[id]; | ||
| 464 | if t.msg_count as usize >= MSG_CAP { | ||
| 465 | return false; | ||
| 466 | } | ||
| 467 | t.msg[t.msg_tail as usize] = msg; | ||
| 468 | t.msg_tail = (t.msg_tail + 1) & (MSG_CAP as u8 - 1); | ||
| 469 | t.msg_count += 1; | ||
| 470 | true | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | /// Pop from the current task's ring (caller holds the scheduler lock). | ||
| 475 | unsafe fn pop_msg(id: usize) -> Option<TaskMsg> { | ||
| 476 | unsafe { | ||
| 477 | let t = &mut TASKS[id]; | ||
| 478 | if t.msg_count == 0 { | ||
| 479 | return None; | ||
| 480 | } | ||
| 481 | let m = t.msg[t.msg_head as usize]; | ||
| 482 | t.msg_head = (t.msg_head + 1) & (MSG_CAP as u8 - 1); | ||
| 483 | t.msg_count -= 1; | ||
| 484 | Some(m) | ||
| 485 | } | ||
| 486 | } | ||
| 487 | |||
| 488 | /// Pending message count of a task (for `jobs`). | ||
| 489 | pub fn pending_msgs(id: usize) -> usize { | ||
| 490 | lock(); | ||
| 491 | let n = unsafe { TASKS[id].msg_count as usize }; | ||
| 492 | unlock(); | ||
| 493 | n | ||
| 494 | } | ||
| 495 | |||
| 496 | /// Is the task slot live (spawned and not Done)? Used by the shell's | ||
| 497 | /// `mouse <id>` routing check and by the window manager later. | ||
| 498 | pub fn is_alive(id: usize) -> bool { | ||
| 499 | if id >= MAX_TASKS { | ||
| 500 | return false; | ||
| 501 | } | ||
| 502 | lock(); | ||
| 503 | let alive = unsafe { TASKS[id].name_len != 0 && TASKS[id].state != TaskState::Done }; | ||
| 504 | unlock(); | ||
| 505 | alive | ||
| 506 | } | ||
| 507 | |||
| 322 | /// Software-interrupt the scheduler (vector 0x60). Net stack usage is | 508 | /// Software-interrupt the scheduler (vector 0x60). Net stack usage is |
| 323 | /// zero (the frame is popped by iretq), so no `nostack` — the compiler | 509 | /// zero (the frame is popped by iretq), so no `nostack` — the compiler |
| 324 | /// must not use the red zone around the interrupt. | 510 | /// must not use the red zone around the interrupt. |
| @@ -418,11 +604,12 @@ pub fn jobs() -> Vec<String> { | |||
| 418 | } | 604 | } |
| 419 | }; | 605 | }; |
| 420 | let mut line = alloc::format!( | 606 | let mut line = alloc::format!( |
| 421 | " {:>2} {:<12} {:<15} {:>6} ticks", | 607 | " {:>2} {:<12} {:<15} {:>6} ticks q={}", |
| 422 | i, | 608 | i, |
| 423 | name_str(i), | 609 | name_str(i), |
| 424 | state, | 610 | state, |
| 425 | t.ticks | 611 | t.ticks, |
| 612 | t.msg_count | ||
| 426 | ); | 613 | ); |
| 427 | if t.panic_len > 0 { | 614 | if t.panic_len > 0 { |
| 428 | let m = core::str::from_utf8(&t.panic[..t.panic_len as usize]).unwrap_or("?"); | 615 | let m = core::str::from_utf8(&t.panic[..t.panic_len as usize]).unwrap_or("?"); |
| @@ -505,6 +692,10 @@ pub fn init() { | |||
| 505 | ticks: 0, | 692 | ticks: 0, |
| 506 | panic: [0; PANIC_MAX], | 693 | panic: [0; PANIC_MAX], |
| 507 | panic_len: 0, | 694 | panic_len: 0, |
| 695 | msg: [TaskMsg::User(0); MSG_CAP], | ||
| 696 | msg_head: 0, | ||
| 697 | msg_tail: 0, | ||
| 698 | msg_count: 0, | ||
| 508 | }; | 699 | }; |
| 509 | spawn_at(IDLE_TID, idle_main, "idle"); | 700 | spawn_at(IDLE_TID, idle_main, "idle"); |
| 510 | SCHED_READY.store(true, Ordering::Relaxed); | 701 | SCHED_READY.store(true, Ordering::Relaxed); |
