MIT 6.S081 2024

6.S081 Lab: mmap lab

In this blog post, I'll be diving into the details of the mmap system call as implemented in the 6.S081.

This lab is designed to help students understand the concepts of virtual memory, memory-mapped files and how memory-mapping can be used to efficiently work with large files.

Notice

If you haven't started writing code yet. Don't refer to my code.
This won't make you progress. Reading source code is usually a good habit, but not now.

If you are nearing the completion of this lab, here are a few debugging tips:

print is more efficient
I didn't use gdb in this lab. I think print is usually more efficient than gdb because I know where wrong happens when my code runs badly.
If you know how the code works, don't use gdb.

Reading mmaptest.c is important
Don't ignore testfile. I don't think anyone can finish this lab and pass all the test examples without reading mmaptest.c.
It's useful to print logs in mmaptest.c.

file size is constant
mmap don't change the file's size. Let me show you an example to illustrate this point.
fileA: size == 5000
addr = mmap(fileA, 10000, ...)
addr[6000] = xxx
This will not cause panic. But this shouldn't extend the file. Check if your code implement this situation correctly.

mmap doesn't directly complete mapping
If uvmunmap report not mapped, it might be OK. Here is an example:
p1 = mmap(fileB, ...)
munmap(p1)
This situation, original uvmunmap will report an error. But it's OK.
So Changing the implement or checking this situation in your munmap is both right.

Find safe virtual memory
XV6 doesn't provide a interface to get safe virtual memory. So we have to do this job.
I can provide a way. It can pass all tests but not really safe.
I use myproc()->sz as the virtual memory mapped.

Other details are clearly stated in the lab description. Don't skip any instructions.
Hint: use differs from Hint: see
Don't use filewrite, you should decide the offset to write to.

I won't go into all details. For example I think you know how to add a system call in XV6.
Copying the code shown here won't work. It's not a good way. I provide my code in my github. If this helps you pass the tests, you can give me a star. Thanks.

We focus on functions below:

sysfile.c
sys_mmap
sys_munmap
writeback(helper function)

file.c
filedup
filewrite
fileclose

vm.c
mappages
uvmunmap

proc.c
usertrap
exit
fork

About 100 lines will be modified.

struct vma

struct vma {
        uint64  addr;
        uint    length;
        uint    prot;	//PROT_READ PROT_WRITE...
        uint    flags;  //MAP_PRIVATE MAP_SHARED
        struct  file *f;

        uint64  start; //set when mmap is called the first time.
};

sys_mmap

This function is easy.
Just find a empty vma in proc struct and fill it.

uint64 sys_mmap(void)
{
        struct file *f;
        uint64 addr;
        int length;
        int prot;
        int flags;
        int offset;
        argaddr(0, &addr);
        argint(1, &length);
        argint(2, &prot);
        argint(3, &flags);
        argfd(4, 0, &f);
        argint(5, &offset);
        /*
        printf("debug: argstest...\n");
        printf("addr = %ld, length = %d, prot = %d, flags = %d, offset = %d\n",
               addr, length, prot, flags, offset);
        */
        if (!f->writable && (prot & PROT_WRITE) && (flags & MAP_SHARED))
                return -1;
        filedup(f);
        struct proc *p = myproc();
        for (int i = 0; i < NVMA; i++) {
                struct vma *vma = &p->vmas[i];
                if (vma->prot == PROT_NONE) { // found unused vma
                        vma->addr = p->sz;
                        vma->length = PGROUNDUP(length);
                        vma->prot = prot;
                        vma->flags = flags;
                        vma->f = f;
                        vma->start = vma->addr;
                        //printf("mmap: address = %p\n", (void *)addr);
                        p->sz += vma->length;
                        return vma->addr;
                }
        }
        return 0;
}

I use p->sz as the virtual memmory alloced. This way can pass all tests(including some new tests). I saw someone use TRAPOLINES, might that's a better way?
Don't forget to use filedup to add file refcnt, this is mentioned in the lab description.

usertrap

read pagefault cause 0xd
write pagefault cause 0xf
So we should do pagefault handle when r_scause() == 0xd || 0xf
When we capture pagefault, we should check if it's asking for a mmaped page.
If so, we should alloc a physical page(In fact, XV6 only provides physical page alloc interface), and map vma->addr to it.
I don't recommend to use uvmalloc to do this because we just need mappage one page per trap.
I used uvmalloc firstly, and didn't pass. But I don't really have reasons to ask you not do so.

uint64 va = r_stval();
if (va >= p->sz || va > MAXVA ||
    PGROUNDUP(va) == PGROUNDDOWN(p->trapframe->sp))
	setkilled(p);
else {
	struct vma *vma = (struct vma *)0;
	for (int i = 0; i < NVMA; i++) {
		if (p->vmas[i].prot != PROT_NONE &&
		    va >= p->vmas[i].addr &&
		    va < p->vmas[i].addr + p->vmas[i].length) {
			vma = &p->vmas[i];
			break;
		}
	}
	if (vma) { // mmaped
		va = PGROUNDDOWN(va);
		uint64 offset = va - vma->addr;
		uint64 mem = (uint64)kalloc();
		if (mem == 0) {
			setkilled(p);
		} else {
			memset((void *)mem, 0, PGSIZE);
			ilock(vma->f->ip);
			if (readi(vma->f->ip, 0, mem, offset,
				  PGSIZE) < 0)
				setkilled(p);
			iunlock(vma->f->ip);
			int flag = PTE_U;
			if (vma->prot & PROT_READ)
				flag |= PTE_R;
			if (vma->prot & PROT_WRITE)
				flag |= PTE_W;
			if (vma->prot & PROT_EXEC)
				flag |= PTE_X;
			// cannot write a readonly page
			if (r_scause() == 15 &&
			    !(vma->prot & PROT_WRITE)) {
				kfree((void *)mem);
				setkilled(p);
			}
			if (mappages(p->pagetable, va, PGSIZE,
				     mem, flag) != 0) {
				kfree((void *)mem);
				setkilled(p);
			}
		}
	} else { // not mmaped
		setkilled(p);
	}

sys_munmap

before writing code, you'd better know what are the legal parameters for munmap.
Unlike Linux kernel, our munmap implement doesn't occur in the middle of vma because we don't maintain a linked list for vmas.
Here is an example:

vma->addr = 0x8000;
vma->length = 8192;

legal pamameters:

addr = 0x8000 0 <= length <= 8192(automatic alignment)

this case:

vma->addr += length vma->length -= length;

or

0x8000 < addr < 0x8000 + 8192 length = 0x8000 + 8192 - addr

this case:

vma->length -= length;

mmaptest.c doesn't test other cases, so make sure you have implementations for both of them.

uint64 sys_munmap(void)
{
#define MIN(x, y) ((x) < (y) ? (x) : (y))
        uint64 addr;
        int length;
        argaddr(0, &addr);
        argint(1, &length);
        length = PGROUNDUP(length);
        struct proc *p = myproc();
        for (int i = 0; i < NVMA; i++) {
                struct vma *v = &p->vmas[i];
                if (v->prot != PROT_NONE &&
                    (v->addr <= addr && addr < v->addr + v->length)) {
                        if ((v->flags & MAP_SHARED) && (v->prot & PROT_WRITE)) {
                                int max =
                                        ((MAXOPBLOCKS - 1 - 1 - 2) / 2) * BSIZE;
                                int foff = addr - v->start;
                                writeback(v->f, addr, foff,
                                          MIN(length, (v->f->ip->size - foff)),
                                          max);
                        }
                        uvmunmap(p->pagetable, addr, length / PGSIZE, 1);
                        v->length -= length;
                        if (v->length == 0) {
                                fileclose(v->f);
                                memset(v, 0, sizeof(*v));
                                return 0;
                        }
                        if (addr == v->addr) {
                                v->addr += length;
                        }
                }
        }
        return 0;
}

Please check this:

int foff = addr - v->start;

So I reminded you at the beginning. Don't use filewrite, you must calculate the offset yourself.
Here is an example:

vma->addr = 0x0000;
vma->length = 8192;

munmap(vma->addr, 4096);
writeback(file_offset = 0);

vma->addr = 0x1000;
vma->length = 4096;

munmap(vma->addr, 4096);
writeback(file_offset = 4096); (file_offset = 4096 - vma->start(0))

If you don't store the address mmaped, calculate file offset can be difficult.
Here is the implementation of writeback:

// referring filewrite
void writeback(struct file *f, uint64 addr, int foff, int n, int limit)
{
        int i = 0;
        int r = 0;
        while (i < n) {
                int n1 = n - i;
                if (n1 > limit)
                        n1 = limit;
                begin_op();
                ilock(f->ip);
                if ((r = writei(f->ip, 1, addr + i, foff, n1)) > 0)
                        foff += r;
                iunlock(f->ip);
                end_op();
                if (r != n1) {
                        // error from writei
                        break;
                }
                //printf("hello\n");
                i += r;
        }
}

The last job is to improve the implementation of exit and fork, which is pretty easy.
If you read the important points I mentioned carefully, debugging is not difficult either.

Complete code implementation is in my github repository.
There is also a simple implementation of C compiler. You can read it if you are interested.

If any problem, please contact me.
my email: xiongzile99@gmail.com
my github repository url: Withmm

Wish you good day!

posted @ 2024-11-28 14:10  Withm  阅读(20)  评论(1编辑  收藏  举报