rust-kernel
Rustbelt: a hobby x86_64 kernel written in Rust
| author | Tyler Fowler <tyler@localhost> | 2026-10-30 10:38:16 -0700 |
|---|---|---|
| commit | f689a14d102d6273fce5f7a8c0219ef2dc03462e | |
| parent | a4c179499f861606b1c24cc62a5d62eaf3112e1b | |
| tree | browse files at this commit | |
WIP stage 15: select/ppoll/readv wrappers, more nbtest/ptytest checks, headless fixes
| M | user/nbtest.rs | +34 -0 |
| M | user/ptytest.rs | +35 -1 |
| M | user/ulib.rs | +24 -0 |
3 files changed, 93 insertions, 1 deletion
@@ -105,6 +105,40 @@ fn main() -> i64 { c.check(&buf[..n.max(0) as usize] == b"HELLO vectored world", "pwrite overwrote the start"); close(fd); unlink("/tmp/nbtest.txt");+ // readv: one read filling several buffers in turn.+ let fd = open("/tmp/nbtest.txt", O_CREAT | O_TRUNC | O_RDWR);+ write(fd, b"abcdefgh");+ lseek(fd, 0, SEEK_SET);+ let (mut a, mut b) = ([0u8; 3], [0u8; 5]);+ let n = readv(fd, &mut [&mut a, &mut b]);+ c.check(n == 8 && &a == b"abc" && &b == b"defgh", "readv fills the buffers in order");+ close(fd);+ unlink("/tmp/nbtest.txt");++ // select and ppoll on a pipe: not readable, then readable.+ let (r, w) = pipe2(0).unwrap();+ let (mut rs, mut ws) = ([0u64; 16], [0u64; 16]);+ fd_set(&mut rs, r);+ let t0 = now_us();+ c.check(select(r + 1, &mut rs, &mut ws, 20_000) == 0 && now_us() - t0 >= 20_000, "select: times out after 20 ms on an empty pipe");+ write(w, b"!");+ let (mut rs, mut ws) = ([0u64; 16], [0u64; 16]);+ fd_set(&mut rs, r);+ fd_set(&mut ws, w);+ c.check(select(w.max(r) + 1, &mut rs, &mut ws, 0) == 2 && fd_isset(&rs, r) && fd_isset(&ws, w), "select: pipe readable + writable");+ let mut p = [PollFd { fd: r as i32, events: POLLIN, revents: 0 }];+ c.check(ppoll_us(&mut p, 0) == 1 && p[0].revents & POLLIN != 0, "ppoll: POLLIN");+ close(r);+ close(w);++ // getcwd and uname, which libc start-up and `uname -a` use.+ let mut cwd = [0u8; 64];+ let n = getcwd(&mut cwd);+ c.check(n > 0 && cwd[0] == b'/', "getcwd gives an absolute path");+ let mut ub = [0u8; 6 * 65];+ let u = uname(&mut ub);+ c.check(u[0] == "Rustbelt" && u[3].starts_with("stage 15"), "uname: Rustbelt, stage 15");+ let (secs, _) = clock_gettime(0); c.check(secs > 1_600_000_000, "CLOCK_REALTIME is a real date (after 2020)"); let t0 = now_us();
@@ -82,7 +82,12 @@ fn main() -> i64 { if fd < 0 { exit(3); }- write(fd, b"ready\n");+ // /dev/tty is "my controlling terminal", whichever that is.+ let me = open("/dev/tty", O_RDWR);+ if me < 0 {+ exit(4);+ }+ write(me, b"ready\n"); loop { pause(); }@@ -98,6 +103,35 @@ fn main() -> i64 { let got = read_some(master, &mut buf); c.check(got >= 2 && &buf[..2] == b"^C", "^C is echoed as \"^C\""); + // ^\ is SIGQUIT (it would dump core on Unix).+ let pid = fork();+ if pid == 0 {+ setsid();+ if open(&path, O_RDWR) < 0 {+ exit(3);+ }+ loop {+ pause();+ }+ }+ sleep_ms(100); // let the child open the pty first+ write(master, b"\x1c");+ let status = match wait4(pid, 0) {+ Ok((_, s)) => s,+ Err(_) => 0,+ };+ c.check(wifsignaled(status) && wtermsig(status) == SIGQUIT, "^\\ on the master: SIGQUIT");+ set_nonblocking(master, true);+ while read(master, &mut buf) > 0 {} // drop the "^\\" echo+ set_nonblocking(master, false);+ // Without a controlling terminal, /dev/tty doesn't exist for us.+ let pid = fork();+ if pid == 0 {+ setsid();+ exit(if open("/dev/tty", O_RDWR) < 0 { 0 } else { 1 });+ }+ c.check(matches!(wait4(pid, 0), Ok((_, 0))), "no controlling terminal: /dev/tty fails");+ // Hang-up: closing the master gives the slave end-of-file. close(master); c.check(read(slave, &mut buf) == 0, "master closed: the slave reads end-of-file");
@@ -1179,6 +1179,8 @@ impl Heap { // --- Stage 15: what a libc's plumbing needs ---------------------------------- pub const SYS_POLL: u64 = 7;+pub const SYS_SELECT: u64 = 23;+pub const SYS_PPOLL: u64 = 271; pub const SYS_PREAD64: u64 = 17; pub const SYS_PWRITE64: u64 = 18; pub const SYS_READV: u64 = 19;@@ -1218,6 +1220,10 @@ pub fn pwrite(fd: i64, buf: &[u8], offset: u64) -> i64 { syscall6(SYS_PWRITE64, fd as u64, buf.as_ptr() as u64, buf.len() as u64, offset, 0, 0) } /// writev: several buffers in one call (`struct iovec {base, len}`).+pub fn readv(fd: i64, bufs: &mut [&mut [u8]]) -> i64 {+ let iov: alloc::vec::Vec<[u64; 2]> = bufs.iter_mut().map(|b| [b.as_mut_ptr() as u64, b.len() as u64]).collect();+ syscall3(SYS_READV, fd as u64, iov.as_ptr() as u64, iov.len() as u64)+} pub fn writev(fd: i64, bufs: &[&[u8]]) -> i64 { let iov: alloc::vec::Vec<[u64; 2]> = bufs.iter().map(|b| [b.as_ptr() as u64, b.len() as u64]).collect(); syscall3(SYS_WRITEV, fd as u64, iov.as_ptr() as u64, iov.len() as u64)@@ -1250,6 +1256,24 @@ pub struct PollFd { pub fn poll(fds: &mut [PollFd], timeout_ms: i64) -> i64 { syscall3(SYS_POLL, fds.as_mut_ptr() as u64, fds.len() as u64, timeout_ms as u64) }+/// ppoll: poll with a timespec timeout (and a signal mask, unused here).+pub fn ppoll_us(fds: &mut [PollFd], timeout_us: u64) -> i64 {+ let ts = [timeout_us / 1_000_000, timeout_us % 1_000_000 * 1000];+ syscall5(SYS_PPOLL, fds.as_mut_ptr() as u64, fds.len() as u64, ts.as_ptr() as u64, 0, 8)+}+/// select(nfds, read set, write set, NULL, timeval): the 1983 interface.+/// The sets are bitmaps of fds (64 per word); on return only the ready+/// ones are still set.+pub fn select(nfds: i64, read: &mut [u64; 16], write: &mut [u64; 16], timeout_us: u64) -> i64 {+ let tv = [timeout_us / 1_000_000, timeout_us % 1_000_000];+ syscall5(SYS_SELECT, nfds as u64, read.as_mut_ptr() as u64, write.as_mut_ptr() as u64, 0, tv.as_ptr() as u64)+}+pub fn fd_set(set: &mut [u64; 16], fd: i64) {+ set[fd as usize / 64] |= 1 << (fd % 64);+}+pub fn fd_isset(set: &[u64; 16], fd: i64) -> bool {+ set[fd as usize / 64] & (1 << (fd % 64)) != 0+} pub const EPOLL_CTL_ADD: u64 = 1; pub const EPOLL_CTL_DEL: u64 = 2;