Search code examples
coperating-systemkernelshared-memoryxv6

xv6 - I'm trying to implement shared memory, but getting a segfault-type usertrap error


I am trying to implement a primitive form of shared memory called “Shrimpy Memory” into my xv6 repo. So far, I’ve gotten xv6 to compile and run, but whenever I try to set a value for anything in that space, it causes an error that I hear is equivalent to a segfault. To summarize, all child processes share one page with their parent and sibling processes. The page one shares with their parent is called the “shrimp” page, and the page one shares with their children is called the “fry” page. (This is for a school assignment and I can’t switch to a structure other than Shrimpy.)

Here are the professor’s instructions (I thought I'd made it as far as the "Test it" stage, but there might be a problem somewhere in "Mapping the shrimp page")

Here's my test program (user/shrimpy-test.c):

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"


int
main(int argc, char *argv[])
{
    // The parent process retrieves a pointer to its own shared memory space.
    void *sharedspace;
    int *sharedint;
    int pid;
    printf("vars declared, getting fry page\n");
    sharedspace = getFryPage(); printf("acquired - %p\n", sharedspace);
    // The parent process writes a specific value into this shared memory space. This can just be a number.
    sharedint = (int *) sharedspace; printf("shared int addr - %p\n", sharedint);
    *sharedint = 3; printf("acquired - %p\n", sharedspace);
    // The parent process prints out the value in the shared memory space to confirm it was written successfully.
    printf("Shared value: %d", *sharedint);
    // The parent process creates a child process using fork.
    pid = fork();
    if (pid < 0) {
        printf("error: fork"); // If fork() fails, print an error message and exit
        exit(1);
    }
    
    // In the child process:
    if (pid == 0) {  
        sharedspace = getShrimpPage(); // The child process retrieves a pointer to its shared memory space. This shared memory space should be the same physical memory that the parent process used.
        sharedint = (int *) sharedspace;
        printf("Child accessed value: %d", *sharedint); // The child process prints out the value in the shared memory space to confirm that it sees the same value that the parent wrote.
    }

    exit(0);
}

Here’s the error message:

xv6 kernel is booting
hart 2 starting
hart 1 starting
init: starting sh
$ shrimpy-test
vars declraed, getting fry page
acquired - 0x0000000000000000
shared int addr - 0x0000000000000000
usertrap(): unexpected scause 0x000000000000000f pid=3
            sepc=0x000000000000004a stval=0x0000000000000000
$ QEMU: Terminated

And here is the kernel code I’ve altered:

In kernel/memlayout.h:

#define TRAPFRAME (TRAMPOLINE - PGSIZE)
#define SHRIMP (TRAPFRAME - PGSIZE)
#define FRY (SHRIMP - PGSIZE)

In kernel/proc.c:

static struct proc*
allocproc(void)
{
  struct proc *p;
  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    if(p->state == UNUSED) {
      goto found;
    } else {
      release(&p->lock);
    }
  }
  return 0;
found:
  p->pid = allocpid();
  p->state = USED;
  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

    // Allocate a shrimp page.
  if((p->shrimp = (void *)kalloc()) == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  // Allocate a fry page.
  if((p->fry = (void *)kalloc()) == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }



  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  // Set up new context to start executing at forkret,
  // which returns to user space.
  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;
  return p;
}

[...] <-- Means I'm leaving some functions out since I didn't touch them (yes, some people have gotten that confused before)

// Create a user page table for a given process, with no user memory,
// but with trampoline and trapframe pages.
pagetable_t
proc_pagetable(struct proc *p)
{
  pagetable_t pagetable;
  // An empty page table.
  pagetable = uvmcreate();
  if(pagetable == 0)
    return 0;
  // map the trampoline code (for system call return)
  // at the highest user virtual address.
  // only the supervisor uses it, on the way
  // to/from user space, so not PTE_U.
  if(mappages(pagetable, TRAMPOLINE, PGSIZE,
              (uint64)trampoline, PTE_R | PTE_X) < 0){
    uvmfree(pagetable, 0);
    return 0;
  }
  // map the trapframe page just below the trampoline page, for
  // trampoline.S.
  if(mappages(pagetable, TRAPFRAME, PGSIZE,
              (uint64)(p->trapframe), PTE_R | PTE_W) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }

  // map the shrimp page for init
  if(mappages(pagetable, SHRIMP, PGSIZE,
              (uint64)(p->shrimp), PTE_R | PTE_W | PTE_U) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmunmap(pagetable, TRAPFRAME, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }
  // map the fry page just below the shrimp page
  if(mappages(pagetable, FRY, PGSIZE,
              (uint64)(p->fry), PTE_R | PTE_W | PTE_U) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmunmap(pagetable, TRAPFRAME, 1, 0);
    uvmunmap(pagetable, SHRIMP, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }

  return pagetable;
}

// Free a process's page table, and free the
// physical memory it refers to.
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
  uvmunmap(pagetable, TRAMPOLINE, 1, 0);
  uvmunmap(pagetable, TRAPFRAME, 1, 0);
  uvmunmap(pagetable, SHRIMP, 1, 0); 
  uvmunmap(pagetable, FRY, 1, 1);
  uvmfree(pagetable, sz);
}

[...]

int
fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();
  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }
  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  np->sz = p->sz;
  // copy saved user registers.
  *(np->trapframe) = *(p->trapframe);

  // unmap fry page in child
  uvmunmap(np->pagetable, FRY, 1, 0);


  // create new mapping for fry page in child
  if(mappages(np->pagetable, FRY, PGSIZE,
              (uint64)(np->fry), PTE_R | PTE_W | PTE_U) < 0){
    uvmunmap(np->pagetable, TRAMPOLINE, 1, 0);
    uvmfree(np->pagetable, 0);
    return 0;
  }

  // unmap shrimp page in child
  uvmunmap(np->pagetable, SHRIMP, 1, 0);

  // map child's shrimp page as parent's fry page 
  if (mappages(np->pagetable, SHRIMP, PGSIZE, (uint64) (p->fry), PTE_R | PTE_W | PTE_U) < 0){
    printf("Error in fork()");
    return 0;
  }

  // Cause fork to return 0 in the child.
  np->trapframe->a0 = 0;

  // increment reference counts on open file descriptors.
  for(i = 0; i < NOFILE; i++)
    if(p->ofile[i])
      np->ofile[i] = filedup(p->ofile[i]);
  np->cwd = idup(p->cwd);
  safestrcpy(np->name, p->name, sizeof(p->name));
  pid = np->pid;
  release(&np->lock);
  acquire(&wait_lock);
  np->parent = p;
  release(&wait_lock);
  acquire(&np->lock);
  np->state = RUNNABLE;
  release(&np->lock);
  return pid;
}

[...]

void *
getFryPage(void){
  struct proc *p;

  p = myproc();
  return p->fry;

}

void *
getShrimpPage(void){
  struct proc *p;

  p = myproc();
  return p->shrimp;

}

In proc.h:

struct proc {
  struct spinlock lock;
  // p->lock must be held when using these:
  enum procstate state;        // Process state
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID
  // wait_lock must be held when using this:
  struct proc *parent;         // Parent process
  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Virtual address of kernel stack
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // User page table
  struct trapframe *trapframe; // data page for trampoline.S
  void *fry;                   // fry page phys addr
  void *shrimp;                // shrimp page phys addr
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
};

And, of course, all the hoops you have to jump through to get new system calls (getShrimpPage() and getFryPage()) in:

syscall.c:
extern uint64 sys_getShrimpPage(void);
extern uint64 sys_getFryPage(void);

[...]

static uint64 (*syscalls[])(void) = {

[...]

[SYS_getShrimpPage]  sys_getShrimpPage,
[SYS_getFryPage]  sys_getFryPage,
};

syscall.h:
#define SYS_getShrimpPage 23
#define SYS_getFryPage 24

sysproc.c

uint64
sys_getShrimpPage(void)
{
  getShrimpPage();
  return 0;
}

uint64
sys_getFryPage(void)
{
  getFryPage();
  return 0;
}

user.h
void* getShrimpPage(void);
void* getFryPage(void);

usys.pl
entry("getShrimpPage");
entry("getFryPage");

I've tried asking my fellow students about this, but they were not able to solve this problem, just a separate issue in making sure where to free/unmap the pagetables. I've tried asking ChatGPT, and it's pointed out something interesting that I have no idea how to act on (or if it's even correct):

In the fork function, there is an attempt to unmap the "shrimp" page in the child process and map the parent's "fry" page as the child's "shrimp" page. However, this mapping is done using the parent's p->fry address, which might not be valid in the child process. This can lead to inconsistencies and potential memory access issues. To resolve this, the correct address for the "fry" page in the parent process should be passed to the mappages function instead of p->fry in the child process.

Being a valid place to store the physical address was the whole point of p->fry, so if this rings true, what should I use instead? ChatGPT is not giving clear answers on that, and when I tried to get clear code from it, it either did not change the outcome at all, or simply caused kernel panics upon startup.

In re-mapping the child's shrimp page, I've also tried replacing p->fry with &np->trapframe - (2*PGSIZE), but that made zero difference in results.

So yeah, any help would be appreciated. Thank you!


Solution

  • Your system calls seems suspicious to me, I would have written:

    uint64
    sys_getShrimpPage(void)
    {
      return getShrimpPage();
    }
    
    uint64
    sys_getFryPage(void)
    {
      return getFryPage();
    }