用户进程在特定的场景下是需要进行一些只能在内核中执行的操作(比如硬件的操作)。当用户进程以特定的方式陷入异常后,能够由内核调用对应的函数,我们把这些函数称为系统调用。系统调用实际上是操作系统和用户空间的一组接口。用户空间的程序通过系统调用来访问操作系统的一些服务,谋求操作系统提供必要的帮助。
在本次实验中,函数的调用过程是这样的:
再看一遍C语言中的puts函数的调用过程:
先看一下msyscall的定义:
msyscall 函数一共有6 个参数。前4 个参数会被syscall 开头的函数分别存入a0-a3 寄存器同时栈帧底部保留16 字节的空间(不要求存入参数的值),后2 个参数只会被存入在前4 的参数的预留空间之上的8 字节空间内(没有寄存器传参)。由于指导书说明后两个参数已经被“转移”,我们则不用写出这个过程。
LEAF(msyscall)
// TODO: execute a `syscall` instruction and return from msyscall
// 将 sysno(系统调用号)存储到v0寄存器中
move v0,a0
syscall
jr ra
nop
END(msyscall)
这段代码有些难以理解。重点只有一点:这里所有的a0、t0、c0寄存器都是内核的,TF_REG系列才是用户的。sp是内核态指针,指向TF的开头。TF_REG29(sp)指向用户态栈指针。所以,内核态不能直接用a0访问用户态保存在寄存器中的参数。
NESTED(handle_sys,TF_SIZE, sp)
SAVE_ALL // 保存寄存器的值到当前内核栈的结构体中
CLI // 关闭中断
nop
.set at // 恢复 at寄存器的使用
// TODO: Fetch EPC from Trapframe, calculate a proper value and store it back to trapframe.
// 返回现场时跳到的地址
lw t0, TF_EPC(sp)
addiu t0, 4
sw t0, TF_EPC(sp)
// TODO: Copy the syscall number into $a0.
// 将 syscall编号(sysno)保存到 a0中
// 在 msyscall中,已将 sysno保存到 v0中
lw a0,TF_REG2(sp)
// 寻找特定系统调用函数入口地址,并保存到 t2中
addiu a0, a0, -__SYSCALL_BASE // a0 <- relative syscall number
sll t0, a0, 2 // t0 <- relative syscall number times 4
la t1, sys_call_table // t1 <- syscall table base
addu t1, t1, t0 // t1 <- table entry of specific syscall
lw t2, 0(t1) // t2 <- function entry of specific syscall
// 找到用户栈中存储的第 5、6个参数
lw t0, TF_REG29(sp) // t0 <- user's stack pointer
lw t3, 16(t0) // t3 <- the 5th argument of msyscall
lw t4, 20(t0) // t4 <- the 6th argument of msyscall
// TODO: Allocate a space of six arguments on current kernel stack and copy the six arguments to proper location
// 寻找保存在用户态寄存器 a0-a3中的第1-4个参数
// 保存参数到内核栈中
lw t5, TF_REG4(sp)
lw t6, TF_REG5(sp)
lw t7, TF_REG6(sp)
lw t8, TF_REG7(sp)
addiu sp,sp,-20
sw t5, (sp)
sw t6, 4(sp)
sw t7, 8(sp)
sw t8, 12(sp)
sw t3,16(sp)
sw t4,20(sp)
// 调用sys_x函数
jalr t2 // Invoke sys_* function
nop
// TODO: Resume current kernel stack
// 恢复栈指针到分配之前的状态
addu sp, 20
// 将sys_x函数的返回值存入Trapframe
sw v0, TF_REG2(sp) // Store return value of function sys_* (in $v0) into trapframe
// 恢复现场,从异常中返回
j ret_from_exception // Return from exeception
nop
END(handle_sys)
syscall_all中存储了许多基础的系统调用。
int sys_mem_alloc(int sysno, u_int envid, u_int va, u_int perm)
{
struct Env *env;
struct Page *ppage;
int ret;
ret = 0;
if (va >= UTOP || va < 0) return -E_UNSPECIFIED;
if ((perm & PTE_COW) || !(perm & PTE_V)) return -E_INVAL;
if ((ret = envid2env(envid,&env,1))!=0) return ret;
if ((ret = page_alloc(&ppage))!=0) return ret;
if ((ret = page_insert(env->env_pgdir,ppage,va,perm))!=0) return ret;
return ret;
}
int sys_mem_map(int sysno, u_int srcid, u_int srcva, u_int dstid, u_int dstva,u_int perm)
{
int ret;
u_int round_srcva, round_dstva;
struct Env *srcenv;
struct Env *dstenv;
struct Page *ppage;
Pte *ppte;
ppage = NULL;
ret = 0;
round_srcva = ROUNDDOWN(srcva, BY2PG);
round_dstva = ROUNDDOWN(dstva