Pintos project 2 - 시스템콜 흐름 정리

HiroPark·2023년 5월 8일
0

문제해결

목록 보기
8/11
  1. 태초에 쓰레드가 생성되는 메모리 영역은 커널 영역
  • 새 쓰레드가 생성되는 경우는 두가지

A. initd 에서 새 프로세스(1프로세스 = 1쓰레드) 생성시.
이때, 맨 처음에 생성된 thread(thread_init() 함수 안의 init_thread()) 의 주소값을 가지고 rsp를 지정한다.

static void init_thread(struct thread *t, const char *name, int priority) {
    ASSERT(t != NULL);
    ASSERT(PRI_MIN <= priority && priority <= PRI_MAX);
    ASSERT(name != NULL);

    memset(t, 0, sizeof *t);
    t->status = THREAD_BLOCKED;
    strlcpy(t->name, name, sizeof t->name);
    t->tf.rsp = (uint64_t) t + PGSIZE - sizeof(void *); /
    .
    .
    .

이때, tf->rsp에 커널 스택 포인터를 저장한다

B. 프로세스를 fork할 시 do_fork 실행
이때, do_fork를 실행하는 쓰레드 또한 마찬가지로thread_create() -> init_thread() 를 통해 커널 영역을 잡고 생성된다.

  1. 쓰레드에게 명령어를 수행하도록 함.
  2. exec() 시스템 콜의 결과로 해당 쓰레드를 유저모드로 변경
  • 이때 해당 쓰레드가 실제로 메모리에 존재하는 곳은 여전히 커널모드
  • 유저 영역에서 작업하도록 하기 위해서 해당 쓰레드가 가르키는 rsp레지스터의 주소를 커널 -> 유저 영역으로 변경
    exec() -> process_exec() -> load(file_name, &interrupt_frame) 호출 ->
    setup_stack(&interrupt_frame)에서 인터럽트 프레임의 rsp값을 USER_STACK으로 설정.
kpage = palloc_get_page (PAL_USER | PAL_ZERO);
	if (kpage != NULL) {
		success = install_page (((uint8_t *) USER_STACK) - PGSIZE, kpage, true);
		if (success)
			if_->rsp = USER_STACK;
		else
			palloc_free_page (kpage);
	}

load 안에서 인터럽트 프레임의 rip 값도 ELF바이너리의 e_entry 값으로 설정함

if_->rip = ehdr.e_entry;
  1. 유저 영역에서 작업하던 프로세스는 시스템 콜이 호출되면 커널 모드로 진입
  • 이때, 해당 시스템 콜을 마치고 나면 원래 유저 영역에서 작업하던 명령어로 돌아와야 함, 이를 가능하게 하는것이 intr_frame
  • 시스템콜을 호출한 그 순간의 레지스터 상태를 백업하는 역할임

4번에 대한 자세한 설명
1. 하나의 프로세스가 시스템 콜 호출
2. lib/user/syscall.c의 시스템 콜 호출
3. 이때, syscall 함수의 inline 어셈블리 안에서 syscall\n으로 시스템 콜을 호출

__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_;

	// __ams: 인라인 어셈블리, __volatile : 컴파일러의 최적화 방지
	__asm __volatile(
		/* 어셈블리어들: %0, %1 등을 사용해서 input, output 오퍼랜드를 나타냄.
		 output부터 시작해 input에 나열된 변수의 순서대로 %0, %1 ... 순으로 번호가 매겨짐 */
		 /* input의 파라미터들을 rax, rdi.... 등 레지스터들에 옮김 */
			"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-entry.s 로 진입아니고, syscall자체를 호출하는건데, 커널단에서 시스템콜을 다루는건 syscall_handler
			/* output */
			: "=a" (ret) // 결과값을 출력하는 변수가 ret, 이를 a라는 오퍼랜드에 넣는데, = 을 사용해서 쓰기 전용 오퍼랜드임을 나타냄
			/* input : 인라인 어셈블리에 넘겨주는 파라미터를 적는다 */
			: "g" (num), "g" (a1), "g" (a2), "g" (a3), "g" (a4), "g" (a5), "g" (a6) // g는 일반레지스터, 메모리 혹은, immediate 정수 중 아무것이나 나타내는 오퍼랜드
			/* clobber : output, input에 명시돼 있지는 않지만, asms를 실행해서 값이 변하는 것을 적어준다 */
			: "cc", "memory"); // memory clobber는 GCC가 메모리가 임의적으로 asm블럭에 의해 읽히거나 쓰일것이라고 가정하게 함. 따라서 컴파일러가 reorder하는 것을 막음
			// cc 클로버는 모든 asm()안에 implicit하게 들어있음, 얘는 flag와 condition 코드를 컴파일러가 미리 고려하게 함
	return ret;
}
  1. 해당 시스템콜은 syscall-entry.S 파일을 호출
  • 여기서 프로세스의 rsp를 커널 영역으로 바꿔주고, 시스템 콜 호출 순간의 레지스터 값들을 intr_frame을 만들어 백업

  • 이때 syscall\n 을 호출했을때, syscall-entry 로 넘어갈 수 있는 이유는, syscall_init에서 MSR레지스터에 syscall_entry 함수의 위치 (함수 포인터)를 등록하기 때문.
    (이 syscall_init은 init.c에서 호출)

void syscall_init (void) {
    // MSR: Machine_specific Register - syscall은 MSR의 값을 읽어서 작동한다
	write_msr(MSR_STAR, ((uint64_t)SEL_UCSEG - 0x10) << 48  | ((uint64_t)SEL_KCSEG) << 32); // code segment와 stack segment를 로드할때쓴느 레지스터 
	write_msr(MSR_LSTAR, (uint64_t) syscall_entry); // MSR_LSTAR는 시스템 콜 핸들러 함수의 주소를 저장 -> syscall\n 호출시 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.
     * MSR_SYSCALL_MASK에 인터럽트 처리시 레지스터 상태를 저장
     * 시스템콜 호출시 유저모드 -> 커널 모드 전환 과정에서 스택이 변경되는데, 커널모드에서 실행되야 하는 중요한 작업이 수행전까지는
     * 인터럽트를 처리하지 않기 위해서, 인터럽트 서비스 루틴은 syscall_entry가 유저 모드 스택을 커널 모드 스택으로 교체 전까지
     * 어떠한 인터럽트도 처리하지 않게 FLAG_FL을 마스킹함
     *  */
	write_msr(MSR_SYSCALL_MASK, FLAG_IF | FLAG_TF | FLAG_DF | FLAG_IOPL | FLAG_AC | FLAG_NT);

    // ******************************LINE ADDED****************************** //
    // Project 2-2-2 : User Programs - System Call - File Descriptor
    /* 파일 사용 시 lock 초기화 */
    lock_init(&filesys_lock);
    // *************************ADDED LINE ENDS HERE************************* //
}
  1. 이 intr_frame을 userprog/syscall.c의 syscall_handler의 인자로 넘김
  2. 시스템콜 함수들의 리턴값을 intr_frame의 rax로 넘기며 시스템 콜 작업 완료
profile
https://de-vlog.tistory.com/ 이사중입니다

0개의 댓글