diff --git a/Makefile b/Makefile index 6a3f3cc..a237f5f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -OBJS = entry.o vectors.o trapasm.o +OBJS = entry.o vectors.o trapasm.o swtch.o RS = src/*.rs # Cross-compiling (e.g., on Mac OS X) @@ -82,6 +82,12 @@ bootblock: bootasm.S bootmain.c linkers/bootblock.ld $(OBJDUMP) -S -D bootblock.o > bootblock.asm $(OBJCOPY) -S -O binary bootblock.o bootblock +initcode: initcode.S + $(CC) $(CFLAGS) -nostdinc -I. -c initcode.S + $(LD) $(LDFLAGS) -N -e start -Ttext 0 -o initcode.out initcode.o + $(OBJCOPY) -S -O binary initcode.out initcode + $(OBJDUMP) -S initcode.o > initcode.asm + kernel.a: $(RS) cargo +nightly rustc \ -Z build-std=core \ @@ -91,8 +97,8 @@ kernel.a: $(RS) --lib --release \ -- -A warnings --emit link=kernel.a -kernel: kernel.a $(OBJS) ./linkers/kernel.ld - $(LD) -m elf_i386 -T ./linkers/kernel.ld -o kernel $(OBJS) kernel.a +kernel: kernel.a $(OBJS) ./linkers/kernel.ld initcode + $(LD) -m elf_i386 -T ./linkers/kernel.ld -o kernel $(OBJS) kernel.a -b binary initcode $(OBJDUMP) -S -D kernel > kernel.asm $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernel.sym diff --git a/initcode.S b/initcode.S new file mode 100644 index 0000000..ea554dc --- /dev/null +++ b/initcode.S @@ -0,0 +1,10 @@ +# Initial process execs /init. +# This code runs in user space. + +# movl %eax, 0x10001 +# movl %eax, 0x1000100 +.globl start +start: + add $1, %eax + movl %eax, 0x10001 + jmp start diff --git a/linkers/kernel.ld b/linkers/kernel.ld index cf6630e..0b70493 100644 --- a/linkers/kernel.ld +++ b/linkers/kernel.ld @@ -8,7 +8,7 @@ ENTRY(_start) SECTIONS { /* Link the kernel at this address: "." means the current address */ - /* Must be equal to KERNLINK */ + /* Must be equal to KERNLINK */ . = 0x00100000; .text : AT(0x100000) { diff --git a/src/constants.rs b/src/constants.rs index d1b519d..9306052 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -15,15 +15,39 @@ pub const SEG_TSS: u16 = 5; // this process's task state // cpu->gdt[NSEGS] holds the above segments. pub const NSEGS: usize = 6; +// Privilege level +pub const DPL_USER: u8 = 0x3; // User DPL + // Application segment type bits pub const STA_X: u8 = 0x8; // Executable segment pub const STA_W: u8 = 0x2; // Writeable (non-executable segments) pub const STA_R: u8 = 0x2; // Readable (executable segments) // Memory layout +pub const EXTMEM: u32 = 0x100000; // Start of extended memory +pub const PHYSTOP: u32 = 0xE000000; // Top physical memory +pub const DEVSPACE: u32 = 0xFE000000; // Other devices are at high addresses + +// Key addresses for address space layout +pub const KERNBASE: u32 = 0x0; // First kernel virtual address +pub const KERNLINK: u32 = KERNBASE + EXTMEM; // Address where kernel is linked + // We assume that kernel.asm can fit in first 2MB pub const STARTPROC: u32 = 0x200000; // Start allocating process from here (2MB) -pub const PROCSIZE: u32 = 0x100000; // Size of each process (1MB) +pub const PROCSIZE: u32 = 0x100; // 1MB is the size of each process (in multiple of 4KB) + +// Page table constants +pub const PGSIZE: u32 = 4096; // bytes mapped by a page +pub const NPDENTRIES: usize = 1024; // # directory entries per page directory +pub const NPTENTRIES: usize = 1024; // # PTEs per page table +pub const PTXSHIFT: u32 = 12; // offset of PTX in a linear address +pub const PDXSHIFT: u32 = 22; // offset of PDX in a linear address + +// Page table/directory entry flags +pub const PTE_P: u32 = 0x001; // Present +pub const PTE_W: u32 = 0x002; // Writeable +pub const PTE_U: u32 = 0x004; // User +pub const PTE_PS: u32 = 0x080; // Page Size // System segment type bits pub const STS_T32A: u8 = 0x9; // Available 32-bit TSS diff --git a/src/lapic.rs b/src/lapic.rs index fc3dbca..5612dbb 100644 --- a/src/lapic.rs +++ b/src/lapic.rs @@ -1,6 +1,6 @@ use core::ptr::{read_volatile, write_volatile}; use crate::mp::MP_ONCE; -use crate::constants::{IRQ_ERROR, IRQ_SPURIOUS, IRQ_TIMER, T_IRQ0}; +use crate::constants::{IRQ_ERROR, IRQ_SPURIOUS, T_IRQ0}; const ID: isize = 0x0020 / 4; const VER: isize = 0x0030 / 4; @@ -68,9 +68,10 @@ pub fn lapicinit() { // from lapic[TICR] and then issues an interrupt. // If xv6 cared more about precise timekeeping, // TICR would be calibrated using an external time source. - lapicw(TDCR, X1); - lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER)); - lapicw(TICR, 10000000); + // Timer disabled for p15 - will be re-enabled later + // lapicw(TDCR, X1); + // lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER)); + // lapicw(TICR, 10000000); // Disable logical interrupt lines. diff --git a/src/main.rs b/src/main.rs index 99d6e4c..0853b3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ mod file; mod log; mod mmu; mod vm; +mod spinlock; use crate::traps::*; fn halt() -> ! { @@ -82,18 +83,42 @@ pub extern "C" fn entryofrust() -> ! { x86::sti(); fs::iinit(param::ROOTDEV); log::initlog(param::ROOTDEV); + // cli(); // disable interrupts file::mknod("/console", param::CONSOLE as i16, param::CONSOLE as i16); vm::seginit(); // segment descriptors - welcome(); - - loop { - x86::wfi(); - } + proc::pinit(); // first process + proc::scheduler(); // start running processes (never returns) } +static mut PANICKED: bool = false; + #[cfg(not(test))] #[panic_handler] fn panic(info: &PanicInfo) -> ! { println!("Kernel Panic: {:?}", info); - halt() + use core::fmt::Write; + + // Disable interrupts to prevent interrupt handlers from interfering + cli(); + + // Print panic message with LAPIC ID to identify which CPU panicked + let mut console = console::Console {}; + let _ = write!(&mut console, "lapicid {}: panic: ", lapicid()); + let _ = writeln!(&mut console, "{}", info); + + // Print stack trace + let mut pcs = [0u32; 10]; + let stack_ptr = &info as *const _ as *const u32; + spinlock::getcallerpcs(stack_ptr, &mut pcs); + + for &pc in &pcs { + if pc != 0 { + let _ = writeln!(&mut console, " {:#x}", pc); + } + } + + unsafe { PANICKED = true; } + + // Halt the system + halt(); } diff --git a/src/mmu.rs b/src/mmu.rs index 505b33a..7e5d378 100644 --- a/src/mmu.rs +++ b/src/mmu.rs @@ -1,9 +1,9 @@ #![allow(unused_parens)] // False positive from bitfield macro use modular_bitfield::prelude::*; +use crate::constants::{PDXSHIFT, PTXSHIFT}; // Page table/directory helper functions // Segment Descriptor - #[bitfield] #[repr(C, packed)] #[derive(Clone, Copy, Default, Debug)] @@ -44,3 +44,33 @@ impl SegDesc { seg } } + +// Extract page directory index from virtual address +#[inline] +pub fn pdx(va: u32) -> usize { + ((va >> PDXSHIFT) & 0x3FF) as usize +} + +// Extract page table index from virtual address +#[inline] +pub fn ptx(va: u32) -> usize { + ((va >> PTXSHIFT) & 0x3FF) as usize +} + +// Construct virtual address from page directory index, page table index, and offset +#[inline] +pub fn pgaddr(d: u32, t: u32, o: u32) -> u32 { + (d << PDXSHIFT) | (t << PTXSHIFT) | o +} + +// Extract address from page table entry +#[inline] +pub fn pte_addr(pte: u32) -> u32 { + pte & !0xFFF +} + +// Extract flags from page table entry +#[inline] +pub fn pte_flags(pte: u32) -> u32 { + pte & 0xFFF +} \ No newline at end of file diff --git a/src/param.rs b/src/param.rs index 4a269c3..2b4ebc4 100644 --- a/src/param.rs +++ b/src/param.rs @@ -1,6 +1,8 @@ +pub const NPROC: usize = 64; // maximum number of processes pub const KSTACKSIZE: usize = 4096; // size of per-process kernel stack (unused in p3) pub const NCPU: usize = 8; // maximum number of CPUs pub const MAXOPBLOCKS: usize = 10; // max # of blocks any FS op writes +pub const NOFILE: usize = 16; // open files per process pub const NFILE: usize = 100; // open files per system pub const NINODE: usize = 50; // maximum number of active i-nodes pub const NDEV: usize = 10; // maximum major device number diff --git a/src/proc.rs b/src/proc.rs index 44c0fad..014ad47 100644 --- a/src/proc.rs +++ b/src/proc.rs @@ -1,29 +1,233 @@ -use crate::mp::MP_ONCE; // Import the MP_ONCE static from mp.rs -// use core::ptr; -use crate::constants::NSEGS; +use crate::mp::MP_ONCE; +use crate::constants::{NSEGS, SEG_UCODE, SEG_UDATA, DPL_USER, FL_IF, PGSIZE, STARTPROC, PROCSIZE}; +use crate::fs::namei; use crate::mmu::SegDesc; +use crate::println; +use crate::x86::{TrapFrame, sti}; +use crate::param::NPROC; +use core::ptr::null_mut; +// use core::cell::OnceCell; + +// Saved registers for kernel context switches. +// Don't need to save all the segment registers (%cs, etc), +// because they are constant across kernel contexts. +// Don't need to save %eax, %ecx, %edx, because the +// x86 convention is that the caller has saved them. +// Contexts are stored at the bottom of the stack they +// describe; the stack pointer is the address of the context. +// The layout of the context matches the layout of the stack in swtch.S +// at the "Switch stacks" comment. Switch doesn't save eip explicitly, +// but it is on the stack and allocproc() manipulates it. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Context { + pub edi: u32, + pub esi: u32, + pub ebx: u32, + pub ebp: u32, + pub eip: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProcState { + Unused, + Embryo, + Runnable, + Running, +} + +// Per-process state +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Proc { + pub kstack: *mut u8, // Bottom of kernel stack for this process (unused for now) + pub state: ProcState, // Process state + pub pid: i32, // Process ID + pub parent: *mut Proc, // Parent process + pub tf: *mut TrapFrame, // Trap frame for current syscall + pub context: *mut Context, // swtch() here to run process + pub cwd: usize, // Current directory (inode number) + pub name: [u8; 16], // Process name (debugging) +} + +impl Proc { + pub const fn new() -> Self { + Self { + kstack: null_mut(), + state: ProcState::Unused, + pid: 0, + parent: null_mut(), + tf: null_mut(), + context: null_mut(), + cwd: 0, + name: [0; 16], + } + } +} + +// Process table +struct PTable { + proc: [Proc; NPROC], +} + +static mut PTABLE: PTable = PTable { + proc: [Proc::new(); NPROC], +}; + +static mut NEXTPID: i32 = 1; #[derive(Debug, Clone, Copy)] pub struct Cpu { - pub apicid: u8, // Local APIC ID - pub gdt: [SegDesc; NSEGS], // x86 global descriptor table + pub apicid: u8, // Local APIC ID + pub scheduler: *mut Context, // swtch() here to enter scheduler + pub gdt: [SegDesc; NSEGS], // x86 global descriptor table + pub proc: *mut Proc, // The process running on this cpu or null } impl Cpu { pub const fn new() -> Self { Self { apicid: 0, + scheduler: null_mut(), gdt: [SegDesc::new(); NSEGS], + proc: null_mut(), } } } pub fn cpuid() -> usize { - let cpus = MP_ONCE.cpus.get().expect("CPUs not initialized"); - unsafe { (mycpu() as *const Cpu).offset_from(cpus.as_ptr()) as usize } + // For now, always return 0 (single CPU) + 0 } -pub fn mycpu() -> &'static Cpu { +pub fn mycpu() -> &'static mut Cpu { let cpus = MP_ONCE.cpus.get().expect("CPUs not initialized"); - &cpus[0] + unsafe { &mut *(cpus.as_ptr() as *mut Cpu).add(cpuid()) } +} + +// Read proc from the cpu structure +pub fn myproc() -> Option<&'static mut Proc> { + let c = mycpu(); + if c.proc.is_null() { + None + } else { + Some(unsafe { &mut *c.proc }) + } +} + +// External symbols from assembly +extern "C" { + fn trapret(); + pub fn swtch(context: *mut Context); +} + +// Look in the process table for an UNUSED proc. +// If found, change state to EMBRYO and initialize +// state required to run in the kernel. +// Otherwise return None. +fn allocproc() -> Option<&'static mut Proc> { + unsafe { + let ptable = &raw mut PTABLE; + + for p in &mut (*ptable).proc { + if p.state == ProcState::Unused { + // Found an unused process + p.state = ProcState::Embryo; + p.pid = NEXTPID; + NEXTPID += 1; + + // Calculate stack pointer at the end of process memory + let sp = (STARTPROC + (PROCSIZE << 12)) as *mut u8; + + // Leave room for trap frame + let sp = sp.sub(core::mem::size_of::()); + p.tf = sp as *mut TrapFrame; + + // Leave room for context + let sp = sp.sub(core::mem::size_of::()); + p.context = sp as *mut Context; + + // Initialize context + core::ptr::write_bytes(p.context, 0, 1); + (*p.context).eip = trapret as *const () as usize as u32; + + return Some(p); + } + } + + None + } +} + +// Set up first process. +pub fn pinit() { + unsafe { + extern "C" { + static _binary_initcode_start: u8; + static _binary_initcode_size: usize; + } + + let p = allocproc().expect("Failed to allocate first process"); + + // Copy initcode binary to STARTPROC + let dst = STARTPROC as *mut u8; + let src = &_binary_initcode_start as *const u8; + let size = &_binary_initcode_size as *const usize as usize; + core::ptr::copy_nonoverlapping(src, dst, size); + + // Initialize trapframe + core::ptr::write_bytes(p.tf, 0, 1); + + (*p.tf).cs = ((SEG_UCODE << 3) | DPL_USER as u16) as u16; + (*p.tf).ds = ((SEG_UDATA << 3) | DPL_USER as u16) as u16; + (*p.tf).es = (*p.tf).ds; + (*p.tf).ss = (*p.tf).ds; + (*p.tf).eflags = FL_IF; + (*p.tf).esp = PGSIZE; + (*p.tf).eip = 0; // beginning of initcode.S + + // Set process name + let name = b"initcode"; + for (i, &byte) in name.iter().enumerate() { + p.name[i] = byte; + } + // Set current working directory to root + p.cwd = namei("/").expect("Failed to find root directory"); + + p.state = ProcState::Runnable; + } +} + +// Process scheduler. +// Scheduler never returns. It loops, doing: +// - choose a process to run +// - swtch to start running that process +pub fn scheduler() -> ! { + let c = mycpu(); + c.proc = null_mut(); + + loop { + // Enable interrupts on this processor. + sti(); + + // Loop over process table looking for process to run. + unsafe { + + let ptable = &raw mut PTABLE; + for p in &mut (*ptable).proc { + if p.state != ProcState::Runnable { + continue; + } + + // Switch to chosen process. + c.proc = p as *mut Proc; + p.state = ProcState::Running; + + swtch(p.context); + + // Process is done running for now. + c.proc = null_mut(); + } + } + } } \ No newline at end of file diff --git a/src/spinlock.rs b/src/spinlock.rs new file mode 100644 index 0000000..2e7b2f2 --- /dev/null +++ b/src/spinlock.rs @@ -0,0 +1,32 @@ +// Mutual exclusion spin locks and debugging utilities + +/// Record the current call stack in pcs[] by following the %ebp chain. +/// This function walks the stack frame pointers to collect return addresses. +pub fn getcallerpcs(v: *const u32, pcs: &mut [u32; 10]) { + unsafe { + let mut ebp = v.offset(-2) as *const u32; + let mut i = 0; + + while i < 10 { + // Check for invalid ebp values + // if(ebp == 0 || ebp < (uint*)KERNBASE || ebp == (uint*)0xffffffff) + if ebp.is_null() || ebp == 0xffffffff as *const u32 { + break; + } + + // pcs[i] = ebp[1]; // saved %eip + pcs[i] = *ebp.offset(1); + + // ebp = (uint*)ebp[0]; // saved %ebp + ebp = *ebp as *const u32; + + i += 1; + } + + // Fill rest with zeros + while i < 10 { + pcs[i] = 0; + i += 1; + } + } +} diff --git a/src/vm.rs b/src/vm.rs index 20baffb..598f42f 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,4 +1,4 @@ -use crate::constants::{SEG_KCODE, SEG_KDATA, SEG_UCODE, SEG_UDATA, STA_X, STA_W, STA_R, STARTPROC, PROCSIZE}; +use crate::constants::{SEG_KCODE, SEG_KDATA, SEG_UCODE, SEG_UDATA, STA_X, STA_W, STA_R, STARTPROC, PROCSIZE, DPL_USER}; use crate::mmu::SegDesc; use crate::proc::cpuid; use crate::mp::MP_ONCE; @@ -10,14 +10,17 @@ use core::mem::size_of_val; pub fn seginit() { unsafe { // Map "logical" addresses to virtual addresses using identity map. + // Cannot share a CODE descriptor for both kernel and user + // because it would have to have DPL_USR, but the CPU forbids + // an interrupt from CPL=0 to DPL=3. let cpus = MP_ONCE.cpus.get().expect("CPUs not initialized"); let cpu_ptr = cpus.as_ptr() as *mut crate::proc::Cpu; let c = &mut *cpu_ptr.add(cpuid()); c.gdt[SEG_KCODE as usize] = SegDesc::seg(STA_X | STA_R, 0, 0xffffffff, 0); c.gdt[SEG_KDATA as usize] = SegDesc::seg(STA_W, 0, 0xffffffff, 0); - c.gdt[SEG_UCODE as usize] = SegDesc::seg(STA_X | STA_R, STARTPROC, PROCSIZE, 0); - c.gdt[SEG_UDATA as usize] = SegDesc::seg(STA_W, STARTPROC, PROCSIZE, 0); + c.gdt[SEG_UCODE as usize] = SegDesc::seg(STA_X | STA_R, STARTPROC, PROCSIZE << 12, DPL_USER); + c.gdt[SEG_UDATA as usize] = SegDesc::seg(STA_W, STARTPROC, PROCSIZE << 12, DPL_USER); lgdt(&c.gdt, size_of_val(&c.gdt)); } } diff --git a/src/x86.rs b/src/x86.rs index 8a65859..48f24df 100644 --- a/src/x86.rs +++ b/src/x86.rs @@ -139,6 +139,42 @@ pub fn rcr2() -> u32 { } } +pub fn stosb(addr: *mut u8, data: u8, cnt: usize) { + unsafe { + asm!( + "cld", + "rep stosb", + inout("edi") addr => _, + inout("ecx") cnt => _, + in("al") data, + options(nostack) + ); + } +} + +pub fn stosl(addr: *mut u32, data: u32, cnt: usize) { + unsafe { + asm!( + "cld", + "rep stosl", + inout("edi") addr => _, + inout("ecx") cnt => _, + in("eax") data, + options(nostack) + ); + } +} + +pub fn loadgs(v: u16) { + unsafe { + asm!( + "mov gs, {0:x}", + in(reg) v, + options(nomem, nostack) + ); + } +} + #[repr(C)] pub struct TrapFrame { // registers as pushed by pusha @@ -151,6 +187,15 @@ pub struct TrapFrame { pub ecx: u32, pub eax: u32, + // segment registers + pub gs: u16, + pub padding1: u16, + pub fs: u16, + pub padding2: u16, + pub es: u16, + pub padding3: u16, + pub ds: u16, + pub padding4: u16, pub trapno: u32, // below here defined by x86 hardware diff --git a/swtch.S b/swtch.S new file mode 100644 index 0000000..60479ec --- /dev/null +++ b/swtch.S @@ -0,0 +1,22 @@ +# Context switch +# +# void swtch(struct context *new); +# +# Save the current registers on the stack, creating +# a struct context, and save its address in *old. +# Switch stacks to new and pop previously-saved registers. + +.globl swtch +swtch: + movl 4(%esp), %eax + + # Switch stacks + movl %eax, %esp + movl $0, %eax + + # Load new callee-saved registers + popl %edi + popl %esi + popl %ebx + popl %ebp + ret diff --git a/trapasm.S b/trapasm.S index 477a5ee..7f46ce1 100644 --- a/trapasm.S +++ b/trapasm.S @@ -4,8 +4,17 @@ .globl alltraps alltraps: # Build trap frame. + pushl %ds + pushl %es + pushl %fs + pushl %gs pushal + # Set up data segments. + movw $(SEG_KDATA<<3), %ax + movw %ax, %ds + movw %ax, %es + # Call trap(tf), where tf=%esp pushl %esp call trap @@ -15,6 +24,10 @@ alltraps: .globl trapret trapret: popal + popl %gs + popl %fs + popl %es + popl %ds addl $0x8, %esp # trapno and errcode iret