Rewrite spinlock.c

This commit is contained in:
Garen Tyler 2023-10-15 12:51:16 -06:00
parent b30ea849d1
commit 709ec3a50f
Signed by: garentyler
GPG Key ID: D7A048C454CB7054
16 changed files with 596 additions and 126 deletions

View File

@ -10,7 +10,6 @@ OBJS = \
$K/printf.o \
$K/uart.o \
$K/kalloc.o \
$K/spinlock.o \
$K/string.o \
$K/main.o \
$K/vm.o \
@ -30,7 +29,8 @@ OBJS = \
$K/sysfile.o \
$K/kernelvec.o \
$K/plic.o \
$K/virtio_disk.o
$K/virtio_disk.o \
$K/riscv.o
# riscv64-unknown-elf- or riscv64-linux-gnu-
# perhaps in /opt/riscv/bin
@ -78,6 +78,7 @@ LDFLAGS = -z max-page-size=4096
$K/kernel: $(OBJS) $K/kernel.ld $U/initcode $R/src
cargo +nightly -Z unstable-options -C $R build
$(OBJDUMP) -S $R/target/$(TARGET_TRIPLE)/debug/librustkernel.a > $R/target/$(TARGET_TRIPLE)/debug/librustkernel.asm
$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS) $R/target/$(TARGET_TRIPLE)/debug/librustkernel.a
$(OBJDUMP) -S $K/kernel > $K/kernel.asm
$(OBJDUMP) -t $K/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $K/kernel.sym

View File

@ -110,7 +110,7 @@ void procdump(void);
// swtch.S
void swtch(struct context*, struct context*);
// spinlock.c
// spinlock.rs
void acquire(struct spinlock*);
int holding(struct spinlock*);
void initlock(struct spinlock*, char*);

View File

@ -61,12 +61,12 @@ procinit(void)
// Must be called with interrupts disabled,
// to prevent race with process being moved
// to a different CPU.
int
cpuid()
{
int id = r_tp();
return id;
}
// int
// cpuid()
// {
// int id = r_tp();
// return id;
// }
// Return this CPU's cpu struct.
// Interrupts must be disabled.

View File

@ -105,3 +105,5 @@ struct proc {
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};
int cpuid();

30
kernel/riscv.c Normal file
View File

@ -0,0 +1,30 @@
#include "kernel/riscv.h"
unsigned long rv_r_mhartid() {
return r_mhartid();
}
unsigned int rv_r_tp() {
return r_tp();
}
unsigned long rv_r_sstatus() {
return r_sstatus();
}
void rv_w_sstatus(unsigned long x) {
w_sstatus(x);
}
void rv_intr_on() {
intr_on();
}
void rv_intr_off() {
intr_off();
}
int rv_intr_get() {
return intr_get();
}

View File

@ -1,5 +1,7 @@
#ifndef __ASSEMBLER__
#include "./types.h"
// which hart (core) is this?
static inline uint64
r_mhartid()

View File

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "libc"
version = "0.2.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]]
name = "rustkernel"
version = "0.1.0"
dependencies = [
"libc",
]

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
libc = { version = "0.2.149", default-features = false }
[lib]
crate-type = ["staticlib"]

View File

@ -0,0 +1,28 @@
extern "C" {
fn kalloc() -> *mut u8;
fn kfree(ptr: *mut u8);
}
use core::alloc::{GlobalAlloc, Layout};
struct KernelAllocator;
unsafe impl GlobalAlloc for KernelAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if layout.size() > 4096 {
panic!("can only allocate one page of memory at a time");
}
let ptr = kalloc();
if ptr.is_null() {
panic!("kernel could not allocate memory");
}
ptr
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
kfree(ptr);
}
}
#[global_allocator]
static GLOBAL: KernelAllocator = KernelAllocator;

View File

@ -1,12 +1,26 @@
#![no_std]
#![no_main]
#![no_std]
#![allow(dead_code)]
#![allow(clippy::missing_safety_doc)]
extern crate alloc;
extern crate core;
extern "C" {
fn print(message: *const c_char);
fn panic(panic_message: *const c_char) -> !;
}
mod kalloc;
pub(crate) mod param;
pub mod proc;
pub(crate) mod riscv;
pub mod spinlock;
use core::ffi::{c_char, CStr};
extern "C" {
pub fn print(message: *const c_char);
fn panic(panic_message: *const c_char) -> !;
}
pub use proc::*;
pub use spinlock::*;
#[no_mangle]
pub extern "C" fn rust_main() {
@ -19,8 +33,11 @@ pub extern "C" fn rust_main() {
}
}
#[panic_handler]
unsafe fn panic_wrapper(_panic_info: &core::panic::PanicInfo) -> ! {
panic(CStr::from_bytes_with_nul(b"panic from rust\0").unwrap_or_default().as_ptr())
panic(
CStr::from_bytes_with_nul(b"panic from rust\0")
.unwrap_or_default()
.as_ptr(),
)
}

View File

@ -0,0 +1,26 @@
/// Maximum number of processes
pub const NPROC: usize = 64;
/// Maximum number of CPUs
pub const NCPU: usize = 8;
/// Maximum number of open files per process
pub const NOFILE: usize = 16;
/// Maximum number of open files per system
pub const NFILE: usize = 100;
/// Maximum number of active inodes
pub const NINODE: usize = 50;
/// Maximum major device number
pub const NDEV: usize = 10;
/// Device number of file system root disk
pub const ROOTDEV: usize = 1;
/// Max exec arguments
pub const MAXARG: usize = 32;
/// Max num of blocks any FS op writes
pub const MAXOPBLOCKS: usize = 10;
/// Max data blocks in on-disk log
pub const LOGSIZE: usize = MAXOPBLOCKS * 3;
/// Size of disk block cache
pub const NBUF: usize = MAXOPBLOCKS * 3;
/// Size of file system in blocks
pub const FSSIZE: usize = 2000;
/// Maximum file path size
pub const MAXPATH: usize = 128;

View File

@ -0,0 +1,162 @@
use crate::{
riscv::{self, Pagetable},
spinlock::Spinlock,
};
use core::ffi::c_char;
/// Saved registers for kernel context switches.
#[repr(C)]
pub struct Context {
pub ra: u64,
pub sp: u64,
// callee-saved
pub s0: u64,
pub s1: u64,
pub s2: u64,
pub s3: u64,
pub s4: u64,
pub s5: u64,
pub s6: u64,
pub s7: u64,
pub s8: u64,
pub s9: u64,
pub s10: u64,
pub s11: u64,
}
/// Per-CPU state.
#[repr(C)]
pub struct Cpu {
/// The process running on this cpu, or null.
pub proc: *mut Proc,
/// swtch() here to enter scheduler()
pub context: Context,
/// Depth of push_off() nesting.
pub noff: i32,
/// Were interrupts enabled before push_off()?
pub intena: i32,
}
/// Per-process data for the trap handling code in trampoline.S.
///
/// sits in a page by itself just under the trampoline page in the
/// user page table. not specially mapped in the kernel page table.
/// uservec in trampoline.S saves user registers in the trapframe,
/// then initializes registers from the trapframe's
/// kernel_sp, kernel_hartid, kernel_satp, and jumps to kernel_trap.
/// usertrapret() and userret in trampoline.S set up
/// the trapframe's kernel_*, restore user registers from the
/// trapframe, switch to the user page table, and enter user space.
/// the trapframe includes callee-saved user registers like s0-s11 because the
/// return-to-user path via usertrapret() doesn't return through
/// the entire kernel call stack.
#[repr(C)]
pub struct TrapFrame {
/// Kernel page table.
pub kernel_satp: u64,
/// Top of process's kernel stack.
pub kernel_sp: u64,
/// usertrap()
pub kernel_trap: u64,
/// Saved user program counter.
pub epc: u64,
/// Saved kernel tp.
pub kernel_hartid: u64,
pub ra: u64,
pub sp: u64,
pub gp: u64,
pub tp: u64,
pub t0: u64,
pub t1: u64,
pub t2: u64,
pub s0: u64,
pub s1: u64,
pub a0: u64,
pub a1: u64,
pub a2: u64,
pub a3: u64,
pub a4: u64,
pub a5: u64,
pub a6: u64,
pub a7: u64,
pub s2: u64,
pub s3: u64,
pub s4: u64,
pub s5: u64,
pub s6: u64,
pub s7: u64,
pub s8: u64,
pub s9: u64,
pub s10: u64,
pub s11: u64,
pub t3: u64,
pub t4: u64,
pub t5: u64,
pub t6: u64,
}
#[repr(C)]
pub enum ProcState {
Unused,
Used,
Sleeping,
Runnable,
Running,
Zombie,
}
/// Per-process state.
#[repr(C)]
pub struct Proc {
pub lock: Spinlock,
// p->lock must be held when using these:
/// Process state
pub state: ProcState,
/// If non-zero, sleeping on chan
pub chan: *mut u8,
/// If non-zero, have been killed
pub killed: i32,
/// Exit status to be returned to parent's wait
pub xstate: i32,
/// Process ID
pub pid: i32,
// wait_lock msut be held when using this:
/// Parent process
pub parent: *mut Proc,
// These are private to the process, so p->lock need not be held.
/// Virtual address of kernel stack
pub kstack: u64,
/// Size of process memory (bytes)
pub sz: u64,
/// User page table
pub pagetable: Pagetable,
/// Data page for trampoline.S
pub trapframe: *mut TrapFrame,
/// swtch() here to run process
pub context: Context,
/// Open files
pub ofile: *mut u8, // TODO: Change u8 ptr to File ptr.
/// Current directory
pub cwd: *mut u8, // TODO: Change u8 ptr to inode ptr.
/// Process name (debugging)
pub name: [c_char; 16],
}
/// Must be called with interrupts disabled
/// to prevent race with process being moved
/// to a different CPU.
#[no_mangle]
pub unsafe extern "C" fn cpuid() -> i32 {
riscv::r_tp() as i32
}
extern "C" {
// pub fn cpuid() -> i32;
/// Return this CPU's cpu struct.
/// Interrupts must be disabled.
pub fn mycpu() -> *mut Cpu;
}

View File

@ -0,0 +1,190 @@
use core::arch::asm;
pub type Pte = u64;
pub type Pagetable = *mut [Pte; 512];
/// Previous mode
pub const MSTATUS_MPP_MASK: u64 = 3 << 11;
pub const MSTATUS_MPP_M: u64 = 3 << 11;
pub const MSTATUS_MPP_S: u64 = 1 << 11;
pub const MSTATUS_MPP_U: u64 = 0 << 11;
/// Machine-mode interrupt enable.
pub const MSTATUS_MIE: u64 = 1 << 3;
/// Previous mode: 1 = Supervisor, 0 = User
pub const SSTATUS_SPP: u64 = 1 << 8;
/// Supervisor Previous Interrupt Enable
pub const SSTATUS_SPIE: u64 = 1 << 5;
/// User Previous Interrupt Enable
pub const SSTATUS_UPIE: u64 = 1 << 4;
/// Supervisor Interrupt Enable
pub const SSTATUS_SIE: u64 = 1 << 1;
/// User Interrupt Enable
pub const SSTATUS_UIE: u64 = 1 << 0;
/// Supervisor External Interrupt Enable
pub const SIE_SEIE: u64 = 1 << 9;
/// Supervisor Timer Interrupt Enable
pub const SIE_STIE: u64 = 1 << 5;
/// Supervisor Software Interrupt Enable
pub const SIE_SSIE: u64 = 1 << 1;
/// Machine-mode External Interrupt Enable
pub const MIE_MEIE: u64 = 1 << 11;
/// Machine-mode Timer Interrupt Enable
pub const MIE_MTIE: u64 = 1 << 7;
/// Machine-mode Software Interrupt Enable
pub const MIE_MSIE: u64 = 1 << 3;
pub const SATP_SV39: u64 = 8 << 60;
/// Bytes per page
pub const PGSIZE: u64 = 4096;
/// Bits of offset within a page
pub const PGSHIFT: u64 = 12;
pub const PTE_V: u64 = 1 << 0;
pub const PTE_R: u64 = 1 << 1;
pub const PTE_W: u64 = 1 << 2;
pub const PTE_X: u64 = 1 << 3;
pub const PTE_U: u64 = 1 << 4;
/// Which hart (core) is this?
#[inline(always)]
pub unsafe fn r_mhartid() -> u64 {
let x: u64;
asm!("csrr {}, mhartid", out(reg) x);
x
}
#[inline(always)]
pub unsafe fn r_tp() -> u64 {
let x: u64;
asm!("mv {}, tp", out(reg) x);
x
}
#[inline(always)]
pub unsafe fn w_sstatus(x: u64) {
asm!("csrw sstatus, {}", in(reg) x);
}
#[inline(always)]
pub unsafe fn r_sstatus() -> u64 {
let x: u64;
asm!("csrr {}, sstatus", out(reg) x);
x
}
#[inline(always)]
pub unsafe fn intr_on() {
w_sstatus(r_sstatus() | SSTATUS_SIE);
}
#[inline(always)]
pub unsafe fn intr_off() {
w_sstatus(r_sstatus() & !SSTATUS_SIE);
}
#[inline(always)]
pub unsafe fn intr_get() -> i32 {
if (r_sstatus() & SSTATUS_SIE) > 0 {
1
} else {
0
}
}
extern "C" {
/// Which hart (core) is this?
pub fn rv_r_mhartid() -> u64;
// Machine Status Register, mstatus
pub fn r_mstatus() -> u64;
pub fn w_mstatus(x: u64);
// Machine Exception Program Counter
// MEPC holds the instruction address to which a return from exception will go.
pub fn w_mepc(x: u64);
// Supervisor Status Register, sstatus
pub fn rv_r_sstatus() -> u64;
pub fn rv_w_sstatus(x: u64);
// Supervisor Interrupt Pending
pub fn r_sip() -> u64;
pub fn w_sip(x: u64);
// Supervisor Interrupt Enable
pub fn r_sie() -> u64;
pub fn w_sie(x: u64);
// Machine-mode Interrupt Enable
pub fn r_mie() -> u64;
pub fn w_mie(x: u64);
// Supervisor Exception Program Counter
// SEPC holds the instruction address to which a return from exception will go.
pub fn r_sepc() -> u64;
pub fn w_sepc(x: u64);
// Machine Exception Deletgation
pub fn r_medeleg() -> u64;
pub fn w_medeleg(x: u64);
// Machine Interrupt Deletgation
pub fn r_mideleg() -> u64;
pub fn w_mideleg(x: u64);
// Supervisor Trap-Vector Base Address
pub fn r_stvec() -> u64;
pub fn w_stvec(x: u64);
// Machine-mode Interrupt Vector
pub fn w_mtvec(x: u64);
// Physical Memory Protection
pub fn w_pmpcfg0(x: u64);
pub fn w_pmpaddr0(x: u64);
// Supervisor Address Translation and Protection
// SATP holds the address of the page table.
pub fn r_satp() -> u64;
pub fn w_satp(x: u64);
pub fn w_mscratch(x: u64);
// Supervisor Trap Cause
pub fn r_scause() -> u64;
// Supervisor Trap Value
pub fn r_stval() -> u64;
// Machine-mode Counter-Enable
pub fn r_mcounteren() -> u64;
pub fn w_mcounteren(x: u64);
// Machine-mode cycle counter
pub fn r_time() -> u64;
// /// Enable device interrupts
// pub fn intr_on();
// /// Disable device interrupts
// pub fn intr_off();
// // Are device interrupts enabled?
// pub fn intr_get() -> i32;
pub fn r_sp() -> u64;
// Read and write TP (thread pointer), which xv6 uses
// to hold this core's hartid, the index into cpus[].
// pub fn rv_r_tp() -> u64;
pub fn w_tp(x: u64);
pub fn r_ra() -> u64;
/// Flush the TLB.
pub fn sfence_vma();
}

View File

@ -0,0 +1,110 @@
use crate::{
proc::{mycpu, Cpu},
riscv,
};
use core::{
ffi::c_char,
ptr::null_mut,
sync::atomic::{AtomicBool, Ordering},
};
#[repr(C)]
pub struct Spinlock {
pub locked: AtomicBool,
pub name: *mut c_char,
pub cpu: *mut Cpu,
}
impl Spinlock {
/// Initializes a `Spinlock`.
pub fn new(name: *mut c_char) -> Spinlock {
Spinlock {
locked: AtomicBool::new(false),
cpu: null_mut(),
name,
}
}
/// Check whether this cpu is holding the lock.
///
/// Interrupts must be off.
pub fn held_by_current_cpu(&self) -> bool {
self.cpu == unsafe { mycpu() } && self.locked.load(Ordering::Relaxed)
}
pub unsafe fn lock(&mut self) {
push_off();
if self.held_by_current_cpu() {
panic!("Attempt to acquire twice by the same CPU");
}
while self.locked.swap(true, Ordering::Acquire) {
core::hint::spin_loop();
}
// The lock is now locked and we can write our CPU info.
self.cpu = mycpu();
}
pub unsafe fn unlock(&mut self) {
if !self.held_by_current_cpu() {
panic!("Attempt to release lock from different CPU");
}
self.cpu = null_mut();
self.locked.store(false, Ordering::Release);
pop_off();
}
}
#[no_mangle]
pub unsafe extern "C" fn initlock(lock: *mut Spinlock, name: *mut c_char) {
*lock = Spinlock::new(name);
}
#[no_mangle]
pub unsafe extern "C" fn holding(lock: *mut Spinlock) -> i32 {
(*lock).held_by_current_cpu().into()
}
#[no_mangle]
pub unsafe extern "C" fn acquire(lock: *mut Spinlock) {
(*lock).lock();
}
#[no_mangle]
pub unsafe extern "C" fn release(lock: *mut Spinlock) {
(*lock).unlock();
}
// push_off/pop_off are like intr_off()/intr_on() except that they are matched:
// it takes two pop_off()s to undo two push_off()s. Also, if interrupts
// are initially off, then push_off, pop_off leaves them off.
#[no_mangle]
pub unsafe extern "C" fn push_off() {
let old = riscv::intr_get();
let cpu = mycpu();
riscv::intr_off();
if (*cpu).noff == 0 {
(*cpu).intena = old;
}
(*cpu).noff += 1;
}
#[no_mangle]
pub unsafe extern "C" fn pop_off() {
let cpu = mycpu();
if riscv::intr_get() == 1 {
panic!("pop_off - interruptible");
} else if (*cpu).noff < 1 {
panic!("pop_off");
}
(*cpu).noff -= 1;
if (*cpu).noff == 0 && (*cpu).intena == 1 {
riscv::intr_on();
}
}

View File

@ -1,110 +0,0 @@
// Mutual exclusion spin locks.
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "spinlock.h"
#include "riscv.h"
#include "proc.h"
#include "defs.h"
void
initlock(struct spinlock *lk, char *name)
{
lk->name = name;
lk->locked = 0;
lk->cpu = 0;
}
// Acquire the lock.
// Loops (spins) until the lock is acquired.
void
acquire(struct spinlock *lk)
{
push_off(); // disable interrupts to avoid deadlock.
if(holding(lk))
panic("acquire");
// On RISC-V, sync_lock_test_and_set turns into an atomic swap:
// a5 = 1
// s1 = &lk->locked
// amoswap.w.aq a5, a5, (s1)
while(__sync_lock_test_and_set(&lk->locked, 1) != 0)
;
// Tell the C compiler and the processor to not move loads or stores
// past this point, to ensure that the critical section's memory
// references happen strictly after the lock is acquired.
// On RISC-V, this emits a fence instruction.
__sync_synchronize();
// Record info about lock acquisition for holding() and debugging.
lk->cpu = mycpu();
}
// Release the lock.
void
release(struct spinlock *lk)
{
if(!holding(lk))
panic("release");
lk->cpu = 0;
// Tell the C compiler and the CPU to not move loads or stores
// past this point, to ensure that all the stores in the critical
// section are visible to other CPUs before the lock is released,
// and that loads in the critical section occur strictly before
// the lock is released.
// On RISC-V, this emits a fence instruction.
__sync_synchronize();
// Release the lock, equivalent to lk->locked = 0.
// This code doesn't use a C assignment, since the C standard
// implies that an assignment might be implemented with
// multiple store instructions.
// On RISC-V, sync_lock_release turns into an atomic swap:
// s1 = &lk->locked
// amoswap.w zero, zero, (s1)
__sync_lock_release(&lk->locked);
pop_off();
}
// Check whether this cpu is holding the lock.
// Interrupts must be off.
int
holding(struct spinlock *lk)
{
int r;
r = (lk->locked && lk->cpu == mycpu());
return r;
}
// push_off/pop_off are like intr_off()/intr_on() except that they are matched:
// it takes two pop_off()s to undo two push_off()s. Also, if interrupts
// are initially off, then push_off, pop_off leaves them off.
void
push_off(void)
{
int old = intr_get();
intr_off();
if(mycpu()->noff == 0)
mycpu()->intena = old;
mycpu()->noff += 1;
}
void
pop_off(void)
{
struct cpu *c = mycpu();
if(intr_get())
panic("pop_off - interruptible");
if(c->noff < 1)
panic("pop_off");
c->noff -= 1;
if(c->noff == 0 && c->intena)
intr_on();
}

View File

@ -1,3 +1,5 @@
#pragma once
typedef unsigned int uint;
typedef unsigned short ushort;
typedef unsigned char uchar;