이 포스팅은 제가 친구와 PintOS 과제를 하면서 떠올린 생각이나 삽질을 하는 과정을 의식의 흐름대로 적은 글이며 글을 작성한 이후 원래 코드에서 일부 오타나 버그가 수정되었을 수 있습니다. 즉 이 포스팅은 정답을 알려주는 포스팅이 아님을 밝힙니다.
개강이 코앞인 지금, 이번 학기에 들을 '운영체제 및 실험' 과목의 악명 높은 과제인 'PintOS'를 살짝 보고 있다!
Project 1은 이미 완료했는데, 같이 하고 있는 친구가 README를 열심히 적고 있어서 따로 적지 않기로 하고 이 블로그는 내 공부를 위해서 그냥 작업 순서나 시행착오를 위주로 적을 것 같다!
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 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
관련된 내용은 사실 filesys.h
에 함수가 다 정의되어 있어서 예외 처리를 약간 하고 가져다 쓰기만 하면 되는데, open()
을 짤 때는 우리가 정의한 fd_table
배열에 추가하는 작업을 해야 한다. 파일이 삭제되어도 닫히지는 않으니, remove()
에서는 굳이 배열에서 뺄 필요는 없겠지?
일단 여기까지만 하고 다시 args-single
예제를 돌려 보니 잘 돌아간다! wait()
를 짜기 전까지는 테스트를 돌려 볼 수 없지만 이렇게 돌려 줘야 어디서 문제가 생겼는지 빠르게 찾아서 고칠 수 있으니 자주 돌려야지...!
이것도 사실 file.h
에 필요한 함수들이 거의 들어 있고 아래의 file
구조체 포인터를 갖다 주거나, fd = 0
또는 fd = 1
인 경우 stdin
과 stdout
의 처리만 해 주면 될 것 같다!
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은 다른 포스트에 써야겠다!