pid_t fork(const char *thread_name){
return process_fork(thread_name, NULL);
}
fork()시작...
전달된 pte_for_each_func의 누락된 부분을 채워야 한다.
if (!pml4_for_each (parent->pml4, duplicate_pte, parent))
이 말은 곧 인자로 들어가는 duplicate_pte()함수를 수정하라는 말이다.
한마디로 process_fork(), __do_fork(), duplicate_pte() 3개의 함수를 수정하라는 말이다... + thread_create()
file name으로 새 프로세스 생성 → %rbx, %rsp, %bps, %R12 ~ %R15까지 복제 → 자식 프로세스 pid 반환. → fork()에서는 자식의 복제 여부를 확인하고 반환해야 함(복제가 실패했다면 TID ERROR를 자식에서 반환할 것이다).
tid_t
process_fork (const char *name, struct intr_frame *if_ UNUSED) {
/* Clone current thread to new thread.*/
struct thread *cur = thread_current();
struct intr_frame *cpy_regi = pg_round_up(rrsp());
tid_t tid = thread_create (name, PRI_DEFAULT, __do_fork, cur);
}
(gdb) p cur->name
$2 = "fork-boundary\000\000"
(gdb) p cpy_regi
$3 = (struct intr_frame *) 0x8004240f10
(gdb) p cpy_regi
$7 = (struct intr_frame *) 0x8004241000
$7 = (struct intr_frame *) 0x8004241000
(gdb) p cpy_regi->R.rsp
There is no member named rsp.
(gdb) p cpy_regi->R.rbx
$8 = 0
(gdb) p cpy_regi->R.r15
$9 = 2367527
(gdb) p cpy_regi->R.r14
$10 = 2285607
(gdb) p cpy_regi->R.r13
$11 = 0
(gdb) p cpy_regi->R.r12
$12 = 0
(gdb) p cpy_regi->R.r11
$13 = 0
(gdb) p cpy_regi->R.r10
$14 = 0
(gdb) p cpy_regi->R.r9
$15 = 0
(gdb) p cpy_regi->R.r8
$16 = 0
(gdb) p cpy_regi->R.r7
There is no member named r7.
(gdb) p cpy_regi->R.r6
There is no member named r6.
(gdb) p cpy_regi->R.rsi
$17 = 0
(gdb) p cpy_regi->R.rdi
$18 = 0
실험삼아 값을 다 찍어봤음...
하지만 동기가 조언을 줘서 더 쉬운 방법을 알게 됨
tid_t
process_fork (const char *name, struct intr_frame *if_ UNUSED) {
/* Clone current thread to new thread.*/
struct thread *cur = thread_current();
// rbx, rsp, rbp, r12 ~ r15까지의 레지스터 값들을 copy 뜹니다.
memcpy(&cur->parent_if, if_, sizeof(struct intr_frame));
tid_t tid = thread_create (name, PRI_DEFAULT, __do_fork, cur);
}
유후~ 이렇게 간단한 방법이 있었다니!!
시스템 핸들러에서 인터럽트 프레임을 인자로 받아서 넘겨줬다. 어치피 시스템 콜하기 전의 인터럽트 프레임의 레지스터 값만 저장하면 되니까 fork로 넘겨주면 복제 떠서 사용해도 되겠지!! 다만…
(gdb) display cur->parent_if
1: cur->parent_if = {R = {r15 = 0, r14 = 0, r13 = 0, r12 = 0, r11 = 0, r10 = 0, r9 = 0, r8 = 0,
rsi = 0, rdi = 0, rbp = 0, rdx = 0, rcx = 0, rbx = 0, rax = 0}, es = 0, __pad1 = 0, __pad2 = 0,
ds = 0, __pad3 = 0, __pad4 = 0, vec_no = 0, error_code = 0, rip = 0, cs = 0, __pad5 = 0, __pad6 = 0,
eflags = 0, rsp = 0, ss = 0, __pad7 = 0, __pad8 = 0}
(gdb) n
91 tid_t tid = thread_create (name, PRI_DEFAULT, __do_fork, cur);
1: cur->parent_if = {R = {r15 = 0, r14 = 0, r13 = 0, r12 = 0, r11 = 0, r10 = 0, r9 = 0, r8 = 0,
rsi = 0, rdi = 6316026, rbp = 1195900784, rdx = 0, rcx = 0, rbx = 0, rax = 2}, es = 27,
__pad1 = 0, __pad2 = 0, ds = 27, __pad3 = 0, __pad4 = 0, vec_no = 32, error_code = 0, rip = 4207787,
cs = 35, __pad5 = 0, __pad6 = 0, eflags = 534, rsp = 1195900712, ss = 27, __pad7 = 0, __pad8 = 0}
memcpy로 복제를 하긴 했는데 필요없는 값까지 다 복제가 돼버렸다… 이걸 어떻게 수정하지??
tid_t
process_fork (const char *name, struct intr_frame *if_ UNUSED) {
/* Clone current thread to new thread.*/
struct thread *cur = thread_current();
struct intr_frame *parent_if = if_;
struct intr_frame *copy_if = &cur->parent_if;
// rbx, rsp, rbp, r12 ~ r15까지의 레지스터 값들을 copy 뜹니다.
copy_if->R.rbx = parent_if->R.rbx;
copy_if->rsp = parent_if->rsp;
copy_if->R.rbp = parent_if->R.rbp;
copy_if->R.r12 = parent_if->R.r12;
copy_if->R.r13 = parent_if->R.r13;
copy_if->R.r14 = parent_if->R.r14;
copy_if->R.r15 = parent_if->R.r15;
tid_t tid = thread_create (name, PRI_DEFAULT, __do_fork, cur);
}
(gdb) p cur->parent_if
$6 = {R = {r15 = 0, r14 = 0, r13 = 0, r12 = 0, r11 = 0, r10 = 0, r9 = 0, r8 = 0, rsi = 0, rdi = 0,
rbp = 1195900784, rdx = 0, rcx = 0, rbx = 0, rax = 0}, es = 0, __pad1 = 0, __pad2 = 0, ds = 0,
__pad3 = 0, __pad4 = 0, vec_no = 0, error_code = 0, rip = 0, cs = 0, __pad5 = 0, __pad6 = 0,
eflags = 0, rsp = 1195900712, ss = 0, __pad7 = 0, __pad8 = 0}
그렇다. 하드코딩하면 된다.
tid_t
thread_create (const char *name, int priority,
thread_func *function, void *aux) {
struct thread *t;
tid_t tid;
ASSERT (function != NULL);
/* Allocate thread. */
t = palloc_get_page (PAL_ZERO);
if (t == NULL)
return TID_ERROR;
/* Initialize thread. */
init_thread (t, name, priority);
tid = t->tid = allocate_tid ();
/* Call the kernel_thread if it scheduled.
* Note) rdi is 1st argument, and rsi is 2nd argument. */
t->tf.rip = (uintptr_t) kernel_thread;
t->tf.R.rdi = (uint64_t) function; <- duplicate_pte()을 여기서 사용한다.
t->tf.R.rsi = (uint64_t) aux;
t->tf.ds = SEL_KDSEG;
t->tf.es = SEL_KDSEG;
t->tf.ss = SEL_KDSEG;
t->tf.cs = SEL_KCSEG;
t->tf.eflags = FLAG_IF;
/* Add to run queue. */
thread_unblock (t);
/* 현재와 가장 높은 우선순위 비교후 현재보다 우선순위가 높다면 양보 */
if(t->priority > thread_current()->priority)
thread_yield();
return tid;
}
thread → intr_f → gp_registers → rdi = duplicate_pte이 할당되는 거 확인했지만 정확히 어디서 함수가 호출되는 지 알 수는 없다...
static void
__do_fork (void *aux) {
struct intr_frame if_;
struct thread *parent = (struct thread *) aux;
struct thread *current = thread_current ();
/* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */
struct intr_frame *parent_if;
bool succ = true;
/* 1. Read the cpu context to local stack. */
memcpy (&if_, parent_if, sizeof (struct intr_frame));
fork()에서 부모 인터럽트 프레임을 복제해놓고, 왜 do_fork()에서 한 번 더 복제하는 거지???
일단 넘어가자.
(gdb) p parent->tid
$2 = 3
(gdb) p current->tid
$3 = 4
aux가 부모 스레드 주소를 가지고 있는 것 같다.
지금 current→tid를 보면 do_fork()함수가 실행하면서 새로운 스레드가 생성이 된 것으로 보인다.
(gdb) p parent_if->R
$5 = {r15 = 549825282032, r14 = 549825050356, r13 = 549825282048, r12 = 549825134420, r11 = 0,
r10 = 0, r9 = 4294967299, r8 = 8462090172068163430, rsi = 521610028142, rdi = 31, rbp = 0, rdx = 0,
rcx = 549825182464, rbx = 549822922800, rax = 31}
(gdb) p current->parent_if->R
$6 = {r15 = 0, r14 = 0, r13 = 0, r12 = 0, r11 = 0, r10 = 0, r9 = 0, r8 = 0, rsi = 0, rdi = 0, rbp = 0,
rdx = 0, rcx = 0, rbx = 0, rax = 0}
위를 보면 부모 인터럽트 프레임의 레지스터 값과 cur->parent_if로 접근했을 때 레지스터 값이 다르다. current는 현재 새로 생성된 자식 스레드이기 때문에 부모 인터럽트 프레임의 레지스터 값을 복제해줘야 한다.
static void
__do_fork (void *aux) {
struct intr_frame if_; <- 여기서 자식에게 인터럽터 프레임을 전달
struct thread *parent = (struct thread *) aux;
struct thread *current = thread_current ();
/* TODO: somehow pass the parent_if. (i.e. process_fork()'s if_) */
struct intr_frame *parent_if;
bool succ = true;
살짝 인터럽트 프레임, pte에 대한 개념이 잡히지 않는다…..
/* 2. Duplicate PT */
current->pml4 = pml4_create();
(gdb) p current->pml4
$9 = (uint64_t *) 0x8004240000
duplicate_pte()함수가 호출되지 않는 문제 발생함!!
(gdb) p parent->pml4
$11 = (uint64_t *) 0x0
(gdb) p parent->tid
$12 = 3
tid는 찍히는데 parent→pml4가 null값으로 찍힘…이거 왜 이러지??
버그를 찾아야 한다…..
process_fork (const char *name, struct intr_frame *if_ UNUSED)
struct thread *cur = thread_current();
(gdb) p cur->pml4
$1 = (uint64_t *) 0x8004241000
(gdb) p cur->tid
$2 = 3
(gdb) p cur->name
$3 = "fork-boundary\000\000"
일단 생각해봤다. fork하기 전의 현재 스레드는 부모 스레드일 거고, 부모 스레드는 pml4값을 가지고 있는 상태라는 걸 확인했다.
static void
__do_fork (void *aux) {
struct intr_frame if_;
struct thread *parent = (struct thread *) aux;
(gdb) p parent
$8 = (struct thread *) 0x8004240000
(gdb) p parent->tid
$9 = -858993460
어?????
부모가 존재하지 않는다?? 왜 그렇지??
몇 가지 가정을 해보자. 부모가 없다는 건 어떤 의미일까?
부모가 자식을 생성한 후에 바로 죽을 수 있을까? 부모 스레드에서 자식 스레드로 context swiching 후에 자식 스레드에서 __do_fork()가 실행되고, 부모는 wait상태에서 자식의 종료를 기다리지 않고 바로 죽는다…
int
process_wait (tid_t child_tid UNUSED) {
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
while (1){
}
// struct thread *child = get_child(child_tid);
// return -1;
}
하지만 부모는 스레드는 무한 루프를 돌고 있을텐데 어떻게 바로 죽을 수가 있지?
그런데 test case를 보면
void
test_main (void)
{
pid_t pid = fork (copy_string_across_boundary ("child-simple"));
if (pid == 0){
msg ("child run");
exit(54);
} else {
int exit_val = wait(pid);
CHECK (pid > 0, "fork");
CHECK (exit_val == 54, "wait");
}
}
fork()함수에서 반환되는 pid값에 따라 분기가 나뉜다.
pid가 0이면 자식 스레드라고 간주하고, 실행 후에 종료시킨다… ← 이게 문제인 것 같다.
exit()을 실행시키기 때문에 부모 스레드는 wait이 아니라 죽어버린다.
tid_t
process_fork (const char *name, struct intr_frame *if_ UNUSED) {
/* Clone current thread to new thread.*/
struct thread *cur = thread_current();
struct intr_frame *parent_if = if_;
struct intr_frame *copy_if = &cur->parent_if;
// rbx, rsp, rbp, r12 ~ r15까지의 레지스터 값들을 copy 뜹니다.
copy_if->R.rbx = parent_if->R.rbx;
copy_if->rsp = parent_if->rsp;
copy_if->R.rbp = parent_if->R.rbp;
copy_if->R.r12 = parent_if->R.r12;
copy_if->R.r13 = parent_if->R.r13;
copy_if->R.r14 = parent_if->R.r14;
copy_if->R.r15 = parent_if->R.r15;
tid_t tid = thread_create (name, PRI_DEFAULT, __do_fork, thread_current());
나는 여기까지 구현을 하고, 새로운 스레드를 생성할 때 인자로 do_fork()를 받으니까 create() → do_fork() → duplicate_pte() → 순차적으로 실행한 뒤에 다음 line으로 넘어가는 줄 알았다…
하지만 __do_fork()를 실행한 순간부터 자식 스레드가 생성된다는 생각을 하지 못했다. 여기서 자식으로 분기되고, 부모는 자식을 생성한 뒤에 다음 line을 실행한다. 이 때의 반환값은 존재하지 않으므로 pid = 0이 되고, wait상태로 대기하고 있을 거라는 생각과는 달리 exit()되면서 부모 스레드는 존재하지 않았던 것이다.
static void
__do_fork (void *aux) {
struct intr_frame if_;
struct thread *parent = (struct thread *) aux;
(gdb) p parent->tid
$1 = 3
(gdb) p parent->name
$2 = "fork-boundary\000\000"
(gdb) p parent->pml4
$3 = (uint64_t *) 0x0
여기서 왜 parent→pml4 주소 값이 없는거지?? 부모 스레드를 받아왔는데 말이야…
언뜻 보기에는 같아보이지만 다르다. 일단 페이지 폴트가 난 주소가 다르고, 실제로 페이지 폴트가 발생한 위치도 다르다.
tid_t
process_fork (const char *name, struct intr_frame *if_ UNUSED) {
/* Clone current thread to new thread.*/
struct thread *cur = thread_current();
struct intr_frame *parent_if = if_;
struct intr_frame *copy_if = &cur->parent_if;
// rbx, rsp, rbp, r12 ~ r15까지의 레지스터 값들을 copy 뜹니다.
copy_if->R.rbx = parent_if->R.rbx;
copy_if->rsp = parent_if->rsp;
copy_if->R.rbp = parent_if->R.rbp;
copy_if->R.r12 = parent_if->R.r12;
copy_if->R.r13 = parent_if->R.r13;
copy_if->R.r14 = parent_if->R.r14;
copy_if->R.r15 = parent_if->R.r15;
tid_t tid = thread_create (name, PRI_DEFAULT, __do_fork, cur);
struct thread *child = get_child_tid(tid); <- 이거 한 줄로 문제가 해결
// sema_down(&child->fork_sema);
return tid;
}
struct thread *child = get_child_tid(tid);
이 한 줄로 발생했던 문제가 해결되었다… 왜지?
struct thread *get_child_tid(tid_t child_tid){
struct thread *cur = thread_current();
struct thread *child_thread = NULL;
for(struct list_elem *i = list_begin(&cur->child_list); i != list_end(&cur->child_list); i = i->next){
struct thread *find_child_t = list_entry(i, struct thread, child_elem);
if(find_child_t->tid == child_tid){
child_thread = find_child_t;
break;
}
}
return child_thread;
}
수정하기 전
Putting 'fork-boundary' into the file system...
Executing 'fork-boundary':
(fork-boundary) begin
Page fault at 0: not present error reading page in kernel context.
Interrupt 0x0e (#PF Page-Fault Exception) at rip=800420d24f
cr2=0000000000000000 error= 0
rax 0000000000000000 rbx 0000000000000000 rcx 00000080042c1000 rdx 0000000000000000
rsp 00000080042b7ea0 rbp 00000080042b7ed0 rsi 000000800421bf13 rdi 0000000000000000
rip 000000800420d24f r8 0000000000000000 r9 0000000000000000 r10 0000000000000000
r11 0000000000000000 r12 0000000000000000 r13 0000000000000000 r14 0000000000000000
r15 0000000000000000 rflags 00000246
es: 0010 ds: 0010 cs: 0008 ss: 0010
Kernel PANIC at ../../userprog/exception.c:97 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0x80042186d4 0x800421d13c 0x800421d2bb 0x8004209680 0x8004209a9e 0x800421c0a2 0x80042076f4.
The `backtrace' program can make call stacks useful.
Read "Backtraces" in the "Debugging Tools" chapter
of the Pintos documentation for more information.
Timer: 292 ticks
Thread: 41 idle ticks, 240 kernel ticks, 11 user ticks
hd0:0: 0 reads, 0 writes
hd0:1: 80 reads, 240 writes
hd1:0: 106 reads, 0 writes
Console: 1732 characters output
수정한 후
Putting 'fork-boundary' into the file system...
Executing 'fork-boundary':
(fork-boundary) begin
Page fault at 0xfffffffffffffd40: not present error reading page in kernel context.
Interrupt 0x0e (#PF Page-Fault Exception) at rip=800421bdf6
cr2=fffffffffffffd40 error= 0
rax fffffffffffffd40 rbx 0000000000000000 rcx 00000080042b7030 rdx 00000080042b7030
rsp 00000080042b8e70 rbp 00000080042b8ea0 rsi 00000080042b7030 rdi 00000080042b82a0
rip 000000800421bdf6 r8 0000000000000000 r9 0000000000000000 r10 0000000000000000
r11 0000000000000216 r12 000000800421d498 r13 0000000000000000 r14 0000000000000000
r15 0000000000000000 rflags 00000283
es: 001b ds: 001b cs: 0008 ss: 0010
Kernel PANIC at ../../userprog/exception.c:97 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0x80042186d4 0x800421d151 0x800421d2d0 0x8004209680 0x8004209a9e 0x800421bf1f 0x800421dbd9 0x800421d513 0x800421d345 0x400115 0x400302 0x400da3.
The `backtrace' program can make call stacks useful.
Read "Backtraces" in the "Debugging Tools" chapter
of the Pintos documentation for more information.
Timer: 310 ticks
Thread: 47 idle ticks, 240 kernel ticks, 23 user ticks
hd0:0: 0 reads, 0 writes
hd0:1: 80 reads, 240 writes
hd1:0: 106 reads, 0 writes
Console: 1802 characters output
이 코드가 어떻게 위 문제를 해결할 수 있는거지?
진짜 모르겠다….진짜…