SchoolWork-LaTeX/操作系统/实验报告/Lab4.tex

295 lines
13 KiB
TeX
Raw Normal View History

2024-09-02 17:47:53 +08:00
\documentclass[a4paper]{ctexart}
\input{mypreamble}
\renewcommand{\mychapternum}{4}
\renewcommand{\mylabname}{内核线程管理}
\renewcommand{\mydate}{2024年5月17日}
\begin{document}
\mytitle
\begin{enumerate}
\item{\textbf{研读理解相关代码后,回答以下问题:}}
\begin{enumerate}
\questionandanswer[]{
请简单说明\mintinline{C}{proc_struct}中各个成员变量含义以及作用。它与理论课中讲述的PCB结构有何不同之处
}{}
{\kaishu
\setlength{\parskip}{2em}
\begin{minted}[]{C}
struct proc_struct {
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
list_entry_t list_link; // Process link list
list_entry_t hash_link; // Process hash list
};
\end{minted}
\mintinline{C}{state}是进程的状态,\mintinline{C}{pid}是进程的ID\mintinline{C}{runs}是进程的被调度到的次数,\mintinline{C}{kstack}是进程的堆栈,\mintinline{C}{need_resched}用于非抢占式调度表示进程是否运行结束可以把CPU重新调度给其他进程\mintinline{C}{parent}是进程的父进程,\mintinline{C}{mm}是进程的内存管理的部分的指针,\mintinline{C}{context}是进程的上下文,即
\begin{minted}{C}
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;
};
\end{minted}
\mintinline{C}{tf}是进程的中断帧,记录了中断的相关信息,\mintinline{C}{cr3}是进程的页目录表的指针,\mintinline{C}{flags}是进程的各种标志位,\mintinline{C}{name}是进程的名称,\mintinline{C}{list_link}是所有进程的链表表项,\mintinline{C}{hash_link}是具有同一个哈希值的进程的链表表项。
它与理论课中讲述的PCB结构的不同之处在于没有优先级、进程组、进程运行时间、进程使用的CPU时间、进程的子进程的CPU时间、文件管理因为还没实现文件系统
}
\questionandanswer[]{
试简单分析 uCore中 内核线程的创建过程。(要求说明处理流程,相关的主要函数、它们的功能以及调用关系。)
}{}
{\kaishu
\setlength{\parskip}{2em}
\begin{center}
\includegraphics[width=1\linewidth]{imgs/2024-05-17-19-18-00.png}
\end{center}
\begin{minted}{C}
// 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;
set_proc_name(idleproc, "idle");
nr_process ++;
current = idleproc;
int pid = kernel_thread(init_main, "Hello world!!", 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);
}
\end{minted}
主要的内核线程创建过程在\mintinline{C}{proc_init}中,首先调用\mintinline{C}{list_init}初始化全部进程的链表和相同哈希的进程列表,然后调用 \mintinline{C}{alloc_proc}给当前进程分配一个进程控制块,并且设置相关属性,其中调用\mintinline{C}{set_proc_name}设置当前进程的名称为\mintinline{C}{"idle"},之后调用\mintinline{C}{kernel_thread}创建一个\mintinline{C}{init_main}进程,并调用\mintinline{C}{find_proc},再调用\mintinline{C}{set_proc_name}将其名称设置为\mintinline{C}{init}
\begin{minted}{C}
// 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);
}
\end{minted}
\mintinline{C}{kernel_thread}的功能是通过调用一个函数创建一个新的线程,由于新的线程是由当前线程创建的,这里就调用了\mintinline{C}{do_fork}函数。
\begin{minted}{C}
/* 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
/*
* 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
* 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
// 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 wakeup_proc to make the new child process RUNNABLE
// 7. set ret vaule using child proc's pid
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);
ret = proc->pid;
fork_out:
return ret;
bad_fork_cleanup_kstack:
put_kstack(proc);
bad_fork_cleanup_proc:
kfree(proc);
goto fork_out;
}
\end{minted}
\mintinline{C}{do_fork}函数中,先调用\mintinline{C}{alloc_proc}分配一个进程控制块,再调用\mintinline{C}{setup_kstack}分配一个内核堆栈,再调用\mintinline{C}{copy_mm}将父进程的内存空间复制或者共享给子进程,再调用\mintinline{C}{copy_thread}设置子进程的中断处理,并设置上下文的变量。之后关中断,将新的进程控制块插入到全部进程链表与相同哈希链表中,再开中断。然后把子进程设置成状态为\mintinline{C}{RUNNABLE},返回子进程的\mintinline{C}{pid}
}
\questionandanswer[]{
试简单分析 uCore中 内核线程的切换过程。(要求说明处理流程,相关的主要函数、它们的功能以及调用关系。)
}{}
{\kaishu
\setlength{\parskip}{2em}
切换线程主要使用的是\mintinline{C}{schedule}函数。
\begin{minted}{C}
void
schedule(void) {
bool intr_flag;
list_entry_t *le, *last;
struct proc_struct *next = NULL;
local_intr_save(intr_flag);
{
current->need_resched = 0;
last = (current == idleproc) ? &proc_list : &(current->list_link);
le = last;
do {
if ((le = list_next(le)) != &proc_list) {
next = le2proc(le, list_link);
if (next->state == PROC_RUNNABLE) {
break;
}
}
} while (le != last);
if (next == NULL || next->state != PROC_RUNNABLE) {
next = idleproc;
}
next->runs ++;
if (next != current) {
proc_run(next);
}
}
local_intr_restore(intr_flag);
}
\end{minted}
\mintinline{C}{schedule}中,我们要找到下一次调度的线程,即找到状态为\mintinline{C}{RUNNABLE}的线程,如果当前的线程为\mintinline{C}{idleproc}即CPU空闲的线程则从线进程链表头开始找因为\mintinline{C}{idleproc}线程不在线程链表中),否则从当前线程的下一个线程开始找。找到需要调度的\mintinline{C}{RUNNABLE}线程后,如果新的线程和老的线程不是同一个线程,则需要调用\mintinline{C}{proc_run}进行切换。
\begin{minted}{C}
// 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);
}
}
\end{minted}
\mintinline{C}{proc_run}中,调用\mintinline{C}{load_esp0}设置任务状态段,调用\mintinline{C}{lcr3}加载新的进程的页表,之后调用\mintinline{C}{switch_to}保存老的进程的上下文,恢复新的进程的上下文。
\begin{minted}{asm}
switch_to: # switch_to(from, to)
# save from's registers
movl 4(%esp), %eax # eax points to from
popl 0(%eax) # save eip !popl
movl %esp, 4(%eax)
movl %ebx, 8(%eax)
movl %ecx, 12(%eax)
movl %edx, 16(%eax)
movl %esi, 20(%eax)
movl %edi, 24(%eax)
movl %ebp, 28(%eax)
# restore to's registers
movl 4(%esp), %eax # not 8(%esp): popped return address already
# eax now points to to
movl 28(%eax), %ebp
movl 24(%eax), %edi
movl 20(%eax), %esi
movl 16(%eax), %edx
movl 12(%eax), %ecx
movl 8(%eax), %ebx
movl 4(%eax), %esp
pushl 0(%eax) # push eip
ret
\end{minted}
\mintinline{C}{switch_to}中,从\mintinline{C}{esp+4}的位置加载\mintinline{C}{eax}指针(即\mintinline{C}{&(prev->context)}),并把老的线程中当前的寄存器中的值都放到这个指针所在的\mintinline{C}{context}结构体中,再从\mintinline{C}{esp+8}的位置加载\mintinline{C}{eax}指针(即\mintinline{C}{&(next->context)}),并从这个指针的位置加载新的线程中的上下文寄存器的值,其中\mintinline{C}{eip}需要用\mintinline{C}{popl}\mintinline{C}{pushl}来设置,不能使用\mintinline{C}{movl}来设置。
}
\end{enumerate}
\end{enumerate}
\end{document}