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를 호출하는 경우도 고려하기
c언어에서 <
, >
따위의 비교 연산자는 2항 연산자이다.
따라서 1 < a < 10
은 (1 < a) < 10
과 동일하며, a가 1보다 크기만 하다면 항상 참이된다.
1 < a && a < 10
따위로 써야 의도한대로 동작한다.
일단 흐름을 대충 살펴보면
process_fork()
에서 thread_create
로 자식 프로세스를 생성한다.__do_fork
를 실행한다.__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
는 무엇인가?thread create
함수를 보면 새로 만들어지는 sturct thread
의 tf.rip
에 kernel_thread
주를 저장한다. tf.R.rdi
(첫 번째 인자)에는 thread_create
의 세 번째 인자인 함수 포인터를 저장한다. kernel_thread
함수는 인자로 받은 함수를 실행하고 실행이 끝나면 스레드를 exit하는 커널 함수이다.thread_launch
함수(schedule()
의 마지막 부분에서 호출되는)는 인자로 받은 struct thread *th
로의 문맥 전환을 수행한다. 그 방식을 살펴보면, 현재 스레드의 레지스터 정보를 현재 스레드 구조체의 tf
에 저장하고 실행할 스레드의 tf
주소를 인자로 하여 do_iret
을 호출한다. do_iret
에서는 인자로 받은 tf
주소로 부터 레지스터로 컨텍스트를 복원한다.tf
에는 컨텍스트 스위치 직후 수행하게될 작업의 컨텍스트를 저장되는 것을 알 수 있다.struct thread *if_
와의 차이는 if는 인터럽트(유저 모드에서 커널 모드로 전환) 시의 유저 컨텍스트를 저장하기 위해 사용되는 것이고, tf는 스레드 간 컨텍스트 스위치 시의 스레드 컨텍스트(커널 모드에서)를 저장하기 위해 사용되는 것이다. 만약 유저 컨텍스트 도중 스케줄러에 의해 다른 스레드(유저 컨텍스트)로 전환된다면, if 저장->tf 저장->(스위치 된 새로운 스레드의)tf 복원->if 복원 순으로 작업이 진행될 것이다.fork
함수는 유저 컨텍스트에서 호출되므로, fork
를 구현함에 있어서는 tf
가 아닌 유저 컨텍스트가 담겨있을 인터럽트 프레임을 복사해야 한다.