이 포스팅은 제가 친구와 PintOS 과제를 하면서 떠올린 생각이나 삽질을 하는 과정을 의식의 흐름대로 적은 글이며 글을 작성한 이후 원래 코드에서 일부 오타나 버그가 수정되었을 수 있습니다. 즉 이 포스팅은 정답을 알려주는 포스팅이 아님을 밝힙니다.
일단 이것부터 보자!
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의 오류를 모두 수정하였다!