
너무 어려워서 글이 다소 날림조입니다.
이 글보다는 훌륭한 정글 동기들과 소통하면서 집단지성을 활용하시는 게 좋을 듯해보입니다.
소스코드는 깃허브에 올라와 있습니다.
wait 시스템 콜
wait는 fork로 생성한 자식 프로세스만 기다릴 수 있게 동작해야 함struct child_infostruct child_info 구조체 선언// include/threads/thread.h
struct child_info {
tid_t tid; // 쓰레드 번호
struct list_elem c_elem; // 리스트 삽입용
int exit_code; // 삭제코드 저장
struct semaphore w_sema; // wait 시스템콜을 위한 세마포어
bool alive; // 자신의 생존 여부
bool parent_alive; // 부모의 생존 여부
bool waiting; // 부모가 자신을 대기하는 중인지
};
struct thread 수정// include/threads/thread.h
struct thread {
// 생략....
// wait 시스템 콜에 필요
struct list children; /* 자식 프로세스의 리스트 */
struct child_info *ci; /* 자신의 child_info의 주소 저장*/
}
struct thread에 다음과 같은 멤버를 추가한다struct list children: 자식들을 연결 리스트로 관리. 편의상 앞으로 자식 리스트로 부르겠음struct child_info의 struct list_elem c_elem을 삽입struct child_info *ci: 본인의 child_info의 주소 저장fork() 시스템 콜에서 자식 프로세스를 생성할 때마다 실행되는 process_init()에 아래 코드 추가// userprog/process.c 내 process_init() 함수 수정
// [구현 8-1] children / child_info 초기화
list_init(&(current -> children)); // children list 초기화
// struct child_info 초기화
struct child_info *ci = palloc_get_page(PAL_ZERO);
ci -> parent_alive = true; // 부모로부터 fork되어 생성된 거니, 부모는 당연히 살아 있음
ci -> tid = current -> tid; // tid는 자신의 tid로
ci -> alive = true; // 쓰레드 자신은 살아 있음
ci -> waiting = false; // 지금은 부모가 대기하고 있지 않음
sema_init(&(ci -> w_sema), 0); // 세마포어 0으로 초기화
current -> ci = ci; // struct thread의 ci에 주소 저장
왜 굳이 struct child_info를 만드나요? struct thread에 저장하면 안 되나요?
struct thread는 thread_exit() 하면 스케줄러가 나중에 메모리 할당을 free하게끔 구현되어 있습니다. (threads/thread.c의 do_schedule 함수의 palloc_free_page 참고하기)struct child_info를 만들어 줍니다. 대신 할당 해제는 저희가 알아서 해 줘야 하는데, 그것도 이따가 구현해야 함 ^^wait() 보내고 자식이 종료된 걸 확인하면, 그때 free해 줍니다.exit()할 때 스스로 free해 줘야 합니다. (그래서 parent_alive 멤버가 있는겁니다...)fork() 시스템 콜에서 자식이 생성될 때마다, 부모의 struct list children에 생성된 자식을 삽입해야 함__do_fork()을 수정하기// userprog/process.c 내 __do_fork() 함수 수정
/* Finally, switch to the newly created process. */
if (succ){
// [구현 8-2] fork 이후 부모/자식 관계 설정
list_push_back(&(parent -> children), &(current -> ci -> c_elem)); // 부모의 children 리스트에 자식 삽입
// [구현 7-6] 자식프로세스 처리 종료 후 세마포어 올리기
sema_up(&(parent -> f_sema));
do_iret (&if_);
}
sema_up이랑 do_iret 하기 직전에 자식을 삽입process_wait 수정process_wait 함수를 수정해야 함process_wait은 기다릴 자식의 쓰레드 id child_tid를 전달받음tid가 child_tid가 동일한 자식을 찾기struct child_info의 waiting 멤버로 확인 가능)-1을 반환해야 함struct child_info를 child 변수에 저장// userprog/process.c
int process_wait (tid_t child_tid UNUSED) {
// [구현 8-3] 자식 리스트 순회하며, 기다릴 자식 찾기
struct thread *curr = thread_current();
struct child_info *child;
struct list_elem *e;
int flag = 0; // 찾은 경우 1, 못 찾은 경우 0
int wait_return; // 추후 반환값 저장 용도
// 리스트를 순회
if (!list_empty(&(curr->children))){
for (e = list_begin(&(curr -> children)); e != list_end(&(curr -> children)); e = list_next(e)){
// 자식의 tid를 child_tid와 비교
child = list_entry(e, struct child_info, c_elem);
if (child -> tid == child_tid){
flag = 1; // 찾으면 flag를 1로 바꾸고 break
break;
}
}
}
// (1)(2) 자식프로세스가 없거나, 리스트에서 찾지 못한 경우
// (3) 이미 동일 자식을 대기 중인 경우 --> -1을 반환.
if (flag == 0 || child -> waiting){
return -1;
}
// 계속
}
child -> waiting을 true로 설정 -> 동일한 자식을 다시 대기하지 못하게 함-1을 반환하게 됨child -> w_sema` 세마포어에 sema_down 적용해 대기child -> waiting을 false로 설정exit()한 경우), 위 과정을 생략. 이미 죽은 자식이니 대기할 필요가 없음child -> alive이 true(자식이 살아있는 경우)일 때만 실행되게 조건문을 만들어 두기// userprog/process.c
// [구현 8-4] 자식의 종료 전까지 대기
// 이미 자식이 죽은 경우, 대기할 필요가 없음
if (child -> alive){
// waiting을 true로 설정하고, 세마포어를 내리기.
child -> waiting = true;
sema_down(&(child -> w_sema));
// 대기 종료 후엔 waiting을 false로 설정.
child -> waiting = false;
}
process_exit 수정thread_exit() (threads/thread.c) 호출 -> 함수 내부에서 process_exit()를 호출bool 변수를 만들고 참일 때만 실행되게 구현하자.// 사용자 프로세스만 pml4 테이블을 가짐
bool user_process = curr -> pml4 != NULL;
if (user_process){
// ....
}
struct child_info의 parent_alive를 false로 설정 -struct child_info의 할당 해제 시점은, parent_alive에서 확인할 수 있는 부모의 생존/사망 시점에 따라 달라짐wait() 보내고 자식이 종료된 걸 확인하면, 그때 free해 줌exit()할 때 스스로 free해 줘야 함// [구현 8-5] 자식에게 부모의 죽음 알리기
struct list_elem *e;
// 모든 자식의 parent_alive를 false처리
if (!list_empty(&(curr->children))){
for (e = list_begin(&(curr -> children)); e != list_end(&(curr -> children)); e = list_next(e)){
list_entry(e, struct child_info, c_elem) -> parent_alive = false;
}
}
palloc_free_page로 프로세스의 child_info (ci 멤버)의 메모리 할당 해제// [구현 8-6] 부모가 죽은 경우 child_info free
if (!(curr -> ci -> parent_alive)){
// 부모가 죽은 경우, 바로 여기서 free
palloc_free_page(curr -> ci);
}
struct child_info 멤버들을 수정해 줘야 함struct thread 내 ci 멤버는 child_info의 주소를 저장하고 있음// [구현 8-7] 자신의 child_info 수정
struct thread *curr = thread_current ();
curr -> ci -> alive = false;
curr -> ci -> exit_code = curr -> exit_code;
alive을 false로 설정wait() 시스템 콜이 주어지는 경우, [구현 8-4]에서 설정했던 조건문 덕분에, 부모가 굳이 대기하지 않고 바로 처리함.exit 시스템 콜을 수행하면, 자신의 struct thread의 exit_code 멤버에 exit의 매개변수로 보낸 exit status가 저장됨 (앞서 exit 구현할 때 하셨을 겁니다...)child_info의 exit_code에도 저장해 두기// [구현 8-8] 세마포아를 올려 부모의 대기 해제
sema_up(&(curr -> ci -> w_sema));
child_info에 있던 w_sema를 sema_down했었음.sema_up으로 올려주기./* Exit the process. This function is called by thread_exit (). */
void
process_exit (void) {
struct thread *curr = thread_current ();
// 사용자 프로세스만 pml4 테이블을 가짐
bool user_process = curr -> pml4 != NULL;
process_cleanup ();
// 사용자 프로세스일 때만 아래 코드를 실행.
if(user_process){
// [구현 5-2] exit 및 exit 메시지 띄우기 구현
printf("%s: exit(%d)\n", curr -> name, curr -> exit_code);
// [구현 8-5] 자식에게 부모의 죽음 알리기
struct list_elem *e;
// 모든 자식의 parent_alive를 false처리
if (!list_empty(&(curr->children))){
for (e = list_begin(&(curr -> children)); e != list_end(&(curr -> children)); e = list_next(e)){
list_entry(e, struct child_info, c_elem) -> parent_alive = false;
}
}
// [구현 8-6] 부모가 죽은 경우 child_info free
if (!(curr -> ci -> parent_alive)){
// 부모가 죽은 경우, 바로 여기서 free
palloc_free_page(curr -> ci);
}
else {
// [구현 8-7] 자신의 child_info 수정
curr -> ci -> alive = false;
curr -> ci -> exit_code = curr -> exit_code;
// [구현 8-8] 세마포아를 올려 부모의 대기 해제
sema_up(&(curr -> ci -> w_sema));
}
}
}
process_wait 다시 수정// userprog/process.c process_wait() 내부
// [구현 8-9] 자식이 종료되면...
// 자식을 리스트에서 없애고, free하고, 자식의 exit 코드 반환.
wait_return = child -> exit_code;
list_remove(&(child -> c_elem));
palloc_free_page(child);
return wait_return;
wait_return은 [구현 8-7]에서 설정된 자식의 exit status. 이 값을 반환palloc_free_page로 메모리 할당 해제해 줌.int
process_wait (tid_t child_tid UNUSED) {
//[구현 8-3] children 리스트에서 child_tid를 가진 자식을 찾기
struct thread *curr = thread_current();
struct child_info *child;
struct list_elem *e;
int flag = 0;
int wait_return;
if (!list_empty(&(curr->children))){
for (e = list_begin(&(curr -> children)); e != list_end(&(curr -> children)); e = list_next(e)){
child = list_entry(e, struct child_info, c_elem);
//printf("찾는 %d, 실제 %d\n", child_tid, child -> tid);
if (child -> tid == child_tid){
flag = 1;
break;
}
}
}
if (flag == 0 || child -> waiting){
// 찾지 못한 경우 / 리스트가 빈 경우 -1 반환
// 동일 child를 대기하는 경우도 -1 반환
return -1;
}
// [구현 8-4] 자식의 종료 전까지 대기
// 이미 자식이 죽은 경우, 바로 exit code를 얻기
if (child -> alive){
// waiting을 true로 설정하고, 세마포어를 내리기.
child -> waiting = true;
sema_down(&(child -> w_sema));
child -> waiting = false;
}
// [구현 8-9] 종료된 자식의 child_info free하기
// 자식을 리스트에서 없애고, free하고, 자식의 exit 코드 반환.
wait_return = child -> exit_code;
//printf("자식 %d의 exit 코드: %d", child->tid, child->exit_code);
list_remove(&(child -> c_elem));
palloc_free_page(child);
return wait_return;
}
process_fork(부모) 및 __do_fork(자식)를 통해 생성되는 자식 프로세스만 부모와의 관계 설정이 이루어짐process_create_initd(부모) 및 initd(자식)을 이용함wait가 의도한 대로 동작함process_create_initd 수정[구현 8-10]
children 등 멤버들 초기화를 위해, 부모도 process_init()을 해 줘야 함 (기존엔 없음). 함수 추가.children 리스트에 자식을 삽입할 수 있게, 기존의 fn_copy를 포함해 struct thread *parent도 인수로 보내줘야 함struct init_aux 선언 후, thread_create로 initd에 묶어서 보내줌[구현 8-11]
wait() 등 시스템 콜이 호출될 때 race condition 발생 가능하니, 부모의 f_sema를 내려주기// [구현 8-10] 부모의 struct thread 전달 위한 구조체 선언
struct init_aux {
struct thread *parent;
char *fn_copy;
};
tid_t
process_create_initd (const char *file_name) {
tid_t tid;
// 버퍼 준비
char buffer[128];
char *file_token;
strlcpy(buffer, file_name, 128);
// [구현 8-10] 첫 프로세스에 부모의 struct thread 전달하기
process_init();
struct init_aux *ia = malloc(sizeof(struct init_aux));
ia -> fn_copy = palloc_get_page(0);
if (ia -> fn_copy == NULL){
return TID_ERROR;
}
strlcpy(ia -> fn_copy, file_name, PGSIZE);
ia -> parent = thread_current();
char *save_ptr;
// [구현 1-2] 현재 file_name은 "echo x y z"
// file_name은 "echo"만 전달해야 함
file_token = strtok_r(buffer, " ", &save_ptr); // 첫 번째 argument만 저장됨
/* Create a new thread to execute FILE_NAME. */
tid = thread_create (file_token, PRI_DEFAULT, initd, ia);
if (tid == TID_ERROR){
// palloc_free_page (fn_copy);
palloc_free_page(ia -> fn_copy);
free(ia);
return tid;
}
// [구현 8-11] 첫 사용자 프로그램이 자식으로 설정될 때까지 세마포어로 대기.
sema_down(&(thread_current() -> f_sema));
return tid;
}
initd 수정[구현 8-12]
struct thread와 f_name 저장하고, 포인터 free해 주기[구현 8-13]
static void
initd (void *aux_){
#ifdef VM
supplemental_page_table_init (&thread_current ()->spt);
#endif
process_init ();
// [구현 8-12] 부모에게서 전달받은 데이터 확인
struct thread *parent = ((struct fork_aux *)(aux_)) -> parent;
char *f_name = ((struct init_aux *) aux_) -> fn_copy;
struct thread *curr = thread_current();
free(aux_);
// [구현 8-13] 리스트에 삽입 및 sema-up
list_push_back(&(parent -> children), &(curr -> ci -> c_elem));
sema_up(&(parent -> f_sema)); // 설정 완료 후 부모의 대기 해제
if (process_exec (f_name) < 0)
PANIC("Fail to launch initd\n");
NOT_REACHED ();
}
rax 레지스터에 잘 저장하면 됨case SYS_WAIT:
/* Wait for a child process to die. */
// [구현 8-14]
f -> R.rax = process_wait(f -> R.rdi);
break;