핀토스 삽질 기록 9/22

윤종성·2024년 9월 22일
0

핀토스

목록 보기
3/7

wait()

int
process_wait (tid_t child_tid) {
    struct thread *t = thread_current();
    struct thread *child = tid_to_threadp(child_tid);
    tid_t exit_status;
  
    sema_down(&child->wait_sema);
    exit_status = child->exit_status;
    list_remove(&child->child_elem);
    
    return exit_status; // child_tid
}

세마포어로 자식스레드가 종료되기까지 대기하도록 구성했다.
스레드 exit에서는 새로 스케줄링하기 전에 세마포어를 up시켜서 대기하는 부모스레드가 스레드가 종료되었음을 알 수 있도록한다.
자식스레드가 모두 종료되기 전, 세마포어를 up하면서 부모스레드로 cpu를 양보하는 상황이 생길 수도 있다고 생각했다.
그러면 자식스레드가 완전히 종료되지 않았는데 부모스레드가 process_wait을 리턴하는 상황이 생긴다.
하지만 process_exit도 인터럽트 컨텍스트에서 다뤄지기 때문에 즉시 양도되지 않는다.
즉, 양자성을 가지므로 자식스레드가 완전히 종료된 후에야 부모스레드가 cpu를 점유할 수 있다.
해야할 일: 자식스레드가 종료된 후 wait를 호출하는 경우도 고려하기

close() & fd_to_file()

c언어에서 <, >따위의 비교 연산자는 2항 연산자이다.
따라서 1 < a < 10(1 < a) < 10과 동일하며, a가 1보다 크기만 하다면 항상 참이된다.
1 < a && a < 10따위로 써야 의도한대로 동작한다.

process_fork()

일단 흐름을 대충 살펴보면

  1. process_fork()에서 thread_create로 자식 프로세스를 생성한다.
  2. 자식 프로세스가 실행되면 __do_fork를 실행한다.
  3. __do_fork에서는 페이지 테이블, 파일테이블을 복사하고, 부모 프로세스의 인터럽트 프레임을 복사해온다. 따라서 유저모드로 전환됐을 때엔 부모 프로세스에서 fork가 리턴된 시점의 컨텍스트와 동일해진다.

다만 __do_fork주석에 써있는 힌트를 보면 부모 프로세스의 struct thread의 멤버 tf에는 인터럽트 프레임이 저장되어 있지 않다고 한다.
나는 처음에 이 힌트가 thread_create에 의해 tf에 담긴 기존의 컨텍스트를 변경시키기 때문에 thread_create를 호출하기 전 백업해 두라는 의미인 줄 알았다.

    t->tf_user = t->tf;
    child_tid = thread_create (name,
                        PRI_DEFAULT, __do_fork, thread_current ());

... 그래서 말 그대로 백업을 해놓고 tf_user를 사용했다.
하지만 __do_fork에서 복사해야 하는 것은 tf가 아니라 유저 컨텍스트가 담긴 인터럽트 프레임이다.
인터럽트 핸들러에서 전달받은 주소(struct intr_frame *f)에 인터럽트 프레임이 저장되어있다.

그렇다면 tf는 무엇인가?

  1. thread create함수를 보면 새로 만들어지는 sturct threadtf.ripkernel_thread주를 저장한다. tf.R.rdi(첫 번째 인자)에는 thread_create의 세 번째 인자인 함수 포인터를 저장한다. kernel_thread함수는 인자로 받은 함수를 실행하고 실행이 끝나면 스레드를 exit하는 커널 함수이다.
  2. 또한 thread_launch함수(schedule()의 마지막 부분에서 호출되는)는 인자로 받은 struct thread *th로의 문맥 전환을 수행한다. 그 방식을 살펴보면, 현재 스레드의 레지스터 정보를 현재 스레드 구조체의 tf에 저장하고 실행할 스레드의 tf주소를 인자로 하여 do_iret을 호출한다. do_iret에서는 인자로 받은 tf 주소로 부터 레지스터로 컨텍스트를 복원한다.
  3. 즉, tf에는 컨텍스트 스위치 직후 수행하게될 작업의 컨텍스트를 저장되는 것을 알 수 있다.
  4. 인터럽트 핸들러에서 사용하는 struct thread *if_와의 차이는 if는 인터럽트(유저 모드에서 커널 모드로 전환) 시의 유저 컨텍스트를 저장하기 위해 사용되는 것이고, tf는 스레드 간 컨텍스트 스위치 시의 스레드 컨텍스트(커널 모드에서)를 저장하기 위해 사용되는 것이다. 만약 유저 컨텍스트 도중 스케줄러에 의해 다른 스레드(유저 컨텍스트)로 전환된다면, if 저장->tf 저장->(스위치 된 새로운 스레드의)tf 복원->if 복원 순으로 작업이 진행될 것이다.
  5. 그렇다면 fork함수는 유저 컨텍스트에서 호출되므로, fork를 구현함에 있어서는 tf가 아닌 유저 컨텍스트가 담겨있을 인터럽트 프레임을 복사해야 한다.
profile
알을 깬 개발자

0개의 댓글