使用meld
可以简单地将前几个lab的代码填入lab5中,但是要注意在这次实验中,部分代码需要做出修改,如下,主要是idt_init
、trap_dispatch
、alloc_proc
、do_fork
这四个函数
kern/trap/trap.c
中lab1的部分代码/* LAB5 YOUR CODE */
//you should update your lab1 code (just add ONE or TWO lines of code), let user app to use syscall to get the service of ucore
//so you should setup the syscall interrupt gate in here
extern uintptr_t __vectors[];
for(int i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i++){
SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
}
SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER); //设置系统调用的中断门,特权级为用户级,则允许用户进程通过系统调用来完成不同的系统调用服务
lidt(&idt_pd);
...
/* LAB5 YOUR CODE */
/* you should upate you lab1 code (just add ONE or TWO lines of code):
* Every TICK_NUM cycle, you should set current process's current->need_resched = 1
*/
ticks++;
if(ticks % TICK_NUM == 0){
assert(current != NULL); //注意,LAB1中这里还有一行print_ticks(),必须去掉,否在在make grade中会由于spin和waitkill函数耗时过长导致这里print_ticks()输出的信息被当作错误处理
current->need_resched = 1; //每当时钟走过TICK_NUM次,就将当前进程切换掉(设置被需要被调度),意为时间片已用完
}
kern/process/proc.c
中lab4的部分代码//LAB5 YOUR CODE : (update LAB4 steps)
/*
* below fields(add in LAB5) in proc_struct need to be initialized
* uint32_t wait_state; // waiting state
* struct proc_struct *cptr, *yptr, *optr; // relations between processes
*/
proc->wait_state = 0; //初始化等待状态为0
proc->cptr = proc->yptr = proc->optr = NULL; //相关进程设置为NULL,cptr为子线程children,yptr和optr为兄弟线程younger/older
...
//LAB5 YOUR CODE : (update LAB4 steps)
/* Some Functions
* set_links: set the relation links of process. ALSO SEE: remove_links: lean the relation links of process
* -------------------
* update step 1: set child proc's parent to current process, make sure current process's wait_state is 0
* update step 5: insert proc_struct into hash_list && proc_list, set the relation links of process
*/
proc->parent = current; //设置进程为当前进程的子进程
assert(current->wait_state == 0); //确保当前进程在等待
bool intr_flag; //锁的作用参考lab4实验报告
local_intr_save(intr_flag);
{
proc->pid = get_pid();
hash_proc(proc);
set_links(proc); //单独的计数不满足调度的要求,使用set_links来设置进程关系(父子/兄弟)并加入到链表中
}
local_intr_restore(intr_flag);
由于文件系统尚未实现,在本实验中make
的最后一步通过一个ld
命令将应用程序的执行码在生成kernel时直接链接到kernel的末端,并且将程序的位置和大小记录在两个全局变量中,供内核后续通过全局变量加载并执行应用程序
tools/user.ld
中记录了用户虚拟空间的执行入口虚拟地址,tools/kernel.ld
中记录了内核虚拟空间的起始入口虚拟地址,kern\mm\memlayout.h
中描述了虚拟地址空间的分配
SECTIONS {
/* Load programs at this address: "." means the current address */
. = 0x800020;
SECTIONS {
/* Load the kernel at this address: "." means the current address */
. = 0xC0100000;
/* *
* Virtual memory map: Permissions
* kernel/user
*
* 4G ------------------> +---------------------------------+
* | |
* | Empty Memory (*) |
* | |
* +---------------------------------+ 0xFB000000
* | Cur. Page Table (Kern, RW) | RW/-- PTSIZE
* VPT -----------------> +---------------------------------+ 0xFAC00000
* | Invalid Memory (*) | --/--
* KERNTOP -------------> +---------------------------------+ 0xF8000000
* | |
* | Remapped Physical Memory | RW/-- KMEMSIZE
* | |
* KERNBASE ------------> +---------------------------------+ 0xC0000000
* | Invalid Memory (*) | --/--
* USERTOP -------------> +---------------------------------+ 0xB0000000
* | User stack |
* +---------------------------------+
* | |
* : :
* | ~~~~~~~~~~~~~~~~ |
* : :
* | |
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* | User Program & Heap |
* UTEXT ---------------> +---------------------------------+ 0x00800000
* | Invalid Memory (*) | --/--
* | - - - - - - - - - - - - - - - |
* | User STAB Data (optional) |
* USERBASE, USTAB------> +---------------------------------+ 0x00200000
* | Invalid Memory (*) | --/--
* 0 -------------------> +---------------------------------+ 0x00000000
* (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
* "Empty Memory" is normally unmapped, but user programs may map pages
* there if desired.
*
* */
在实验四练习3中详细介绍了线程的创建、切换和执行过程,这里第一个用户进程是由第二个内核线程initproc
通过把应用程序执行码覆盖到initproc
的用户虚拟内存空间来创建的,前面过程参考
http://blog.csdn.net/jasonyuchen/article/details/76622612
init_main
调用kernel_thread
加载user_main
,利用宏及在链接时确定的程序位置和大小的全局变量,进而调用kernel_execve
来调用SYS_exec
系统调用,让内核来创建此用户进程,调用顺序为
...->init_main(proc.c)->...->user_main(proc.c)->kernel_execve(proc.c)
->vector128(vectors.S)->__alltraps(trapentry.S)->trap(trap.c)->trap_dispatch(trap.c)
->syscall(syscall.c)->sys_exec(syscall.c)->do_execve(proc.c)
// kernel_execve - do SYS_exec syscall to exec a user program called by user_main kernel_thread
static int
kernel_execve(const char *name, unsigned char *binary, size_t size) {
int ret, len = strlen(name);
asm volatile (
"int %1;"
: "=a" (ret)
: "i" (T_SYSCALL), "0" (SYS_exec), "d" (name), "c" (len), "b" (binary), "D" (size)
: "memory");
return ret;
}
#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); \
})
#define __KERNEL_EXECVE2(x, xstart, xsize) ({ \
extern unsigned char xstart[], xsize[]; \
__KERNEL_EXECVE(#x, xstart, (size_t)xsize); \
})
#define KERNEL_EXECVE2(x, xstart, xsize) __KERNEL_EXECVE2(x, xstart, xsize)
// user_main - kernel thread used to exec a user program
static int
user_main(void *arg) {
#ifdef TEST
KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE);
#else
KERNEL_EXECVE(hello);
#endif
panic("user_main execve failed.\n");
}
do_execve
过程
if (mm != NULL) {
lcr3(boot_cr3); //将页表基址指向内核页表boot_cr3
if (mm_count_dec(mm) == 0) {//假如引用次数归零
exit_mmap(mm); //取消映射
put_pgdir(mm); //释放PDT占用的空间
mm_destroy(mm); //释放mm占用的空间
}
current->mm = NULL; //释放指向mm的指针
}
load_icode
填入新的内容if ((ret = load_icode(binary, size)) != 0) {
goto execve_exit;
}
load_icode
过程
mm_create
函数来申请进程的内存管理数据结构并初始化,调用setup_pgdir
函数来申请一个页目录表所需的一个页并让mm->pgdir
指向此页目录表mm_map
根据ELF格式的执行程序说明的各个段的起始位置和大小建立相应的vma结构,完成合法空间的建立mm_mmap
建立用户栈的vma结构,并分配一定数量的物理内存建立栈的物理地址和虚拟地址的映射关系mm->pgdir
赋值给cr3寄存器iret
后转到用户态特权级并回到用户态内存空间,且能够跳转到用户进程的第一跳指令执行,并确保能够响应中断最后一条是本次练习的实现部分,load_icode
完整程序如下
/* load_icode - load the content of binary program(ELF format) as the new content of current process
* @binary: the memory addr of the content of binary program
* @size: the size of the content of binary program
*/
static int
load_icode(unsigned char *binary, size_t size) {
if (current->mm != NULL) {
panic("load_icode: current->mm must be empty.\n");
}
int ret = -E_NO_MEM;
struct mm_struct *mm;
//(1) create a new mm for current process
if ((mm = mm_create()) == NULL) {
goto bad_mm;
}
//(2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT
if (setup_pgdir(mm) != 0) {
goto bad_pgdir_cleanup_mm;
}
//(3) copy TEXT/DATA section, build BSS parts in binary to memory space of process
struct Page *page;
//(3.1) get the file header of the bianry program (ELF format)
struct elfhdr *elf = (struct elfhdr *)binary;
//(3.2) get the entry of the program section headers of the bianry program (ELF format)
struct proghdr *ph = (struct proghdr *)(binary + elf->e_phoff);
//(3.3) This program is valid?
if (elf->e_magic != ELF_MAGIC) {
ret = -E_INVAL_ELF;
goto bad_elf_cleanup_pgdir;
}
uint32_t vm_flags, perm;
struct proghdr *ph_end = ph + elf->e_phnum;
for (; ph < ph_end; ph ++) {
//(3.4) find every program section headers
if (ph->p_type != ELF_PT_LOAD) {
continue ;
}
if (ph->p_filesz > ph->p_memsz) {
ret = -E_INVAL_ELF;
goto bad_cleanup_mmap;
}
if (ph->p_filesz == 0) {
continue ;
}
//(3.5) call mm_map fun to setup the new vma ( ph->p_va, ph->p_memsz)
vm_flags = 0, perm = PTE_U;
if (ph->p_flags & ELF_PF_X) vm_flags |= VM_EXEC;
if (ph->p_flags & ELF_PF_W) vm_flags |= VM_WRITE;
if (ph->p_flags & ELF_PF_R) vm_flags |= VM_READ;
if (vm_flags & VM_WRITE) perm |= PTE_W;
if ((ret = mm_map(mm, ph->p_va, ph->p_memsz, vm_flags, NULL)) != 0) {
goto bad_cleanup_mmap;
}
unsigned char *from = binary + ph->p_offset;
size_t off, size;
uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE);
ret = -E_NO_MEM;
//(3.6) alloc memory, and copy the contents of every program section (from, from+end) to process's memory (la, la+end)
end = ph->p_va + ph->p_filesz;
//(3.6.1) copy TEXT/DATA section of bianry program
while (start < end) {
if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
goto bad_cleanup_mmap;
}
off = start - la, size = PGSIZE - off, la += PGSIZE;
if (end < la) {
size -= la - end;
}
memcpy(page2kva(page) + off, from, size);
start += size, from += size;
}
//(3.6.2) build BSS section of binary program
end = ph->p_va + ph->p_memsz;
if (start < la) {
/* ph->p_memsz == ph->p_filesz */
if (start == end) {
continue ;
}
off = start + PGSIZE - la, size = PGSIZE - off;
if (end < la) {
size -= la - end;
}
memset(page2kva(page) + off, 0, size);
start += size;
assert((end < la && start == end) || (end >= la && start == la));
}
while (start < end) {
if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
goto bad_cleanup_mmap;
}
off = start - la, size = PGSIZE - off, la += PGSIZE;
if (end < la) {
size -= la - end;
}
memset(page2kva(page) + off, 0, size);
start += size;
}
}
//(4) build user stack memory
vm_flags = VM_READ | VM_WRITE | VM_STACK;
if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) {
goto bad_cleanup_mmap;
}
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL);
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-2*PGSIZE , PTE_USER) != NULL);
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-3*PGSIZE , PTE_USER) != NULL);
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-4*PGSIZE , PTE_USER) != NULL);
//(5) set current process's mm, sr3, and set CR3 reg = physical addr of Page Directory
mm_count_inc(mm);
current->mm = mm;
current->cr3 = PADDR(mm->pgdir);
lcr3(PADDR(mm->pgdir));
//(6) setup trapframe for user environment
struct trapframe *tf = current->tf;
memset(tf, 0, sizeof(struct trapframe));
/* LAB5:EXERCISE1 YOUR CODE
* should set tf_cs,tf_ds,tf_es,tf_ss,tf_esp,tf_eip,tf_eflags
* NOTICE: If we set trapframe correctly, then the user level process can return to USER MODE from kernel. So
* tf_cs should be USER_CS segment (see memlayout.h)
* tf_ds=tf_es=tf_ss should be USER_DS segment
* tf_esp should be the top addr of user stack (USTACKTOP)
* tf_eip should be the entry point of this binary program (elf->e_entry)
* tf_eflags should be set to enable computer to produce Interrupt
*/
tf->tf_cs = USER_CS; //用户代码段为USER_CS(memlayout.h)
tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS; //用户数据段为USER_DS(memlayout.h)
tf->tf_esp = USTACKTOP; //用户栈栈顶USTACKTOP=0xB0000000见2中的虚拟内存空间结构
tf->tf_eip = elf->e_entry;
tf->tf_eflags = FL_IF; //FL_IF为中断允许标记(mmu.h)
ret = 0;
out:
return ret;
bad_cleanup_mmap:
exit_mmap(mm);
bad_elf_cleanup_pgdir:
put_pgdir(mm);
bad_pgdir_cleanup_mm:
mm_destroy(mm);
bad_mm:
goto out;
}
用户进程的环境搭建完毕后,initproc
按产生系统调用的函数调用路径原理返回到trapentry.S
中的call trap
下继续执行__trapret
到iret
时将切换到用户进程的第一条语句的位置_start
继续执行
创建子进程的do_fork
函数在执行中将拷贝当前进程的用户内存地址空间中的合法内容到新进程,即lab4中被搁置的copy_mm
函数完成的功能,调用关系如下,copy_range
函数的功能是本练习需要实现的
do_fork(proc.c)->copy_mm(proc.c)->dup_mmap(vmm.c)->copy_range(pmm.c)
copy_range
函数完成的功能很简单,就是将父进程的一段内存内容复制到子进程中,实现如下
/* copy_range - copy content of memory (start, end) of one process A to another process B
* @to: the addr of process B's Page Directory
* @from: the addr of process A's Page Directory
* @share: flags to indicate to dup OR share. We just use dup method, so it didn't be used.
*
* CALL GRAPH: copy_mm-->dup_mmap-->copy_range
*/
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;
/* LAB5:EXERCISE2 YOUR CODE
* replicate content of page to npage, build the map of phy addr of nage with the linear addr start
*
* Some Useful MACROs and DEFINEs, you can use them in below implementation.
* MACROs or Functions:
* page2kva(struct Page *page): return the kernel vritual addr of memory which page managed (SEE pmm.h)
* page_insert: build the map of phy addr of an Page with the linear addr la
* memcpy: typical memory copy function
*
* (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
*/
void * src_kvaddr = page2kva(page);
void * dst_kvaddr = page2kva(npage);
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;
}
在练习1的分析过程中已经可以初步看出利用系统调用实现用户进程的创建执行过程,下面简单分析(参考kern/syscall/syscall.c
)
fork使用了系统调用SYS_fork
,由do_fork
来完成,fork的过程在lab4中有非常详细的介绍,参考实验报告
http://blog.csdn.net/jasonyuchen/article/details/76622612
exec使用了系统调用SYS_exec
,由do_execve
来完成,exec的过程在练习1中有非常详细的介绍,参考上文练习1的部分
wait使用了系统调用SYS_wait
,由do_wait
来完成,do_wait
源码如下,主要完成了等待指定的一个或任意一个处于僵死状态的子进程,并且释放掉其占用的空间
// do_wait - wait one OR any children with PROC_ZOMBIE state, and free memory space of kernel stack
// - proc struct of this child.
// NOTE: only after do_wait function, all resources of the child proces are free.
int
do_wait(int pid, int *code_store) {
struct mm_struct *mm = current->mm;
if (code_store != NULL) {
if (!user_mem_check(mm, (uintptr_t)code_store, sizeof(int), 1)) {
return -E_INVAL;
}
}
struct proc_struct *proc;
bool intr_flag, haskid;
repeat:
haskid = 0;
if (pid != 0) { //如果pid不为0,则寻找指定pid的僵死子进程
proc = find_proc(pid);
if (proc != NULL && proc->parent == current) {
haskid = 1;
if (proc->state == PROC_ZOMBIE) {
goto found;
}
}
}
else { //如果pid为0,则寻找任意一个僵死子进程
proc = current->cptr;
for (; proc != NULL; proc = proc->optr) {
haskid = 1;
if (proc->state == PROC_ZOMBIE) {
goto found;
}
}
}
if (haskid) { //如果找到的子进程不是僵死状态,说明还未退出,设置当前进程状态为睡眠状态,等待状态为等待子进程退出状态,然后调度新进程
current->state = PROC_SLEEPING; //睡眠状态
current->wait_state = WT_CHILD; //等待子进程退出状态
schedule(); //调度新进程运行
if (current->flags & PF_EXITING) { //如果正在关闭,则退出进程(PF_EXITING定义在proc.h中表示getting shutdown)
do_exit(-E_KILLED);
}
goto repeat;
}
return -E_BAD_PROC;
found: //找到僵死子进程,开始回收资源
if (proc == idleproc || proc == initproc) {
panic("wait idleproc or initproc.\n");
}
if (code_store != NULL) {
*code_store = proc->exit_code;
}
local_intr_save(intr_flag); //注意加锁使得线程安全
{
unhash_proc(proc);
remove_links(proc);
}
local_intr_restore(intr_flag);
put_kstack(proc);
kfree(proc);
return 0;
}
exit使用了系统调用SYS_exit
,由do_exit
来完成,do_exit
源码如下,主要完成了退出当前进程并回收资源,然后唤醒其父进程的工作
// do_exit - called by sys_exit
// 1. call exit_mmap & put_pgdir & mm_destroy to free the almost all memory space of process
// 2. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself.
// 3. call scheduler to switch to other process
int
do_exit(int error_code) {
if (current == idleproc) {
panic("idleproc exit.\n");
}
if (current == initproc) {
panic("initproc exit.\n");
}
struct mm_struct *mm = current->mm;
if (mm != NULL) { //回收用户虚拟内存空间
lcr3(boot_cr3);
if (mm_count_dec(mm) == 0) {
exit_mmap(mm);
put_pgdir(mm);
mm_destroy(mm);
}
current->mm = NULL;
}
current->state = PROC_ZOMBIE; //设置进程状态为僵死状态
current->exit_code = error_code; //设置进程退出码为`error_code`
bool intr_flag; //使用锁使得线程安全
struct proc_struct *proc;
local_intr_save(intr_flag);
{
proc = current->parent;
if (proc->wait_state == WT_CHILD) { //若当前进程的父进程正在等待当前进程结束,则唤醒父进程
wakeup_proc(proc);
}
while (current->cptr != NULL) { //若当前进程还有子进程,则将这些进程的父进程设置为内核线程
proc = current->cptr;
current->cptr = proc->optr;
proc->yptr = NULL;
if ((proc->optr = initproc->cptr) != NULL) {
initproc->cptr->yptr = proc;
}
proc->parent = initproc;
initproc->cptr = proc;
if (proc->state == PROC_ZOMBIE) { //如果子进程是僵死进程(此时它的父进程已经被改为内核线程)
if (initproc->wait_state == WT_CHILD) { //如果内核线程等待子进程结束,则唤醒内核线程
wakeup_proc(initproc);
}
}
}
}
local_intr_restore(intr_flag);
schedule(); //调度执行新进程
panic("do_exit will not return!! %d.\n", current->pid);
}
这个工作在lab1中就已涉及,在本实验中对代码进行了修改,参考练习0
在用户态中建立中间层对系统调用进行封装,实现在user/libs/ulib.[ch]
和user/libs/syscall.[ch]
中
static inline int
syscall(int num, ...) {
va_list ap;
va_start(ap, num);
uint32_t a[MAX_ARGS];
int i, ret;
for (i = 0; i < MAX_ARGS; i ++) { //最多6个寄存器来传递系统调用,MAX_ARGS=5,5个参数1个系统调用号
a[i] = va_arg(ap, uint32_t);
}
va_end(ap);
asm volatile (
"int %1;"
: "=a" (ret) //返回值放在EAX
: "i" (T_SYSCALL), //系统调用
"a" (num), //系统调用号放在EAX
"d" (a[0]), //参数a[0]放在EDX
"c" (a[1]), //参数a[1]放在ECX
"b" (a[2]), //参数a[2]放在EBX
"D" (a[3]), //参数a[3]放在EDI
"S" (a[4]) //参数a[4]放在ESI
: "cc", "memory");
return ret;
}
SYS_exit : process exit, -->do_exit
SYS_fork : create child process, dup mm -->do_fork-->wakeup_proc
SYS_wait : wait process -->do_wait
SYS_exec : after fork, process execute a program -->load a program and refresh the mm
SYS_clone : create child thread -->do_fork-->wakeup_proc
SYS_yield : process flag itself need resecheduling -->proc->need_sched=1, then scheduler will rescheule this process
SYS_sleep : process sleep -->do_sleep
SYS_kill : kill process -->do_kill-->proc->flags |= PF_EXITING-->wakeup_proc-->do_wait-->do_exit
SYS_getpid : get the process's pid
与函数调用的区别主要有四点:
1、不是通过
CALL
指令而是通过INT
指令调用
2、不是通过RET
指令而是通过IRET
指令返回
3、到达内核后严格检查传递的参数,确保安全
4、执行系统调用可导致进程等待某事件发生,从而引起进程切换
以SYS_getpid
系统调用为例,当用户系统调用时,执行到INT T_SYSCALL
指令,根据操作系统建立的中断描述符转入内核态,并跳转到vector128
处,开始系统调用执行过程
vector128(vectors.S)->__alltraps(trapentry.S)->trap(trap.c)->trap_dispatch(trap.c)
->syscall(syscall.c)->sys_getpid(syscall.c)->...->__trapret(trapentry.S)
执行trap
前软件进一步保存执行系统调用的执行现场(见kern/trap/trapentry.S
),将相关内容保存到当前进程的中断帧中
随后操作系统开始完成具体的系统调用,在SYS_getpid
中将pid值返回就是具体的系统调用服务,按路径原路返回到__alltraps
中,然后继续执行恢复现场操作,直到IRET
将EIP指向tf_eip
即INT_SYSCALL
后的指令,继续执行,系统调用结束
__alltraps:
# push registers to build a trap frame
# therefore make the stack look like a struct trapframe
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushal
# load GD_KDATA into %ds and %es to set up data segments for kernel
movl $GD_KDATA, %eax
movw %ax, %ds
movw %ax, %es
# push %esp to pass a pointer to the trapframe as an argument to trap()
pushl %esp
# call trap(tf), where tf=%esp
call trap
#从系统调用中返回到这里,下面的过程可以参考lab4实验报告中练习3的第7步
# pop the pushed stack pointer
popl %esp
# return falls through to trapret...
.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
全部编码完成后,运行make grade
可以获得输出如下,可以看出实验成功
注意若在
spin
和waitkill
出现错误,可以尝试将kern/trap/trap.c
中的trap_dispatch
里lab1完成的代码部分注释掉print_ticks()
make --quiet --no-print-directory clean
sh tools/grade.sh
badsegment: (3.4s)
-check result: OK
-check output: OK
divzero: (1.7s)
-check result: OK
-check output: OK
softint: (1.7s)
-check result: OK
-check output: OK
faultread: (1.7s)
-check result: OK
-check output: OK
faultreadkernel: (1.7s)
-check result: OK
-check output: OK
hello: (1.7s)
-check result: OK
-check output: OK
testbss: (1.7s)
-check result: OK
-check output: OK
pgdir: (1.7s)
-check result: OK
-check output: OK
yield: (1.7s)
-check result: OK
-check output: OK
badarg: (1.5s)
-check result: OK
-check output: OK
exit: (1.6s)
-check result: OK
-check output: OK
spin: (4.7s)
-check result: OK
-check output: OK
waitkill: (13.7s)
-check result: OK
-check output: OK
forktest: (1.7s)
-check result: OK
-check output: OK
forktree: (1.7s)
-check result: OK
-check output: OK
Total Score: 150/150