# HG changeset patch # User John Tsiombikas # Date 1313370219 -10800 # Node ID 88a6c4e192f916e5bcc582e7e4cd47651f26c118 # Parent 4eaecb14fe31ca6e36f73c99573b063d88bde952 Fixed most important task switching bugs. Now it seems that I can switch in and out of user space reliably. diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/intr-asm.S --- a/src/intr-asm.S Sun Aug 14 16:57:23 2011 +0300 +++ b/src/intr-asm.S Mon Aug 15 04:03:39 2011 +0300 @@ -81,10 +81,11 @@ /* if we entered from userspace ss and cs is set correctly, but * we must make sure all the other selectors are set to the * kernel data segment */ - mov $SEGM_KDATA, %ds - mov $SEGM_KDATA, %es - mov $SEGM_KDATA, %fs - mov $SEGM_KDATA, %gs + mov %ss, %eax + mov %eax, %ds + mov %eax, %es + mov %eax, %fs + mov %eax, %gs call dispatch_intr intr_ret_local: /* restore general purpose registers */ diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/intr.c --- a/src/intr.c Sun Aug 14 16:57:23 2011 +0300 +++ b/src/intr.c Mon Aug 15 04:03:39 2011 +0300 @@ -4,6 +4,7 @@ #include "segm.h" #include "asmops.h" #include "panic.h" +#include "syscall.h" /* IDT gate descriptor bits */ #define GATE_TASK (5 << 8) @@ -49,6 +50,7 @@ static intr_func_t intr_func[256]; static struct intr_frame *cur_intr_frame; +static int eoi_pending; void init_intr(void) @@ -74,6 +76,7 @@ * setting up the maping of IRQs [0, 15] to interrupts [32, 47] */ init_pic(IRQ_OFFSET); + eoi_pending = 0; } /* retrieve the current interrupt frame. @@ -98,6 +101,10 @@ { cur_intr_frame = &frm; + if(IS_IRQ(frm.inum)) { + eoi_pending = frm.inum; + } + if(intr_func[frm.inum]) { intr_func[frm.inum](frm.inum); } else { @@ -107,8 +114,9 @@ printf("unhandled interrupt %d\n", frm.inum); } - if(IS_IRQ(frm.inum)) { - end_of_irq(INTR_TO_IRQ(frm.inum)); + disable_intr(); + if(eoi_pending) { + end_of_irq(INTR_TO_IRQ(eoi_pending)); } } @@ -149,13 +157,31 @@ static void set_intr_entry(int num, void (*handler)(void)) { int type = IS_TRAP(num) ? GATE_TRAP : GATE_INTR; - gate_desc(idt + num, selector(SEGM_KCODE, 0), (uint32_t)handler, 0, type); + + /* the syscall interrupt has to have a dpl of 3 otherwise calling it from + * user space will raise a general protection exception. All the rest should + * have a dpl of 0 to disallow user programs to execute critical interrupt + * handlers and possibly crashing the system. + */ + int dpl = (num == SYSCALL_INT) ? 3 : 0; + + gate_desc(idt + num, selector(SEGM_KCODE, 0), (uint32_t)handler, dpl, type); } void end_of_irq(int irq) { + int intr_state = get_intr_state(); + disable_intr(); + + if(!eoi_pending) { + return; + } + eoi_pending = 0; + if(irq > 7) { outb(OCW2_EOI, PIC2_CMD); } outb(OCW2_EOI, PIC1_CMD); + + set_intr_state(intr_state); } diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/proc.c --- a/src/proc.c Sun Aug 14 16:57:23 2011 +0300 +++ b/src/proc.c Mon Aug 15 04:03:39 2011 +0300 @@ -1,6 +1,7 @@ #include #include #include +#include "config.h" #include "proc.h" #include "tss.h" #include "vm.h" @@ -11,10 +12,12 @@ #include "sched.h" #include "tss.h" -#define FLAGS_INTR_BIT 9 +#define FLAGS_INTR_BIT (1 << 9) static void start_first_proc(void); +/* defined in proc-asm.S */ +uint32_t switch_stack(uint32_t new_stack); /* defined in test_proc.S */ void test_proc(void); @@ -36,7 +39,7 @@ if((tss_page = pgalloc(1, MEM_KERNEL)) == -1) { panic("failed to allocate memory for the task state segment\n"); } - tss = (struct tss*)PAGE_TO_ADDR(tss_page); + tss = (struct task_state*)PAGE_TO_ADDR(tss_page); /* the kernel stack segment never changes so we might as well set it now * the only other thing that we use in the tss is the kernel stack pointer @@ -45,7 +48,7 @@ memset(tss, 0, sizeof *tss); tss->ss0 = selector(SEGM_KDATA, 0); - set_tss((uint32_t)virt_to_phys(tss)); + set_tss((uint32_t)tss); /* initialize system call handler (see syscall.c) */ init_syscall(); @@ -58,7 +61,7 @@ { struct process *p; int proc_size_pg, img_start_pg, stack_pg; - uint32_t img_start_addr, ustack_addr; + uint32_t img_start_addr; struct intr_frame ifrm; /* prepare the first process */ @@ -66,6 +69,12 @@ p->id = 1; p->parent = 0; /* no parent for init */ + p->ticks_left = TIMESLICE_TICKS; + p->next = p->prev = 0; + + /* the first process may keep this existing page table */ + p->ctx.pgtbl_paddr = get_pgdir_addr(); + /* allocate a chunk of memory for the process image * and copy the code of test_proc there. */ @@ -102,7 +111,7 @@ ifrm.esp = PAGE_TO_ADDR(stack_pg) + PGSIZE; ifrm.ss = selector(SEGM_UDATA, 3); /* instruction pointer at the beginning of the process image */ - ifrm.regs.eip = img_start_addr; + ifrm.eip = img_start_addr; ifrm.cs = selector(SEGM_UCODE, 3); /* make sure the user will run with interrupts enabled */ ifrm.eflags = FLAGS_INTR_BIT; @@ -110,7 +119,9 @@ ifrm.ds = ifrm.es = ifrm.fs = ifrm.gs = ifrm.ss; /* add it to the scheduler queues */ - add_proc(p->id, STATE_RUNNABLE); + add_proc(p->id); + + cur_pid = p->id; /* make it current */ /* execute a fake return from interrupt with the fake stack frame */ intr_ret(ifrm); @@ -121,6 +132,8 @@ { struct process *prev, *new; + assert(get_intr_state() == 0); + if(cur_pid == pid) { return; /* nothing to be done */ } @@ -141,7 +154,7 @@ /* make sure we'll return to the correct kernel stack next time * we enter from userspace */ - tss->esp0 = PAGE_TO_ADDR(p->kern_stack_pg) + KERN_STACK_SIZE; + tss->esp0 = PAGE_TO_ADDR(new->kern_stack_pg) + KERN_STACK_SIZE; } int get_current_pid(void) diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/proc.h --- a/src/proc.h Sun Aug 14 16:57:23 2011 +0300 +++ b/src/proc.h Mon Aug 15 04:03:39 2011 +0300 @@ -26,6 +26,9 @@ int id, parent; enum proc_state state; + /* when blocked it's waiting for a wakeup on this address */ + void *wait_addr; + int ticks_left; /* extends of the process heap, increased by sbrk */ diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/sched.c --- a/src/sched.c Sun Aug 14 16:57:23 2011 +0300 +++ b/src/sched.c Mon Aug 15 04:03:39 2011 +0300 @@ -1,35 +1,36 @@ #include +#include #include "sched.h" #include "proc.h" #include "intr.h" #include "asmops.h" #include "config.h" -#define EMPTY(q) ((q).head == 0) +#define EMPTY(q) ((q)->head == 0) struct proc_list { struct process *head, *tail; }; -static void ins_back(struct proc_list *q, struct process *proc); -static void ins_front(struct proc_list *q, struct process *proc); -static void remove(struct proc_list *q, struct process *proc); +static void idle_proc(void); +static void ins_back(struct proc_list *list, struct process *proc); +static void ins_front(struct proc_list *list, struct process *proc); +static void remove(struct proc_list *list, struct process *proc); +static int hash_addr(void *addr); static struct proc_list runq; -static struct proc_list waitq; static struct proc_list zombieq; +#define HTBL_SIZE 101 +static struct proc_list wait_htable[HTBL_SIZE]; + + void schedule(void) { disable_intr(); - if(EMPTY(runq)) { - /* idle "process". - * make sure interrupts are enabled before halting - */ - enable_intr(); - halt_cpu(); - printf("fuck you!\n"); + if(EMPTY(&runq)) { + idle_proc(); /* this won't return, it'll just wake up in an interrupt later */ } @@ -47,14 +48,13 @@ runq.head->ticks_left = TIMESLICE_TICKS; } - /* no need to re-enable interrupts, they will be enabled with the iret */ + /* always enter context_switch with interrupts disabled */ context_switch(runq.head->id); } -int add_proc(int pid, enum proc_state state) +void add_proc(int pid) { int istate; - struct proc_list *q; struct process *proc; istate = get_intr_state(); @@ -62,85 +62,102 @@ proc = get_process(pid); - q = state == STATE_RUNNABLE ? &runq : &waitq; - - ins_back(q, proc); - proc->state = state; - - set_intr_state(istate); - return 0; -} - -int block_proc(int pid) -{ - int istate; - struct process *proc = get_process(pid); - - if(proc->state != STATE_RUNNABLE) { - printf("block_proc: process %d not running\n", pid); - return -1; - } - - istate = get_intr_state(); - disable_intr(); - - remove(&runq, proc); - ins_back(&waitq, proc); - proc->state = STATE_BLOCKED; - - set_intr_state(istate); - return 0; -} - -int unblock_proc(int pid) -{ - int istate; - struct process *proc = get_process(pid); - - if(proc->state != STATE_BLOCKED) { - printf("unblock_proc: process %d not blocked\n", pid); - return -1; - } - - istate = get_intr_state(); - disable_intr(); - - remove(&waitq, proc); ins_back(&runq, proc); proc->state = STATE_RUNNABLE; set_intr_state(istate); - return 0; } +/* block the process until we get a wakeup call for address ev */ +void wait(void *wait_addr) +{ + struct process *p; + int hash_idx; -static void ins_back(struct proc_list *q, struct process *proc) + disable_intr(); + + p = get_current_proc(); + assert(p); + + /* remove it from the runqueue ... */ + remove(&runq, p); + + /* and place it in the wait hash table based on sleep_addr */ + hash_idx = hash_addr(wait_addr); + ins_back(wait_htable + hash_idx, p); + + p->state = STATE_BLOCKED; + p->wait_addr = wait_addr; +} + +/* wake up all the processes sleeping on this address */ +void wakeup(void *wait_addr) { - if(EMPTY(*q)) { - q->head = proc; + int hash_idx; + struct process *iter; + struct proc_list *list; + + hash_idx = hash_addr(wait_addr); + list = wait_htable + hash_idx; + + iter = list->head; + while(iter) { + if(iter->wait_addr == wait_addr) { + /* found one, remove it, and make it runnable */ + struct process *p = iter; + iter = iter->next; + + remove(list, p); + p->state = STATE_RUNNABLE; + ins_back(&runq, p); + } else { + iter = iter->next; + } + } +} + +static void idle_proc(void) +{ + /* make sure we send any pending EOIs if needed. + * end_of_irq will actually check if it's needed first. + */ + struct intr_frame *ifrm = get_intr_frame(); + end_of_irq(INTR_TO_IRQ(ifrm->inum)); + + /* make sure interrupts are enabled before halting */ + enable_intr(); + halt_cpu(); +} + + +/* list operations */ +static void ins_back(struct proc_list *list, struct process *proc) +{ + if(EMPTY(list)) { + list->head = proc; } else { - q->tail->next = proc; + list->tail->next = proc; } proc->next = 0; - proc->prev = q->tail; - q->tail = proc; + proc->prev = list->tail; + list->tail = proc; } -static void ins_front(struct proc_list *q, struct process *proc) +static void ins_front(struct proc_list *list, struct process *proc) { - if(EMPTY(*q)) { - q->tail = proc; + if(EMPTY(list)) { + list->tail = proc; } else { - q->head->prev = proc; + list->head->prev = proc; } - proc->next = q->head; + proc->next = list->head; proc->prev = 0; - q->head = proc; + list->head = proc; } -static void remove(struct proc_list *q, struct process *proc) +static void remove(struct proc_list *list, struct process *proc) { if(proc->prev) { proc->prev->next = proc->next; @@ -148,10 +165,15 @@ if(proc->next) { proc->next->prev = proc->prev; } - if(q->head == proc) { - q->head = proc->next; + if(list->head == proc) { + list->head = proc->next; } - if(q->tail == proc) { - q->tail = proc->prev; + if(list->tail == proc) { + list->tail = proc->prev; } } + +static int hash_addr(void *addr) +{ + return (uint32_t)addr % HTBL_SIZE; +} diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/sched.h --- a/src/sched.h Sun Aug 14 16:57:23 2011 +0300 +++ b/src/sched.h Mon Aug 15 04:03:39 2011 +0300 @@ -5,8 +5,9 @@ void schedule(void); -int add_proc(int pid, enum proc_state state); -int block_proc(int pid); -int unblock_proc(int pid); +void add_proc(int pid); + +void wait(void *wait_addr); +void wakeup(void *wait_addr); #endif /* SCHED_H_ */ diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/segm-asm.S --- a/src/segm-asm.S Sun Aug 14 16:57:23 2011 +0300 +++ b/src/segm-asm.S Mon Aug 15 04:03:39 2011 +0300 @@ -42,5 +42,6 @@ * loads the TSS selector in the task register */ .globl set_task_reg set_task_reg: + mov 4(%esp), %eax ltr 4(%esp) ret diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/segm.c --- a/src/segm.c Sun Aug 14 16:57:23 2011 +0300 +++ b/src/segm.c Mon Aug 15 04:03:39 2011 +0300 @@ -1,6 +1,7 @@ #include #include "segm.h" #include "desc.h" +#include "tss.h" /* bits for the 3rd 16bt part of the descriptor */ #define BIT_ACCESSED (1 << 8) @@ -21,10 +22,11 @@ enum {TYPE_DATA, TYPE_CODE}; -#define TSS_TYPE_BITS (BIT_ACCESSED | BIT_CODE) +/* we need the following bit pattern at the 8th bit excluding the busy bit: 1001 */ +#define TSS_TYPE_BITS (9 << 8) static void segm_desc(desc_t *desc, uint32_t base, uint32_t limit, int dpl, int type); -static void task_desc(desc_t *desc, uint32_t base, uint32_t limit, int dpl, unsigned int busy); +static void task_desc(desc_t *desc, uint32_t base, uint32_t limit, int dpl); /* these functions are implemented in segm-asm.S */ void setup_selectors(uint16_t code, uint16_t data); @@ -57,7 +59,7 @@ void set_tss(uint32_t addr) { - task_desc(gdt + SEGM_TASK, 0, sizeof(struct tss) - 1, 3, TSS_BUSY); + task_desc(gdt + SEGM_TASK, addr, sizeof(struct task_state) - 1, 3); set_task_reg(selector(SEGM_TASK, 0)); } @@ -79,12 +81,44 @@ desc->d[3] = ((limit >> 16) & 0xf) | ((base >> 16) & 0xff00) | BIT_GRAN | BIT_BIG; } -static void task_desc(desc_t *desc, uint32_t base, uint32_t limit, int dpl, unsigned int busy) +static void task_desc(desc_t *desc, uint32_t base, uint32_t limit, int dpl) { desc->d[0] = limit & 0xffff; desc->d[1] = base & 0xffff; desc->d[2] = ((base >> 16) & 0xff) | ((dpl & 3) << 13) | BIT_PRESENT | - TSS_TYPE_BITS | busy; + TSS_TYPE_BITS; /* XXX busy ? */ desc->d[3] = ((limit >> 16) & 0xf) | ((base >> 16) & 0xff00) | BIT_GRAN; } +/* +static void dbg_print_gdt(void) +{ + int i; + + printf("Global Descriptor Table\n"); + printf("-----------------------\n"); + + for(i=0; i<6; i++) { + print_desc(gdt + i); + } +} + +static void print_desc(desc_t *desc) +{ + uint32_t base, limit; + int dpl, g, db, l, avl, p, s, type; + char *type_str; + + base = (uint32_t)desc->d[1] | ((uint32_t)(desc->d[2] & 0xff) << 16) | ((uint32_t)(desc->d[3] >> 8) << 24); + limit = (uint32_t)desc->d[0] | ((uint32_t)(desc->d[3] & 0xf) << 16); + dpl = (desc->d[2] >> 13) & 3; + type = (desc->d[2] >> 8) & 0xf; + g = (desc->d[3] >> 23) & 1; + db = (desc->d[3] >> 22) & 1; + l = (desc->d[3] >> 21) & 1; + avl = (desc->d[3] >> 20) & 1; + + p = (desc->d[2] >> 15) & 1; + s = (desc->d[2] >> 12) & 1; +} +*/ diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/timer.c --- a/src/timer.c Sun Aug 14 16:57:23 2011 +0300 +++ b/src/timer.c Mon Aug 15 04:03:39 2011 +0300 @@ -45,10 +45,6 @@ struct timer_event { int dt; /* remaining ticks delta from the previous event */ - - void (*callback)(void*); - void *cbarg; - struct timer_event *next; }; @@ -79,24 +75,21 @@ interrupt(IRQ_TO_INTR(0), intr_handler); } -int start_timer(unsigned long msec, timer_func_t cbfunc, void *cbarg) +void sleep(unsigned long msec) { int ticks, tsum, istate; struct timer_event *ev, *node; - printf("start_timer(%lu)\n", msec); + printf("sleep(%lu)\n", msec); if((ticks = MSEC_TO_TICKS(msec)) <= 0) { - cbfunc(cbarg); - return 0; + return; } if(!(ev = malloc(sizeof *ev))) { - printf("start_timer: failed to allocate timer_event structure\n"); - return -1; + printf("sleep: failed to allocate timer_event structure\n"); + return; } - ev->callback = cbfunc; - ev->cbarg = cbarg; istate = get_intr_state(); disable_intr(); @@ -110,29 +103,30 @@ if(ev->next) { ev->next->dt -= ticks; } - set_intr_state(istate); - return 0; - } + } else { - tsum = evlist->dt; - node = evlist; + tsum = evlist->dt; + node = evlist; - while(node->next && ticks > tsum + node->next->dt) { - tsum += node->next->dt; - node = node->next; - } + while(node->next && ticks > tsum + node->next->dt) { + tsum += node->next->dt; + node = node->next; + } - ev->next = node->next; - node->next = ev; + ev->next = node->next; + node->next = ev; - /* fix the relative times */ - ev->dt = ticks - tsum; - if(ev->next) { - ev->next->dt -= ev->dt; + /* fix the relative times */ + ev->dt = ticks - tsum; + if(ev->next) { + ev->next->dt -= ev->dt; + } } set_intr_state(istate); - return 0; + + /* wait on the address of this timer event */ + wait(ev); } /* This will be called by the interrupt dispatcher approximately @@ -160,17 +154,15 @@ evlist = evlist->next; printf("timer going off!!!\n"); - ev->callback(ev->cbarg); + /* wake up all processes waiting on this address */ + wakeup(ev); free(ev); } } if((cur_proc = get_current_proc())) { + /* if the timeslice of this process has expire, call the scheduler */ if(--cur_proc->ticks_left <= 0) { - /* since schedule will not return, we have to notify - * the PIC that we're done with the IRQ handling - */ - end_of_irq(INTR_TO_IRQ(inum)); schedule(); } } diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/vm-asm.S --- a/src/vm-asm.S Sun Aug 14 16:57:23 2011 +0300 +++ b/src/vm-asm.S Mon Aug 15 04:03:39 2011 +0300 @@ -34,6 +34,13 @@ movl %eax, %cr3 ret +/* get_pgdir_addr(void) + * returns the physical address of the page table directory (cr3) */ + .globl get_pgdir_addr +get_pgdir_addr: + movl %cr3, %eax + ret + /* flush_tlb(void) * invalidates the whole TLB. entries for pages marked as global * are unaffected */ diff -r 4eaecb14fe31 -r 88a6c4e192f9 src/vm.c --- a/src/vm.c Sun Aug 14 16:57:23 2011 +0300 +++ b/src/vm.c Mon Aug 15 04:03:39 2011 +0300 @@ -129,8 +129,19 @@ pgidx = PAGE_TO_PGTBL_PG(vpage); if(!(pgdir[diridx] & PG_PRESENT)) { + /* no page table present, we must allocate one */ uint32_t addr = alloc_phys_page(); - pgdir[diridx] = addr | (attr & ATTR_PGDIR_MASK) | PG_PRESENT; + + /* make sure all page directory entries in the below the kernel vm + * split have the user and writable bits set, otherwise further user + * mappings on the same 4mb block will be unusable in user space. + */ + unsigned int pgdir_attr = attr; + if(vpage < ADDR_TO_PAGE(KMEM_START)) { + pgdir_attr |= PG_USER | PG_WRITABLE; + } + + pgdir[diridx] = addr | (pgdir_attr & ATTR_PGDIR_MASK) | PG_PRESENT; pgtbl = pgon ? PGTBL(diridx) : (uint32_t*)addr; memset(pgtbl, 0, PGSIZE); @@ -265,7 +276,6 @@ { int intr_state, ret = -1; struct page_range *node, *prev, dummy; - unsigned int attr = 0; /* TODO */ intr_state = get_intr_state(); disable_intr(); @@ -296,6 +306,9 @@ } if(ret >= 0) { + /*unsigned int attr = (area == MEM_USER) ? (PG_USER | PG_WRITABLE) : PG_GLOBAL;*/ + unsigned int attr = (area == MEM_USER) ? (PG_USER | PG_WRITABLE) : 0; + /* allocate physical storage and map */ if(map_page_range(ret, num, -1, attr) == -1) { ret = -1; @@ -310,7 +323,6 @@ { struct page_range *node, *prev, dummy; int area, intr_state, ret = -1; - unsigned int attr = 0; /* TODO */ area = (start >= ADDR_TO_PAGE(KMEM_START)) ? MEM_KERNEL : MEM_USER; if(area == MEM_USER && start + num > ADDR_TO_PAGE(KMEM_START)) { @@ -376,6 +388,9 @@ } if(ret >= 0) { + /*unsigned int attr = (area == MEM_USER) ? (PG_USER | PG_WRITABLE) : PG_GLOBAL;*/ + unsigned int attr = (area == MEM_USER) ? (PG_USER | PG_WRITABLE) : 0; + /* allocate physical storage and map */ if(map_page_range(ret, num, -1, attr) == -1) { ret = -1; @@ -464,21 +479,20 @@ uint32_t fault_addr = get_fault_addr(); /* the fault occured in user space */ - if(frm->esp < KMEM_START + 1) { + if(frm->err & PG_USER) { int fault_page = ADDR_TO_PAGE(fault_addr); struct process *proc = get_current_proc(); + printf("DBG: page fault in user space\n"); assert(proc); - printf("DBG: page fault in user space\n"); - if(frm->err & PG_PRESENT) { /* it's not due to a missing page, just panic */ goto unhandled; } /* detect if it's an automatic stack growth deal */ - if(fault_page < proc->stack_start_pg && proc->stack_start_pg - fault_page < USTACK_MAXGROW) { - int num_pages = proc->stack_start_pg - fault_page; + if(fault_page < proc->user_stack_pg && proc->user_stack_pg - fault_page < USTACK_MAXGROW) { + int num_pages = proc->user_stack_pg - fault_page; printf("growing user (%d) stack by %d pages\n", proc->id, num_pages); if(pgalloc_vrange(fault_page, num_pages) != fault_page) { @@ -486,8 +500,7 @@ /* TODO: in the future we'd SIGSEGV the process here, for now just panic */ goto unhandled; } - proc->stack_start_pg = fault_page; - + proc->user_stack_pg = fault_page; return; } } @@ -500,8 +513,8 @@ if(frm->err & 8) { printf("reserved bit set in some paging structure\n"); } else { - printf("%s protection violation ", (frm->err & PG_WRITABLE) ? "write" : "read"); - printf("in %s mode\n", frm->err & PG_USER ? "user" : "kernel"); + printf("%s protection violation ", (frm->err & PG_WRITABLE) ? "WRITE" : "READ"); + printf("in %s mode\n", (frm->err & PG_USER) ? "user" : "kernel"); } } else { printf("page not present\n"); @@ -553,7 +566,7 @@ /*printf("free_node\n");*/ } - +#if 0 /* clone_vm makes a copy of the current page tables, thus duplicating the * virtual address space. * @@ -594,7 +607,7 @@ for(i=0; i