spinlocks using gcc intrinsics

push_off() / pop_off()
set up per-hart plic stuff so all harts get device interrupts
This commit is contained in:
Robert Morris 2019-06-05 14:05:46 -04:00
parent f1a727b971
commit 3113643768
11 changed files with 96 additions and 105 deletions

View file

@ -182,7 +182,7 @@ QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \
then echo "-gdb tcp::$(GDBPORT)"; \ then echo "-gdb tcp::$(GDBPORT)"; \
else echo "-s -p $(GDBPORT)"; fi) else echo "-s -p $(GDBPORT)"; fi)
ifndef CPUS ifndef CPUS
CPUS := 1 CPUS := 2
endif endif
QEMUOPTS = -machine virt -kernel kernel -m 3G -smp $(CPUS) -nographic QEMUOPTS = -machine virt -kernel kernel -m 3G -smp $(CPUS) -nographic
QEMUOPTS += -initrd fs.img QEMUOPTS += -initrd fs.img

8
defs.h
View file

@ -130,12 +130,11 @@ void swtch(struct context*, struct context*);
// spinlock.c // spinlock.c
void acquire(struct spinlock*); void acquire(struct spinlock*);
void getcallerpcs(void*, uint64*);
int holding(struct spinlock*); int holding(struct spinlock*);
void initlock(struct spinlock*, char*); void initlock(struct spinlock*, char*);
void release(struct spinlock*); void release(struct spinlock*);
void pushcli(void); void push_off(void);
void popcli(void); void pop_off(void);
// sleeplock.c // sleeplock.c
void acquiresleep(struct sleeplock*); void acquiresleep(struct sleeplock*);
@ -168,6 +167,7 @@ void timerinit(void);
// trap.c // trap.c
extern uint ticks; extern uint ticks;
void trapinit(void); void trapinit(void);
void trapinithart(void);
extern struct spinlock tickslock; extern struct spinlock tickslock;
void usertrapret(void); void usertrapret(void);
@ -179,6 +179,7 @@ int uartgetc(void);
// vm.c // vm.c
void kvminit(void); void kvminit(void);
void kvminithart(void);
pagetable_t uvmcreate(void); pagetable_t uvmcreate(void);
void uvminit(pagetable_t, char *, uint); void uvminit(pagetable_t, char *, uint);
uint64 uvmalloc(pagetable_t, uint64, uint64); uint64 uvmalloc(pagetable_t, uint64, uint64);
@ -194,6 +195,7 @@ int copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64
// plic.c // plic.c
void plicinit(void); void plicinit(void);
void plicinithart(void);
uint64 plic_pending(void); uint64 plic_pending(void);
int plic_claim(void); int plic_claim(void);
void plic_complete(int); void plic_complete(int);

42
main.c
View file

@ -4,25 +4,39 @@
#include "riscv.h" #include "riscv.h"
#include "defs.h" #include "defs.h"
volatile static int started = 0;
// Bootstrap processor starts running C code here. // Bootstrap processor starts running C code here.
// Allocate a real stack and switch to it, first // Allocate a real stack and switch to it, first
// doing some setup required for memory allocator to work. // doing some setup required for memory allocator to work.
void void
main(int hartid) main()
{ {
w_tp(hartid); // save hartid where cpuid() can find it if(cpuid() == 0){
uartinit(); // serial port uartinit(); // serial port
consoleinit(); consoleinit();
printf("entering main() on hart %d\n", hartid); printf("hart %d starting\n", cpuid());
kinit(); // physical page allocator kinit(); // physical page allocator
kvminit(); // kernel page table kvminit(); // create kernel page table
procinit(); // process table kvminithart(); // turn on paging
trapinit(); // trap vectors procinit(); // process table
plicinit(); // set up interrupt controller trapinit(); // trap vectors
binit(); // buffer cache trapinithart(); // install kernel trap vector
fileinit(); // file table plicinit(); // set up interrupt controller
ramdiskinit(); // disk plicinithart(); // ask PLIC for device interrupts
userinit(); // first user process binit(); // buffer cache
fileinit(); // file table
ramdiskinit(); // disk
userinit(); // first user process
started = 1;
} else {
while(started == 0)
;
printf("hart %d starting\n", cpuid());
kvminithart(); // turn on paging
trapinithart(); // install kernel trap vector
plicinithart(); // ask PLIC for device interrupts
}
scheduler(); scheduler();
} }

View file

@ -28,6 +28,14 @@
// qemu puts programmable interrupt controller here. // qemu puts programmable interrupt controller here.
#define PLIC 0x0c000000L #define PLIC 0x0c000000L
#define PLIC_PRIORITY (PLIC + 0x0)
#define PLIC_PENDING (PLIC + 0x1000)
#define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart)*0x100)
#define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100)
#define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart)*0x2000)
#define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000)
#define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart)*0x2000)
#define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000)
#define RAMDISK 0x88000000L #define RAMDISK 0x88000000L

5
proc.c
View file

@ -360,7 +360,7 @@ scheduler(void)
{ {
struct proc *p; struct proc *p;
struct cpu *c = mycpu(); struct cpu *c = mycpu();
c->proc = 0; c->proc = 0;
for(;;){ for(;;){
// Enable interrupts on this processor. // Enable interrupts on this processor.
@ -385,7 +385,6 @@ scheduler(void)
c->proc = 0; c->proc = 0;
} }
release(&ptable.lock); release(&ptable.lock);
} }
} }
@ -393,7 +392,7 @@ scheduler(void)
// and have changed proc->state. Saves and restores // and have changed proc->state. Saves and restores
// intena because intena is a property of this // intena because intena is a property of this
// kernel thread, not this CPU. It should // kernel thread, not this CPU. It should
// be proc->intena and proc->ncli, but that would // be proc->intena and proc->noff, but that would
// break in the few places where a lock is held but // break in the few places where a lock is held but
// there's no process. // there's no process.
void void

5
proc.h
View file

@ -22,9 +22,8 @@ struct context {
struct cpu { struct cpu {
struct proc *proc; // The process running on this cpu or null struct proc *proc; // The process running on this cpu or null
struct context scheduler; // swtch() here to enter scheduler struct context scheduler; // swtch() here to enter scheduler
volatile uint started; // Has the CPU started? int noff; // Depth of push_off() nesting.
int ncli; // Depth of pushcli nesting. int intena; // Were interrupts enabled before push_off()?
int intena; // Were interrupts enabled before pushcli?
}; };
extern struct cpu cpus[NCPU]; extern struct cpu cpus[NCPU];

View file

@ -5,6 +5,7 @@
#include "memlayout.h" #include "memlayout.h"
#include "spinlock.h" #include "spinlock.h"
#include "riscv.h" #include "riscv.h"
#include "proc.h"
#include "defs.h" #include "defs.h"
void void
@ -15,27 +16,6 @@ initlock(struct spinlock *lk, char *name)
lk->cpu = 0; lk->cpu = 0;
} }
void
acquire(struct spinlock *lk)
{
lk->locked = 1;
lk->cpu = mycpu();
}
void
release(struct spinlock *lk)
{
lk->locked = 0;
lk->cpu = 0;
}
int
holding(struct spinlock *lk)
{
return lk->locked && lk->cpu == mycpu();
}
#if 0
// Acquire the lock. // Acquire the lock.
// Loops (spins) until the lock is acquired. // Loops (spins) until the lock is acquired.
// Holding a lock for a long time may cause // Holding a lock for a long time may cause
@ -43,12 +23,14 @@ holding(struct spinlock *lk)
void void
acquire(struct spinlock *lk) acquire(struct spinlock *lk)
{ {
pushcli(); // disable interrupts to avoid deadlock. push_off(); // disable interrupts to avoid deadlock.
if(holding(lk)) if(holding(lk))
panic("acquire"); panic("acquire");
// The xchg is atomic. // The xchg is atomic.
while(xchg(&lk->locked, 1) != 0) //while(xchg(&lk->locked, 1) != 0)
// ;
while(__sync_lock_test_and_set(&lk->locked, 1) != 0)
; ;
// Tell the C compiler and the processor to not move loads or stores // Tell the C compiler and the processor to not move loads or stores
@ -58,7 +40,6 @@ acquire(struct spinlock *lk)
// Record info about lock acquisition for holding() and debugging. // Record info about lock acquisition for holding() and debugging.
lk->cpu = mycpu(); lk->cpu = mycpu();
getcallerpcs(&lk, lk->pcs);
} }
// Release the lock. // Release the lock.
@ -68,7 +49,6 @@ release(struct spinlock *lk)
if(!holding(lk)) if(!holding(lk))
panic("release"); panic("release");
lk->pcs[0] = 0;
lk->cpu = 0; lk->cpu = 0;
// Tell the C compiler and the processor to not move loads or stores // Tell the C compiler and the processor to not move loads or stores
@ -81,27 +61,10 @@ release(struct spinlock *lk)
// Release the lock, equivalent to lk->locked = 0. // Release the lock, equivalent to lk->locked = 0.
// This code can't use a C assignment, since it might // This code can't use a C assignment, since it might
// not be atomic. A real OS would use C atomics here. // not be atomic. A real OS would use C atomics here.
asm volatile("movl $0, %0" : "+m" (lk->locked) : ); //asm volatile("movl $0, %0" : "+m" (lk->locked) : );
__sync_lock_release(&lk->locked);
popcli(); pop_off();
}
// Record the current call stack in pcs[] by following the %ebp chain.
void
getcallerpcs(void *v, uint64 pcs[])
{
uint64 *ebp;
int i;
asm volatile("mov %%rbp, %0" : "=r" (ebp));
for(i = 0; i < 10; i++){
if(ebp == 0 || ebp < (uint64*)KERNBASE || ebp == (uint64*)0xffffffff)
break;
pcs[i] = ebp[1]; // saved %eip
ebp = (uint64*)ebp[0]; // saved %ebp
}
for(; i < 10; i++)
pcs[i] = 0;
} }
// Check whether this cpu is holding the lock. // Check whether this cpu is holding the lock.
@ -109,37 +72,37 @@ int
holding(struct spinlock *lk) holding(struct spinlock *lk)
{ {
int r; int r;
pushcli(); push_off();
r = lk->locked && lk->cpu == mycpu(); r = lk->locked && lk->cpu == mycpu();
popcli(); pop_off();
return r; return r;
} }
// push_off/pop_off are like intr_off()/intr_on() except that they are matched:
// Pushcli/popcli are like cli/sti except that they are matched: // it takes two pop_off to undo two push_off. Also, if interrupts
// it takes two popcli to undo two pushcli. Also, if interrupts // are initially off, then push_off, pop_off leaves them off.
// are off, then pushcli, popcli leaves them off.
void void
pushcli(void) push_off(void)
{ {
int eflags; struct cpu *c = mycpu();
int old = intr_get();
eflags = readeflags(); intr_off();
cli(); if(c->noff == 0)
if(mycpu()->ncli == 0) c->intena = old;
mycpu()->intena = eflags & FL_IF; c->noff += 1;
mycpu()->ncli += 1;
} }
void void
popcli(void) pop_off(void)
{ {
if(readeflags()&FL_IF) struct cpu *c = mycpu();
panic("popcli - interruptible"); if(intr_get())
if(--mycpu()->ncli < 0) panic("pop_off - interruptible");
panic("popcli"); c->noff -= 1;
if(mycpu()->ncli == 0 && mycpu()->intena) if(c->noff < 0)
sti(); panic("pop_off");
if(c->noff == 0 && c->intena)
intr_on();
} }
#endif

View file

@ -5,7 +5,5 @@ struct spinlock {
// For debugging: // For debugging:
char *name; // Name of lock. char *name; // Name of lock.
struct cpu *cpu; // The cpu holding the lock. struct cpu *cpu; // The cpu holding the lock.
uint64 pcs[10]; // The call stack (an array of program counters)
// that locked the lock.
}; };

14
start.c
View file

@ -9,12 +9,12 @@ void main();
// entry.S needs one stack per CPU. // entry.S needs one stack per CPU.
__attribute__ ((aligned (16))) char stack0[4096 * NCPU]; __attribute__ ((aligned (16))) char stack0[4096 * NCPU];
// assembly code in kernelvec for machine-mode timer interrupt.
extern void machinevec();
// scratch area for timer interrupt, one per CPU. // scratch area for timer interrupt, one per CPU.
uint64 mscratch0[NCPU * 32]; uint64 mscratch0[NCPU * 32];
// assembly code in kernelvec for machine-mode timer interrupt.
extern void machinevec();
// entry.S jumps here in machine mode on stack0. // entry.S jumps here in machine mode on stack0.
void void
mstart() mstart()
@ -48,7 +48,9 @@ mstart()
w_mstatus(r_mstatus() | MSTATUS_MIE); w_mstatus(r_mstatus() | MSTATUS_MIE);
w_mie(r_mie() | MIE_MTIE); w_mie(r_mie() | MIE_MTIE);
// call main(hartid) in supervisor mode. // keep each CPU's hartid in its tp register, for cpuid().
asm("csrr a0, mhartid ; \ w_tp(id);
mret");
// call main() in supervisor mode.
asm("mret");
} }

12
trap.c
View file

@ -19,14 +19,16 @@ extern int devintr();
void void
trapinit(void) trapinit(void)
{ {
int i;
// set up to take exceptions and traps while in the kernel.
w_stvec((uint64)kernelvec);
initlock(&tickslock, "time"); initlock(&tickslock, "time");
} }
// set up to take exceptions and traps while in the kernel.
void
trapinithart(void)
{
w_stvec((uint64)kernelvec);
}
// //
// handle an interrupt, exception, or system call from user space. // handle an interrupt, exception, or system call from user space.
// called from trampoline.S // called from trampoline.S

8
vm.c
View file

@ -54,9 +54,13 @@ kvminit()
// the highest virtual address in the kernel. // the highest virtual address in the kernel.
mappages(kernel_pagetable, TRAMPOLINE, PGSIZE, mappages(kernel_pagetable, TRAMPOLINE, PGSIZE,
(uint64)trampout, PTE_R | PTE_X); (uint64)trampout, PTE_R | PTE_X);
}
// Switch h/w page table register to the kernel's page table, // Switch h/w page table register to the kernel's page table,
// and enable paging. // and enable paging.
void
kvminithart()
{
w_satp(MAKE_SATP(kernel_pagetable)); w_satp(MAKE_SATP(kernel_pagetable));
} }