英文:
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 declared, 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);
u
<details>
<summary>英文:</summary>
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](https://docs.google.com/document/d/1CcHc2FVbYz6GUIZ0A8JHa2Zkn9n73mHVBT1_g3gljrw/edit?usp=sharing) (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!
</details>
# 答案1
**得分**: 0
你的系统调用看起来让我感到怀疑,我会这样写:
```c
uint64
sys_getShrimpPage(void)
{
return getShrimpPage();
}
uint64
sys_getFryPage(void)
{
return getFryPage();
}
英文:
Your system calls seems suspicious to me, I would have written:
uint64
sys_getShrimpPage(void)
{
return getShrimpPage();
}
uint64
sys_getFryPage(void)
{
return getFryPage();
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论