[운체] 오늘의 삽질 - 0720

방법이있지·2025년 7월 19일
post-thumbnail

일요일은 제가 공부동에 안 나갈거기 때문에 오늘 미리 올립니다.

시스템 콜 핸들러 구현

int N 대신 syscall을 사용하는 경우

  • 핀토스에선 시스템 콜을 처리할 때, int 0x30 말고 syscall을 사용
    • 이 경우 종료 후 iretq 말고 sysretq를 사용
  • 인터럽트 걸고 시스템 콜 핸들러 실행하여, 커널 스택포인터 주소를 syscall_handler에 전달하는 것까진 동일
  • 하지만 일부 차이가 있음
    • syscall 실행 시, CPU가 자동으로 커널에 값을 푸시해 주지 않음.
      • 어셈블리어 코드에서 새로 저장된 해당 레지스터 값을 직접 푸시해야 함.
      • CPU는 사용자 RIPRCX에, EFLAGSR11에 저장만 해 줌. 믈론 어셈블리어에서 수동으로 푸시해야 함.
      • RSP는 어셈블리어 코드에서 수동으로 RBX에 복사한 뒤, 해당 값을 푸시해야 함.
    • syscall은 CPU가 자동으로 커널 스택으로 RSP를 전환해 주지도 않음.
      • 얘도 어셈블리어 코드에서 수동으로 해야 함. 설명은 생략.
    • sysretq에서도 cpu가 자동으로 레지스터를 팝해주지 않음.
      • sysretqRCX, R11에 저장된 값을 RIP, EFLAGS로 복원만 해 줌.
      • 따라서 sysretq 전 어셈블리어 코드에서 RSP를 팝해주고, RIP에 넣을 값은 RCX, EFLAGS에 넣을 값은 R11로 팝해야 함.
  • 그런데 확실히 차이 없는 건, syscall_handler 인자에서 struct intr_frame*로 커널 스택에 동일하게 접근 가능
    • 어셈블리어 코드에서 값을 intr_frame 구조체 순서대로 푸시하기 때문
#include "threads/loader.h"
// syscall 실행 후 CPU 자동 처리 후 실행되는 어셈블리어 코드

syscall_entry:
	movq %rbx, temp1(%rip)
	movq %r12, temp2(%rip)

	/* rsp(사용자 스택포인터 값)를 rbx에 저장    */
	movq %rsp, %rbx

	/* 사용자 스택 -> 커널 스택으로 주소 전환 */
	movabs $tss, %r12
	movq (%r12), %r12
	movq 4(%r12), %rsp

	// push A는 A 레지스터의 값을 푸시한다는 뜻
	// int N에선 CPU가 자동으로 했었지만, 여기선 우리가 직접 푸시해야 함
	push $(SEL_UDSEG)      /* if->ss */
	push %rbx              /* if->rsp */
	push %r11              /* if->eflags */
	push $(SEL_UCSEG)      /* if->cs */
	push %rcx              /* if->rip */

	// int N에선 error_code, vec_no을 푸시했지만, 여기선 건너뜀
	subq $16, %rsp         /* skip error_code, vec_no */

	// 여기서부터 int N에서도 어셈블리어가 푸시하던 애들이 나옴 (범용 레지스터)
	push $(SEL_UDSEG)      /* if->ds */
	push $(SEL_UDSEG)      /* if->es */
	push %rax
	movq temp1(%rip), %rbx
	push %rbx
	pushq $0	/* rcx는 생략 */
	push %rdx
	push %rbp
	push %rdi
	push %rsi
	push %r8
	push %r9
	push %r10
	pushq $0 	/* r11은 생략 */
	movq temp2(%rip), %r12
	push %r12
	push %r13
	push %r14
	push %r15

	// syscall_handler의 인자로 스택 포인터를 전달
	movq %rsp, %rdi

// 몰라도됨
check_intr:
	btsq $9, %r11          /* Check whether we recover the interrupt */
	jnb no_sti
	sti                    /* restore interrupt */
no_sti:

	// 여기서 syscall_handler 함수가 실행됨
	movabs $syscall_handler, %r12
	call *%r12

	// 이후 값을 다시 팝
	// pop B는 커널스택의 값을 팝해 B에 복사한다는 뜻
	popq %r15
	popq %r14
	popq %r13
	popq %r12
	popq %r11
	popq %r10
	popq %r9
	popq %r8
	popq %rsi
	popq %rdi
	popq %rbp
	popq %rdx
	popq %rcx
	popq %rbx
	popq %rax
	addq $32, %rsp
	popq %rcx              /* if->rip 값을 RCX에 저장 -> sysretq에서 RIP로 복원됨 */
	addq $8, %rsp
	popq %r11              /* if->eflags 값을 R11에 저장 -> sysretq에서 EFLAGS로 복원됨 */
	popq %rsp              /* if->rsp 값을 RSP에 저장 */

	// sysretq로 종료
	sysretq				   /* `RCX`, `R11`에 저장된 값을 `RIP`, `EFLAGS`로 복원해 줌. */

syscall_handler

  • 위 그림을 보세요. 대충 왼쪽 위 "사용자 프로그램"에서 시작하심 됩니다
  • [구현 2-1] userprog/syscall.csyscall_handler 함수 구현
    • 시스템 콜 번호에 따라, 올바른 함수가 실행되도록 구현해야 함
      • 시스템 콜 번호는 rax 레지스터에 저장됨 -> f -> R -> rax로 확인
      • 시스템 콜 번호의 종류는 include/lib/syscall_nr.h에서 확인 가능 (열거형인데 0, 1, 2 순)
    • 시스템 콜에 전해진 인자는 f -> Rrdi, rsi, rdx, r10, r8, r9 멤버로 확인 가능
    • 시스템 콜의 반환값 역시 rax 레지스터에 저장해야 함
// userprog/syscall.c
void
syscall_handler (struct intr_frame *f UNUSED) {
	// TODO: Your implementation goes here.
	int number = f -> R -> rax;	// ??? 맞는진 모름
	switch(number){
		case SYS_HALT:					/* Halt the operating system. */
		case SYS_EXIT:					/* Terminate this process. */
		case SYS_FORK:					/* Clone current process. */
		case SYS_EXEC:                   /* Switch current process. */
		case SYS_WAIT:                   /* Wait for a child process to die. */
		case SYS_CREATE:              /* Create a file. */
		case SYS_REMOVE:                 /* Delete a file. */
		case SYS_OPEN:                   /* Open a file. */
		case SYS_FILESIZE:               /* Obtain a file's size. */
		case SYS_READ:                   /* Read from a file. */
		case SYS_WRITE:                  /* Write to a file. */
		case SYS_SEEK:                   /* Change position in a file. */
		case SYS_TELL:                   /* Report current position in a file. */
		case SYS_CLOSE:  				/* Close a file. */
	}
	printf ("system call!\n"); // 이걸 다른 코드로 바꿔야겠지
	thread_exit ();
}
// include/lib/syscall_nr.h
/* System call numbers. */
enum {
	/* Projects 2 and later. */
	SYS_HALT,                   /* Halt the operating system. */
	SYS_EXIT,                   /* Terminate this process. */
	SYS_FORK,                   /* Clone current process. */
	SYS_EXEC,                   /* Switch current process. */
	SYS_WAIT,                   /* Wait for a child process to die. */
	SYS_CREATE,                 /* Create a file. */
	SYS_REMOVE,                 /* Delete a file. */
	SYS_OPEN,                   /* Open a file. */
	SYS_FILESIZE,               /* Obtain a file's size. */
	SYS_READ,                   /* Read from a file. */
	SYS_WRITE,                  /* Write to a file. */
	SYS_SEEK,                   /* Change position in a file. */
	SYS_TELL,                   /* Report current position in a file. */
	SYS_CLOSE,                  /* Close a file. */

	// 뒤에서부턴 Project 3 이후 내용이라 생략
};

Address validation

  • [구현 2-2] syscall_handler에서 Address validation 수행
    • 시스템 콜에 전해진 인자는 f -> Rrdi, rsi, rdx, r10, r8, r9 멤버로 확인 가능
    • 해당 인자가 invalid pointer인지 감지해야 함
      • 널 포인터 / 매핑되지 않은 가상 메모리 주소 / 커널 영역의 주소일 때...
    • 어떻게 감지하냐면, threads/vaddr.h threads/mmu.h 의 함수들을 사용하면 됨
      • 널 포인터인지 확인은 단순할 것이라고 생각함. 걍 0인지 확인 -> 빡꾸
      • is_kernel_vaddr(주소)true면 커널영역의 주소 -> 빡꾸
      • pml4_get_page(thread_current() -> pml4, 주소)가 널 포인터를 반환하면 매핑되지 않은 주소 -> 빡꾸
    • invalid pointer인 경우 프로세스를 terminate하기... 즉 thread_exit() 쓰면 될듯?
  • 포인터를 매개변수로 받으면 이렇게 3개를 체크해 주는 함수를 만드는 게 좋을 듯.

Implement system calls

  • 사용자 측 시스템 콜은 syscall 함수를 통해 시스템 콜을 보냄
    • 이후 커널 측 코드 switch문에서 시스템 콜 번호에 따른 올바른 처리를 하면 됨.
    • 비교적 간단히 구현할 수 있는 콜도 있고, 아닌 콜도 있고
  • syscall0, syscall1, ... 등 함수 뒤에 붙은 숫자는, 시스템 콜에 보내는 인자 수를 뜻함. 결국엔 해당 함수 안에서 syscall이 호출됨. rdi부터 r9까지 6개 레지스터가 있으므로 syscall6까지 존재.

halt

  • [구현 3] halt 구현하기
    • 핀토스를 종료하는 함수.
    • 매개변수 없으니까 address validation 할 필요도 없음.
    • 단순히 src/include/threads/init.h에 선언된 power_off()를 호출하면 해결됨. 참 쉽죠?
// 사용자 측 (lib/user/syscall.c`)
void halt (void) {
	syscall0 (SYS_HALT);
	NOT_REACHED ();
}
// 커널 측 (userprog/syscall.c)
switch(number) {
	// 생략
	case SYS_HALT:
		power_off();
}

그 외에 구현해야 하는 애들

switch(number){
	case SYS_HALT:
		/* Halt the operating system. */
		// [구현 3] 핀토스를 종료. 매우 쉽죠?
		power_off();
	case SYS_EXIT:
		/* Terminate this process. */
	case SYS_FORK:
		/* Clone current process. */
	case SYS_EXEC:                   /* Switch current process. */
	case SYS_WAIT:                   /* Wait for a child process to die. */
	case SYS_CREATE:              /* Create a file. */
	case SYS_REMOVE:                 /* Delete a file. */
	case SYS_OPEN:                   /* Open a file. */
	case SYS_FILESIZE:               /* Obtain a file's size. */
	case SYS_READ:                   /* Read from a file. */
	case SYS_WRITE:                  /* Write to a file. */
	case SYS_SEEK:                   /* Change position in a file. */
	case SYS_TELL:                   /* Report current position in a file. */
	case SYS_CLOSE:  				/* Close a file. */
}
  • 갈 길이 멀다. 화이팅...
profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글