《操作系统》的实验代码。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

434 lines
15 KiB

#include <proc.h>
#include <kmalloc.h>
#include <string.h>
#include <sync.h>
#include <pmm.h>
#include <error.h>
#include <sched.h>
#include <elf.h>
//#include <vmm.h>
#include <trap.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
/* ------------- 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;
// idle proc
struct proc_struct *idleproc = NULL;
// init procs
struct proc_struct *initproc1 = NULL;
struct proc_struct *initproc2 = 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 2012011346
/*
* 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
*/
memset(proc, 0, sizeof(struct proc_struct));
proc->state = PROC_UNINIT;
proc->pid = -1;
proc->cr3 = boot_cr3;
}
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);
}
// 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);
}
// 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 = &proc_list, *le = list;
while ((le = list_next(le)) != list) {
struct proc_struct *proc = le2proc(le, list_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);
}
// 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);
}
/* 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 2012011346
/*
* 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_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
* wakeup_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
proc = alloc_proc();
proc->pid = get_pid();
// 2. call setup_kstack to allocate a kernel stack for child process
setup_kstack(proc);
// 3. call copy_thread to setup tf & context in proc_struct
copy_thread(proc, stack, tf);
// 4. insert proc_struct into proc_list
list_add_before(&proc_list, &proc->list_link);
// 5. call wakeup_proc to make the new child process RUNNABLE
wakeup_proc(proc);
// 7. set ret vaule using child proc's pid
nr_process++;
ret = proc->pid;
// 8. set parent
proc->parent=current;
fork_out:
return ret;
bad_fork_cleanup_kstack:
put_kstack(proc);
bad_fork_cleanup_proc:
kfree(proc);
goto fork_out;
}
// remove_links - clean the relation links of process
static void
remove_links(struct proc_struct *proc) {
list_del(&(proc->list_link));
nr_process --;
}
// 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 proc_struct *proc;
bool intr_flag, haskid;
repeat:
cprintf("do_wait: begin\n");
haskid = 0;
list_entry_t *list = &proc_list, *le = list;
while ((le = list_next(le)) != list) {
proc = le2proc(le, list_link);
if (proc != NULL) {
haskid = 1;
if (proc->state == PROC_ZOMBIE) {
goto found;
}
}
}
if (haskid) {
cprintf("do_wait: has kid begin\n");
current->state = PROC_SLEEPING;
current->wait_state = WT_CHILD;
schedule();
goto repeat;
}
return -E_BAD_PROC;
found:
cprintf("do_wait: has kid find child pid%d\n",proc->pid);
if (proc == idleproc ) {
panic("wait idleproc \n");
}
local_intr_save(intr_flag);
{
remove_links(proc);
}
local_intr_restore(intr_flag);
put_kstack(proc);
kfree(proc);
return 0;
}
// do_exit - called by sys_exit
// 1. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself.
// 2. call scheduler to switch to other process
int
do_exit(int error_code) {
if (current == idleproc) {
panic("idleproc exit.\n");
}
cprintf(" do_exit: proc pid %d will exit\n", current->pid);
cprintf(" do_exit: proc parent %x\n", current->parent);
current->state = PROC_ZOMBIE;
bool intr_flag;
struct proc_struct *proc;
local_intr_save(intr_flag);
{
proc = current->parent;
if (proc->wait_state == WT_CHILD) {
wakeup_proc(proc);
}
}
local_intr_restore(intr_flag);
schedule();
panic("do_exit will not return!! %d.\n", current->pid);
}
// init_main - the second kernel thread used to create user_main kernel threads
static int
init_main(void *arg) {
cprintf(" kernel_thread, pid = %d, name = %s\n", current->pid, get_proc_name(current));
schedule();
cprintf(" kernel_thread, pid = %d, name = %s , arg %s \n", current->pid, get_proc_name(current), (const char *)arg);
schedule();
cprintf(" kernel_thread, pid = %d, name = %s , en.., Bye, Bye. :)\n",current->pid, get_proc_name(current));
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);
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;
set_proc_name(idleproc, "idle");
nr_process ++;
current = idleproc;
int pid1= kernel_thread(init_main, "init main1: Hello world!!", 0);
int pid2= kernel_thread(init_main, "init main2: Hello world!!", 0);
if (pid1 <= 0 || pid2<=0) {
panic("create kernel thread init_main1 or 2 failed.\n");
}
initproc1 = find_proc(pid1);
initproc2 = find_proc(pid2);
set_proc_name(initproc1, "init1");
set_proc_name(initproc2, "init2");
cprintf("proc_init:: Created kernel thread init_main--> pid: %d, name: %s\n",initproc1->pid, initproc1->name);
cprintf("proc_init:: Created kernel thread init_main--> pid: %d, name: %s\n",initproc2->pid, initproc2->name);
assert(idleproc != NULL && idleproc->pid == 0);
}
// 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();
}
}
}