Pintos week02 System calls and handlers

정글일지 33

System calls and Handlers

개요

주 목표

  • 기존 : system call handler table 비어있음.
  • 수정 후
    • Pintos의 system call handler 완성
    • 유저에게 서비스를 제공하기 위한 시스템 콜 추가
      • 프로세스 관련 : halt, exit, exec, wait
      • 파일 관련 : create, remove, open, filesize, read, write, seek, tell, close

수정할 파일

  • threads/thread
  • userprog/syscall
  • userprog/process

System call

  • 운영체제가 제공하는 서비스를 위한 programming interface

  • 유저 모드 프로그램이 커널 기능을 사용하게 해준다.
  • 시스템 콜은 커널 모드에서 실행되고 유저 모드에게 반환한다.
  • 시스템 콜의 주요 포인트는 시스템 콜을 호출하기 위해 하드웨어 interrupts가 발생하여서 실행 모드의 우선순위가 특수(special) 모드로 상승한다는 점이다.

Pintos에서 시스템 콜의 호출 프로세스

그림

system call handler

시스템 콜 number를 사용하여 시스템 콜 handler에서 시스템 콜을 호출한다.

시스템 콜 number : syscall.c에 정의되어 있음

Requirement

  • 시스템 콜 handler가 시스템 콜 number를 사용하여 시스템 콜을 호출하도록 해야함
  • parameter 리스트에 있는 포인터의 타당함(validation) 확인
    • 이 포인터들은 kernel area가 아닌 user area를 가리켜야 한다.
    • 이 포인터들이 타당한 주소를 가리키지 않으면, page fault이다.
  • user stack의 arguments를 kernel로 복사함
  • eax 레지스터에서 시스템 콜의 반환 값을 저장함

Address Validation

  • 유저는 시스템 콜을 통해 잘못된 포인터를 전달할 수 있다.
    • null pointer나 mapping되지 않은 가상 메모리를 가리키는 포인터
    • 커널 가상 메모리 주소 공간(PHYS_BASE)을 가리키는 포인터
  • 커널은 부적절한 포인터를 감지해야 하고 커널이나 다른 작동중인 프로세스들에게 악영향 없이 프로세스를 종결시켜 한다.
  • 감지하는 방법
    • 방법 1 : 유저가 제공하는 포인터의 타당성 검사
      • 유저 메모리 접근을 다루는 가장 간단한 방식
      • userprog/pagedir.c 나 threads/vaddr.h 에 있는 함수 사용
      • 포인터의 타당성 확인한 페이지만 lock이나 할당
    • 방법 2 : PHYS_BASE 밑만 가리키는 것만 확인
      • 잘못된 포인터는 page_fault 야기. page_fault() 코드를 수정하여 해결 가능
      • MMU에서 강점이 있어 첫번째 방법보다 빠름
      • 보통 실제 kernel에서 사용
      • 메모리 접근에서 에러코드를 반환할 방법이 없기 때문에 더 어렵다.
      • 후술할 함수들을 사용하여 처리할 수 있음
      • get_user, put_user
      • page_fault에서 eax0xffffffff로 설정하고 이전의 값을 eip로 복사하는 방법도 있음

추가할 시스템 콜

  • void halt(void)
    • Pintos 종료
    • void shutdown_power_off(void) 사용
  • void exit(int status)
    • 프로세스를 나간다(exit)
    • void thread_exit(void) 사용
    • “Name of process: exit(status)”라는 메시지를 출력해야 함
  • pid_t exec (const char *cmd_line)
    • 자식 프로세스를 만들고 cmd_line에 상응하는 프로그램을 실행한다.
  • int wait (pid_t pid)
    • process id 가 pid인 자식 프로세스의 종결을 기다린다.

Process Hierarchy(계층)

  • 존재하는 프로세스를 프로세스 계층에 따라 상승시킨다.
  • 부모 자식간 관계를 나타낸다.
    • 부모 프로세스를 가리키는 포인터 : struct thread*
    • 형제 프로세스를 가리키는 포인터 : struct list
    • 자식 프로세스를 가리키는 포인터 : struct list_elem

wait() system call

int wait (pid_t pid)

  • 자식 프로세스 pid가 exit하길 기다리고 exit status를 되찾아옴
  • pid가 아직 있다면, 종결되길 기다린다. pid가 exit하며 전달받은 status 반환
  • pidexit를 호출하지 않았지만 kernel이 종결시켰다면, -1을 반환
  • 부모 프로세스는 종결된 자식 프로세스를 기다리기 위해 호출할 수 있다.
    • 종결된 자식 프로세스의 exit status 반환
  • 자식이 종결된 이후, 부모는 자식의 process descriptor을 비할당화 해야한다.
  • wait이 실패하고 -1을 반환하는 경우
    • pid 호출 프로세스의 직접적인 자식을 가리키지 않을 때
    • wait을 호출한 프로세스가 이미 pidwait을 호출했었을

Correct implementation

  • process_wait()
    • child_tid를 사용하여 자식 프로세스의 descriptor를 찾는다.
    • 자식 프로세스가 exit 하기 전까지 호출자를 막는다.
    • 자식 프로세스가 exit하면, 자식 프로세스의 descriptor를 비할당화 하고 자식 프로세스의 exit status를 반환한다.
  • Semaphore
    • 쓰레드 구조체에 “wait”에 관한 세마포어 추가
    • 쓰레드가 처음 생성될 때 세마포어는 0으로 초기화 된다.
    • wait(tid)에서, tid의 세마포어에 대한 sema_down을 호출한다.
    • 프로세스 tid의 exit()에서, sema_up을 호출한다.
    • sema_downsema_up의 올바른 자리가 중요하다
  • Exit status
    • 쓰레드 구조체에 exit status를 나타내는 field 추가

wait을 호출한 Parent와 child 의 흐름

그림

exec() system call

pid_t exec(const *cmd_line)

  • cmd_line을 실행하는 프로그램 가동
  • 쓰레드를 생성하고 가동한다. pintos에서 exec()는 unix에서 fork()+exec()와 같음
  • arguments를 실행될 프로그램에 전달
  • 새로운 자식 프로세스의 pid전달
  • 프로그램을 load하거나 프로세스를 생성하는데 실패하면 -1 반환
  • exec를 호출하는 부모 프로세스는 자식 프로세스가 생성되고 excutable을 완전히 load하기 전까지 기다려야 한다.

Kernel function for exec() : process_execute()

  • 부모는 자식 프로세스가 성공적으로 생성되고 binary file이 완벽히 load 된걸 알 때까지 기다려야 한다.
  • 세마포어

    • exec()에 대한 세마포어를 쓰레드 구조체에 추가한다.
    • 쓰레드가 처음 생성될 때 세마포어는 0으로 초기화 된다.
    • sema_down 을 호출하여 자식 프로세스가 excutable file을 잘 load하길 기다린다.
    • excutable file이 load되면 sema_up을 호출한다.
    • sema_downsema_up의 위치가 중요
  • load status
    • 쓰레드 구조체에서, 파일이 성공적으로 load되었는지를 나타내는 field가 필요함

exec를 호출한 부모와 자식의 흐름

그림

exit()

  • 유저 프로그램을 종결시키고 커널에게 상태 반환
  • 프로세스의 부모가 기다린다면, 이 함수가 반환될 status임
1
2
3
4
5
6
7
void exit (int status)
{
    struct thread *cur = thread_current ();
    /* 프로세스 descriptor에 exit status 저장 */
    printf("%s: exit(%d)\n" , cur -> name , status);
    thread_exit();
}

Kernenl function for exit() : thread_exit

  • Exit status
    • 프로세스의 status에 저장한다.
  • 세마포어
    • 현재 프로세스에 sema_up 호출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 스케쥴에서 현재 쓰레드를 제외하고(deschedule) 파괴한다. 호출자에게 반환되지 않는다. */
void
    thread_exit(void)
{
    ASSERT (!intr_context ());

    #ifdef USERPROG
    	process_exit();
    #endif

    /* 모든 쓰레드 리스트에서 쓰레드를 제거하고, status를 dying으로 설정하고, 다른 프로세스를 스케쥴링한다. 그 프로세스는 thread_schedule_tail()를 호출할때 우릴 파괴한다..? */
    intr_disable ();
    list_remove (&thread_current()->allelem);
    thread_current ()-> status = THREAD_DYING;
    schedule ();
    NOT_REACHED ();
}