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

295 lines
13 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\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}