本实验依赖实验1/2/3/4。请把你做的实验1/2/3/4的代码填入本实验中代码中有“LAB1”/“LAB2”/“LAB3”/“LAB4”的注释相应部分。注意:为了能够正确执行lab5的测试应用程序,可能需对已完成的实验1/2/3/4的代码进行进一步改进。
static struct proc_struct *
alloc_proc(void)
{
struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
if (proc != NULL)
{
proc->state = PROC_UNINIT;
proc->pid = -1;
proc->runs = 0;
proc->kstack = 0;
proc->need_resched = 0;
proc->parent = NULL;
proc->mm = NULL;
memset(&(proc->context), 0, sizeof(struct context));
proc->tf = NULL;
proc->cr3 = boot_cr3;
proc->flags = 0;
memset(proc->name, 0, sizeof(proc->name));
//以下是增加条目
proc->wait_state = 0;
proc->cptr = proc->yptr = proc->optr = NULL;
}
return proc;
}
void idt_init(void)
{
extern uintptr_t __vectors[];
int i;
for (i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i++)
{
SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
}
/**
* 【注意】除了系统调用中断(T_SYSCALL)使用陷阱门描述符且权限为用户态权限以外
* ,其它中断均使用特权级(DPL)为0的中断门描述符,权限为内核态权限;而ucore的
* 应用程序处于特权级3,需要采用`int 0x80`指令操作(这种方式称为软中断,软件
* 中断,Tra中断,在lab5会碰到)来发出系统调用请求,并要能实现从特权级3到特权
* 级0的转换,所以系统调用中断(T_SYSCALL)所对应的中断门描述符中的特权级(DPL)
* 需要设置为3。【lab1给出的实验说明】
*/
SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);
// this is different than answer
lidt(&idt_pd);
}
ticks++;
if (ticks % TICK_NUM == 0)
{
assert(current != NULL);
current->need_resched = 1;
}
下面只给出部分代码
int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf)
{
...
parent = current;//设置parent
assert(current->wait_state == 0);//检测是否为零
if (setup_kstack(proc) != 0)
{ // 2. call setup_kstack to allocate a kernel stack for child process
goto bad_fork_cleanup_proc;
}
if (copy_mm(clone_flags, proc) != 0)
{ // 3. call copy_mm to dup OR share mm according clone_flag
goto bad_fork_cleanup_kstack;
}
copy_thread(proc, stack, tf); // 4. call copy_thread to setup tf & context in proc_struct
bool intr_flag;
local_intr_save(intr_flag);
{
proc->pid = get_pid();
hash_proc(proc);
set_links(proc);//已经对nr_process增加了
// list_add(&proc_list, &proc->list_link); // 5. insert proc_struct into hash_list && proc_list
// nr_process++;
}
...
1 程序进入kern_init开始执行
2 proc_init(); 在调用该函数时创建了俩个内核线程
void proc_init(void)
//为idleproc分配内存
if ((idleproc = alloc_proc()) == NULL)
{
panic("cannot alloc idleproc.\n");
}
//设置idleproc内核线程信息
...
current = idleproc; //设置当前为idleproc
...
//又创建了另一个内核线程,其中(init_main 是该线程的执行流)
int pid = kernel_thread(init_main, NULL, 0);
...
set_proc_name(initproc, "init");
3 查看 init_main 发现init线程只是创建了个子线程,然后就进入循环调度
init_main(void *arg)
...
int pid = kernel_thread(user_main, NULL, 0);
if (pid <= 0)
{
panic("create user_main failed.\n");
}
while (do_wait(0, NULL) == 0)
{
schedule();
}
...
4 查看user_main发现调用了KERNEL_EXECVE宏
static int
user_main(void *arg)
{
#ifdef TEST
KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE);
#else
KERNEL_EXECVE(exit);
#endif
panic("user_main execve failed.\n");
}
5 查看该宏最终调用kernel_execve创建进程
#define __KERNEL_EXECVE(name, binary, size) ({ \
cprintf("kernel_execve: pid = %d, name = \"%s\".\n", \
current->pid, name); \
kernel_execve(name, binary, (size_t)(size)); \
})
#define KERNEL_EXECVE(x) ({ \
extern unsigned char _binary_obj___user_##x##_out_start[], \
_binary_obj___user_##x##_out_size[]; \
__KERNEL_EXECVE(#x, _binary_obj___user_##x##_out_start, \
_binary_obj___user_##x##_out_size); \
})
6 而kernel_execve执行的是系统调用
kernel_execve(const char *name, unsigned char *binary, size_t size)
{
int ret, len = strlen(name);
asm volatile(
"int %1;"
: "=a"(ret)//执行SYS_exec系统调用
: "i"(T_SYSCALL), "0"(SYS_exec), "d"(name), "c"(len), "b"(binary), "D"(size)
: "memory");
return ret;
}
7 系统调用在syscall.c中给出,通过设置了个函数指针数组进行绑定系统调用与对应函数
static int (*syscalls[])(uint32_t arg[]) = {
[SYS_exit] sys_exit,
[SYS_fork] sys_fork,
[SYS_wait] sys_wait,
[SYS_exec] sys_exec,
[SYS_yield] sys_yield,
[SYS_kill] sys_kill,
[SYS_getpid] sys_getpid,
[SYS_putc] sys_putc,
[SYS_pgdir] sys_pgdir,
};
void
syscall(void) {
struct trapframe *tf = current->tf;
uint32_t arg[5];
int num = tf->tf_regs.reg_eax;
if (num >= 0 && num < NUM_SYSCALLS) {
if (syscalls[num] != NULL) {
arg[0] = tf->tf_regs.reg_edx;
arg[1] = tf->tf_regs.reg_ecx;
arg[2] = tf->tf_regs.reg_ebx;
arg[3] = tf->tf_regs.reg_edi;
arg[4] = tf->tf_regs.reg_esi;
tf->tf_regs.reg_eax = syscalls[num](arg);
return ;
}
}
print_trapframe(tf);
panic("undefined syscall %d, pid = %d, name = %s.\n",
num, current->pid, current->name);
}
由于最终是在用户态下运行的,所以需要将段寄存器初始化为用户态的代码段、数据段、堆栈段;
esp应当指向先前的步骤中创建的用户栈的栈顶;
eip应当指向ELF可执行文件加载到内存之后的入口处;
eflags中应当初始化为中断使能,注意eflags的第1位是恒为1的;
设置ret为0,表示正常返回;
tf->tf_cs = USER_CS;
tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;
tf->tf_esp = USTACKTOP;
tf->tf_eip = elf->e_entry;
tf->tf_eflags = FL_IF |0x00000002;//参考AmadeusChan 不加 |0x00000002 make grade有点问题
ret = 0;
int copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share)
{
assert(start % PGSIZE == 0 && end % PGSIZE == 0);
assert(USER_ACCESS(start, end));
// copy content by page unit.
do
{
// call get_pte to find process A's pte according to the addr start
pte_t *ptep = get_pte(from, start, 0), *nptep;
if (ptep == NULL)
{
start = ROUNDDOWN(start + PTSIZE, PTSIZE);
continue;
}
// call get_pte to find process B's pte according to the addr start. If pte is NULL, just alloc a PT
if (*ptep & PTE_P)
{
if ((nptep = get_pte(to, start, 1)) == NULL)
{
return -E_NO_MEM;
}
uint32_t perm = (*ptep & PTE_USER);
// get page from ptep
struct Page *page = pte2page(*ptep);
// alloc a page for process B
struct Page *npage = alloc_page();
assert(page != NULL);
assert(npage != NULL);
int ret = 0;
/*
* (1) find src_kvaddr: the kernel virtual address of page
* (2) find dst_kvaddr: the kernel virtual address of npage
* (3) memory copy from src_kvaddr to dst_kvaddr, size is PGSIZE
* (4) build the map of phy addr of nage with the linear addr start
*/
uintptr_t *dst_kvaddr = page2kva(npage);
uintptr_t *src_kvaddr = page2kva(page);
memcpy(dst_kvaddr, src_kvaddr, PGSIZE);
ret = page_insert(to, npage, start, perm);
assert(ret == 0);
}
start += PGSIZE;
} while (start != 0 && start < end);
return 0;
}
copy_range函数就是调用一个memcpy
将父进程的内存直接复制给子进程即可。