//! cowtest -- checks that fork really gives the child its *own* memory. //! //! fork doesn't copy anything up front: parent and child share every page, //! read-only, and the first write to a page makes the copy (copy-on-write). //! This checks that the copies are made at the right moments: writes in the //! child don't show in the parent and vice versa -- for global data, the //! heap and the stack -- while MAP_SHARED memory *is* shared. #![no_std] #![no_main] extern crate alloc; #[macro_use] #[path = "ulib.rs"] mod ulib; use alloc::vec; use ulib::*; /// 64 KiB of global data (16 pages, in .bss). static mut GLOBAL: [u8; 65536] = [0; 65536]; fn global() -> &'static mut [u8; 65536] { unsafe { &mut *core::ptr::addr_of_mut!(GLOBAL) } } fn all(buf: &[u8], value: u8) -> bool { buf.iter().all(|&b| b == value) } fn main() -> i64 { let mut c = Checks::new("cowtest"); global().fill(1); let mut heap = vec![2u8; 100_000]; let mut stack = [3u8; 2000]; // 1. The child writes; the parent must not see it. let pid = fork(); if pid == 0 { let before = all(global(), 1) && all(&heap, 2) && all(&stack, 3); global().fill(0xc1); heap.fill(0xc2); stack.fill(0xc3); let after = all(global(), 0xc1) && all(&heap, 0xc2) && all(core::hint::black_box(&stack), 0xc3); exit(if before && after { 0 } else { 1 }); } c.check(wait(pid) == 0, "the child starts with the parent's data and can change its copy"); c.check(all(global(), 1), "the child's writes to global data stay in the child"); c.check(all(&heap, 2), "... and to the heap"); c.check(all(core::hint::black_box(&stack), 3), "... and to the stack"); // 2. The parent writes after fork; the child must keep the old data. // A pipe orders things: the child only checks after we wrote. let (r, w) = pipe().unwrap_or((-1, -1)); let pid = fork(); if pid == 0 { close(w); let mut b = [0u8]; read(r, &mut b); // wait for the parent's go exit(if all(global(), 1) && all(&heap, 2) { 0 } else { 1 }); } close(r); global().fill(0x77); heap.fill(0x77); write(w, b"!"); close(w); c.check(wait(pid) == 0, "the parent's writes after fork stay in the parent"); c.check(all(global(), 0x77), "and the parent sees its own writes"); // 3. A grandchild: three generations share pages, each writes. let pid = fork(); if pid == 0 { let pid2 = fork(); if pid2 == 0 { global()[0] = 3; exit(if global()[1] == 0x77 { 0 } else { 1 }); } global()[0] = 2; let code = wait(pid2); exit(if code == 0 && global()[0] == 2 { 0 } else { 1 }); } c.check(wait(pid) == 0 && global()[0] == 0x77, "three generations: each keeps its own copy"); // 4. MAP_SHARED memory is *not* copied: both see the same pages. let shared = mmap(0, 8192, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); let private = mmap(0, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); c.check(shared > 0 && private > 0, "mmap shared + private anonymous memory"); let (sp, pp) = (shared as *mut u8, private as *mut u8); unsafe { sp.write_volatile(10); pp.write_volatile(10); } let pid = fork(); if pid == 0 { unsafe { sp.write_volatile(42); sp.add(4096).write_volatile(43); // a page nobody touched before fork pp.write_volatile(42); } exit(0); } wait(pid); let (s0, s1, p0) = unsafe { (sp.read_volatile(), sp.add(4096).read_volatile(), pp.read_volatile()) }; c.check(s0 == 42 && s1 == 43, "MAP_SHARED: the child's writes show up in the parent"); c.check(p0 == 10, "MAP_PRIVATE: they don't"); c.done() }