뭐요

예제로 알아보는 xv6에서 시스템 호출 구현하기 (2) 본문

Operating System/xv6

예제로 알아보는 xv6에서 시스템 호출 구현하기 (2)

욕심만 많은 사람 2023. 2. 28. 12:38

예제로 알아보는 xv6에서 시스템 호출 구현하기

1편 바로가기

시스템 호출 구현

지난 시간에 이어 xv6에서 시스템 호출을 구현해보자.
추적할 시스템 콜을 지정하는 정수 [mask]를 인자로 받아 리턴될 때 프로세스 아이디, 시스템 콜 이름, 리턴 값이 출력되는 시스템호출을 구현해보려고 한다.

우리의 목표

  • trace 시스템 콜 추가 및 호출하는 쉘 프로그램 구현


trace 시스템호출 구현

추적할 시스템 콜을 지정하는 정수 [mask]를 인자로 받아 리턴될 때 프로세스 아이디, 시스템 콜 이름, 리턴 값이 출력되는 시스템호출을 구현해보자.

1) proc.h에서 struct proc에 int형 tracemask 변수 추가

// Per-process state
struct proc {
  uint sz;                     // Size of process memory (bytes)
  pde_t* pgdir;                // Page table
  char *kstack;                // Bottom of kernel stack for this process
  enum procstate state;        // Process state
  int pid;                     // Process ID
'
'
'
  int tracemask;    // add field
};

2) sysproc.c에서 sys_trace() 추가

User-level function에서 Kernel-level function으로 바로 argument를 전달할 수 없다. 따라서 xv6의 자체적인 built-in function을 사용해서 인자를 넘겨주어야 한다. 대표적으로 argint, argptr, argstr이 있다. 우리는 int형을 인자로 받을 것이기에, argint를 사용한다.

// trace (pass the integer argument (user to kernel))
int
sys_trace(void){
  if(argint(0, &myproc()->tracemask) < 0){
    return -1;
  }

  return 0;
}

3) syscall.h에서 sys_trace() 추가

#define SYS_mkdir  20
#define SYS_close  21
#define SYS_memsize 22
#define SYS_trace 23     // 추가

4) syscall.c에서 sys_trace() 추가

extern int sys_uptime(void);
extern int sys_memsize(void);
'
'
'
extern int sys_trace(void); // 추가

static int (*syscalls[])(void) = {
[SYS_close]   sys_close,
[SYS_memsize]   sys_memsize,
'
'
'
[SYS_trace]   sys_trace, // 추가
};

5) syscall.c에서 syscall() 수정

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

  num = curproc->tf->eax;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    curproc->tf->eax = syscalls[num]();
    // trace
    if(curproc->tracemask >> num){
      cprintf("syscall traced: pid = %d, syscall = %s, %d returned\\n", curproc->pid, syscallnames[num], curproc->tf->eax);
    }
  } else {
    cprintf("%d %s: unknown sys call %d\\n",
            curproc->pid, curproc->name, num);
    curproc->tf->eax = -1;
  }
}

또한 syscall 이름을 불러오기 위해 char *[]을 하나 생성한다.

char *syscallnames[] = {
[SYS_fork]    "fork",
[SYS_exit]    "exit",
[SYS_wait]    "wait",
[SYS_pipe]    "pipe",
[SYS_read]    "read",
[SYS_kill]    "kill",
[SYS_exec]    "exec",
[SYS_fstat]   "fstat",
[SYS_chdir]   "chdir",
[SYS_dup]     "dup",
[SYS_getpid]  "getpid",
[SYS_sbrk]    "sbrk",
[SYS_sleep]   "sleep",
[SYS_uptime]  "uptime",
[SYS_open]    "open",
[SYS_write]   "write",
[SYS_mknod]   "mknod",
[SYS_unlink]  "unlink",
[SYS_link]    "link",
[SYS_mkdir]   "mkdir",
[SYS_close]   "close",
[SYS_memsize]   "memsize",
[SYS_trace]   "trace",
};

6) proc.c에서 fork() 수정

자식 프로세스 생성하면서 부모 프로세스의 정보를 자식 프로세스로 복사해주는데, 우리가 만든 tracemask 또한 그래야 한다.

// copy trace mask
  np->tracemask = curproc->tracemask;    // 추가

7) user.h에서 trace() 추가

char* sbrk(int);
int sleep(int);
int uptime(void);
int memsize(void);  // 새로운 시스템 호출 추가
int trace(int);   // 새로운 시스템 호출 추가

8) usys.S에서 trace() 추가

SYSCALL(sleep)
SYSCALL(uptime)
SYSCALL(memsize)     // 새로운 시스템 호출 추가
SYSCALL(trace)     // 새로운 시스템 호출 추가

9) ssu_trace.c 파일 생성

#include "types.h"
#include "stat.h"
#include "user.h"

int main(int argc, char *argv[]){
    int pid;
    int mask = atoi(argv[1]);
    int flag;
    int wpid;

    // argv 초기화
    char *tmpArgv[argc-2];
    int idx = 0;
    for(int i = 2 ; i < argc; i++){
        tmpArgv[idx++] = argv[i];
    }

    pid = fork();
    if(pid < 0){
        printf(1, "init: fork failid\\n");
        exit();
    }
    if(pid == 0){
        flag = trace(mask);
        if(flag == 0){
            exec(tmpArgv[0], tmpArgv);
        }else{
         printf(1, "trace failed");
        }
        exit();
    }
    while((wpid = wait()) >= 0 && wpid != pid){
        printf(1, "zombie!\\n");
    }
}

10) 실행 결과

image

간단하게 실행의 흐름을 말해보면 fork 후 자식 프로세스에서 trace 시스템 호출을 통해 인자로받은 mask 값을 넘겨준다. 성공적으로 trace()가 호출되었으면 인자로 받은 명령어를 실행한다.
이후 memsize()처럼 작동 확인을 위해 쉘 프로그램을 등록했다.