diff options
Diffstat (limited to 'shell')
| -rw-r--r-- | shell/src/lib.rs | 155 |
1 files changed, 153 insertions, 2 deletions
diff --git a/shell/src/lib.rs b/shell/src/lib.rs index 28eb737..97fa6c2 100644 --- a/shell/src/lib.rs +++ b/shell/src/lib.rs | |||
| @@ -83,9 +83,11 @@ fn term_print(s: &str) { | |||
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | fn term_println(s: &str) { | 85 | fn term_println(s: &str) { |
| 86 | term_print(s); | 86 | gop_print!("{}", s); |
| 87 | gop_println!(); | 87 | gop_println!(); |
| 88 | rsnix::serial::write_str("\r\n"); | 88 | // One locked call (line + CRLF) so a tick can't split the pair and |
| 89 | // let a background task's serial line interleave mid-line. | ||
| 90 | rsnix::serial::write_line(s); | ||
| 89 | } | 91 | } |
| 90 | 92 | ||
| 91 | fn term_err(msg: &str) { | 93 | fn term_err(msg: &str) { |
| @@ -696,6 +698,11 @@ write `{} <path> TIL <marker>`", | |||
| 696 | "touch" => cmd_touch(&parsed), | 698 | "touch" => cmd_touch(&parsed), |
| 697 | "rm" => cmd_rm(&parsed), | 699 | "rm" => cmd_rm(&parsed), |
| 698 | "edit" => cmd_edit(&parsed), | 700 | "edit" => cmd_edit(&parsed), |
| 701 | "bg" => cmd_bg(&parsed), | ||
| 702 | "jobs" => cmd_jobs(&parsed), | ||
| 703 | "sleep" => cmd_sleep(&parsed), | ||
| 704 | "kill" => cmd_kill(&parsed), | ||
| 705 | "demo" => cmd_demo(&parsed), | ||
| 699 | _ => term_err(&alloc::format!("unknown command: {}", cmd)), | 706 | _ => term_err(&alloc::format!("unknown command: {}", cmd)), |
| 700 | } | 707 | } |
| 701 | } | 708 | } |
| @@ -718,6 +725,11 @@ fn cmd_help(_p: &Parsed) { | |||
| 718 | date — show current date/time (RTC) | 725 | date — show current date/time (RTC) |
| 719 | pci — list PCI devices | 726 | pci — list PCI devices |
| 720 | rescan — re-scan for hotplugged/removed disks (also runs automatically) | 727 | rescan — re-scan for hotplugged/removed disks (also runs automatically) |
| 728 | bg <name> — spawn a background task (spinner, ticker, counter, boom, overflow) | ||
| 729 | jobs — list tasks (id, name, state, ticks) | ||
| 730 | sleep <ms> — block the shell for ms; background tasks keep running | ||
| 731 | kill <id> — stop a background task (id from jobs) | ||
| 732 | demo — spawn spinner + ticker + counter | ||
| 721 | ls — list a directory | 733 | ls — list a directory |
| 722 | cat — print files (cat USE a TO b copies a→b; cat TIL END is a heredoc) | 734 | cat — print files (cat USE a TO b copies a→b; cat TIL END is a heredoc) |
| 723 | klog — kernel boot log (cat /dev/klog; WRITING <dst> persists it) | 735 | klog — kernel boot log (cat /dev/klog; WRITING <dst> persists it) |
| @@ -1562,6 +1574,145 @@ fn cmd_rescan(_p: &Parsed) { | |||
| 1562 | } | 1574 | } |
| 1563 | } | 1575 | } |
| 1564 | 1576 | ||
| 1577 | // --------------------------------------------------------------------------- | ||
| 1578 | // Multitasking: bg / jobs / sleep / kill / demo | ||
| 1579 | // | ||
| 1580 | // Background tasks are headless: they may sleep, yield, and log to | ||
| 1581 | // serial/klog only. Only the shell touches the disk, filesystem, and | ||
| 1582 | // terminal (the ADR 0003 invariant). | ||
| 1583 | // --------------------------------------------------------------------------- | ||
| 1584 | |||
| 1585 | fn cmd_bg(p: &Parsed) { | ||
| 1586 | let Some(name) = p.args.first() else { | ||
| 1587 | term_err("bg: needs a task name — spinner, ticker, counter, boom, overflow"); | ||
| 1588 | return; | ||
| 1589 | }; | ||
| 1590 | let entry: fn() = match name.as_str() { | ||
| 1591 | "spinner" => task_spinner, | ||
| 1592 | "ticker" => task_ticker, | ||
| 1593 | "counter" => task_counter, | ||
| 1594 | "boom" => task_boom, | ||
| 1595 | "overflow" => task_overflow, | ||
| 1596 | _ => { | ||
| 1597 | term_err(&alloc::format!( | ||
| 1598 | "bg: unknown task '{}' (spinner, ticker, counter, boom, overflow)", | ||
| 1599 | name | ||
| 1600 | )); | ||
| 1601 | return; | ||
| 1602 | } | ||
| 1603 | }; | ||
| 1604 | match rsnix::sched::spawn(entry, name) { | ||
| 1605 | Some(id) => term_println(&alloc::format!("bg: spawned task {} ({})", id, name)), | ||
| 1606 | None => term_err("bg: task table full"), | ||
| 1607 | } | ||
| 1608 | } | ||
| 1609 | |||
| 1610 | fn cmd_jobs(p: &Parsed) { | ||
| 1611 | let mut pipe = Pipeline::new(&p.filters, &p.output); | ||
| 1612 | let lines = rsnix::sched::jobs(); | ||
| 1613 | if lines.is_empty() { | ||
| 1614 | let _ = pipe.line("jobs: no tasks"); | ||
| 1615 | } | ||
| 1616 | for l in lines { | ||
| 1617 | if !pipe.line(&l) { | ||
| 1618 | break; | ||
| 1619 | } | ||
| 1620 | } | ||
| 1621 | pipe.end(); | ||
| 1622 | } | ||
| 1623 | |||
| 1624 | fn cmd_sleep(p: &Parsed) { | ||
| 1625 | let ms: u64 = match p.args.first().and_then(|s| s.parse().ok()) { | ||
| 1626 | Some(m) if m > 0 => m, | ||
| 1627 | _ => { | ||
| 1628 | term_err("sleep: needs a duration in ms (e.g. sleep 2000)"); | ||
| 1629 | return; | ||
| 1630 | } | ||
| 1631 | }; | ||
| 1632 | term_println(&alloc::format!( | ||
| 1633 | "sleep: blocking shell for {} ms — background tasks keep running", | ||
| 1634 | ms | ||
| 1635 | )); | ||
| 1636 | rsnix::sched::sleep_ms(ms); | ||
| 1637 | term_println(&alloc::format!("sleep: woke after {} ms", ms)); | ||
| 1638 | } | ||
| 1639 | |||
| 1640 | fn cmd_kill(p: &Parsed) { | ||
| 1641 | let id: usize = match p.args.first().and_then(|s| s.parse().ok()) { | ||
| 1642 | Some(id) => id, | ||
| 1643 | None => { | ||
| 1644 | term_err("kill: needs a task id (see jobs)"); | ||
| 1645 | return; | ||
| 1646 | } | ||
| 1647 | }; | ||
| 1648 | match rsnix::sched::kill(id) { | ||
| 1649 | Ok(()) => term_println(&alloc::format!("kill: task {} marked done", id)), | ||
| 1650 | Err(e) => term_err(&alloc::format!("kill: {}", e)), | ||
| 1651 | } | ||
| 1652 | } | ||
| 1653 | |||
| 1654 | fn cmd_demo(_p: &Parsed) { | ||
| 1655 | let mut n = 0; | ||
| 1656 | for (entry, name) in [ | ||
| 1657 | (task_spinner as fn(), "spinner"), | ||
| 1658 | (task_ticker as fn(), "ticker"), | ||
| 1659 | (task_counter as fn(), "counter"), | ||
| 1660 | ] { | ||
| 1661 | if rsnix::sched::spawn(entry, name).is_some() { | ||
| 1662 | n += 1; | ||
| 1663 | } | ||
| 1664 | } | ||
| 1665 | term_println(&alloc::format!( | ||
| 1666 | "demo: spawned {} task(s) — spinner 250ms, ticker 500ms, counter 1000ms (try jobs)", | ||
| 1667 | n | ||
| 1668 | )); | ||
| 1669 | } | ||
| 1670 | |||
| 1671 | // --- Demo task bodies (headless: sleep + serial/klog only) --- | ||
| 1672 | |||
| 1673 | fn task_spinner() { | ||
| 1674 | let mut n: u64 = 0; | ||
| 1675 | loop { | ||
| 1676 | rsnix::serial_println!("spinner: {}", n); | ||
| 1677 | n += 1; | ||
| 1678 | rsnix::sched::sleep_ms(250); | ||
| 1679 | } | ||
| 1680 | } | ||
| 1681 | |||
| 1682 | fn task_ticker() { | ||
| 1683 | let mut n: u64 = 0; | ||
| 1684 | loop { | ||
| 1685 | rsnix::serial_println!("ticker: {}", n); | ||
| 1686 | n += 1; | ||
| 1687 | rsnix::sched::sleep_ms(500); | ||
| 1688 | } | ||
| 1689 | } | ||
| 1690 | |||
| 1691 | fn task_counter() { | ||
| 1692 | let mut n: u64 = 0; | ||
| 1693 | loop { | ||
| 1694 | rsnix::serial_println!("counter: {}", n); | ||
| 1695 | n += 1; | ||
| 1696 | rsnix::sched::sleep_ms(1000); | ||
| 1697 | } | ||
| 1698 | } | ||
| 1699 | |||
| 1700 | fn task_boom() { | ||
| 1701 | panic!("boom: deliberate task panic"); | ||
| 1702 | } | ||
| 1703 | |||
| 1704 | /// Recurses until the guard page faults — the task should die with a | ||
| 1705 | /// "stack overflow" panic while the shell keeps running. | ||
| 1706 | fn task_overflow() { | ||
| 1707 | #[allow(unconditional_recursion)] | ||
| 1708 | fn recurse(d: u64) { | ||
| 1709 | let keep = [0u8; 512]; | ||
| 1710 | core::hint::black_box(&keep); | ||
| 1711 | recurse(d + 1); | ||
| 1712 | } | ||
| 1713 | recurse(0); | ||
| 1714 | } | ||
| 1715 | |||
| 1565 | fn cmd_pci(p: &Parsed) { | 1716 | fn cmd_pci(p: &Parsed) { |
| 1566 | let devices = rsnix::pci::probe(); | 1717 | let devices = rsnix::pci::probe(); |
| 1567 | let mut pipe = Pipeline::new(&p.filters, &p.output); | 1718 | let mut pipe = Pipeline::new(&p.filters, &p.output); |
