sw사관학교정글/PintOS(KAIST's CS330 class)

[week09] PintOS - Project 2(User Programs) : System Calls All Pass 받기 위한 험난한 과정

D cron 2022. 1. 12. 02:08

앞선 게시물과 중복되는 코드가 있을 수 있다. 왜냐하면 All Pass 를 받고나서 정리를 했기 때문이다.
그래도 혹시 같은 문제로 고생하는 사람들을 위해 내가 어떻게 문제를 해결했는지 정리해 놓은 것을 공유한다.
확실히 fail을 pass로 만드는 과정에서 더 알아보게 되고, 더 남는게 많은 것 같다.

현재 8개 fail

FAIL tests/userprog/open-twice 
FAIL tests/userprog/read-stdout 
FAIL tests/userprog/write-stdin 
FAIL tests/userprog/exec-read 
FAIL tests/userprog/rox-simple 
FAIL tests/userprog/rox-child 
FAIL tests/userprog/rox-multichild 
FAIL tests/userprog/no-vm/multi-oom
// 8 of 95 tests failed.

tests/userprog/read-stdout ✔

read에서 stdout=1이 들어올 때 처리를 해주니까 pass로 바뀌었다.

int read(int fd, void *buffer, unsigned size)
{
    check_address(buffer);
    off_t read_byte;
    uint8_t *read_buffer = buffer;
    if (fd == 0) // stdin
    {
        char key;
        for (read_byte = 0; read_byte < size; read_byte++)
        {
            key = input_getc();
            *read_buffer++ = key;
            if (key == '\0')
            {
                break;
            }
        }
    }
    else if (fd == 1) // stdout
    {
        return -1;
    }
    else
    {
        struct file *read_file = find_file_by_fd(fd);
        if (read_file == NULL)
        {
            return -1;
        }
        lock_acquire(&filesys_lock);
        read_byte = file_read(read_file, buffer, size);
        lock_release(&filesys_lock);
    }
    return read_byte;

tests/userprog/write-stdin ✔

write에서 fd로 stdin이 들어올 때 처리를 해주면 pass로 바뀐다.

int write(int fd, const void *buffer, unsigned size)
{
    check_address(buffer);

    int write_result;

    if (fd == 0) // stdin
    {
        return 0;
    }
    else if (fd == 1) // stdout
    {
        putbuf(buffer, size);
        return size;
    }
    else
    {
        struct file *write_file = find_file_by_fd(fd);
        if (write_file == NULL)
        {
            return 0;
        }
        lock_acquire(&filesys_lock);
        off_t write_result = file_write(write_file, buffer, size);
        lock_release(&filesys_lock);
        return write_result;
    }
}

tests/userprog/open-twice ✔

open-twice.output을 살펴보면

Executing 'open-twice':
(open-twice) begin
(open-twice) open "sample.txt" once
(open-twice) open "sample.txt" again
(open-twice) open() returned 2 both times: FAILED

같은 파일을 두 번 열었는데 둘다 2를 return해서 실패한 것 같다.

같은 파일을 열더라도 다른 fd를 return해야 한다(GITBOOK에 써있음).

int add_file_to_fdt(struct file *file)
{
    struct thread *cur = thread_current();
    struct file **fdt = cur->fd_table;

    // Find open spot from the front
    //  fd 위치가 제한 범위 넘지않고, fd table의 인덱스 위치와 일치한다면
    while (cur->fd_idx < FDCOUNT_LIMIT && fdt[cur->fd_idx])
    {
        cur->fd_idx++; //현재의 fd_idx를 +1
    }

    ...
}

이 코드를 추가해주었다.


?!


이 코드를 추가해주니까 open-twice 문제가 해결되었는데

pass tests/userprog/open-twice // -> clear

갑자기 exec-read와 multi-oom도 pass가 떠버렸다!!

pass tests/userprog/exec-read // -> clear
pass tests/userprog/no-vm/multi-oom // -> clear

그 이유를 한 번 생각해보면

exec-read에서 부모 thread가 sample.txt를 open한 후에 child가 다시 한번 sample.txt를 open하는데 이 때, fd가 다르게 부여받지 못해서 fail이 떴던 것으로 유추해볼 수 있다.


multi-oom은 메모리 누수(memory leak)를 check하는데 추가해준 코드와 정확하게 어떤 연관관계로 통과하게 되었을까? 이 코드를 수정하기 전에 분명 모든 palloc_get_page 에 대해 palloc_free_page 을 해주었다.

tests/userprog/no-vm/muti-oom.c 를 들어가보면 주석에

/* Open as many files as we can, up to fdmax.
     Depending on how file descriptors are allocated inside
     the kernel, open() may fail if the kernel is low on memory.
     A low-memory condition in open() should not lead to the
     termination of the process.  */

라고 써있는데,

재귀적으로 child가 fork를 실패할 때까지 돌린다.

fdmax까지 file을 계속 open하는데, 이때 중복되는 fd에서 문제가 생기는 것으로 유추해볼 수 있다.


multi-oom에서 memory leak을 측정하는 방식이 신기했다. file을 계속 열다보면 kernel의 memory가 꽉 차게 되어서 못 여는 순간이 찾아오고, 이 때 kernel이 매번 동일한 수준의 깊이를 허용하는지 측정한다고 한다.

tests/userprog/exec-read ✔

exec-read.output을 살펴보면 다음과 같다.

Boot complete.
Putting 'exec-read' into the file system...
Putting 'sample.txt' into the file system...
Putting 'child-read' into the file system...
Executing 'exec-read':
(exec-read) begin
(exec-read) open "sample.txt"
(exec-read) read "sample.txt" first 20 bytes
(child-read) begin
(child-read) open "sample.txt"
(child-read) open "sample.txt": FAILED
child-read: exit(1)
(exec-read) Parent success
(exec-read) end
exec-read: exit(0)
Execution of 'exec-read' complete.

얘는 한번 연 파일을 다른 thread가 또 여니까 failed 가 떴다... ?

→ open-twice test 해결하니까 해결됨

tests/userprog/no-vm/multi-oom ✔

pintos -v -k -T 600 -m 20 -m 20   --fs-disk=10 -p tests/userprog/no-vm/multi-oom:multi-oom -- -q   -f run multi-oom < /dev/null 2> tests/userprog/no-vm/multi-oom.errors > tests/userprog/no-vm/multi-oom.output

여기도 시간 오래걸리는데, multi-oom은 뭐하는 놈일까??

multi-oom은 메모리 누수를 검사한다.

그렇기 때문에 여기서는 palloc_get_page에 대해서 palloc_free_page 을 꼭 해줘야한다.

process_exit()에서 받아온 palloc을 해제해준다.

void process_exit(void)
{
    ...

    // for multi-oom(메모리 누수)
    palloc_free_multiple(cur->fd_table, FDT_PAGES);

    ...
}

→ open-twice test 해결하니까 해결됨

FAIL tests/userprog/rox-simple ✔

FAIL tests/userprog/rox-child ✔
FAIL tests/userprog/rox-multichild ✔

현재 실행중인 파일을 쓰지 못하게 해준다.

// project 2 : system call
// 현재 실행중인 파일의 경우 write 할 수 없도록 설정
t->running = file;
file_deny_write(file);

load에서 file_close(file)을 해주면 file이 닫히면서 lock이 풀리게 된다. 따라서 load에서 닫지 말고(주석처리)

// userprog/process.c

static bool
load(const char *file_name, struct intr_frame *if_){
...
done:
    // file_close(file);
    return success;
}

process_exit()에서 닫아준다.

void process_exit(void)
{
    ...
    // for rox- (실행중에 수정 못하도록)
    file_close(cur->running);

    ...
}

결과 → All 95 tests passed.