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

Leesoft·2023년 3월 5일
0

학교 과제 : PintOS

목록 보기
4/6
post-thumbnail

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

단일 프로세스 파일 관련 system call

일단 이것부터 보자!

FAIL tests/userprog/open-null
FAIL tests/userprog/open-bad-ptr

우리가 짠 _open()은 다음과 같다.

/* Opens the file called file.
   Returns a nonnegative integer handle called a "file descriptor" (fd),
   or -1 if the file could not be opened. */
int _open(const char *file)
{
	struct file *f = filesys_open(file);
	if (f == NULL)
		return -1;
	struct thread *curr = thread_current();
	int fd = 2; /* 0 for stdin and 1 for stdout. */
	while (curr->fd_table[fd] != NULL)
		fd++;
	if (fd == MAX_FILE_NUM)
		return -1;
	curr->fd_table[fd] = f;
	curr->running_file = f;
	return fd;
}

open_null 테스트 케이스에는 open(NULL)이 있는데, filesys_open()과 거기에 사용되는 함수를 보면 파일 이름이 NULL이 아니라는 ASSERT가 있었다. 즉 우리가 미리 NULL을 체크해서 filesys_open()까지 가지 않도록 해야겠다.

void check_address(void *p)
{
	if (p == NULL | is_kernel_vaddr(p) | (pml4_get_page(thread_current()->pml4, p) == NULL))
		_exit(-1); /* This system call will be implemented in project 2-3. */
}

int _open(const char *file)
{
	check_address(file);
	struct file *f = filesys_open(file);
    // ...
{
pass tests/userprog/open-null
pass tests/userprog/open-bad-ptr

다음은 파일 관련 system call인데, fd를 검사하는 로직을 추가하지 않은 것 같으니 fd를 찾았을 때 나오는 file 구조체의 주소를 앞의 check_address() 함수에 넣어 검사하는 부분을 추가하면 어느 정도 해결되지 않을까?

FAIL tests/userprog/close-bad-fd
FAIL tests/userprog/read-bad-ptr
FAIL tests/userprog/read-bad-fd
FAIL tests/userprog/write-normal
FAIL tests/userprog/write-bad-ptr
FAIL tests/userprog/write-boundary
FAIL tests/userprog/write-zero
FAIL tests/userprog/write-bad-fd

그 결과 대부분의 테스트가 다시 실패하였는데, 아직 Project 2에서는 User program이 커널에서 동작하는데 우리가 커널 주소를 확인하는 코드를 짰기 때문에 아직 테스트를 통과하지 못한다고 생각하고, 일단은 NULL인지만 확인하기로 하였다.

void check_address(void *p)
{
	/* This check will be used in project 3,
	since all user program runs in kernel context in project 2.
	p == NULL || is_kernel_vaddr(p) || (pml4e_walk(thread_current()->pml4, p, false) == NULL) */
	if (p == NULL)
		_exit(-1); /* This system call will be implemented in project 2-3. */
}

테스트 결과 상당한 발전이 있었고, 단일 파일 관련 테스트는 이것만 남았다!

FAIL tests/userprog/create-bad-ptr
FAIL tests/userprog/open-bad-ptr
FAIL tests/userprog/close-bad-fd
FAIL tests/userprog/read-bad-ptr
FAIL tests/userprog/read-bad-fd
FAIL tests/userprog/write-bad-ptr
FAIL tests/userprog/write-bad-fd

bad가 들어간 테스트는 이렇게 생겼다! 프로그램이 작동하는 것은 커널이지만, 파일을 만들고 여는 등의 작업은 유저 영역에서 해야 하는데 이를 확인하지 않아 발생한 문제라고 생각한다. 그래서 다시 check_address() 함수를 원상복구시켰다!

/* Passes a bad pointer to the create system call,
   which must cause the process to be terminated with exit code
   -1. */

#include "tests/lib.h"
#include "tests/main.h"

void
test_main (void) 
{
  msg ("create(0x20101234): %d", create ((char *) 0x20101234, 0));
}

또한 bad-fd 관련 문제를 보면 ``fd의 값으로 fd_table의 최대 크기를 초과한 값이나 음수 값 등 이상한 값이 들어오는 부분을 고치면 되지 않을까? 그래서 fd를 받는 함수에서 fd >= MAX_FILE_NUM```을 체크하는 부분을 추가하였다.

void _close(int fd)
{
	if (fd < 2 || fd >= MAX_FILE_NUM)
		return; /* Ignore stdin and stdout. */
	struct thread *curr = thread_current();
	curr->fd_table[fd] = NULL;
	if (curr->running_file == curr->fd_table[fd])
		curr->running_file = NULL;
}

이제 이것만 남았다!

FAIL tests/userprog/read-normal
FAIL tests/userprog/read-boundary
FAIL tests/userprog/read-zero
FAIL tests/userprog/write-normal
FAIL tests/userprog/write-boundary
FAIL tests/userprog/write-zero

확인해보니 read()write()exit(-1)을 반환하는 것 같았다.

FAIL
Test output failed to match any acceptable form.

Acceptable output:
  (write-normal) begin
  (write-normal) create "test.txt"
  (write-normal) open "test.txt"
  (write-normal) end
  write-normal: exit(0)
Differences in `diff -u' format:
  (write-normal) begin
  (write-normal) create "test.txt"
  (write-normal) open "test.txt"
- (write-normal) end
- write-normal: exit(0)
+ write-normal: exit(-1)

이 두 함수는 buffer 포인터를 받는데, 이것이 이상한 포인터일 수도 있으니 체크해 주도록 하자!

FAIL tests/userprog/read-normal

이제 하나 남았다! 이번에도 이게 exit(-1)로 끝나는 것 같은데 왜지?

void
test_main (void) 
{
  check_file ("sample.txt", sample, sizeof sample - 1);
}

이것 때문에 check_address()에서 exit()가 되는 상황을 나누어 프린트해 보니, buffer가 커널 주소기 때문에 실패하였다!

check_file() 내부를 보면 check_file_handle()이라는 함수가 있고, 현재 프로세스가 커널 영역에서 돌아가고 있기 때문에 여기에서 read()를 호출하면 지역 변수 block의 주소가 커널 주소가 될 것이다.

void
check_file_handle (int fd,
                   const char *file_name, const void *buf_, size_t size) 
{
  // ...
  while (ofs < size)
    {
      char block[512];
      // ...
      ret_val = read (fd, block, block_size);
      // ...
    }

  // ...
}

즉, 아까 원상복구시켰던 check_address() 함수를 수정해야 하는데 유저 영역의 주소에서만 작동하는 pml4_get_page() 함수 대신 커널 영역의 주소를 확인하는 부분을 없애고 이 ASSERT 문이 없는 pml4e_walk() 함수를 사용하기로 하였다!

void *
pml4_get_page (uint64_t *pml4, const void *uaddr) {
	ASSERT (is_user_vaddr (uaddr));
    // ...
}

void check_address(void *p)
{
	if (p == NULL || pml4e_walk(thread_current()->pml4, p, false) == NULL)
		_exit(-1); /* This system call will be implemented in project 2-3. */
}

이렇게 단일 프로세스에서 일어나는 파일 관련 system call의 오류를 모두 수정하였다!

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

0개의 댓글