[운체] 오늘의 삽질 - 0721

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

syscall_handler

[구현 2-1] userprog/syscall.csyscall_handler 함수 구현

  • 시스템 콜 번호에 따라, 올바른 함수가 실행되도록 구현해야 함
  • switch~case문 쓰면 되는데, 각 case 밑에 break 두는 거 잊지 말기
  • 시스템 콜 번호는 rax 레지스터에 저장됨 -> f -> R -> rax로 확인
  • 시스템 콜 번호의 종류는 include/lib/syscall_nr.h에서 확인 가능 (열거형임에 유의)
  • 시스템 콜에 전해진 인자는 f -> Rrdi, rsi, rdx, r10, r8, r9 멤버로 확인 가능
  • 시스템 콜의 반환값 역시 rax 레지스터에 저장해야 함
  • 맨 마지막의 printf("system call!\n")thread_exit()은 기본값
  • 얘넨 지워야 함. 안 지우면 시스템 콜 하나만 처리하고 바로 쓰레드가 종료해 버려서, 정상적으로 실행이 안 됨
// userprog/syscall.c
syscall_handler (struct intr_frame *f UNUSED) {
	// [구현 2-1] 시스템 콜 번호에 따라, 올바른 함수가 실행되도록 구현한다.
	// 인가는 rdi rsi rdx
	int number = f -> R.rax;

	switch(number){
		case SYS_HALT:
			/* Halt the operating system. */
			// 코드 넣기
			break;
		case SYS_EXIT:
			/* Terminate this process. */
			// 코드 넣기
			break;
		case SYS_WRITE:
			/* Write to a file. */
			// 코드 넣기
            break;
		// 이하 생략...
	}

    // 얘네는 없애줘야 함
	//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] Address validation 수행 함수 만들기

  • 시스템 콜에 전해진 인자가 포인터일 때, invalid pointer인지 감지해야 함
  • 아래 3개의 조건을 확인하는 함수 만들기
    • 널 포인터인지 확인은 단순할 것이라고 생각함. 걍 0인지 확인 -> 빡꾸
    • threads/vaddr.h -> is_kernel_vaddr(주소)true면 커널영역의 주소 -> 빡꾸
    • threads/mmu.h -> pml4_get_page(thread_current() -> pml4, 주소)가 널 포인터를 반환하면 매핑되지 않은 주소 -> 빡꾸
  • 각 시스템 콜을 구현하면서, 포인터가 인자로 들어온 경우 지금 만든 함수로 체크하면 됨
    • invalid 포인터인 경우 process에서 나가기
// userprog/syscall.c
// [구현 2-2] 포인터가 valid한지 확인하는 함수를 만든다.
bool valid_pointer (void *p){
	// 널 주소-/ 커널 영역의 주소 / 매핑되지 않은 페이지
	if (p == NULL || is_kernel_vaddr(p) || pml4_get_page(thread_current() -> pml4, p) == NULL){
		thread_current() -> exit_code = -1;
		thread_exit();
	};
}

Implement system calls

  • 이제 기본적인 시스템 콜 핸들러를 만들었으니, 실제 시스템 콜을 구현할 차례
  • 아래 흐름대로 진행하면 됨
    • (1) 인자들은 f -> Rrdi, rsi, rdx, r10, r8, r9 멤버에서 차례로 확인한다.
    • (2) 인자 중 포인터가 있는 경우, [구현 2-2]에서 만든 함수로 valid 여부 체크한다.
    • (3) 시스템 콜에서 요구되는 동작을 수행한다.
    • (4) 반환값은 f -> Rrax 멤버에 저장한다.
  • 우선 구현하기 제일 쉬운 애들 3개부터 대충 구현은 해 봄...

halt - 핀토스 종료

  • [구현 3] halt 구현하기
    • 핀토스를 종료하는 함수.
    • 매개변수 없으니까 address validation 할 필요도 없음.
    • 단순히 src/include/threads/init.h에 선언된 power_off()를 호출하면 해결됨. 참 쉽죠?
// 사용자 측 (lib/user/syscall.c`)
void halt (void) {
	syscall0 (SYS_HALT);
	NOT_REACHED ();
}
  • syscallX 계열 함수는, 인자가 X개인 syscall을 보낸다고 생각하면 됨
    • syscallX의 맨 처음 인수는 시스템 콜 번호 (SYS_HALT%rax에 저장됨)
    • 그 다음 인수들은 시스템 콜에 보낼 실제 인자들 (여기선 인자가 없지만, 인자가 존재하면 %rdi부터 6개 레지스터에 저장됨)
    • 이후 syscall 명령으로 시스템콜 호출
// 커널 측 (userprog/syscall.c)
switch(number) {
	case SYS_HALT:
        /* Halt the operating system. */
		// [구현 3] 핀토스를 종료. 매우 쉽죠?
		power_off();
        break;
}

exit - 프로세스, 쓰레드의 종료

  • [구현 4-임시] exit 구현하기
    • 최종적으로는 이 방법이 아니라 process.cprocess_wait()을 수정해야 할 것 같긴 한데, 일단 임시 방편으로...
    • 쓰레드를 종료하는데, exit code를 인자 status로 설정.
// 사용자 측 (lib/user/syscall.c`)
void exit (int status) {
	syscall1 (SYS_EXIT, status);
	NOT_REACHED ();
}
  • 앞서 [구현 2-3]에서 만든 exit() 함수를 사용하면 됨
    • 인수로 status에 대응되는 f -> R.rdi를 보내주기
case SYS_EXIT:
    /* Terminate this process. */
    // [구현 4] 쓰레드/프로세스 종료 후, 종료코드 반환
    exit((int)(f -> R.rdi));
    break;

write 약간 - 콘솔 출력만 구현

  • [구현 5-임시] write - 콘솔창 출력만 구현
// 사용자
int write (int fd, const void *buffer, unsigned size) {
	return syscall3 (SYS_WRITE, fd, buffer, size);
}
  • buffer의 내용을 size만큼 파일 명시자 fd에 출력한다.
  • write 시스템 콜은 fd == 1일 때는 파일이 아닌 콘솔에 출력되게끔 구현해야 함
  • 일단 테스트 케이스 채점을 위해서는 콘솔 창에 출력을 어떻게든 해야 함
    • 따라서 fd == 1일 때만 먼저 구현할 수 있음
// 커널
case SYS_WRITE:
    /* Write to a file. */
    // [구현 5] 콘솔 창에다 출력하는 부분만 일단 우선 구현 가능.
    valid_pointer(f -> R.rsi);

    if ((int)(f -> R.rdi) == 1){
        putbuf((char *)(f -> R.rsi), (size_t)(f -> R.rdx));
    }
    break;
  • [구현 2-2]에서 만든 valid_pointer(f -> R.rsi)buffer가 valid pointer인지 확인
  • 이후 fd(RDI)가 1인 경우, putbuf을 이용해 buffer(RSI)의 내용을 size(RDX)만큼 출력하기
    • RDI가 1이 아닌 경우는 나중에 구현하기

앞으로 해야 할 일들

fork

[구현 4] fork - 자식 프로세스 만들기

// 사용자
pid_t fork (const char *thread_name){
	return (pid_t) syscall1 (SYS_FORK, thread_name);
}
  • 새로운 자식 프로세스의 이름은 thread_name으로 설정
  • 레지스터 rbx, rsp, rbp, r12 ~ r15 7개의 값만 복제하면 됨 (??????)
  • 부모 프로세스에선, 새로운 자식 프로세스의 pid 반환
    • 자식 프로세스가 생성된 이후에 반환해야 함
    • 자식 프로세스에서 clone이 성공적으로 이루어지지 않은 경우, TID_ERROR 반환
  • 자식 프로세스에선, 0 반환
  • file descriptor and virtual memory space를 포함해, 부모 프로세스의 자원을 복제해야 함
    • threads/mmu.cpml4_for_each()를 사용할 것

사용해야 할 것 같은 함수들

  • process_fork (userprog/process.c)
    • 일단 설정한 이름으로 새 쓰레드를 만들고 __do_fork를 호출함.
/* 현재 프로세스를 clone. 이름은 매개변수 name으로 설정.
성공 시 쓰레드 id를, 실패 시 TID_ERROR를 반환. */
tid_t process_fork (const char *name, struct intr_frame *if_ UNUSED){
	/* Clone current thread to new thread.*/
	return thread_create (name,
			PRI_DEFAULT, __do_fork, thread_current ());
};
  • __do_fork (userprog/process.c)
    • [구현 4-1] 채워야 할 게 많음.
// 부모의 execution context를 복사함
/* A thread function that copies parent's execution context.
 * Hint) parent->tf does not hold the userland context of the process.
 *       That is, you are required to pass second argument of process_fork to
 *       this function. */
static void
__do_fork (void *aux) {
	struct intr_frame if_;
	struct thread *parent = (struct thread *) aux;
	struct thread *current = thread_current ();
	/* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */
	struct intr_frame *parent_if;
	bool succ = true;

	/* 1. Read the cpu context to local stack. */
	memcpy (&if_, parent_if, sizeof (struct intr_frame));

	/* 2. Duplicate PT */
	current->pml4 = pml4_create();
	if (current->pml4 == NULL)
		goto error;

	process_activate (current);
#ifdef VM
	supplemental_page_table_init (&current->spt);
	if (!supplemental_page_table_copy (&current->spt, &parent->spt))
		goto error;
#else
	if (!pml4_for_each (parent->pml4, duplicate_pte, parent))
		goto error;
#endif

	/* TODO: Your code goes here.
	 * TODO: Hint) To duplicate the file object, use `file_duplicate`
	 * TODO:       in include/filesys/file.h. Note that parent should not return
	 * TODO:       from the fork() until this function successfully duplicates
	 * TODO:       the resources of parent.*/

	process_init ();

	/* Finally, switch to the newly created process. */
	if (succ)
		do_iret (&if_);
error:
	thread_exit ();
}
  • duplicate_pte (userprog/process.c)
    • 위 함수의 pml4_for_each에서 이 함수를 호출해 복사를 진행함
    • [구현 4-2] 이쪽도 애석하게도 우리가 채워야 할 게 많음
/*부모 프로세스의 주소 공간을 복사함 *
/* pml4_for_each에 이 함수를 보내면 됨 */
static bool duplicate_pte (uint64_t *pte, void *va, void *aux)

exec

[구현 5] exec - 현재 프로세스에서 file에 주어진 프로그램 실행.

  • file은 실제로 command line이라, 인자도 포함되어 있을 수 있음
  • echo x y z와 같은 식
// 사용자
int exec (const char *file) {
	return (pid_t) syscall1 (SYS_EXEC, file);
}
  • 성공 시 반환하지 않음
  • 실패 시 -1을 반환

사용해야 할 것 같은 함수들

  • process_exec (userprog/process.c)
    • [구현 1-3] Argument passing 할 때 열심히 구현했었음
    • 이 함수가 이미 실패 시 -1을 반환하고, 성공 시 반환을 하지 않음
int process_exec (void *f_name);

wait

[구현 6] wait- 자식 프로세스의 종료 대기 구현

// 사용자
int wait (pid_t pid) {
	return syscall1 (SYS_WAIT, pid);
}
  • 자식 프로세스 pid의 종료를 기다리고, 종료 코드를 반환한다.
    • pid가 살아 있으면, 종료 전까지 wait -> 종료될 때 pidexit에 전달한 종료 코드 반환
    • pidexit을 명시적으로 호출하지 않은 경우, 즉 커널에 의해 강제 terminate된 경우, -1 반환
  • pid가 이미 terminate된 프로세스인 경우, 해당 프로세스의 종료 코드를 바로 반환한다.
  • 자식의 종료 이후, 부모 프로세스는 process descriptor의 할당을 해제해야 한다.
  • 실패하는 경우, -1을 반환한다.
    • pid가 부모 프로세스의 직계 자식이 아닌 경우
    • 이미 동일한 pidwait이 호출되어, 대기 상태인 경우

사용해야 할 것 같은 함수들

  • process_wait (userprog/process.c)
    • [구현 1-1]에서 무한 루프 넣었던 함수였음
    • 여기는 주석으로 설명해 주는 것도 없어서 우리가 직접 넣어야 함
int process_wait (tid_t child_tid UNUSED);

exit

[구현 7] - 프로세스 terminate

void exit (int status) {
	syscall1 (SYS_EXIT, status);
	NOT_REACHED ();
}
  • thread_exit()를 호출해서 정리시키면 되는데, termination message를 출력해야 한다는 점이 종료,
  • thread_exit()process_exit()을 출력하는데, 이 과정에서 termination mesㄹsage 출력 및 프로세스의 종료(process_cleanup())이 이루어짐
void
process_exit (void) {
	// [구현 7] exit 및 exit 메시지 띄우기 구현
	struct thread *curr = thread_current ();
	printf("%s: exit(%d)\n", curr -> name, curr -> exit_code);

	process_cleanup ();
}
  • 문제는 process_exit은 인자를 받지 않아, 바로 status 값을 전달할 순 없음
// userprog/syscall.c
case SYS_EXIT:
	/* Terminate this process. */
	// [구현 7] 쓰레드/프로세스 종료
	thread_current() -> exit_code = (int)(f -> R.rdi);
	thread_exit();
	break;
  • struct threadint exit_code 멤버 추가 -> 시스템 핸들러 측에서 exit_codestatus(RDI)로 바꿔 주고, process_exit 실행하면 됨
  • 그 다음에 process_exit에서 현재 쓰레드의 exit_code값을 받아와 사용
profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글