오늘 하루에 집중하자
  • [CS] PintOS Project 2 - User Program(3) - System Calls(feat. User Memory) - 추가예정
    2022년 12월 25일 03시 21분 48초에 업로드 된 글입니다.
    작성자: nickhealthy

    목표: system call - infrastructure(기반 인프라) 구현(feat. User Memory Access)

     

    User Memory Access


    syscalls 을 구현하려면, 사용자 가상 주소 공간(user virtual address space)에서 데이터를 읽고 쓰는 방법을 제공해야 한다. arguments를 얻을 때 이 기능은 필요하지 않다. 하지만 시스템 콜의 인자들로 제공된 포인터에서 데이터를 읽을 땐 이 기능을 통해 중계해야 한다. 하지만 이 기능을 구현하는 것은 조금 까다로울 수 있다. 만약 사용자가 유효하지 않은 포인터, 커널 메모리를 가리키는 포인터 또는 해당 영역 중 하나에 부분적으로 블록을 제공하면 어떻게 될까? user process를 종료(terminating)하여 이러한 경우를 처리하도록 해야한다. 다른 시스템 콜 기능을 구현하기 전 이 코드를 작성하고 테스트하는 것이 좋다. 

     

    System Calls


    userprog/syscall.c에 system call handler를 구현하시오.

    기본으로 제공하는 코드는 프로세스를 종료할 때 "system call!" 을 출력하는 것을 제공한다. 시스템 콜 번호(system call number)를 검색한 다음, 시스템 콜 인자들을 검색하고, 적절한 작업을 수행해야 한다. 

     

    System Call Details

    Project 1은 이미 OS가 user program으로부터 제어권을 다시 얻을 수 있는 한 가지 방법을 다루었다. 타이머 및 I/O 장치의 인터럽트를 처리를 수행함. 이러한 "external"(외부) interrupts 라고 불리우는데, 그 이유는 외부 독립체인 CPU에 의해 발생했기 때문에 외부 인터럽트(external interrupts)라고 불린다.

     

    OS는 프로그램 코드에서 발생하는 이벤트인 software exceptions도 처리한다.

    예를 들어 page fault 또는 0으로 나누기 같은 오류이다. user program이 OS에 services("system calls")를 요청할 수 있는 수단도 exceptions 이다.

     

    x86 아키텍처에서 시스템 콜은 다른 software exceptions들과 동일하게 처리되었다. 하지만 x86-64 제조업체는 'syscall '이라는 특별한 시스템 콜의 지침을 도입한다. 이렇게 하면 system call handler를 빠르게 호출할 수 있다.

     

    오늘날 syscall 명령어는 x86-64에서 시스템 콜을 호출하는데 가장 일반적으로 사용되는 수단이다. PintOS에서는 user program이 syscall 을 호출해 system call을 만든다. 시스템 콜 번호 와 이외 추가적인 인자들은(arguments) 두 지점을 제외한 syscall 명령을 호출하기 전, 일반적인 방식으로 레지스터에 설정될 것으로 예상한다.

    • %RAX 는 system call number 이다.
    • 4번째 인자는 %r10이다. %rcx 가 아님.

     

    따라서 system call handler(syscall_handler()) 가 제어를 받으면 시스템 콜 번호는 RAX 레지스터에 저장되고, 인자들은 %rdi, %rsi, %rdx, %r10, %r8, and %r9  순으로 들어가게 된다.

     

    caller의 레지스터는 struct intr_frame을 통해 접근가능하다.(struct intr_frame은 kernel stack 에 있다.)

     

    함수 리턴 값에 대한 x86-64 convention(규칙)은 RAX 레지스터에 배치하는 것이다. 값을 리턴하는 system call은 struct intr_frame 의 RAX 멤버를 수정하여 수행할 수 있다. 즉, 나중에 살펴보겠지만 create, remove 등의 시스템 콜에서 bool 타입으로 리턴 값을 주게 되는데, RAX 레지스터에 값을 저장하라는 뜻이다. 위에서도 언급했지만 RAX는 시스템 콜을 저장하는 레지스터이다.

     

    Implement the following system calls.

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

     

    void halt (void)

    power_off() (src/include/threads/init.h 에 선언되어 있음)를 호출하여 PintOS를 종료한다.

    이 함수를 수행하게 되면 deadlock situations(교착상태) 등 정보를 잃어버리기 때문에 이 함수는 자주 사용하지 않아야 한다.

     

    void exit (int status)

    현재 사용자 프로그램을 종료하고, 커널에 status를 리턴한다.

    프로세스의 부모가 자식 프로세스를 wait 하는 경우(아래 참조), 이 상태가 반환된다.

    일반적으로 상태 0은 success를 나타내고, 0이 아닌 값은 error를 나타낸다.

     

     

    pid_t fork (const char *thread_name)

    THREAD_NAME을 가지고 현재 프로세스를 복제(clone)하여 새로운 프로세스를 만든다. callee-saved 레지스터들인  (%RBX,%RSP,%RBP, and %R12-%R15) 를 제외한 레지스터들의 값들을 복제(clone)할 필요 없다. 반드시 자식 프로세스(child process)의 PID를 리턴해야한다. 그렇지 않으면 유효한 PID가 아닐 수 있다.

    자식 프로세스에서 리턴 값은 0이어야 한다. 자식은 가상 메모리 공간과 파일 식별자(file descriptor)를 포함한 리소스를 복제하여 가지고 있어야한다. 부모 프로세스는 자식 프로세스가 성공적으로 복제 되었는지의 여부를 알 때까지 fork 함수에서 리턴해서는 안된다. 즉, 자식 프로세스가 리소스를 복제하지 못한다면 fork() 함수를 호출한 부모 프로세스는 TID_ERROR를 리턴해야 한다. 

     

    기본 템플릿은 threads/mmu.c 의 pml4_for_each() 를 사용하여 전체 사용자 메모리 공간을 복사하고, 해당하는 pagetalbe의 구조들을 포함해야 한다. 

     

    int exec (const char *cmd_line)

    인수(cmd_line)를 통하여 주어진 이름으로 현재 프로세스를 실행가능하도록 변경한다.

    만약 함수가 성공적으로 수행되었다면 return 값은 절대로 없다. 함수가 실패한다면 (어떤 이유에서든 프로그램을 load하거나 실행할 수 없는 경우), 프로세스는 종료되어 exit 는 -1 상태로 종료된다. 

    이 함수는 exec() 을 호출한 쓰레드의 이름을 변경하지 않는다. exec() 호출 시에도 파일 식별자(file descriptors) 는 오픈된 상태이다.

     

    int wait (pid_t pid)

    fdfsd

     


     

    Ref

    https://casys-kaist.github.io/pintos-kaist/project2/user_memory.html

    https://www.youtube.com/watch?v=BoJ1eaE5F-I

    https://kangtegong.github.io/self-learning-cs/system_calls/syscalls.html

    댓글