pintos-kaist 프로젝트 : System call (1)

Leesoft·2023년 2월 26일
0

학교 과제 : PintOS

목록 보기
1/6
post-thumbnail

이 포스팅은 제가 친구와 PintOS 과제를 하면서 떠올린 생각이나 삽질을 하는 과정을 의식의 흐름대로 적은 글이며 글을 작성한 이후 원래 코드에서 일부 오타나 버그가 수정되었을 수 있습니다. 즉 이 포스팅은 정답을 알려주는 포스팅이 아님을 밝힙니다.

개강이 코앞인 지금, 이번 학기에 들을 '운영체제 및 실험' 과목의 악명 높은 과제인 'PintOS'를 살짝 보고 있다!

Project 1은 이미 완료했는데, 같이 하고 있는 친구가 README를 열심히 적고 있어서 따로 적지 않기로 하고 이 블로그는 내 공부를 위해서 그냥 작업 순서나 시행착오를 위주로 적을 것 같다!

System call

System call handler를 사실 거의 다 만들긴 했었는데 짜고 보니 부팅부터 안 되기도 하고, 중간에 여행도 가고 여러 일정이 있어서 코드를 잊어버린 관계로 처음부터 짜 보기로 했다.

저번에는 메뉴얼에 나온 순서대로 짰는데, 그렇게 하면 거의 앞에 나오는 exit(), fork(), exec(), wait()가 되게 어렵기도 하고 file 관련 system call이 나중에 나오는데 관련 필드를 thread 구조체에 추가하고 나면 결국 앞쪽 system call도 수정해야 해서 저기 어려운 친구들을 맨 나중에 짜기로 했다.

/* IMPLEMETED IN PROJECT 2-3. */
void syscall_handler(struct intr_frame *f UNUSED)
{
	// Cases from syscall-nr.h
	// The order of the implementation is from easy to hard.
	switch (f->R.rax)
	{
	/* Projects 2 and later. */
	/* System call related to halting. */
	case SYS_HALT:
		break;
	/* System call related to file system. */
	case SYS_CREATE:
		break;
	case SYS_REMOVE:
		break;
	case SYS_OPEN:
		break;
	case SYS_FILESIZE:
		break;
	/* System calls related to file I/O. */
	case SYS_READ:
		break;
	case SYS_WRITE:
		break;
	case SYS_SEEK:
		break;
	case SYS_TELL:
		break;
	case SYS_CLOSE:
		break;
	/* System call related to context change. */
	case SYS_EXIT:
		break;
	case SYS_FORK:
		break;
	case SYS_EXEC:
		break;
	case SYS_WAIT:
		break;
	/* Extra for Project 2 */
	case SYS_DUP2:
		break;
	case SYS_MOUNT:
		break;
	case SYS_UMOUNT:
		break;
	/* Project 3 and optionally project 4. */
	case SYS_MMAP:
		break;
	case SYS_MUNMAP:
		break;
	/* Project 4 only. */
	case SYS_CHDIR:
		break;
	case SYS_MKDIR:
		break;
	case SYS_READDIR:
		break;
	case SYS_ISDIR:
		break;
	case SYS_INUMBER:
		break;
	case SYS_SYMLINK:
		break;
	}
}

프로젝트 2에서는 아마 큰일이 없다면 이 순서대로 짤 것 같다!

File system 관련 fd_table 정의

파일을 관리하기 위해서는 file descriptor table이 필요하니 thread 구조체에 이걸 추가하고, 배열 fd_table이 일단은 하나의 페이지 안에 들어가게 하려고 최대 파일의 개수를 PGSIZE / 8로 결정했다.

struct file **fd_table;    /* Points to starting address of file descriptor table. */
int fd_idx;                /* File descriptor table index. */
struct file *running_file; /* Current file. */

thread 구조체에 뭔가 추가했으니, 초기화하는 코드도 봐야겠지? 여기서 palloc_get_page() 함수가 등장하는데 일단은 malloc(PGSIZE)랑 비슷한 느낌일까? 몰라서 또 Chat GPT에게 물어봤다!

둘 다 메모리를 할당해서 포인터를 던져주는 건 똑같지만, palloc_get_page()는 physical memory에 직접 메모리를 할당하고 malloc()은 heap에 할당하는 차이점과, 할당할 메모리를 찾는 알고리즘이 다르다는 차이점이 있다고 한다.

/* Does basic initialization of T as a blocked thread named
   NAME. */
static void
init_thread(struct thread *t, const char *name, int priority)
{
	// ...
	/* IMPLELEMENTED IN PROJECT 2-3.
	   Initializes data structures for file descriptor table. */
#ifdef USERPROG
	t->fd_table = (struct file **)palloc_get_page(PAL_ZERO);
	t->fd_table[0] = 0; /* For stdin. */
	t->fd_table[1] = 1; /* For stdout. */
#endif
}

여기서 stdin = 0을 하는 것과 stdout = 1을 하는 것은 그냥 내가 보기 편하게 적은 것인데, 굳이 안 해도 될 것 같긴 하다. 처음에는 이렇게 당연히 init_thread() 안에 fd_table을 초기화하면 되지 않을까 하고 다시 args-single을 실행했더니 이렇게 커널 패닉을 마주했다!

혹시 지금까지 system call이 계속 안 되던 건 이것 때문이었을까? 내용을 보면 thread_current() 함수가 호출이 되었는데 그 thread가 THREAD_RUNNING이 아니라는 것이다. 당연히 init_thread()THREAD_BLOCK인 구조체를 만드는 거니까 여기서 thread_current()를 쓰면 안 되겠지!

찾아보니 palloc_get_page() 가 사용되었는데 정의를 따라가보면 결국 락(lock)을 획득하는 부분이 있고 여기서 thread_current()가 사용되는 것이다! 그러니 palloc_get_page() 역시 init_thread() 안에서 쓰면 안 될 것 같다!

init_thread() 안에서는 thread_current()가 들어가는 함수를 쓰면 안 된다!

그러면, init_thread() 대신에 thread_create() 함수 내부에 이걸 초기화하는 코드를 넣고 반대로 종료되는 thread_exit() 함수 내부에 palloc_free_page()를 이용하여 정리하면 되겠다!

tid_t thread_create(const char *name, int priority,
					thread_func *function, void *aux)
{
	struct thread *t;
	tid_t tid;

	ASSERT(function != NULL);

	/* Allocate thread. */
	t = palloc_get_page(PAL_ZERO);
	if (t == NULL)
		return TID_ERROR;

#ifdef USERPROG
	/* IMPLELEMENTED IN PROJECT 2-3. Initializes data structures for file descriptor table. */
	t->fd_table = (struct file **)palloc_get_page(PAL_ZERO);
	if (t->fd_table == NULL)
	{
		palloc_free_page(t);
		return TID_ERROR;
	}
	t->fd_table[0] = 0; /* For stdin. */
	t->fd_table[1] = 1; /* For stdout. */
#endif

	/* Initialize thread. */
	init_thread(t, name, priority);
	tid = t->tid = allocate_tid();

	// ...

	/* Add to run queue. */
	thread_unblock(t);

	try_thread_preempt(); /* IMPLEMENTED IN PROJECT 1-2. */

	return tid;
}

init_thread 함수는 이 함수 내부에서 새로운 thread 구조체를 위한 공간을 할당받고 나서 실행되고, 이 공간 할당을 위해 이미 palloc_get_page()를 사용하니까 이 안에서는 palloc_get_page()를 이용하여 fd_table의 공간을 받을 수 있고, 마지막으로 혹시 할당을 못 받았을 때는 만들려 했던 thread 구조체의 공간도 해제하는 코드를 작성하였다.

thread_exit() 함수 내부에 process_exit() 함수가 있고, 이 안에 넣어주면 될 것 같다!

/* Exit the process. This function is called by thread_exit (). */
void process_exit(void)
{
	struct thread *curr = thread_current();
	/* TODO: Your code goes here.
	 * TODO: Implement process termination message (see
	 * TODO: project2/process_termination.html).
	 * TODO: We recommend you to implement process resource cleanup here. */
	palloc_free_page((void *)curr->fd_table); /* IMPLEMETED IN PROJECT 2-3. */
	process_cleanup();
}

여기에는 process termination message를 짜라고 되어 있는데, 이 함수 내부에서 thread의 이름과 exit code를 가져올 수 없으므로 여기서 짜는 것보다는 나중에 exit() 속에 넣으면 될 것 같다.

File system 관련 system call

file 관련된 내용은 사실 filesys.h에 함수가 다 정의되어 있어서 예외 처리를 약간 하고 가져다 쓰기만 하면 되는데, open()을 짤 때는 우리가 정의한 fd_table 배열에 추가하는 작업을 해야 한다. 파일이 삭제되어도 닫히지는 않으니, remove()에서는 굳이 배열에서 뺄 필요는 없겠지?

일단 여기까지만 하고 다시 args-single 예제를 돌려 보니 잘 돌아간다! wait()를 짜기 전까지는 테스트를 돌려 볼 수 없지만 이렇게 돌려 줘야 어디서 문제가 생겼는지 빠르게 찾아서 고칠 수 있으니 자주 돌려야지...!

File I/O 관련 system call

이것도 사실 file.h에 필요한 함수들이 거의 들어 있고 아래의 file 구조체 포인터를 갖다 주거나, fd = 0 또는 fd = 1인 경우 stdinstdout의 처리만 해 주면 될 것 같다!

struct thread *curr = thread_current();
struct file *f = curr->fd_table[fd];

궁금한 점 하나는 close()에서, curr->fd_table[fd] = NULL이라고 하면 포인터만 NULL로 만드는 건데, 이러면 원래 파일 자체는 안 건드리는 거니까 메모리 누수가 일어나지 않을까에 대한 내용이다!

/* Closes file descriptor fd. Exiting or terminating a process implicitly closes all its open file descriptors,
   as if by calling this function for each one. */
void close(int fd)
{
	if (fd < 2)
		return; /* Ignore stdin and stdout. */
	struct thread *curr = thread_current();
	curr->fd_table[fd] = NULL; /* Will this cause memory leak? */
}

하지만 애초에 open()close()는 포인터만 다루는 거지 file을 직접적으로 건드리지는 않고, file 구조체를 없애는 것은 close()가 아닌 remove()에서 하니까 괜찮겠지?

ChatGPT한테 혹시나 해서 물어봤는데 이것도 알려주는 걸 보고 소름이 돋고 말았다.

이 정도 짜고 나니까 저번에 왜 그렇게 실행이 안 됐는지 어느 정도 감이 온 것 같기도 한데, 어차피 기억이 안 나니까 다시 짜야지... 나머지 프로세스 관련 system call은 다른 포스트에 써야겠다!

profile
🧑‍💻 이제 막 시작한 빈 집 블로그...

0개의 댓글