xv6:labs2 syscall

Jyang~發表於2023-11-29

lab2

1、lab2的內容總結:關於系統呼叫整個跟蹤過程:

使用系統呼叫時,使用者態會透過軟中斷(trap,陷阱)進入核心中,由trap識別中斷來自系統呼叫,然後呼叫syscall函式,

跟蹤過程:

1、開啟gdb:

2、跟蹤使用者態trace執行過程:

首先執行以下兩條指令,為trace的main函式打上斷點

file user/_trace

b main

三次c之後成功進入trace的main函式

接下來單步執行到exec語句,這裡有點問題,s跳入函式時只能跳入atoi,而trace沒有反應。所以trace(atoi(argv[1]))中的trace作用是什麼?

s跳入exec函式,會進入usys.S檔案:

si單步執行,然後列印出a7的值,其實就是exec的系統呼叫索引(該索引在syscall.h和syscall.c中規定),所以系統呼叫號被儲存在了a7中,後面進入核心態syscall.c會讀出a7的內容並呼叫系統呼叫函式:

再次執行si,ecall之後發現是進不去的,根據文件描述,ecall之後會進入核心態,首先跳轉到trampoline.S檔案的usevec函式:

.globl uservec
uservec:    
	#
        # trap.c sets stvec to point here, so
        # traps from user space start here,
        # in supervisor mode, but with a
        # user page table.
        #
        # sscratch points to where the process's p->trapframe is
        # mapped into user space, at TRAPFRAME.
        #
        
	# swap a0 and sscratch
        # so that a0 is TRAPFRAME
        csrrw a0, sscratch, a0

        # save the user registers in TRAPFRAME
        sd ra, 40(a0)
        sd sp, 48(a0)
        sd gp, 56(a0)
        sd tp, 64(a0)
        sd t0, 72(a0)
        sd t1, 80(a0)
        sd t2, 88(a0)
        sd s0, 96(a0)
        sd s1, 104(a0)
        sd a1, 120(a0)
        sd a2, 128(a0)
        sd a3, 136(a0)
        sd a4, 144(a0)
        sd a5, 152(a0)
        sd a6, 160(a0)
        sd a7, 168(a0)
        sd s2, 176(a0)
        sd s3, 184(a0)
        sd s4, 192(a0)
        sd s5, 200(a0)
        sd s6, 208(a0)
        sd s7, 216(a0)
        sd s8, 224(a0)
        sd s9, 232(a0)
        sd s10, 240(a0)
        sd s11, 248(a0)
        sd t3, 256(a0)
        sd t4, 264(a0)
        sd t5, 272(a0)
        sd t6, 280(a0)

	# save the user a0 in p->trapframe->a0
        csrr t0, sscratch
        sd t0, 112(a0)

        # restore kernel stack pointer from p->trapframe->kernel_sp
        ld sp, 8(a0)

        # make tp hold the current hartid, from p->trapframe->kernel_hartid
        ld tp, 32(a0)

        # load the address of usertrap(), p->trapframe->kernel_trap
        ld t0, 16(a0)

        # restore kernel page table from p->trapframe->kernel_satp
        ld t1, 0(a0)
        csrw satp, t1
        sfence.vma zero, zero

        # a0 is no longer valid, since the kernel page
        # table does not specially map p->tf.
        
        # jump to usertrap(), which does not return
        jr t0

userevec函式儲存了使用者暫存器,執行一些列操作(載入核心棧頂指標巴拉巴拉,不是很懂)。然後jump到usertrap()函式中,透過trap進入核心態。因為ecall之後不知道為什麼進不去核心,所以接下來跟蹤核心態重開gdb。

3、跟蹤核心態sys_trace:

進入核心態後首先會進入usertrap函式,所以先在usertrap函式達斷點

b kernel/trap.c:usertrap

之後一直按c執行,核心載入完畢,輸入指令trace 32 echo,執行後會進入usertrap函式

單步n執行到syscall(),usertrap會進入系統呼叫,不過剛開始的系統呼叫是shell產生的系統呼叫,然後才是trace

可以在syscall的166行處打個斷點,每次產生系統呼叫的時候列印num(p num),檢視系統呼叫編號,trace的編號為22 。

在syscall函式中其實可以發現,系統呼叫編號num存在 p->trapfram->a7,透過函式指標陣列(syscalls)以及傳入的陣列索引(num),作業系統會執行對應的系統呼叫,系統呼叫的返回值存入 p->trapfram->a0

接下來可以在b sysproc.c:sys_trace ,在sys_trace處打一個斷點後按c進入,也可以直接單步執行到168行,按s步入sys_trace函式。

執行完syscall函式後,會回到usertrap函式中,執行usertrapret()函式

usertrapret()函式後會跳到 trampoline.S 中的userret函式,userret返回使用者態。

#tramponline.S

.globl userret
userret:
        # userret(TRAPFRAME, pagetable)
        # switch from kernel to user.
        # usertrapret() calls here.
        # a0: TRAPFRAME, in user page table.
        # a1: user page table, for satp.

        # switch to the user page table.
        csrw satp, a1
        sfence.vma zero, zero

        # put the saved user a0 in sscratch, so we
        # can swap it with our a0 (TRAPFRAME) in the last step.
        ld t0, 112(a0)
        csrw sscratch, t0
	
		# restore all but a0 from TRAPFRAME
        ld ra, 40(a0)
        ld sp, 48(a0)
        ld gp, 56(a0)
        ld tp, 64(a0)
        ld t0, 72(a0)
        ld t1, 80(a0)
        ld t2, 88(a0)
        ld s0, 96(a0)
        ld s1, 104(a0)
        ld a1, 120(a0)
        ld a2, 128(a0)
        ld a3, 136(a0)
        ld a4, 144(a0)
        ld a5, 152(a0)
        ld a6, 160(a0)
        ld a7, 168(a0)
        ld s2, 176(a0)
        ld s3, 184(a0)
        ld s4, 192(a0)
        ld s5, 200(a0)
        ld s6, 208(a0)
        ld s7, 216(a0)
        ld s8, 224(a0)
        ld s9, 232(a0)
        ld s10, 240(a0)
        ld s11, 248(a0)
        ld t3, 256(a0)
        ld t4, 264(a0)
        ld t5, 272(a0)
        ld t6, 280(a0)

        # restore user a0, and save TRAPFRAME in sscratch
        csrrw a0, sscratch, a0

        # return to user mode and user pc.
        # usertrapret() set up sstatus and sepc.
        sret

trace系統呼叫到此結束,接下來trace會根據傳入的引數32 echo執行exec(echo)系統呼叫。

2、trace

syscall.c

void syscall(void)
{
  int num;
  struct proc *p = myproc();

  num = p->trapframe->a7;
  if (num > 0 && num < NELEM(syscalls) && syscalls[num])
  {
    p->trapframe->a0 = syscalls[num](); // 呼叫syscalls[num], 返回值存入a0

    // printf("num :%d , syscall mask :%d syscall&num :%d\n", num, p->syscall_mask, p->syscall_mask & num);
    if (p->syscall_mask > 0 && (p->syscall_mask & (1 << num))) // trace here
    {
      printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num], p->trapframe->a0);
    }
    
  }
  else
  {
    printf("%d %s: unknown sys call %d\n", p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

sysproc.c

// sys_trace implement here.
uint64
sys_trace(void)
{

  // acquire(&tickslock);
  argint(0, &myproc()->syscall_mask); // for trace, get syscall_mask
  // printf("%d\n",myproc()->syscall_mask);
  // release(&tickslock);
  return 0;
}

3、sysinfo

kalloc.c

uint64 FreeMem = 0;	//labs 2 sysinfo
uint64 
getFreeMem()	//labs2 sysinfo
{
  return FreeMem * 4096;
}

void kfree(void *pa)
{
  struct run *r;

  if (((uint64)pa % PGSIZE) != 0 || (char *)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");

  // Fill with junk to catch dangling refs.
  memset(pa, 1, PGSIZE);

  r = (struct run *)pa;

  acquire(&kmem.lock);
  r->next = kmem.freelist;
  kmem.freelist = r;
  FreeMem++; // lab2 here
  release(&kmem.lock);
}

void *
kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  r = kmem.freelist;
  if (r)
  {
    kmem.freelist = r->next;
    FreeMem--; // lab2 here
  }
  release(&kmem.lock);

  if (r)
    memset((char *)r, 5, PGSIZE); // fill with junk
  return (void *)r;
}

proc.c

uint64 procs = 0; // lab2 sysinfo
// labs 2 sysinfo
uint64
getProcs()
{
  return procs;
}

//這個函式是本來就有的,但是需要對allocproc時,進行procs++;
static struct proc *
allocproc(void)
{
  struct proc *p;

  for (p = proc; p < &proc[NPROC]; p++)
  {
    acquire(&p->lock);
    if (p->state == UNUSED)
    {
      goto found;
    }
    else
    {
      release(&p->lock);
    }
  }
  return 0;

found:
  p->pid = allocpid();

  // Allocate a trapframe page.
  if ((p->trapframe = (struct trapframe *)kalloc()) == 0)
  {
    release(&p->lock);
    return 0;
  }

  procs++; // labs2	here
  // printf("procs :%d\n", getProcs());

  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if (p->pagetable == 0)
  {
    freeproc(p);
    release(&p->lock);
    return 0;
  }
    
//freeproc時,對procs--;
static void
freeproc(struct proc *p)
{
  if (p->trapframe)
    kfree((void *)p->trapframe);
  p->trapframe = 0;
  if (p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);
  p->pagetable = 0;
  p->sz = 0;
  p->pid = 0;
  p->parent = 0;
  p->name[0] = 0;
  p->chan = 0;
  p->killed = 0;
  p->xstate = 0;
  p->state = UNUSED;

  procs--; // labs2 here
  // printf("procs :%d\n", getProcs());
}

sysproc.c

extern uint64 getProcs();
extern uint64 getFreeMem();

//`sysinfo`需要將一個`struct sysinfo`複製回使用者空間
uint64
sys_sysinfo(void)
{
  struct sysinfo
  {
    uint64 freemem;
    uint64 procs;
  } sysinfo;
  sysinfo.freemem = getFreeMem();
  sysinfo.procs = getProcs();
  uint64 addr;
  if (argaddr(0, &addr) < 0){	//獲取引數(一個虛地址,也就是使用者空間的sysinfo的地址)
    return -1;
  }
  // copyout:將sysinfo指向的sizeof(sysinfo)大小的記憶體存到addr所指記憶體中
  if(copyout(myproc()->pagetable, addr, (char *)&sysinfo, sizeof(sysinfo))<0){
    return -1;
  };
  // printf("procs :%d\n", getProcs());
  // printf("freemem :%d\n", getFreeMem());
  return 0;
}