본문 바로가기
TIL

[OS] Pintos - system call 호출 및 흐름

by Lizzie Oh 2022. 11. 25.

유저모드에서 유저 프로세스가 호출한 시스템 콜이 어떻게 커널모드로 바뀌고, 어떻게 해당 시스템 콜에 해당하는 함수가 호출되는지에 대한 과정이 헷갈려서 며칠동안 찾아해멨다. 반에 똑똑한 친구들이 많아서 드디어 흐름을 이해할 수 있어서 짧게 정리해보려고 한다. 

 

우선 유저프로그램에서 fork() 시스템콜을 호출하는 상황을 예로 들어 흐름을 설명해보려고 한다. 

 


1. 유저프로그램에서 fork(스레드이름); 으로 fork 호출

 

2. fork() : syscall1(fork의 시스템콜 번호, 스레드이름); 호출

/* lib/user/syscall.c 파일 */ 

pid_t
fork (const char *thread_name){
	return (pid_t) syscall1 (SYS_FORK, thread_name);
}

 

3. syscall1() : syscall(fork의 시스템 콜 번호, 스레드 이름); 호출

#define syscall1(NUMBER, ARG0) ( \
		syscall(((uint64_t) NUMBER), \
			((uint64_t) ARG0), 0, 0, 0, 0, 0))

 

4. syscall(): CPU 레지스터 %rax에 시스템 콜 번호, %rdi 에 스레드 이름을 넣고, syscall 실행

__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"   /* syscall 실행 */
			: "=a" (ret)
			: "g" (num), "g" (a1), "g" (a2), "g" (a3), "g" (a4), "g" (a5), "g" (a6)
			: "cc", "memory");
	return ret;
}

 

5. syscall : syscall-entry 실행 (핀토스 부팅시  init.c에서 실행되는 syscall_init에서 syscall 호출시 syscall-entry가 실행되도록 이미 설정되어있기 때문)

#define MSR_STAR 0xc0000081         /* Segment selector msr */
#define MSR_LSTAR 0xc0000082        /* Long mode SYSCALL target */
#define MSR_SYSCALL_MASK 0xc0000084 /* Mask for the eflags */

void
syscall_init (void) {
	write_msr(MSR_STAR, ((uint64_t)SEL_UCSEG - 0x10) << 48  |
			((uint64_t)SEL_KCSEG) << 32);
	write_msr(MSR_LSTAR, (uint64_t) syscall_entry); /* syscall ->syscall_entry 실행 */

	/* The interrupt service rountine should not serve any interrupts
	 * until the syscall_entry swaps the userland stack to the kernel
	 * mode stack. Therefore, we masked the FLAG_FL. */
	write_msr(MSR_SYSCALL_MASK,
			FLAG_IF | FLAG_TF | FLAG_DF | FLAG_IOPL | FLAG_AC | FLAG_NT);
}

 

6. syscall-entry.S :

  • CPU의 rsp 값에 tss에 저장된 커널스택의 스택포인터를 저장
    → 이제 rsp는 유저 스택이 아닌 커널 스택을 가리킴 =  커널모드 시작
    /* syscall-entry.S 부분 */ 
    
    movabs $tss, %r12
    	movq (%r12), %r12
    	movq 4(%r12), %rsp         /* Read ring0 rsp from the tss */

 

  • CPU 레지스터 값들을 커널 스택에 push 
    : push 되는 순서는 마지막 들어간 데이터부터 interrupt frame 구조체 순서가 되도록 하는 순서로 push
    /* syscall-entry.S 부분 */
    
    push $(SEL_UDSEG)      /* if->ss */
    	push %rbx              /* if->rsp */
    	push %r11              /* if->eflags */
    	push $(SEL_UCSEG)      /* if->cs */
    	push %rcx              /* if->rip */
    	subq $16, %rsp         /* skip error_code, vec_no */
    	push $(SEL_UDSEG)      /* if->ds */
    	push $(SEL_UDSEG)      /* if->es */
    	push %rax
    	movq temp1(%rip), %rbx
    	push %rbx
    	pushq $0
    	push %rdx
    	push %rbp
    	push %rdi
    	push %rsi
    	push %r8
    	push %r9
    	push %r10
    	pushq $0 /* skip r11 */
    	movq temp2(%rip), %r12
    	push %r12
    	push %r13
    	push %r14
    	push %r15
    	movq %rsp, %rdi
  •  커널 스택에 인터럽트 프레임 구조체 형태의 데이터가 다 들어가 있고, rsp는 이 시작점을 가리키는 형태
     → syscall_handler() 호출 시 인자로 rsp를 넘겨주면 핸들러는 *if 를 받았다고 생각할 수 있음
    /* syscall-entry.S 부분 */
    
    movabs $syscall_handler, %r12
    	call *%r12

 

7. syscall_handler (struct intr_frame *f):

인자로 들어온 f는 커널스택의 rsp이고, rsp부터는 intr_frame 형태의 데이터들이 순차적으로 들어가 있기 때문에 이 자체를 인터럽트 프레임처럼 사용할 수 있는 것 → f의 rax 값을 통해 필요한 시스템 콜을 수행하는 함수를 호출한다.

struct syscall_func syscall_func[] = {
	{SYS_HALT,syscall_halt},
	{SYS_EXIT,syscall_exit},
	{SYS_FORK,syscall_fork},
	{SYS_EXEC,syscall_exec},
	{SYS_WAIT,syscall_wait},
	{SYS_CREATE,syscall_create},
	{SYS_REMOVE,syscall_remove},
	{SYS_OPEN,syscall_open},
	{SYS_FILESIZE,syscall_filesize},
	{SYS_READ,syscall_read},
	{SYS_WRITE,syscall_write},
	{SYS_SEEK,syscall_seek},
	{SYS_TELL,syscall_tell},
	{SYS_CLOSE,syscall_close},
};

void
syscall_handler (struct intr_frame *f) {
	struct syscall_func call = syscall_func[f->R.rax];
	call.function (f);
}

 

 

나처럼 흐름이 궁금했던 사람들에게 도움이 되길 바라며.. 이만... 

반응형

댓글