ostep initial-xv6 - Syscall 추가하기

2022. 5. 13. 16:14컴퓨터 공학/운영체제

반응형

 

본 네이버 블로그는 모바일 환경에 최적화되어 있습니다.

모바일 유저분들은 아래 네이버 블로그를 이용해 주세요.

 

 

 

Ostep initial-xv6 - Syscall 추가하기

본 네이버 블로그는 모바일 환경에 최적화되어 있습니다. PC 유저분들은 아래 티스토리 블로그를 이용해 ...

blog.naver.com

 

안녕하세요, ICMP입니다!

이번 시간에는 학습용 운영체제인 xv6를 설치하고 syscall을 추가해 보도록 하겠습니다.

 

xv6 란?
xv6은 멀티프로세서 x86 및 RISC-V 시스템을 위한 제6판 유닉스의 ANSI C 용의 현대의 내 구현체이다.
MIT의 운영 체제 엔지니어링 코스의 교육 목적으로 개발되었다.
위키백과

 

현대의 운영체제는 다양한 최적화, 보안 기능들이 탑재되어 있기 때문에 직접적인 커널부분과 기능들을 공부를 진행하는 데 있어서 난이도가 상당히 높습니다.

 

xv6의 경우 리눅스처럼 대부분의 코드를 교육용으로 공개하고 있고 그에 따른 설계 과정을 상세히 설명하고 있기 때문에 보다 직관적으로 이해가 가능합니다.

 

그럼 xv6을 설치해 보도록 하겠습니다!

 

1. XV6 Installation

sudo apt update
sudo apt install qemu
git clone git://github.com/mit-pdos/xv6-public.git #만약 잘 안되면 git://~를 https://로 진행하시면 됩니다.
cd xv6-public
make
make qemu # 새 창 띄우지 않고 진행하려면 -nox, gdb를 이용하려면 -gdb 옵션을 붙여주면 됩니다.
 

make명령어를 실행한 뒤, 리눅스 가상머신인 퀘무(qemu)로 make 명령어를 실행해 준다면 아래와 같이 화면이 뜨는데 xv6가 정상적으로 설치된 겁니다.

 

동작을 확인했으니 이제 시스 콜을 한번 추가해 보도록 하겠습니다.

 

우선 시스템 콜을 호출하기 위해서 xv6는 어떤 과정을 거치는지 알아야 합니다.

아래 블로그 내용과 설계 설명 파일을 참고해서 syscall 호출 과정에 대해 파악해 주세요!

 

2. Add Syscall - getreadcount

제가 추가할 기능은 바로 read syscall이 호출된 횟수를 구해주는 기능을 탑재한 syscall입니다.

말이 어렵지만, 실제로 우리가 추가해야 할 코드는 10줄 조차도 안되므로 한번 진행해 보도록 하겠습니다.

 

- syscall.c

시스 콜 넘버를 기반으로 시스 콜을 호출하는 부분입니다.

즉, SYS_read(매크로 변수)에 대한 조건문을 따로 빼서 전역변수로 카운트를 진행하면 쉽게 처리가 가능합니다.

extern int sys_chdir(void);
extern int sys_close(void);
extern int sys_dup(void);
extern int sys_exec(void);
extern int sys_exit(void);
extern int sys_fork(void);
extern int sys_fstat(void);
extern int sys_getpid(void);
extern int sys_kill(void);
extern int sys_link(void);
extern int sys_mkdir(void);
extern int sys_mknod(void);
extern int sys_open(void);
extern int sys_pipe(void);
extern int sys_read(void);
extern int sys_sbrk(void);
extern int sys_sleep(void);
extern int sys_unlink(void);
extern int sys_wait(void);
extern int sys_write(void);
extern int sys_uptime(void);
extern int getreadcount(void); // <- Added this function.
extern int readcount; // <- Added this var.

static int (*syscalls[])(void) = {
[SYS_fork]    sys_fork,
[SYS_exit]    sys_exit,
[SYS_wait]    sys_wait,
[SYS_pipe]    sys_pipe,
[SYS_read]    sys_read,
[SYS_kill]    sys_kill,
[SYS_exec]    sys_exec,
[SYS_fstat]   sys_fstat,
[SYS_chdir]   sys_chdir,
[SYS_dup]     sys_dup,
[SYS_getpid]  sys_getpid,
[SYS_sbrk]    sys_sbrk,
[SYS_sleep]   sys_sleep,
[SYS_uptime]  sys_uptime,
[SYS_open]    sys_open,
[SYS_write]   sys_write,
[SYS_mknod]   sys_mknod,
[SYS_unlink]  sys_unlink,
[SYS_link]    sys_link,
[SYS_mkdir]   sys_mkdir,
[SYS_close]   sys_close,
[SYS_getreadcount] getreadcount, // <- Added this function.
};

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]();
	  if(num == SYS_read) // <- Added this code.
	    readcount++;
  } else {
    cprintf("%d %s: unknown sys call %d\n",
            curproc->pid, curproc->name, num);
    curproc->tf->eax = -1;
  }
}
 

read 시스콜이 호출되면 변수를 카운트하고, getreadcount가 시스 콜로 호출되면 해당 카운터 값을 넘겨주기만 하면 되겠죠?

함수 포인터 부분을 확인하신다면 충분히 쉽게 파악 가능하실 겁니다.

 

 

- syscall.h

여기는 시스 콜 넘버가 매크로 변수로 정의되어 있는 헤더 파일입니다.

맨 아래에 추가하려는 시스 콜 넘버와 변수만 정의해 주면 간단하게 끝낼 수 있습니다.

// System call numbers
#define SYS_fork    1
#define SYS_exit    2
#define SYS_wait    3
#define SYS_pipe    4
#define SYS_read    5
#define SYS_kill    6
#define SYS_exec    7
#define SYS_fstat   8
#define SYS_chdir   9
#define SYS_dup    10
#define SYS_getpid 11
#define SYS_sbrk   12
#define SYS_sleep  13
#define SYS_uptime 14
#define SYS_open   15
#define SYS_write  16
#define SYS_mknod  17
#define SYS_unlink 18
#define SYS_link   19
#define SYS_mkdir  20
#define SYS_close  21
#define SYS_getreadcount 22 // <- Added syscall number.

 

- sysfile.c

실제적으로 시스 콜들의 동작을 기술한 파일입니다.

아까 우리가 사용하려던 함수, 변수는 extern으로 선언했는데 호출 시점에 따라 값을 수정하기 위함도 있지만 관리 측면에서의 효율성을 위해 아래와 같이 작성했습니다.

#include "types.h"
#include "defs.h"
#include "param.h"
#include "stat.h"
#include "mmu.h"
#include "proc.h"
#include "fs.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "file.h"
#include "fcntl.h"

int readcount;   // <- Added this function & var.

int getreadcount(void){
  return readcount;  
}

해당 과정을 진행하다가 추가적인 설정이 필요한 파일 두 개가 더 존재합니다.

유저 입장에서 사용 종류 정보를 담고 있는 파일이라고 합니다. (이 부분은 xv6 book을 참고해 주세요.)

 

- user.h

우리가 호출할 시스 콜 함수의 원형이 적혀있는 파일입니다.

간단하게 우리가 만든 함수 원형인 int getreadcount(void);를 적으시면 됩니다.

# user.h 내용중 ~
int chdir(const char*);
int dup(int);
int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
int getreadcount(void); // <- Added this function.

 

 

- usys.S

usys.S는 유저 권한에서 privilege 권한으로 올리고, 해당 정보를 바탕으로 인터럽트를 실행해 주는 어셈블리 코드가 저장된 파일입니다.

해당 파일에도 우리가 작성한 함수를 형식에 맞춰서 작성해 주시면 됩니다.

SYSCALL(getreadcount) 

 

이제 우리가 만든 시스콜이 정상적으로 작동되는지 확인해 봐야겠죠?

아래 링크를 참고해서 syscall이 정상적으로 작동하는지 확인해 보도록 하겠습니다.

 

GitHub - remzi-arpacidusseau/ostep-projects: Projects for an undergraduate OS course

Projects for an undergraduate OS course. Contribute to remzi-arpacidusseau/ostep-projects development by creating an account on GitHub.

github.com

 

cd ostep-projects/initial-xv6
git clone git clone https://github.com/mit-pdos/xv6-public
mv xv6-public src
// 다음으로 위에서 언급한 파일들을 모두 수정해 주세요.
./test-getreadcounts.sh (만약, 테스트 실행시 너무 오래 걸리면 -s 옵션을 주시면 됩니다.)

해당 스크립트 파일을 실행해서 test 케이스 2개 모두 성공하면 테스트를 통과한 겁니다.

아래는 테스트를 통과한 화면입니다.

 

 

물론 sys_read 함수 부분에 카운트 증가 코드를 삽입하면 구현 자체는 쉬울 수 있지만, 만약 프로세스가 여러 개 실행 중인데 해당 카운트 변수에 동시 접근을 진행하면 값의 충돌이 발생할 수 있습니다.

 

이를 통제하기 위해서는 락을 통해 동시 접근을 통제하거나 그에 준하는 병렬성을 통제할 수 있는 메커니즘을 제공해야 합니다.

(WSL로 해당 로직의 코드를 진행하면 정확히 카운트가 되지 않는 문제가 발생합니다...)

 

아래는 의사 코드?로 작성한 락 기반 카운터 증가 코드 중 일부입니다.

int sys_read(){
    ~
    loak_set();
    readCount++;
    lock_free();
    ~
}

 

 

위 글에서 제가 언급한 syscall 함수 코드를 조작하는 방식은 경우에 따라서 운영체제 관점에서 상당히 위험할 수 있습니다.

제가 작성한 방법이 성공한 이유는 시스콜을 식별하고 호출하는 과정에서 동시성을 제어하기 위한 lock과 같은 통제 코드가 존재했기 때문입니다.

 

시스템 콜을 잘못 건들면 전체적인 퍼포먼스 하락이나 여러 가지 알 수 없는 side effect가 발생할 수 있으므로 여러분들은 필히 트랩을 통한 시스콜 호출 과정을 숙지하시고 접근하시길 추천드립니다.

 

오늘은 여기까지입니다!

이상 ICMP였습니다, 감사합니다!

 

반응형