操作系统实验ucore lab4

练习1:分配并初始化一个进程控制块

任务:
alloc_proc函数(位于kern/process/proc.c中)负责分配并返回一个新的struct proc_struct结构,用于存储新建立的内核线程的管理信息。ucore需要对这个结构进行最基本的初始化,你需要完成这个初始化过程。

static struct proc_struct * alloc_proc(void) {
struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
if (proc != NULL) {
//LAB4:EXERCISE1 YOUR CODE
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, PROC_NAME_LEN);
}
return proc;
}

问题:
请说明proc_struct中struct context context和struct trapframe tf成员变量含义和在本实验中的作用是什么?
struct context context:储存进程当前状态,用于进程切换中上下文的保存与恢复。
struct trapframe
tf:内核态中的线程返回用户态所加载的上下文,中断返回时,新进程会恢复保存的trapframe信息至各个寄存器中,然后开始执行用户代码。

练习2:为新创建的内核线程分配资源

任务:
你需要完成在kern/process/proc.c中的do_fork函数中的处理过程。

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
// 首先分配一个PCB
if ((proc = alloc_proc()) == NULL)
goto fork_out;
// fork肯定存在父进程,所以设置子进程的父进程
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);
// 将子进程的PCB添加进hash list或者list
// 需要注意的是,不能让中断处理程序打断这一步操作
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);
// 返回子进程的pid
ret = proc->pid;

fork_out:
return ret;

bad_fork_cleanup_kstack:
put_kstack(proc);
bad_fork_cleanup_proc:
kfree(proc);
goto fork_out;
}

// 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);
}

// hash_proc - add proc into proc hash_list
static void
hash_proc(struct proc_struct *proc) {
list_add(hash_list + pid_hashfn(proc->pid), &(proc->hash_link));
}

/* *

  • list_add - add a new entry
  • @listelm: list head to add after
  • @elm: new entry to be added
  • Insert the new element @elm after the element @listelm which
  • is already in the list.
  • */
    static inline void
    list_add(list_entry_t *listelm, list_entry_t *elm) {
    list_add_after(listelm, elm);
    }

问题:
请说明ucore是否做到给每个新fork的线程一个唯一的id?请说明你的分析和理由。

是。

(last_pid,nextsafe]这个区间是空闲的。
在函数get_pid中,如果静态成员last_pid小于next_safe,则当前分配的last_pid一定是安全的,即唯一的PID。
但如果last_pid大于等于next_safe,或者last_pid的值超过MAX_PID,则当前的last_pid就不一定是唯一的PID,此时就需要遍历proc_list,重新对last_pid和next_safe进行设置,为下一次的get_pid调用打下基础。
// 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;
}
练习3:阅读代码,理解 proc_run 函数和它调用的函数如何完成进程切换的。

proc_run函数会设置内核栈地址,同时还会加载页目录表的地址。等到这些前置操作完成后,最后执行上下文切换。

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);//开锁
}
}

/* *

  • load_esp0 - change the ESP0 in default task state segment,
  • so that we can use different kernel stack when we trap frame
  • user to kernel.
  • */
    void
    load_esp0(uintptr_t esp0) {
    ts.ts_esp0 = esp0;
    }

static inline void
lcr3(uintptr_t cr3) {
asm volatile (“mov %0, %%cr3” :: “r” (cr3) : “memory”);
}

.text
.globl switch_to
switch_to: # switch_to(from, to)
# save from’s registers
movl 4(%esp), %eax # 获取当前进程的context结构地址
popl 0(%eax) # 将eip保存至当前进程的context结构
movl %esp, 4(%eax) # 将esp保存至当前进程的context结构
movl %ebx, 8(%eax) # 将ebx保存至当前进程的context结构
movl %ecx, 12(%eax) # 将ecx保存至当前进程的context结构
movl %edx, 16(%eax) # 将edx保存至当前进程的context结构
movl %esi, 20(%eax) # 将esi保存至当前进程的context结构
movl %edi, 24(%eax) # 将edi保存至当前进程的context结构
movl %ebp, 28(%eax) # 将ebp保存至当前进程的context结构

# restore to's registers
movl 4(%esp), %eax          # 获取下一个进程的context结构地址
                            # 需要注意的是,其地址不是8(%esp),因为之前已经pop过一次栈。
movl 28(%eax), %ebp         # 恢复ebp至下一个进程的context结构
movl 24(%eax), %edi         # 恢复edi至下一个进程的context结构
movl 20(%eax), %esi         # 恢复esi至下一个进程的context结构
movl 16(%eax), %edx         # 恢复edx至下一个进程的context结构
movl 12(%eax), %ecx         # 恢复ecx至下一个进程的context结构
movl 8(%eax), %ebx          # 恢复ebx至下一个进程的context结构
movl 4(%eax), %esp          # 恢复esp至下一个进程的context结构
pushl 0(%eax)               # 插入下一个进程的eip,以便于ret到下个进程的代码位置。
ret

问题:

1)在本实验的执行过程中,创建且运行了几个内核线程?
两个内核线程,分别是idleproc和initproc。

2)语句local_intr_save(intr_flag);…local_intr_restore(intr_flag);在这里有何作用?
这两句代码的作用分别是阻塞中断和解除中断的阻塞。

理由:
#define local_intr_save(x) do { x = __intr_save(); } while (0)
static inline bool
__intr_save(void) {
if (read_eflags() & FL_IF) {
intr_disable();
return 1;
}
return 0;
}
#define FL_IF 0x00000200 // Interrupt Flag
static inline uint32_t
read_eflags(void) {
uint32_t eflags;
asm volatile (“pushfl; popl %0” : “=r” (eflags));
return eflags;
}
/* intr_disable - disable irq interrupt /
void
intr_disable(void) {
cli();
}
static inline void
cli(void) {
asm volatile (“cli” ::: “memory”);
}
CLI禁止中断发生
#define local_intr_restore(x) __intr_restore(x);
static inline void
__intr_restore(bool flag) {
if (flag) {
intr_enable();
}
}
/
intr_enable - enable irq interrupt */
void
intr_enable(void) {
sti();
}
static inline void
sti(void) {
asm volatile (“sti”);
}
STI允许中断发生

你可能感兴趣的:(网络,单片机,stm32)