Home Pintos project-2-system call implementations
Post
Cancel

Pintos project-2-system call implementations

files to modify

1
2
3
threads/thread.*
userprog/syscall.*
userprog/process.*

System Call

운영체제가 제공하는 일종의 서비스, API를 구현하는 것이다. 유저 프로그램 (Not kernel)이 Kernel의 기능을 사용할 수 있도록 해야 한다. 유저가 특수 권한이 필요한 기능을 요청할 경우, 운영체제는 Kernel mode로 전환하여 필요한 작업을 수행한 뒤, 유저 모드로 전환한다.

시스템 콜의 핵심은 하드웨어 인터럽트를 발생시켜 운영체제의 수행 모드를 특수 모드 (커널 모드)로 전환하는 것이다.

System Call 구조

image

lib/user/syscall.c

1
2
3
4
5
6
7
8
#define syscall5(NUMBER, ARG0, ARG1, ARG2, ARG3, ARG4) (
syscall(((uint64_t) NUMBER),
((uint64_t) ARG0),
((uint64_t) ARG1),
((uint64_t) ARG2),
((uint64_t) ARG3),
((uint64_t) ARG4),
0))

다음과 같이 인자의 개수에 따라 매크로 형식으로 system call이 user program 파트에 구현되어 있다. 유저는 main()에서 이러한 함수들을 호출하면,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
__attribute__((always_inline))
static __inline int64_t syscall (uint64_t num_, uint64_t a1_, uint64_t a2_,
uint64_t a3_, uint64_t a4_, uint64_t a5_, uint64_t a6_) {
int64_t ret;
register uint64_t *num asm ("rax") = (uint64_t *) num_;
register uint64_t *a1 asm ("rdi") = (uint64_t *) a1_;
register uint64_t *a2 asm ("rsi") = (uint64_t *) a2_;
register uint64_t *a3 asm ("rdx") = (uint64_t *) a3_;
register uint64_t *a4 asm ("r10") = (uint64_t *) a4_;
register uint64_t *a5 asm ("r8") = (uint64_t *) a5_;
register uint64_t *a6 asm ("r9") = (uint64_t *) a6_;

__asm __volatile(
"mov %1, %%rax\n"
"mov %2, %%rdi\n"
"mov %3, %%rsi\n"
"mov %4, %%rdx\n"
"mov %5, %%r10\n"
"mov %6, %%r8\n"
"mov %7, %%r9\n"
"syscall\n"

: "=a" (ret)
: "g" (num), "g" (a1), "g" (a2), "g" (a3), "g" (a4), "g" (a5), "g" (a6)
: "cc", "memory");
return ret;
}

inline asm으로 구현된 함수가 User-stack에 필요한 인자들을 넣는다. 그런 다음, Kernel의 System Call handler가 Interrupt Frame에서 Rax의 값에 따라, 다음 코드를 실행한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

/* The main system call interface */
void
syscall_handler (struct intr_frame *f) {
	uint64_t syscall_num;
	syscall_num = f -> R.rax;

	switch (syscall_num){
	    case SYS_HALT:
			break;
	    case SYS_EXIT:
			break;
            ...

	    default :
			halt();
		    break;
	}

	thread_exit ();
}

… 은 생략된 코드이다. switch 문으로 구현되어, syscall_num에 따라 필요한 코드로 분기하게 된다. 이제 우리는 이 syscall_handler를 수정하고, 시스템 콜을 구현하여,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void
syscall_handler (struct intr_frame *f) {
	uint64_t syscall_num;
	syscall_num = f -> R.rax;

	switch (syscall_num){
	    case SYS_HALT:
            halt();
			break;
	    case SYS_EXIT:
            exit();
			break;
            ...

	    default :
			halt();
		    break;
	}

	thread_exit ();
}

처럼 바꾸어 주면 되는 것이다.

syscall의 대략적인 구조

대략적인 구조는 아래 그림과 같다.

syscall 인스트럭션을 실행하면 어떤 일이?

  1. 커널은 미리 정의된 syscall 테이블을 가지고 있다. 이 테이블은 시스템 콜 번호와 해당 핸들러의 주소를 매핑하고 있다.
  2. syscall 명령어가 실행되면, 커널은 RAX 레지스터에 저장된 시스템 콜 번호를 확인한다.
  3. 커널은 syscall 테이블에서 해당 시스템 콜 번호에 매핑된 핸들러의 주소를 찾는다.
  4. 찾은 핸들러의 주소로 실행 흐름을 전달하여 시스템 콜을 처리한다.

테이블이 어딨는데??

pintos가 처음 부팅할 때, 다음과 같은 코드로

This post is written by david61song