운영체제(OS)/with PintOS

(Project2-User Programs) - System Calls

스탠딩 2023. 12. 26. 21:19

System Calls

Implement the following system calls.

나열된 프로토타입은 include/lib/user/syscall.h를 포함하는 사용자 프로그램에서 볼 수 있는 것입니다(이 헤더와 포함/lib/user의 다른 모든 헤더는 사용자 프로그램에서만 사용할 수 있습니다). 각 시스템 호출에 대한 시스템 호출 번호는 include/lib/syscall-nr.h에 정의되어 있습니다:

void syscall_handler(struct intr_frame *f)
{
    // TODO: Your implementation goes here.
    uint64_t args[5] = {f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8};
    thread_current()->user_rsp = f->rsp;

    switch ((int)(f->R.rax))
    {
    case SYS_HALT:
        sys_halt();
        break;

    case SYS_EXIT:
        sys_exit((int)args[0]);
        break;

    case SYS_FORK:
        SET_RAX(f, sys_fork((char *)args[0], f));
        break;

    case SYS_EXEC:
        SET_RAX(f, sys_exec((char *)args[0]));
        break;

    case SYS_WAIT:
        SET_RAX(f, sys_wait((tid_t)args[0]));
        break;

    case SYS_CREATE:
        SET_RAX(f, sys_create((char *)args[0], (unsigned)args[1]));
        break;

    case SYS_REMOVE:
        SET_RAX(f, sys_remove((char *)args[0]));
        break;

    case SYS_OPEN:
        SET_RAX(f, sys_open((char *)args[0]));
        break;

    case SYS_FILESIZE:
        SET_RAX(f, sys_filesize((int)args[0]));
        break;

    case SYS_READ:
        SET_RAX(f, sys_read((int)args[0], (void *)args[1], (unsigned)args[2]));
        break;

    case SYS_WRITE:
        SET_RAX(f, sys_write((int)args[0], (void *)args[1], (unsigned)args[2]));
        break;

    case SYS_SEEK:
        sys_seek((int)args[0], (unsigned)args[1]);
        break;

    case SYS_TELL:
        SET_RAX(f, sys_tell((int)args[0]));
        break;

    case SYS_CLOSE:
        sys_close((int)args[0]);
        break;

    case SYS_DUP2:
        SET_RAX(f, sys_dup2((int)args[0], (int)args[1]));
        break;

    case SYS_MMAP:
        SET_RAX(f, sys_mmap((void *)args[0], (size_t)args[1], (int)args[2], (int)args[3], (off_t)args[4]));
        break;

    case SYS_MUNMAP:
        sys_munmap((void *)args[0]);
        break;

    default:
        thread_exit();
    }
}
void halt (void);

power_off()를 호출하여 핀토를 종료합니다 (src/include/threads/init.h에 선언됨). 교착 상태에 빠질 수 있는 상황 등에 대한 일부 정보를 잃게 되므로 거의 사용하지 않는 것이 좋습니다.

 

void sys_halt(void)
{
    power_off();
    NOT_REACHED();
}

 

void exit (int status);

현재 사용자 프로그램을 종료하여 커널에 상태를 반환합니다. 프로세스의 부모가 기다리는 경우(아래 참조) 반환되는 상태가 이 상태입니다. 일반적으로 상태 0은 성공을 나타내고 0이 아닌 값은 오류를 나타냅니다.

 

void sys_exit(int status)
{
    struct thread *curr = thread_current();
    thread_current()->exit_status = status;
    thread_exit();
}
pid_t fork (const char *thread_name);

현재 프로세스의 복제본인 새 프로세스를 THREAD_NAME이라는 이름으로 생성합니다. 호출자가 저장한 레지스터인 %RBX, %RSP, %RBP, %R12 - %R15를 제외한 레지스터의 값은 복제할 필요가 없습니다. 자식 프로세스의 pid를 반환해야 하며, 그렇지 않으면 유효한 pid가 아니어야 합니다. 자식 프로세스에서 반환 값은 0이어야 합니다. 자식 프로세스는 파일 기술자 및 가상 메모리 공간을 포함한 중복 리소스를 가져야 합니다. 부모 프로세스는 자식 프로세스가 성공적으로 복제되었는지 여부를 알기 전까지는 포크에서 반환하지 않아야 합니다. 즉, 자식 프로세스가 리소스 복제에 실패하면 부모의 포크() 호출은 TID_ERROR를 반환해야 합니다.
템플릿은 threads/mmu.c의 pml4_for_each()를 사용하여 해당 페이지 테이블 구조를 포함한 전체 사용자 메모리 공간을 복사하지만, 전달된 pte_for_each_func의 누락된 부분을 채워야 합니다(가상 주소 참조).

 

tid_t sys_fork(const char *thread_name, struct intr_frame *f)
{
    check_address(thread_name);

    lock_acquire(&filesys_lock);
    tid_t fork_result = process_fork(thread_name, f);
    lock_release(&filesys_lock);

    return fork_result;
}
int exec (const char *cmd_line);

지정된 인수를 전달하여 현재 프로세스를 cmd_line에 지정된 이름의 실행 파일로 변경합니다. 성공하면 절대 반환되지 않습니다. 그렇지 않으면 어떤 이유로든 프로그램을 로드하거나 실행할 수 없는 경우 종료 상태 -1로 프로세스가 종료됩니다. 이 함수는 exec를 호출한 스레드의 이름을 변경하지 않습니다. 파일 설명자는 실행 호출 내내 열려 있는 상태로 유지된다는 점에 유의하세요.

 

int sys_exec(const char *cmd_line)
{
    check_address(cmd_line);

    void *cmd_copy;
    cmd_copy = palloc_get_page(0);
    if (cmd_copy == NULL)
        return -1;
    // cmd_copy += 0x8000000000;
    strlcpy(cmd_copy, cmd_line, PGSIZE);

    // create child process
    process_exec(cmd_copy);
    sys_exit(-1);
    return -1;
}
int wait (pid_t pid);

자식 프로세스 pid를 기다렸다가 자식의 종료 상태를 검색합니다. pid가 아직 살아있다면 종료될 때까지 기다립니다. 그런 다음 pid가 종료하기 위해 전달한 상태를 반환합니다. pid가 exit()를 호출하지 않았지만 커널에 의해 종료된 경우(예: 예외로 인해 종료된 경우), wait(pid)는 -1을 반환해야 합니다. 부모 프로세스가 wait를 호출할 때 이미 종료된 자식 프로세스를 기다리는 것은 완벽하게 합법적이지만, 커널은 여전히 부모가 자식의 종료 상태를 검색하거나 자식이 커널에 의해 종료되었음을 알 수 있도록 허용해야 합니다.
wait는 다음 조건 중 하나라도 참이면 실패하고 즉시 -1을 반환해야 합니다:

  • pid는 호출 프로세스의 직접 자식을 참조하지 않습니다. 호출 프로세스가 포크에 대한 성공적인 호출에서 반환 값으로 pid를 받은 경우에만 호출 프로세스의 직접 자식입니다. 자식은 상속되지 않습니다. A가 자식 B를 생성하고 B가 자식 프로세스 C를 생성하는 경우, B가 죽었더라도 A는 C를 기다릴 수 없습니다. 프로세스 A의 wait(C) 호출은 실패해야 합니다. 마찬가지로, 고아 프로세스는 부모 프로세스가 먼저 종료되면 새 부모에게 할당되지 않습니다.
  • 기다림을 호출하는 프로세스는 이미 pid에서 기다림을 호출했습니다. 즉, 프로세스는 최대 한 번만 특정 자식을 기다릴 수 있습니다.

 

프로세스는 자식을 얼마든지 생성할 수 있고, 어떤 순서로든 자식을 기다릴 수 있으며, 일부 또는 모든 자식을 기다리지 않고 종료할 수도 있습니다. 설계는 대기가 발생할 수 있는 모든 방식을 고려해야 합니다. 구조체 스레드를 포함한 프로세스의 모든 리소스는 부모가 자식을 기다리든 기다리지 않든, 그리고 자식이 부모보다 먼저 종료하든 나중에 종료하든 관계없이 해제되어야 합니다.
초기 프로세스가 종료될 때까지 핀토스가 종료되지 않도록 해야 합니다. 제공된 핀토스 코드는 main()(threads/init.c)에서 process_wait()(userprog/process.c)를 호출하여 이 작업을 시도합니다. 함수 상단의 주석에 따라 process_wait()을 구현한 다음 process_wait()의 관점에서 대기 시스템 호출을 구현하는 것이 좋습니다.
이 시스템 호출을 구현하려면 다른 어떤 것보다 훨씬 더 많은 작업이 필요합니다.

int sys_wait(tid_t pid)
{
    int status = process_wait(pid);
    return status;
}
bool create (const char *file, unsigned initial_size);

초기 초기_크기 바이트 크기의 파일이라는 새 파일을 생성합니다. 성공하면 참을 반환하고, 그렇지 않으면 거짓을 반환합니다. 새 파일을 만든다고 해서 파일이 열리지는 않습니다. 새 파일을 열려면 시스템 호출이 필요한 별도의 작업입니다.

 

bool sys_create(const char *file, unsigned initial_size)
{
    check_address(file);

    lock_acquire(&filesys_lock);
    bool create_result = filesys_create(file, initial_size);
    lock_release(&filesys_lock);
    return create_result;
}
bool remove (const char *file);

file이라는 파일을 삭제합니다. 성공하면 참을 반환하고, 그렇지 않으면 거짓을 반환합니다. 파일은 열려 있는지 여부에 관계없이 제거할 수 있으며 열려 있는 파일을 제거해도 닫히지 않습니다. 자세한 내용은 FAQ에서 열린 파일 제거하기를 참조하세요.

 

bool sys_remove(const char *file)
{
    // check validity
    check_address(file);

    lock_acquire(&filesys_lock);
    bool remove_result = filesys_remove(file);
    lock_release(&filesys_lock);
    return remove_result;
}
int open (const char *file);

file이라는 파일을 엽니다. "파일 기술자"(fd)라는 음수가 아닌 정수 핸들을 반환하거나, 파일을 열 수 없는 경우 -1을 반환합니다. 번호가 0과 1인 파일 기술자는 콘솔용으로 예약되어 있습니다. fd 0(STDIN_FILENO)은 표준 입력이고, fd 1(STDOUT_FILENO)은 표준 출력입니다. 열린 시스템 호출은 이러한 파일 기술자 중 어느 것도 반환하지 않으며, 아래에 명시적으로 설명된 경우에만 시스템 호출 인수로 유효합니다. 각 프로세스에는 독립적인 파일 기술자 집합이 있습니다. 파일 기술자는 자식 프로세스에 의해 상속됩니다. 단일 프로세스에서든 다른 프로세스에서든 단일 파일을 두 번 이상 열면 열 때마다 새 파일 설명자가 반환됩니다. 단일 파일에 대한 서로 다른 파일 기술자는 별도의 닫기 호출을 통해 독립적으로 닫히며 파일 위치를 공유하지 않습니다. 추가 작업을 수행하려면 0부터 시작하는 정수를 반환하는 Linux 스키마를 따라야 합니다.

 

int sys_open(const char *file)
{
    check_address(file);

    if (*file == '\0')
        return -1;

    lock_acquire(&filesys_lock);
    void *f = filesys_open(file);
    lock_release(&filesys_lock);

    if (f == NULL)
        return -1;
    f += 0x8000000000;

    return process_add_file(f);
}
int filesize (int fd);

fd로 열린 파일의 크기(바이트)를 반환합니다.

 

int sys_filesize(int fd)
{
    void *f = process_get_file(fd);

    if (f == NULL)
        return -1;
    f += 0x8000000000;

    lock_acquire(&filesys_lock);
    int length_result = (int)file_length(f);
    lock_release(&filesys_lock);
    return length_result;
}
int read (int fd, void *buffer, unsigned size);

파일의 크기를 바이트 단위로 반환합니다. fd로 열린 파일에서 버퍼로 크기 바이트 단위로 읽습니다. 실제로 읽은 바이트 수(파일 끝에서 0) 또는 파일을 읽을 수 없는 경우(파일 끝이 아닌 다른 조건으로 인해) -1을 반환합니다. fd 0은 input_getc()를 사용하여 키보드에서 읽습니다.

 

int sys_read(int fd, void *buffer, unsigned size)
{

    validate_buffer(buffer, size, true);
    lock_acquire(&filesys_lock);

    int read;

    void *f = process_get_file(fd);
    if (f == NULL)
    {
        lock_release(&filesys_lock);
        sys_exit(-1);
    }
    f += 0x8000000000;

    if (f == (void *)&stdin_file)
    {
        read = input_getc();
        lock_release(&filesys_lock);
        return read;
    }
    if (f == (void *)&stdout_file)
    {
        lock_release(&filesys_lock);
        sys_exit(-1);
    }
    read = (int)file_read(f, buffer, (off_t)size);

    lock_release(&filesys_lock);
    return read;
}
int write (int fd, const void *buffer, unsigned size);

버퍼에서 열린 파일 fd에 크기 바이트를 씁니다. 실제로 쓰여진 바이트 수를 반환하며, 일부 바이트가 쓰여지지 않은 경우 크기보다 작을 수 있습니다. 파일 끝을 지나서 쓰면 일반적으로 파일이 확장되지만 기본 파일 시스템에서는 파일 확장이 구현되지 않습니다. 예상되는 동작은 파일 끝 부분까지 가능한 한 많은 바이트를 쓰고 실제 쓰여진 수를 반환하거나, 바이트를 전혀 쓸 수 없는 경우 0을 반환하는 것입니다. fd 1은 콘솔에 씁니다. 콘솔에 쓰는 코드는 적어도 크기가 수백 바이트보다 크지 않다면 putbuf() 호출 한 번으로 모든 버퍼를 써야 합니다(큰 버퍼는 분할하는 것이 합리적입니다). 그렇지 않으면 서로 다른 프로세스에서 출력된 텍스트 줄이 콘솔에서 인터리빙되어 사람이 읽는 사람과 채점 스크립트 모두를 혼란스럽게 만들 수 있습니다.

 

int sys_write(int fd, const void *buffer, unsigned size)
{
    validate_buffer(buffer, size, false);
    lock_acquire(&filesys_lock);

    void *f = process_get_file(fd);
    if (f == NULL)
    {
        lock_release(&filesys_lock);
        sys_exit(-1);
    }

    f += 0x8000000000;

    if (f == (void *)&stdout_file)
    {
        putbuf(buffer, size);
        lock_release(&filesys_lock);
        return size;
    }

    if (f == (void *)&stdin_file)
    {
        lock_release(&filesys_lock);
        sys_exit(-1);
    }

    int written = (int)file_write(f, buffer, (off_t)size);
    lock_release(&filesys_lock);
    return written;
}
void seek (int fd, unsigned position);

열린 파일 fd에서 읽거나 쓸 다음 바이트를 파일 시작부터 바이트 단위로 표시되는 위치로 변경합니다(따라서 위치가 0이면 파일의 시작입니다). 파일의 현재 끝을 지나서 찾는 것은 오류가 아닙니다. 나중에 읽으면 파일의 끝을 나타내는 0바이트를 얻습니다. 나중에 쓰기는 파일을 확장하여 기록되지 않은 간격을 0으로 채웁니다. (단, 핀토에서는 프로젝트 4가 완료될 때까지 파일 길이가 고정되어 있으므로 파일 끝을 지나서 쓰면 오류가 반환됩니다). 이러한 의미는 파일 시스템에서 구현되며 시스템 호출 구현에 특별한 노력이 필요하지 않습니다.

 

void sys_seek(int fd, unsigned position)
{
    void *f = process_get_file(fd);

    if (f == NULL)
        return;
    f += 0x8000000000;

    lock_acquire(&filesys_lock);
    file_seek(f, (off_t)position);
    lock_release(&filesys_lock);
}
unsigned tell (int fd);

열린 파일 fd에서 읽거나 쓸 다음 바이트의 위치를 파일 시작 부분부터 바이트 단위로 반환합니다.

 

unsigned sys_tell(int fd)
{
    void *f = process_get_file(fd);

    if (f == NULL)
        return -1;

    f += 0x8000000000;
    lock_acquire(&filesys_lock);
    unsigned tell_result = (unsigned)file_tell(f);
    lock_release(&filesys_lock);
    return tell_result;
}
void close (int fd);

파일 기술자 fd를 닫습니다. 프로세스를 종료하거나 종료하면 마치 각 파일에 대해 이 함수를 호출하는 것처럼 열려 있는 모든 파일 기술자가 암시적으로 닫힙니다.

 

void sys_close(int fd)
{
    if (process_close_file(fd) == false)
        sys_exit(-1);
}