diff --git a/Makefile b/Makefile index 980543a..7000fac 100644 --- a/Makefile +++ b/Makefile @@ -107,8 +107,8 @@ initcode: initcode.S $(OBJCOPY) -S -O binary initcode.out initcode $(OBJDUMP) -S initcode.o > initcode.asm -kernel: $(OBJS) multiboot.o bootother initcode - $(LD) $(LDFLAGS) -Ttext 0x100000 -e main -o kernel multiboot.o $(OBJS) -b binary initcode bootother fs.img +kernel: $(OBJS) multiboot.o data.o bootother initcode + $(LD) $(LDFLAGS) -Ttext 0x100000 -e main -o kernel multiboot.o data.o $(OBJS) -b binary initcode bootother $(OBJDUMP) -S kernel > kernel.asm $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernel.sym @@ -119,8 +119,8 @@ kernel: $(OBJS) multiboot.o bootother initcode # great for testing the kernel on real hardware without # needing a scratch disk. MEMFSOBJS = $(filter-out ide.o,$(OBJS)) memide.o -kernelmemfs: $(MEMFSOBJS) multiboot.o bootother initcode fs.img - $(LD) $(LDFLAGS) -Ttext 0x100000 -e main -o kernelmemfs multiboot.o $(MEMFSOBJS) -b binary initcode bootother fs.img +kernelmemfs: $(MEMFSOBJS) multiboot.o data.o bootother initcode fs.img + $(LD) $(LDFLAGS) -Ttext 0x100000 -e main -o kernelmemfs multiboot.o data.o $(MEMFSOBJS) -b binary initcode bootother fs.img $(OBJDUMP) -S kernelmemfs > kernelmemfs.asm $(OBJDUMP) -t kernelmemfs | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernelmemfs.sym @@ -251,14 +251,16 @@ dist-test: rm -rf dist-test mkdir dist-test cp dist/* dist-test - cd dist-test; ../m print - cd dist-test; ../m bochs || true - cd dist-test; ../m qemu + cd dist-test; $(MAKE) print + cd dist-test; $(MAKE) bochs || true + cd dist-test; $(MAKE) qemu -# update this rule (change rev1) when it is time to +# update this rule (change rev#) when it is time to # make a new revision. tar: rm -rf /tmp/xv6 mkdir -p /tmp/xv6 cp dist/* dist/.gdbinit.tmpl /tmp/xv6 - (cd /tmp; tar cf - xv6) | gzip >xv6-rev4.tar.gz + (cd /tmp; tar cf - xv6) | gzip >xv6-rev5.tar.gz + +.PHONY: dist-test dist diff --git a/bootasm.S b/bootasm.S index f5d1678..3cc23e7 100644 --- a/bootasm.S +++ b/bootasm.S @@ -13,7 +13,7 @@ .code16 # Assemble for 16-bit mode .globl start start: - cli # BIOS enabled interrupts ; disable + cli # BIOS enabled interrupts; disable # Set up the important data segment registers (DS, ES, SS). xorw %ax,%ax # Segment number zero @@ -21,10 +21,8 @@ start: movw %ax,%es # -> Extra Segment movw %ax,%ss # -> Stack Segment - # Enable A20: - # For backwards compatibility with the earliest PCs, physical - # address line 20 is tied low, so that addresses higher than - # 1MB wrap around to zero by default. This code undoes this. + # Physical address line A20 is tied to zero so that the first PCs + # with 2 MB would run software that assumed 1 MB. Undo that. seta20.1: inb $0x64,%al # Wait for not busy testb $0x2,%al @@ -41,28 +39,21 @@ seta20.2: movb $0xdf,%al # 0xdf -> port 0x60 outb %al,$0x60 -//PAGEBREAK! - # Switch from real to protected mode, using a bootstrap GDT - # and segment translation that makes virtual addresses - # identical to physical addresses, so that the - # effective memory map does not change after subsequent - # loads of segment registers. + # Switch from real to protected mode. Use a bootstrap GDT that makes + # virtual addresses map dierctly to physical addresses so that the + # effective memory map doesn't change during the transition. lgdt gdtdesc movl %cr0, %eax orl $CR0_PE, %eax movl %eax, %cr0 - - # This ljmp is how you load the CS (Code Segment) register. - # SEG_ASM produces segment descriptors with the 32-bit mode - # flag set (the D flag), so addresses and word operands will - # default to 32 bits after this jump. + +//PAGEBREAK! + # Complete transition to 32-bit protected mode by using long jmp + # to reload %cs and %eip. The segment registers are set up with no + # translation, so that the mapping is still the identity mapping. ljmp $(SEG_KCODE<<3), $start32 -# tell the assembler to generate 0x66 prefixes for 16-bit -# instructions like movw, and to generate 32-bit immediate -# addresses. -.code32 - +.code32 # Tell assembler to generate 32-bit code now. start32: # Set up the protected-mode data segment registers movw $(SEG_KDATA<<3), %ax # Our data segment selector diff --git a/bootother.S b/bootother.S index 186873e..37b899b 100644 --- a/bootother.S +++ b/bootother.S @@ -34,12 +34,12 @@ start: movw %ax,%es movw %ax,%ss -//PAGEBREAK! lgdt gdtdesc movl %cr0, %eax orl $CR0_PE, %eax movl %eax, %cr0 +//PAGEBREAK! ljmp $(SEG_KCODE<<3), $start32 .code32 diff --git a/data.S b/data.S index 47f05b3..c0eb55b 100644 --- a/data.S +++ b/data.S @@ -1,5 +1,24 @@ -# Define "data" symbol to mark beginning of data segment. -# Must be linked before any other data on ld command line. +// The kernel layout is: +// +// text +// rodata +// data +// bss +// +// Conventionally, Unix linkers provide pseudo-symbols +// etext, edata, and end, at the end of the text, data, and bss. +// For the kernel mapping, we need the address at the beginning +// of the data section, but that's not one of the conventional +// symbols, because the convention started before there was a +// read-only rodata section between text and data. +// +// To get the address of the data section, we define a symbol +// named data and make sure this is the first object passed to +// the linker, so that it will be the first symbol in the data section. +// +// Alternative approaches would be to parse our own ELF header +// or to write a linker script, but this is simplest. + .data .globl data data: diff --git a/exec.c b/exec.c index 209bc79..05f80f8 100644 --- a/exec.c +++ b/exec.c @@ -10,8 +10,8 @@ int exec(char *path, char **argv) { char *s, *last; - int i, off, argc; - uint sz, sp, strings[MAXARG]; + int i, off; + uint argc, sz, sp, ustack[3+MAXARG+1]; struct elfhdr elf; struct inode *ip; struct proghdr ph; @@ -53,49 +53,25 @@ exec(char *path, char **argv) if((sz = allocuvm(pgdir, sz, sz + PGSIZE)) == 0) goto bad; - // initialize stack content: - - // "argumentN" -- nul-terminated string - // ... - // "argument0" - // 0 -- argv[argc] - // address of argumentN - // ... - // address of argument0 -- argv[0] - // address of address of argument0 -- argv argument to main() - // argc -- argc argument to main() - // ffffffff -- return PC for main() call - + // Push argument strings, prepare rest of stack in ustack. sp = sz; - - // count arguments - for(argc = 0; argv[argc]; argc++) - ; - if(argc >= MAXARG) - goto bad; - - // push strings and remember where they are - for(i = argc - 1; i >= 0; --i){ - sp -= strlen(argv[i]) + 1; - strings[i] = sp; - copyout(pgdir, sp, argv[i], strlen(argv[i]) + 1); + for(argc = 0; argv[argc]; argc++) { + if(argc >= MAXARG) + goto bad; + sp -= strlen(argv[argc]) + 1; + sp &= ~3; + if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0) + goto bad; + ustack[3+argc] = sp; } + ustack[3+argc] = 0; -#define PUSH(x){ int xx = (int)(x); sp -= 4; copyout(pgdir, sp, &xx, 4); } + ustack[0] = 0xffffffff; // fake return PC + ustack[1] = argc; + ustack[2] = sp - (argc+1)*4; // argv pointer - PUSH(0); // argv[argc] is zero - - // push argv[] elements - for(i = argc - 1; i >= 0; --i) - PUSH(strings[i]); - - PUSH(sp); // argv - - PUSH(argc); - - PUSH(0xffffffff); // in case main tries to return - - if(sp < sz - PGSIZE) + sp -= (3+argc+1) * 4; + if(copyout(pgdir, sp, ustack, (3+argc+1)*4) < 0) goto bad; // Save program name for debugging. @@ -110,9 +86,7 @@ exec(char *path, char **argv) proc->sz = sz; proc->tf->eip = elf.entry; // main proc->tf->esp = sp; - - switchuvm(proc); - + switchuvm(proc); freevm(oldpgdir); return 0; diff --git a/fs.h b/fs.h index 6f92592..1e6137b 100644 --- a/fs.h +++ b/fs.h @@ -41,7 +41,6 @@ struct dinode { // Block containing bit for block b #define BBLOCK(b, ninodes) (b/BPB + (ninodes)/IPB + 3) -// PAGEBREAK: 10 // Directory is a file containing a sequence of dirent structures. #define DIRSIZ 14 diff --git a/ide.c b/ide.c index f4bd210..53293a7 100644 --- a/ide.c +++ b/ide.c @@ -96,7 +96,7 @@ ideintr(void) acquire(&idelock); if((b = idequeue) == 0){ release(&idelock); - cprintf("Spurious IDE interrupt.\n"); + // cprintf("spurious IDE interrupt\n"); return; } idequeue = b->qnext; diff --git a/main.c b/main.c index 8a5f7f1..e6d81f3 100644 --- a/main.c +++ b/main.c @@ -89,7 +89,8 @@ bootothers(void) char *stack; // Write bootstrap code to unused memory at 0x7000. - // The linker has placed the image of bootother.S in _binary_bootother_start. + // The linker has placed the image of bootother.S in + // _binary_bootother_start. code = (uchar*)0x7000; memmove(code, _binary_bootother_start, (uint)_binary_bootother_size); @@ -111,3 +112,7 @@ bootothers(void) ; } } + +//PAGEBREAK! +// Blank page. + diff --git a/mp.c b/mp.c index 44ee020..5ab348e 100644 --- a/mp.c +++ b/mp.c @@ -39,7 +39,6 @@ mpsearch1(uchar *addr, int len) { uchar *e, *p; - cprintf("mpsearch1 0x%x %d\n", addr, len); e = addr+len; for(p = addr; p < e; p += sizeof(struct mp)) if(memcmp(p, "_MP_", 4) == 0 && sum(p, sizeof(struct mp)) == 0) @@ -113,7 +112,6 @@ mpinit(void) switch(*p){ case MPPROC: proc = (struct mpproc*)p; - cprintf("mpproc %d\n", proc->apicid); if(ncpu != proc->apicid){ cprintf("mpinit: ncpu=%d apicid=%d\n", ncpu, proc->apicid); ismp = 0; diff --git a/proc.c b/proc.c index e6ccd9d..eb334d0 100644 --- a/proc.c +++ b/proc.c @@ -25,44 +25,6 @@ pinit(void) initlock(&ptable.lock, "ptable"); } -//PAGEBREAK: 36 -// Print a process listing to console. For debugging. -// Runs when user types ^P on console. -// No lock to avoid wedging a stuck machine further. -void -procdump(void) -{ - static char *states[] = { - [UNUSED] "unused", - [EMBRYO] "embryo", - [SLEEPING] "sleep ", - [RUNNABLE] "runble", - [RUNNING] "run ", - [ZOMBIE] "zombie" - }; - int i; - struct proc *p; - char *state; - uint pc[10]; - - for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ - if(p->state == UNUSED) - continue; - if(p->state >= 0 && p->state < NELEM(states) && states[p->state]) - state = states[p->state]; - else - state = "???"; - cprintf("%d %s %s", p->pid, state, p->name); - if(p->state == SLEEPING){ - getcallerpcs((uint*)p->context->ebp+2, pc); - for(i=0; i<10 && pc[i] != 0; i++) - cprintf(" %p", pc[i]); - } - cprintf("\n"); - } -} - - //PAGEBREAK: 32 // Look in the process table for an UNUSED proc. // If found, change state to EMBRYO and initialize @@ -447,3 +409,41 @@ kill(int pid) return -1; } +//PAGEBREAK: 36 +// Print a process listing to console. For debugging. +// Runs when user types ^P on console. +// No lock to avoid wedging a stuck machine further. +void +procdump(void) +{ + static char *states[] = { + [UNUSED] "unused", + [EMBRYO] "embryo", + [SLEEPING] "sleep ", + [RUNNABLE] "runble", + [RUNNING] "run ", + [ZOMBIE] "zombie" + }; + int i; + struct proc *p; + char *state; + uint pc[10]; + + for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ + if(p->state == UNUSED) + continue; + if(p->state >= 0 && p->state < NELEM(states) && states[p->state]) + state = states[p->state]; + else + state = "???"; + cprintf("%d %s %s", p->pid, state, p->name); + if(p->state == SLEEPING){ + getcallerpcs((uint*)p->context->ebp+2, pc); + for(i=0; i<10 && pc[i] != 0; i++) + cprintf(" %p", pc[i]); + } + cprintf("\n"); + } +} + + diff --git a/runoff.list b/runoff.list index 01147ea..f0edaf0 100644 --- a/runoff.list +++ b/runoff.list @@ -22,6 +22,7 @@ proc.h proc.c swtch.S kalloc.c +data.S vm.c # system calls traps.h @@ -48,6 +49,7 @@ exec.c # pipes pipe.c + # string operations string.c @@ -62,6 +64,7 @@ kbd.c console.c timer.c uart.c +multiboot.S # user-level initcode.S @@ -72,3 +75,4 @@ sh.c + diff --git a/runoff.spec b/runoff.spec index 8a2b5c9..4d00038 100644 --- a/runoff.spec +++ b/runoff.spec @@ -6,8 +6,8 @@ sheet1: left # pages. The file may start in either column. # # "even" and "odd" specify which column a file must start on. "even" -# means it must start in the left of the two columns. "odd" means it -# must start in the right of the two columns. +# means it must start in the left of the two columns (00). "odd" means it +# must start in the right of the two columns (50). # # You'd think these would be the other way around. @@ -33,23 +33,23 @@ left: spinlock.h # mild preference even: spinlock.h # mild preference # This gets struct proc and allocproc on the same spread -right: proc.h -odd: proc.h +left: proc.h +even: proc.h # goal is to have two action-packed 2-page spreads, # one with # userinit growproc fork exit wait # and another with # scheduler sched yield forkret sleep wakeup1 wakeup -left: proc.c # VERY important -odd: proc.c # VERY important +right: proc.c # VERY important +even: proc.c # VERY important # A few more action packed spreads # page table creation and process loading # walkpgdir mappages setupkvm vmenable switch[ku]vm inituvm loaduvm # process memory management # allocuvm deallocuvm freevm -right: vm.c +left: vm.c odd: vm.c # kalloc.c either @@ -69,17 +69,25 @@ odd: vm.c # file.h either # fs.h either # fsvar.h either -left: ide.c +# left: ide.c # mild preference even: ide.c # odd: bio.c + +# with fs.c starting on 2nd column of a left page, we get these 2-page spreads: +# ialloc iupdate iget idup ilock iunlock iput iunlockput +# bmap itrunc stati readi writei +# namecmp dirlookup dirlink skipelem namex namei +# fielinit filealloc filedup fileclose filestat fileread filewrite +# starting on 2nd column of a right page is not terrible either odd: fs.c # VERY important +left: fs.c # mild preference # file.c either # exec.c either # sysfile.c either # even: pipe.c # mild preference # string.c either -left: kbd.h +# left: kbd.h # mild preference even: kbd.h even: console.c odd: sh.c diff --git a/runoff1 b/runoff1 index ba42e8f..532f844 100755 --- a/runoff1 +++ b/runoff1 @@ -33,7 +33,7 @@ for($i=0; $i<@lines; ){ last if $i>=@lines; # If the rest of the file fits, use the whole thing. - if(@lines <= $i+50){ + if(@lines <= $i+50 && !grep { /PAGEBREAK/ } @lines){ $breakbefore = @lines; }else{ # Find a good next page break; diff --git a/toc.ftr b/toc.ftr index 3ed8593..5e15911 100644 --- a/toc.ftr +++ b/toc.ftr @@ -6,8 +6,8 @@ on the same line as the name, the line number (or, in a few cases, numbers) where the name is defined. Successive lines in an entry list the line numbers where the name is used. For example, this entry: - swtch 2308 - 0317 2128 2166 2307 2308 + swtch 2358 + 0317 2128 2166 2357 2358 -indicates that swtch is defined on line 2308 and is mentioned on five lines +indicates that swtch is defined on line 2358 and is mentioned on five lines on sheets 03, 21, and 23. diff --git a/trap.c b/trap.c index b2b2ebb..6651f8e 100644 --- a/trap.c +++ b/trap.c @@ -59,6 +59,9 @@ trap(struct trapframe *tf) ideintr(); lapiceoi(); break; + case T_IRQ0 + IRQ_IDE+1: + // Bochs generates spurious IDE1 interrupts. + break; case T_IRQ0 + IRQ_KBD: kbdintr(); lapiceoi(); diff --git a/usertests.c b/usertests.c index 9a83591..296731a 100644 --- a/usertests.c +++ b/usertests.c @@ -1445,11 +1445,11 @@ bigargtest(void) ppid = getpid(); pid = fork(); if(pid == 0){ - char *args[32]; + char *args[32+1]; int i; - for(i = 0; i < 32-1; i++) + for(i = 0; i < 32; i++) args[i] = "bigargs test: failed\n "; - args[32-1] = 0; + args[32] = 0; printf(stdout, "bigarg test\n"); exec("echo", args); printf(stdout, "bigarg test ok\n"); diff --git a/vm.c b/vm.c index bfc0845..1fe64d2 100644 --- a/vm.c +++ b/vm.c @@ -6,8 +6,18 @@ #include "proc.h" #include "elf.h" +extern char data[]; // defined in data.S + static pde_t *kpgdir; // for use in scheduler() +// Allocate one page table for the machine for the kernel address +// space for scheduler processes. +void +kvmalloc(void) +{ + kpgdir = setupkvm(); +} + // Set up CPU's kernel segment descriptors. // Run once at boot time on each CPU. void @@ -72,7 +82,6 @@ mappages(pde_t *pgdir, void *la, uint size, uint pa, int perm) a = PGROUNDDOWN(la); last = PGROUNDDOWN(la + size - 1); - for(;;){ pte = walkpgdir(pgdir, a, 1); if(pte == 0) @@ -110,40 +119,32 @@ mappages(pde_t *pgdir, void *la, uint size, uint pa, int perm) // range from 0 till 640KB (USERTOP), which where the I/O hole starts // (both in physical memory and in the kernel's virtual address // space). - -// Allocate one page table for the machine for the kernel address -// space for scheduler processes. -void -kvmalloc(void) -{ - kpgdir = setupkvm(); -} +static struct kmap { + void *p; + void *e; + int perm; +} kmap[] = { + {(void*)USERTOP, (void*)0x100000, PTE_W}, // I/O space + {(void*)0x100000, data, 0 }, // kernel text, rodata + {data, (void*)PHYSTOP, PTE_W}, // kernel data, memory + {(void*)0xFE000000, 0, PTE_W}, // device mappings +}; // Set up kernel part of a page table. pde_t* setupkvm(void) { - extern char etext[]; - char *rwstart; pde_t *pgdir; - uint rwlen; + struct kmap *k; - rwstart = PGROUNDDOWN(etext); - rwlen = (uint)rwstart - 0x100000; - - // Allocate page directory if((pgdir = (pde_t*)kalloc()) == 0) return 0; memset(pgdir, 0, PGSIZE); - if(// Map IO space from 640K to 1Mbyte - mappages(pgdir, (void*)USERTOP, 0x60000, USERTOP, PTE_W) < 0 || - // Map kernel instructions - mappages(pgdir, (void*)0x100000, rwlen, 0x100000, 0) < 0 || - // Map kernel data and free memory pool - mappages(pgdir, rwstart, PHYSTOP-(uint)rwstart, (uint)rwstart, PTE_W) < 0 || - // Map devices such as ioapic, lapic, ... - mappages(pgdir, (void*)0xFE000000, 0x2000000, 0xFE000000, PTE_W) < 0) - return 0; + k = kmap; + for(k = kmap; k < &kmap[NELEM(kmap)]; k++) + if(mappages(pgdir, k->p, k->e - k->p, (uint)k->p, k->perm) < 0) + return 0; + return pgdir; } @@ -162,48 +163,27 @@ vmenable(void) // Switch h/w page table register to the kernel-only page table, // for when no process is running. void -switchkvm() +switchkvm(void) { lcr3(PADDR(kpgdir)); // switch to the kernel page table } -// Switch h/w page table and TSS registers to point to process p. +// Switch TSS and h/w page table to correspond to process p. void switchuvm(struct proc *p) { pushcli(); - - // Setup TSS cpu->gdt[SEG_TSS] = SEG16(STS_T32A, &cpu->ts, sizeof(cpu->ts)-1, 0); cpu->gdt[SEG_TSS].s = 0; cpu->ts.ss0 = SEG_KDATA << 3; cpu->ts.esp0 = (uint)proc->kstack + KSTACKSIZE; ltr(SEG_TSS << 3); - if(p->pgdir == 0) - panic("switchuvm: no pgdir\n"); - + panic("switchuvm: no pgdir"); lcr3(PADDR(p->pgdir)); // switch to new address space popcli(); } -// Return the physical address that a given user address -// maps to. The result is also a kernel logical address, -// since the kernel maps the physical memory allocated to user -// processes directly. -char* -uva2ka(pde_t *pgdir, char *uva) -{ - pte_t *pte; - - pte = walkpgdir(pgdir, uva, 0); - if((*pte & PTE_P) == 0) - return 0; - if((*pte & PTE_U) == 0) - return 0; - return (char*)PTE_ADDR(*pte); -} - // Load the initcode into address 0 of pgdir. // sz must be less than a page. void @@ -228,10 +208,10 @@ loaduvm(pde_t *pgdir, char *addr, struct inode *ip, uint offset, uint sz) pte_t *pte; if((uint)addr % PGSIZE != 0) - panic("loaduvm: addr must be page aligned\n"); + panic("loaduvm: addr must be page aligned"); for(i = 0; i < sz; i += PGSIZE){ if((pte = walkpgdir(pgdir, addr+i, 0)) == 0) - panic("loaduvm: address should exist\n"); + panic("loaduvm: address should exist"); pa = PTE_ADDR(*pte); if(sz - i < PGSIZE) n = sz - i; @@ -243,10 +223,8 @@ loaduvm(pde_t *pgdir, char *addr, struct inode *ip, uint offset, uint sz) return 0; } -// Allocate memory to the process to bring its size from oldsz to -// newsz. Allocates physical memory and page table entries. oldsz and -// newsz need not be page-aligned, nor does newsz have to be larger -// than oldsz. Returns the new process size or 0 on error. +// Allocate page tables and physical memory to grow process from oldsz to +// newsz, which need not be page aligned. Returns new size or 0 on error. int allocuvm(pde_t *pgdir, uint oldsz, uint newsz) { @@ -330,9 +308,9 @@ copyuvm(pde_t *pgdir, uint sz) return 0; for(i = 0; i < sz; i += PGSIZE){ if((pte = walkpgdir(pgdir, (void*)i, 0)) == 0) - panic("copyuvm: pte should exist\n"); + panic("copyuvm: pte should exist"); if(!(*pte & PTE_P)) - panic("copyuvm: page not present\n"); + panic("copyuvm: page not present"); pa = PTE_ADDR(*pte); if((mem = kalloc()) == 0) goto bad; @@ -347,16 +325,31 @@ bad: return 0; } -// copy some data to user address va in page table pgdir. -// most useful when pgdir is not the current page table. +//PAGEBREAK! +// Map user virtual address to kernel physical address. +char* +uva2ka(pde_t *pgdir, char *uva) +{ + pte_t *pte; + + pte = walkpgdir(pgdir, uva, 0); + if((*pte & PTE_P) == 0) + return 0; + if((*pte & PTE_U) == 0) + return 0; + return (char*)PTE_ADDR(*pte); +} + +// Copy len bytes from p to user address va in page table pgdir. +// Most useful when pgdir is not the current page table. // uva2ka ensures this only works for PTE_U pages. int -copyout(pde_t *pgdir, uint va, void *xbuf, uint len) +copyout(pde_t *pgdir, uint va, void *p, uint len) { char *buf, *pa0; uint n, va0; - buf = (char*)xbuf; + buf = (char*)p; while(len > 0){ va0 = (uint)PGROUNDDOWN(va); pa0 = uva2ka(pgdir, (char*)va0);