BUAA OS Lab4 实验报告

BUAA OS Lab4

  • 系统调用(System Call)
    • Msyscall
    • syscall.S
    • syscall_all
  • 进程间通信机制(IPC)
  • Fork
    • fork概述
    • 写时复制机制
    • fork函数的返回值
    • syscall_all
    • 父子进程的旅途
    • 缺页中断

系统调用(System Call)

  用户进程在特定的场景下是需要进行一些只能在内核中执行的操作(比如硬件的操作)。当用户进程以特定的方式陷入异常后,能够由内核调用对应的函数,我们把这些函数称为系统调用。系统调用实际上是操作系统和用户空间的一组接口。用户空间的程序通过系统调用来访问操作系统的一些服务,谋求操作系统提供必要的帮助。
  在本次实验中,函数的调用过程是这样的:
BUAA OS Lab4 实验报告_第1张图片
  再看一遍C语言中的puts函数的调用过程:

  1. 调用puts 函数
  2. 在一系列的函数调用后,最终调用了write 函数。
  3. write 函数为寄存器设置了相应的值,并执行了syscall 指令。
  4. 进入内核态,内核中相应的函数或服务被执行。
  5. 回到用户态的write 函数中,将系统调用的结果从相关的寄存器中取回,并返回。
  6. 再次经过一系列的返回过程后,回到了puts 函数中。
  7. puts 函数返回。

Msyscall

  先看一下msyscall的定义:
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)

syscall.S

  这段代码有些难以理解。重点只有一点:这里所有的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

  syscall_all中存储了许多基础的系统调用。

  • sys_mem_alloc
      这个函数的功能是为进程分配内存。用户程序可以通过这个系统调用给该程序的虚拟内存空间va处显式地分配实际的物理内存。
    需要注意以下几点:
  1. 如果va处已经有物理页,则将旧的物理页顶掉(page_insert的功能)
  2. 写时复制关闭(PTE_COW);检测有效位(PTE_V)
  3. va必须小于UTOP,大于等于0
  4. 进程只可以修改自己的、或者是子进程的地址
    分配内存的过程是:
      用envid2env找到需要被分配物理页的进程;
      alloc一个物理页;
      将物理页插入进程的二级页表中;
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;
}
  • sys_mem_map
      这个函数的作用是将源进程(srcid)地址空间中的相应内存(srcva)映射到目标进程(dstid)的相应地址空间的相应虚拟内存(dstva)中。换句话说,就是让两个进程共享一页物理内存。
    需要注意以下几点:
  1. 只能获取UTOP之下的物理页
  2. if (perm & PTE_COW) return -E_INVAL;这句话能不能加??
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

你可能感兴趣的:(操作系统)