标签:结构 数通 rest 打印 为什么 parent ddl 说明 set
alloc_proc函数(位于kern/process/proc.c中) 负责分配并返回一个新的struct proc_struct结构,用于存储新建立的内核线程的管理信息。ucore需要对这个结构进行最基本的初始化,你需要完成这个初始化过程。
【提示】 在alloc_proc函数的实现中,需要初始化的proc_struct结构中的成员变量至少包括:state/pid/runs/kstack/need_resched/parent/mm/context/tf/cr3/flags/name。
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
请说明proc_struct中 struct context context 和 struct trapframe *tf 成员变量含义和在本实验中的作用是啥?(提示通过看代码和编程调试可以判断出来)
alloc_proc函数主要是初始化进程控制块,亦即初始化proc_struct结构体的各成员变量。
struct context {
uint32_t eip;
uint32_t esp;
uint32_t ebx;
uint32_t ecx;
uint32_t edx;
uint32_t esi;
uint32_t edi;
uint32_t ebp;
};
struct trapframe {
struct pushregs tf_regs;
uint16_t tf_gs;
uint16_t tf_padding0;
uint16_t tf_fs;
uint16_t tf_padding1;
uint16_t tf_es;
uint16_t tf_padding2;
uint16_t tf_ds;
uint16_t tf_padding3;
uint32_t tf_trapno;
/* below here defined by x86 hardware */
uint32_t tf_err;
uintptr_t tf_eip;
uint16_t tf_cs;
uint16_t tf_padding4;
uint32_t tf_eflags;
/* below here only when crossing rings, such as from user to kernel */
uintptr_t tf_esp;
uint16_t tf_ss;
uint16_t tf_padding5;
} __attribute__((packed));
创建一个内核线程需要分配和设置好很多资源。kernel_thread函数通过调用do_fork函数完成具体内核线程的创建工作。do_kernel函数会调用alloc_proc函数来分配并初始化一个进程控制块,但alloc_proc只是找到了一小块内存用以记录进程的必要信息,并没有实际分配这些资源。ucore一般通过do_fork实际创建新的内核线程。do_fork的作用是,创建当前内核线程的一个副本,它们的执行上下文、代码、数据都一样,但是存储位置不同。在这个过程中,需要给新内核线程分配资源,并且复制原进程的状态。你需要完成在kern/process/proc.c中do_fork函数中的处理过程。它的大致执行步骤包括:
- 调用alloc_proc,首先获得一块用户信息块。
- 为进程分配一个内核栈。
- 复制原进程的内存管理信息到新进程(但内核线程不必做此事)
- 复制原进程上下文到新进程
- 将新进程添加到进程列表
- 唤醒新进程
- 返回新进程号
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
请说明ucore是否做到给每个新fork的线程一个唯一的id?请说明你的分析和理由。
根据注释提供的步骤,很容易完成do_fork函数的实现。这里需要注意的是:如果前面的步骤失败,比如alloc_proc分配进程控制块失败或建立内核栈失败,那么需要释放已申请的资源。
首先,本实验不提供线程释放的功能,意味着pid只分配不回收。当fork的线程总数小于MAX_PID时,每个线程的pid是唯一的。当fork的线程总数大于MAX_PID时,后面fork的线程的pid可能与前面的线程重复(暂不确定)。
注:get_pid函数没完全看懂,next_safe的含义不理解?
对照答案时,发现自己的代码有几个优化的地方:
没有设置proc->parent,应将其设置为current
由于do_fork已经设置了标签,setup_kstack执行失败后直接跳转到bad_fork_cleanup_proc即可,copy_mm失败后直接跳转到bad_fork_cleanup_kstack即可。
copy_thread的第二个输入参数esp应该使用do_fork的第二个输入参数stack。
将当前进程插入到proc_list和hash_list时需要去使能中断。(为什么?)
我是将proc插入到proc_list的末尾,而答案是插入到proc_list的开头。为何?是不是因为插入到开头的话,schedule选择要执行的线程时会快些?
我的代码:
if (NULL == (proc = alloc_proc())) {
goto fork_out;
}
if (0 != setup_kstack(proc)) {
kfree(proc);
goto fork_out;
}
if (0 != copy_mm(clone_flags, proc)) {
kfree((void *)proc->kstack);
kfree(proc);
goto fork_out;
}
proc->pid = get_pid();
int esp = 0;
asm volatile ("movl %%esp, %0" : "=r" (esp));
copy_thread(proc, esp, tf);
list_add_before(&proc_list, &proc->list_link);
hash_proc(proc);
wakeup_proc(proc);
nr_process++;
答案的代码:
if ((proc = alloc_proc()) == NULL) {
goto fork_out;
}
proc->parent = current;
if (setup_kstack(proc) != 0) {
goto bad_fork_cleanup_proc;
}
if (copy_mm(clone_flags, proc) != 0) {
goto bad_fork_cleanup_kstack;
}
copy_thread(proc, stack, tf);
bool intr_flag;
local_intr_save(intr_flag);
{
proc->pid = get_pid();
hash_proc(proc);
list_add(&proc_list, &(proc->list_link));
nr_process ++;
}
local_intr_restore(intr_flag);
wakeup_proc(proc);
请在实验报告中简要说明你对proc_run函数的分析。并回答如下问题:
- 在本实验的执行过程中,创建且运行了几个内核线程?
- 语句 local_intr_save(intr_flag);....local_intr_restore(intr_flag); 在这里有何作用?请说明理由。
完成代码编写后,编译并运行代码:make qemu
,如果可以得到如附录A所示的显示内容(仅供参考,不是标准答案输出) ,则基本正确。
首先判断要切换到的进程是不是当前进程,若是则不需进行任何处理。
调用local_intr_save和local_intr_restore函数去使能中断,避免在进程切换过程中出现中断。(疑问:进程切换过程中处理中断会有什么问题?)
更新current进程为proc
更新任务状态段的esp0的值(疑问:为什么更新esp0?)
重新加载cr3寄存器,使页目录表更新为新进程的页目录表
上下文切换,把当前进程的当前各寄存器的值保存在其proc_struct结构体的context变量中,再把要切换到的进程的proc_struct结构体的context变量加载到各寄存器。
完成上下文切换后,CPU会根据eip寄存器的值找到下一条指令的地址并执行。根据copy_thread函数可知eip寄存器指向forkret函数,forkret函数的实现为forkrets(current->tf);
.globl __trapret
__trapret:
# restore registers from stack
popal
# restore %ds, %es, %fs and %gs
popl %gs
popl %fs
popl %es
popl %ds
# get rid of the trap number and error code
addl $0x8, %esp
iret
.globl forkrets
forkrets:
# set stack to this new process's trapframe
movl 4(%esp), %esp
jmp __trapret
pushl %edx
将输入参数压栈,然后通过call *%ebx
调用函数fn。.globl kernel_thread_entry
kernel_thread_entry: # void kernel_thread(void)
pushl %edx # push arg
call *%ebx # call fn
pushl %eax # save the return value of fn(arg)
call do_exit # call do_exit to terminate current thread
init_main(void *arg) {
cprintf("this initproc, pid = %d, name = \"%s\"\n", current->pid, get_proc_name(current));
cprintf("To U: \"%s\".\n", (const char *)arg);
cprintf("To U: \"en.., Bye, Bye. :)\"\n");
return 0;
}
答:本实验创建且运行了两个内核线程,分别是idle和init线程。
答:避免在进程切换过程中处理中断。
这不是本实验的内容,其实是上一次实验内存的扩展,但考虑到现在的slab算法比较复杂,有必要实现一个比较简单的任意大小内存分配算法。可参考本实验中的slab如何调用基于页的内存分配算法(注意,不是要你关注slab的具体实现) 来实现first-fit/best-fit/worst-fit/buddy等支持任意大小的内存分配算法。
【注意】 下面是相关的Linux实现文档,供参考
- SLOB
- SLAB
标签:结构 数通 rest 打印 为什么 parent ddl 说明 set
原文地址:https://www.cnblogs.com/wuhualong/p/ucore_lab4_report.html