#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ------------- process/thread mechanism design&implementation ------------- (an simplified Linux process/thread mechanism ) introduction: ucore implements a simple process/thread mechanism. process contains the independent memory sapce, at least one threads for execution, the kernel data(for management), processor state (for context switch), files(in lab6), etc. ucore needs to manage all these details efficiently. In ucore, a thread is just a special kind of process(share process's memory). ------------------------------ process state : meaning -- reason PROC_UNINIT : uninitialized -- alloc_proc PROC_SLEEPING : sleeping -- try_free_pages, do_wait, do_sleep PROC_RUNNABLE : runnable(maybe running) -- proc_init, wakeup_proc, PROC_ZOMBIE : almost dead -- do_exit ----------------------------- process state changing: alloc_proc RUNNING + +--<----<--+ + + proc_run + V +-->---->--+ PROC_UNINIT -- proc_init/wakeup_proc --> PROC_RUNNABLE -- try_free_pages/do_wait/do_sleep --> PROC_SLEEPING -- A + + | +--- do_exit --> PROC_ZOMBIE + + + -----------------------wakeup_proc---------------------------------- ----------------------------- process relations parent: proc->parent (proc is children) children: proc->cptr (proc is parent) older sibling: proc->optr (proc is younger sibling) younger sibling: proc->yptr (proc is older sibling) ----------------------------- related syscall for process: SYS_exit : process exit, -->do_exit SYS_fork : create child process, dup mm -->do_fork-->wakeup_proc SYS_wait : wait process -->do_wait SYS_exec : after fork, process execute a program -->load a program and refresh the mm SYS_clone : create child thread -->do_fork-->wakeup_proc SYS_yield : process flag itself need resecheduling, -- proc->need_sched=1, then scheduler will rescheule this process SYS_sleep : process sleep -->do_sleep SYS_kill : kill process -->do_kill-->proc->flags |= PF_EXITING -->wakeup_proc-->do_wait-->do_exit SYS_getpid : get the process's pid */ // the process set's list list_entry_t proc_list; #define HASH_SHIFT 10 #define HASH_LIST_SIZE (1 << HASH_SHIFT) #define pid_hashfn(x) (hash32(x, HASH_SHIFT)) // has list for process set based on pid static list_entry_t hash_list[HASH_LIST_SIZE]; // idle proc struct proc_struct *idleproc = NULL; // init proc struct proc_struct *initproc = NULL; // current proc struct proc_struct *current = NULL; static int nr_process = 0; void kernel_thread_entry(void); void forkrets(struct trapframe *tf); void switch_to(struct context *from, struct context *to); // alloc_proc - alloc a proc_struct and init all fields of proc_struct static struct proc_struct * alloc_proc(void) { struct proc_struct *proc = kmalloc(sizeof(struct proc_struct)); if (proc != NULL) { //LAB4:EXERCISE1 YOUR CODE /* * below fields in proc_struct need to be initialized * enum proc_state state; // Process state * int pid; // Process ID * int runs; // the running times of Proces * uintptr_t kstack; // Process kernel stack * volatile bool need_resched; // bool value: need to be rescheduled to release CPU? * struct proc_struct *parent; // the parent process * struct mm_struct *mm; // Process's memory management field * struct context context; // Switch here to run process * struct trapframe *tf; // Trap frame for current interrupt * uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) * uint32_t flags; // Process flag * char name[PROC_NAME_LEN + 1]; // Process name */ //LAB8:EXERCISE2 YOUR CODE HINT:need add some code to init fs in proc_struct, ... } return proc; } // set_proc_name - set the name of proc char * set_proc_name(struct proc_struct *proc, const char *name) { memset(proc->name, 0, sizeof(proc->name)); return memcpy(proc->name, name, PROC_NAME_LEN); } // get_proc_name - get the name of proc char * get_proc_name(struct proc_struct *proc) { static char name[PROC_NAME_LEN + 1]; memset(name, 0, sizeof(name)); return memcpy(name, proc->name, PROC_NAME_LEN); } // set_links - set the relation links of process static void set_links(struct proc_struct *proc) { list_add(&proc_list, &(proc->list_link)); proc->yptr = NULL; if ((proc->optr = proc->parent->cptr) != NULL) { proc->optr->yptr = proc; } proc->parent->cptr = proc; nr_process ++; } // remove_links - clean the relation links of process static void remove_links(struct proc_struct *proc) { list_del(&(proc->list_link)); if (proc->optr != NULL) { proc->optr->yptr = proc->yptr; } if (proc->yptr != NULL) { proc->yptr->optr = proc->optr; } else { proc->parent->cptr = proc->optr; } nr_process --; } // get_pid - alloc a unique pid for process static int get_pid(void) { static_assert(MAX_PID > MAX_PROCESS); struct proc_struct *proc; list_entry_t *list = &proc_list, *le; static int next_safe = MAX_PID, last_pid = MAX_PID; if (++ last_pid >= MAX_PID) { last_pid = 1; goto inside; } if (last_pid >= next_safe) { inside: next_safe = MAX_PID; repeat: le = list; while ((le = list_next(le)) != list) { proc = le2proc(le, list_link); if (proc->pid == last_pid) { if (++ last_pid >= next_safe) { if (last_pid >= MAX_PID) { last_pid = 1; } next_safe = MAX_PID; goto repeat; } } else if (proc->pid > last_pid && next_safe > proc->pid) { next_safe = proc->pid; } } } return last_pid; } // proc_run - make process "proc" running on cpu // NOTE: before call switch_to, should load base addr of "proc"'s new PDT void proc_run(struct proc_struct *proc) { if (proc != current) { bool intr_flag; struct proc_struct *prev = current, *next = proc; local_intr_save(intr_flag); { current = proc; load_esp0(next->kstack + KSTACKSIZE); lcr3(next->cr3); switch_to(&(prev->context), &(next->context)); } local_intr_restore(intr_flag); } } // forkret -- the first kernel entry point of a new thread/process // NOTE: the addr of forkret is setted in copy_thread function // after switch_to, the current proc will execute here. static void forkret(void) { forkrets(current->tf); } // hash_proc - add proc into proc hash_list static void hash_proc(struct proc_struct *proc) { list_add(hash_list + pid_hashfn(proc->pid), &(proc->hash_link)); } // unhash_proc - delete proc from proc hash_list static void unhash_proc(struct proc_struct *proc) { list_del(&(proc->hash_link)); } // find_proc - find proc frome proc hash_list according to pid struct proc_struct * find_proc(int pid) { if (0 < pid && pid < MAX_PID) { list_entry_t *list = hash_list + pid_hashfn(pid), *le = list; while ((le = list_next(le)) != list) { struct proc_struct *proc = le2proc(le, hash_link); if (proc->pid == pid) { return proc; } } } return NULL; } // kernel_thread - create a kernel thread using "fn" function // NOTE: the contents of temp trapframe tf will be copied to // proc->tf in do_fork-->copy_thread function int kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) { struct trapframe tf; memset(&tf, 0, sizeof(struct trapframe)); tf.tf_cs = KERNEL_CS; tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS; tf.tf_regs.reg_ebx = (uint32_t)fn; tf.tf_regs.reg_edx = (uint32_t)arg; tf.tf_eip = (uint32_t)kernel_thread_entry; return do_fork(clone_flags | CLONE_VM, 0, &tf); } // setup_kstack - alloc pages with size KSTACKPAGE as process kernel stack static int setup_kstack(struct proc_struct *proc) { struct Page *page = alloc_pages(KSTACKPAGE); if (page != NULL) { proc->kstack = (uintptr_t)page2kva(page); return 0; } return -E_NO_MEM; } // put_kstack - free the memory space of process kernel stack static void put_kstack(struct proc_struct *proc) { free_pages(kva2page((void *)(proc->kstack)), KSTACKPAGE); } // setup_pgdir - alloc one page as PDT static int setup_pgdir(struct mm_struct *mm) { struct Page *page; if ((page = alloc_page()) == NULL) { return -E_NO_MEM; } pde_t *pgdir = page2kva(page); memcpy(pgdir, boot_pgdir, PGSIZE); pgdir[PDX(VPT)] = PADDR(pgdir) | PTE_P | PTE_W; mm->pgdir = pgdir; return 0; } // put_pgdir - free the memory space of PDT static void put_pgdir(struct mm_struct *mm) { free_page(kva2page(mm->pgdir)); } // copy_mm - process "proc" duplicate OR share process "current"'s mm according clone_flags // - if clone_flags & CLONE_VM, then "share" ; else "duplicate" static int copy_mm(uint32_t clone_flags, struct proc_struct *proc) { struct mm_struct *mm, *oldmm = current->mm; /* current is a kernel thread */ if (oldmm == NULL) { return 0; } if (clone_flags & CLONE_VM) { mm = oldmm; goto good_mm; } int ret = -E_NO_MEM; if ((mm = mm_create()) == NULL) { goto bad_mm; } if (setup_pgdir(mm) != 0) { goto bad_pgdir_cleanup_mm; } lock_mm(oldmm); { ret = dup_mmap(mm, oldmm); } unlock_mm(oldmm); if (ret != 0) { goto bad_dup_cleanup_mmap; } good_mm: mm_count_inc(mm); proc->mm = mm; proc->cr3 = PADDR(mm->pgdir); return 0; bad_dup_cleanup_mmap: exit_mmap(mm); put_pgdir(mm); bad_pgdir_cleanup_mm: mm_destroy(mm); bad_mm: return ret; } // copy_thread - setup the trapframe on the process's kernel stack top and // - setup the kernel entry point and stack of process static void copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) { proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; *(proc->tf) = *tf; proc->tf->tf_regs.reg_eax = 0; proc->tf->tf_esp = esp; proc->tf->tf_eflags |= FL_IF; proc->context.eip = (uintptr_t)forkret; proc->context.esp = (uintptr_t)(proc->tf); } //copy_fs&put_fs function used by do_fork in LAB8 static int copy_fs(uint32_t clone_flags, struct proc_struct *proc) { struct files_struct *filesp, *old_filesp = current->filesp; assert(old_filesp != NULL); if (clone_flags & CLONE_FS) { filesp = old_filesp; goto good_files_struct; } int ret = -E_NO_MEM; if ((filesp = files_create()) == NULL) { goto bad_files_struct; } if ((ret = dup_fs(filesp, old_filesp)) != 0) { goto bad_dup_cleanup_fs; } good_files_struct: files_count_inc(filesp); proc->filesp = filesp; return 0; bad_dup_cleanup_fs: files_destroy(filesp); bad_files_struct: return ret; } static void put_fs(struct proc_struct *proc) { struct files_struct *filesp = proc->filesp; if (filesp != NULL) { if (files_count_dec(filesp) == 0) { files_destroy(filesp); } } } /* do_fork - parent process for a new child process * @clone_flags: used to guide how to clone the child process * @stack: the parent's user stack pointer. if stack==0, It means to fork a kernel thread. * @tf: the trapframe info, which will be copied to child process's proc->tf */ int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) { int ret = -E_NO_FREE_PROC; struct proc_struct *proc; if (nr_process >= MAX_PROCESS) { goto fork_out; } ret = -E_NO_MEM; //LAB4:EXERCISE2 YOUR CODE //LAB8:EXERCISE2 YOUR CODE HINT:how to copy the fs in parent's proc_struct? /* * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation. * MACROs or Functions: * alloc_proc: create a proc struct and init fields (lab4:exercise1) * setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack * copy_mm: process "proc" duplicate OR share process "current"'s mm according clone_flags * if clone_flags & CLONE_VM, then "share" ; else "duplicate" * copy_thread: setup the trapframe on the process's kernel stack top and * setup the kernel entry point and stack of process * hash_proc: add proc into proc hash_list * get_pid: alloc a unique pid for process * wakup_proc: set proc->state = PROC_RUNNABLE * VARIABLES: * proc_list: the process set's list * nr_process: the number of process set */ // 1. call alloc_proc to allocate a proc_struct // 2. call setup_kstack to allocate a kernel stack for child process // 3. call copy_mm to dup OR share mm according clone_flag // 4. call copy_thread to setup tf & context in proc_struct // 5. insert proc_struct into hash_list && proc_list // 6. call wakup_proc to make the new child process RUNNABLE // 7. set ret vaule using child proc's pid fork_out: return ret; bad_fork_cleanup_fs: //for LAB8 put_fs(proc); bad_fork_cleanup_kstack: put_kstack(proc); bad_fork_cleanup_proc: kfree(proc); goto fork_out; } // do_exit - called by sys_exit // 1. call exit_mmap & put_pgdir & mm_destroy to free the almost all memory space of process // 2. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself. // 3. call scheduler to switch to other process int do_exit(int error_code) { if (current == idleproc) { panic("idleproc exit.\n"); } if (current == initproc) { panic("initproc exit.\n"); } struct mm_struct *mm = current->mm; if (mm != NULL) { lcr3(boot_cr3); if (mm_count_dec(mm) == 0) { exit_mmap(mm); put_pgdir(mm); mm_destroy(mm); } current->mm = NULL; } put_fs(current); //for LAB8 current->state = PROC_ZOMBIE; current->exit_code = error_code; bool intr_flag; struct proc_struct *proc; local_intr_save(intr_flag); { proc = current->parent; if (proc->wait_state == WT_CHILD) { wakeup_proc(proc); } while (current->cptr != NULL) { proc = current->cptr; current->cptr = proc->optr; proc->yptr = NULL; if ((proc->optr = initproc->cptr) != NULL) { initproc->cptr->yptr = proc; } proc->parent = initproc; initproc->cptr = proc; if (proc->state == PROC_ZOMBIE) { if (initproc->wait_state == WT_CHILD) { wakeup_proc(initproc); } } } } local_intr_restore(intr_flag); schedule(); panic("do_exit will not return!! %d.\n", current->pid); } //load_icode_read is used by load_icode in LAB8 static int load_icode_read(int fd, void *buf, size_t len, off_t offset) { int ret; if ((ret = sysfile_seek(fd, offset, LSEEK_SET)) != 0) { return ret; } if ((ret = sysfile_read(fd, buf, len)) != len) { return (ret < 0) ? ret : -1; } return 0; } // load_icode - called by sys_exec-->do_execve static int load_icode(int fd, int argc, char **kargv) { /* LAB8:EXERCISE2 YOUR CODE HINT:how to load the file with handler fd in to process's memory? how to setup argc/argv? * MACROs or Functions: * mm_create - create a mm * setup_pgdir - setup pgdir in mm * load_icode_read - read raw data content of program file * mm_map - build new vma * pgdir_alloc_page - allocate new memory for TEXT/DATA/BSS/stack parts * lcr3 - update Page Directory Addr Register -- CR3 */ /* (1) create a new mm for current process * (2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT * (3) copy TEXT/DATA/BSS parts in binary to memory space of process * (3.1) read raw data content in file and resolve elfhdr * (3.2) read raw data content in file and resolve proghdr based on info in elfhdr * (3.3) call mm_map to build vma related to TEXT/DATA * (3.4) callpgdir_alloc_page to allocate page for TEXT/DATA, read contents in file * and copy them into the new allocated pages * (3.5) callpgdir_alloc_page to allocate pages for BSS, memset zero in these pages * (4) call mm_map to setup user stack, and put parameters into user stack * (5) setup current process's mm, cr3, reset pgidr (using lcr3 MARCO) * (6) setup uargc and uargv in user stacks * (7) setup trapframe for user environment * (8) if up steps failed, you should cleanup the env. */ } // this function isn't very correct in LAB8 static void put_kargv(int argc, char **kargv) { while (argc > 0) { kfree(kargv[-- argc]); } } static int copy_kargv(struct mm_struct *mm, int argc, char **kargv, const char **argv) { int i, ret = -E_INVAL; if (!user_mem_check(mm, (uintptr_t)argv, sizeof(const char *) * argc, 0)) { return ret; } for (i = 0; i < argc; i ++) { char *buffer; if ((buffer = kmalloc(EXEC_MAX_ARG_LEN + 1)) == NULL) { goto failed_nomem; } if (!copy_string(mm, buffer, argv[i], EXEC_MAX_ARG_LEN + 1)) { kfree(buffer); goto failed_cleanup; } kargv[i] = buffer; } return 0; failed_nomem: ret = -E_NO_MEM; failed_cleanup: put_kargv(i, kargv); return ret; } // do_execve - call exit_mmap(mm)&pug_pgdir(mm) to reclaim memory space of current process // - call load_icode to setup new memory space accroding binary prog. int do_execve(const char *name, int argc, const char **argv) { static_assert(EXEC_MAX_ARG_LEN >= FS_MAX_FPATH_LEN); struct mm_struct *mm = current->mm; if (!(argc >= 1 && argc <= EXEC_MAX_ARG_NUM)) { return -E_INVAL; } char local_name[PROC_NAME_LEN + 1]; memset(local_name, 0, sizeof(local_name)); char *kargv[EXEC_MAX_ARG_NUM]; const char *path; int ret = -E_INVAL; lock_mm(mm); if (name == NULL) { snprintf(local_name, sizeof(local_name), " %d", current->pid); } else { if (!copy_string(mm, local_name, name, sizeof(local_name))) { unlock_mm(mm); return ret; } } if ((ret = copy_kargv(mm, argc, kargv, argv)) != 0) { unlock_mm(mm); return ret; } path = argv[0]; unlock_mm(mm); files_closeall(current->filesp); /* sysfile_open will check the first argument path, thus we have to use a user-space pointer, and argv[0] may be incorrect */ int fd; if ((ret = fd = sysfile_open(path, O_RDONLY)) < 0) { goto execve_exit; } if (mm != NULL) { lcr3(boot_cr3); if (mm_count_dec(mm) == 0) { exit_mmap(mm); put_pgdir(mm); mm_destroy(mm); } current->mm = NULL; } ret= -E_NO_MEM;; if ((ret = load_icode(fd, argc, kargv)) != 0) { goto execve_exit; } put_kargv(argc, kargv); set_proc_name(current, local_name); return 0; execve_exit: put_kargv(argc, kargv); do_exit(ret); panic("already exit: %e.\n", ret); } // do_yield - ask the scheduler to reschedule int do_yield(void) { current->need_resched = 1; return 0; } // do_wait - wait one OR any children with PROC_ZOMBIE state, and free memory space of kernel stack // - proc struct of this child. // NOTE: only after do_wait function, all resources of the child proces are free. int do_wait(int pid, int *code_store) { struct mm_struct *mm = current->mm; if (code_store != NULL) { if (!user_mem_check(mm, (uintptr_t)code_store, sizeof(int), 1)) { return -E_INVAL; } } struct proc_struct *proc; bool intr_flag, haskid; repeat: haskid = 0; if (pid != 0) { proc = find_proc(pid); if (proc != NULL && proc->parent == current) { haskid = 1; if (proc->state == PROC_ZOMBIE) { goto found; } } } else { proc = current->cptr; for (; proc != NULL; proc = proc->optr) { haskid = 1; if (proc->state == PROC_ZOMBIE) { goto found; } } } if (haskid) { current->state = PROC_SLEEPING; current->wait_state = WT_CHILD; schedule(); if (current->flags & PF_EXITING) { do_exit(-E_KILLED); } goto repeat; } return -E_BAD_PROC; found: if (proc == idleproc || proc == initproc) { panic("wait idleproc or initproc.\n"); } if (code_store != NULL) { *code_store = proc->exit_code; } local_intr_save(intr_flag); { unhash_proc(proc); remove_links(proc); } local_intr_restore(intr_flag); put_kstack(proc); kfree(proc); return 0; } // do_kill - kill process with pid by set this process's flags with PF_EXITING int do_kill(int pid) { struct proc_struct *proc; if ((proc = find_proc(pid)) != NULL) { if (!(proc->flags & PF_EXITING)) { proc->flags |= PF_EXITING; if (proc->wait_state & WT_INTERRUPTED) { wakeup_proc(proc); } return 0; } return -E_KILLED; } return -E_INVAL; } // kernel_execve - do SYS_exec syscall to exec a user program called by user_main kernel_thread static int kernel_execve(const char *name, const char **argv) { int argc = 0, ret; while (argv[argc] != NULL) { argc ++; } asm volatile ( "int %1;" : "=a" (ret) : "i" (T_SYSCALL), "0" (SYS_exec), "d" (name), "c" (argc), "b" (argv) : "memory"); return ret; } #define __KERNEL_EXECVE(name, path, ...) ({ \ const char *argv[] = {path, ##__VA_ARGS__, NULL}; \ cprintf("kernel_execve: pid = %d, name = \"%s\".\n", \ current->pid, name); \ kernel_execve(name, argv); \ }) #define KERNEL_EXECVE(x, ...) __KERNEL_EXECVE(#x, #x, ##__VA_ARGS__) #define KERNEL_EXECVE2(x, ...) KERNEL_EXECVE(x, ##__VA_ARGS__) #define __KERNEL_EXECVE3(x, s, ...) KERNEL_EXECVE(x, #s, ##__VA_ARGS__) #define KERNEL_EXECVE3(x, s, ...) __KERNEL_EXECVE3(x, s, ##__VA_ARGS__) // user_main - kernel thread used to exec a user program static int user_main(void *arg) { #ifdef TEST #ifdef TESTSCRIPT KERNEL_EXECVE3(TEST, TESTSCRIPT); #else KERNEL_EXECVE2(TEST); #endif #else KERNEL_EXECVE(sh); #endif panic("user_main execve failed.\n"); } // init_main - the second kernel thread used to create user_main kernel threads static int init_main(void *arg) { int ret; if ((ret = vfs_set_bootfs("disk0:")) != 0) { panic("set boot fs failed: %e.\n", ret); } size_t nr_free_pages_store = nr_free_pages(); size_t slab_allocated_store = kallocated(); int pid = kernel_thread(user_main, NULL, 0); if (pid <= 0) { panic("create user_main failed.\n"); } extern void check_sync(void); check_sync(); // check philosopher sync problem while (do_wait(0, NULL) == 0) { schedule(); } fs_cleanup(); cprintf("all user-mode processes have quit.\n"); assert(initproc->cptr == NULL && initproc->yptr == NULL && initproc->optr == NULL); assert(nr_process == 2); assert(list_next(&proc_list) == &(initproc->list_link)); assert(list_prev(&proc_list) == &(initproc->list_link)); assert(nr_free_pages_store == nr_free_pages()); assert(slab_allocated_store == kallocated()); cprintf("init check memory pass.\n"); return 0; } // proc_init - set up the first kernel thread idleproc "idle" by itself and // - create the second kernel thread init_main void proc_init(void) { int i; list_init(&proc_list); for (i = 0; i < HASH_LIST_SIZE; i ++) { list_init(hash_list + i); } if ((idleproc = alloc_proc()) == NULL) { panic("cannot alloc idleproc.\n"); } idleproc->pid = 0; idleproc->state = PROC_RUNNABLE; idleproc->kstack = (uintptr_t)bootstack; idleproc->need_resched = 1; if ((idleproc->filesp = files_create()) == NULL) { panic("create filesp (idleproc) failed.\n"); } files_count_inc(idleproc->filesp); set_proc_name(idleproc, "idle"); nr_process ++; current = idleproc; int pid = kernel_thread(init_main, NULL, 0); if (pid <= 0) { panic("create init_main failed.\n"); } initproc = find_proc(pid); set_proc_name(initproc, "init"); assert(idleproc != NULL && idleproc->pid == 0); assert(initproc != NULL && initproc->pid == 1); } // cpu_idle - at the end of kern_init, the first kernel thread idleproc will do below works void cpu_idle(void) { while (1) { if (current->need_resched) { schedule(); } } } //FOR LAB6, set the process's priority (bigger value will get more CPU time) void lab6_set_priority(uint32_t priority) { if (priority == 0) current->lab6_priority = 1; else current->lab6_priority = priority; } // do_sleep - set current process state to sleep and add timer with "time" // - then call scheduler. if process run again, delete timer first. int do_sleep(unsigned int time) { if (time == 0) { return 0; } bool intr_flag; local_intr_save(intr_flag); timer_t __timer, *timer = timer_init(&__timer, current, time); current->state = PROC_SLEEPING; current->wait_state = WT_TIMER; add_timer(timer); local_intr_restore(intr_flag); schedule(); del_timer(timer); return 0; }