WEEK 13 PintOS TIL(6월9일 월요일)

Devkty·2025년 6월 10일

[목표]
mmap 전 통과안되는 코드들을 트러블 슈팅합니다. (어제 fork_read 하다가 말았음)(실패로 인한 나중에)
stack growth를 구현한 코드를 이해해봅니다.
mmap 구현을 해봅니다.

09:55 ~ 10:30

졸았다. 어제 3시 넘어서 잔 여파가 큰 것같다.

10:30 ~ 11:00

주말간의 TIL을 통합해서 벨로그에 적었다. 주말간 먹었던 사진들과 바다 사진도 올렸다.

11:00 ~ 12:00

현재 페이지 테이블 해제 부분을 재작성하고 있습니다. fork-read와 페이지 kill 간의 문제가 해결되지 않고 있습니다.

12:00 ~ 13:30

식사를 하고 휴식을 가졌습니다.

13:30 ~ 16:30

식권 수령과 휴대폰 케이스가 불량이라 반품 처리를 했습니다.

fork-read 테스트 케이스에 다음과 같은 문제가 생겨서 확인을 해보고 있는데, 이유를 모르겠다.디스크 스왑이 구현이 안되있어서 그런지...

Boot complete.
Putting 'fork-read' into the file system...
Putting 'sample.txt' into the file system...
Executing 'fork-read':
(fork-read) begin
(fork-read) open "sample.txt"
(fork-read) child run
(fork-read) Child: pintos is funny!
(fork-read) end
child: exit(0)
(fork-read) Parent success
(fork-read) end
fork-read: exit(0)
Interrupt 0x0d (#GP General Protection Exception) at rip=8004218ca0
cr2=0000000000606000 error=               0
rax cccccccccccccccc rbx 000080042450f800 rcx 0000000000000040 rdx 000000800424a158
rsp 0000008004246c70 rbp 0000008004246c80 rsi 000000800424a158 rdi 000000800424a158
rip 0000008004218ca0 r8 0000008004246cf8  r9 000000800421bb65 r10 0000000000000000
r11 0000000000000206 r12 000000800421da95 r13 0000010424617000 r14 00001f0424600000
r15 0000800422bd6800 rflags 00000202
es: 001b ds: 001b cs: 0008 ss: 0010
Kernel PANIC at ../../userprog/exception.c:100 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0x80042186ec 0x800421d75f 0x80042095ea 0x8004209a08 0x8004218e9e 0x800420c5be 0x80042229c0 0x80042227ff 0x800421acba 0x800422283e 0x800421c8a3 0x800421c871 0x8004207296 0x800421e3bb 0x800421db01 0x800421d9d4 0x400fed 0Interrupt 0x0d (#GP General Protection Exception) at rip=8004215726
cr2=0000000000606000 error=               0
rax 0000800420bc7900 rbx 000080042450f800 rcx 00000080040b8000 rdx 00000000000003d4
rsp 0000008004246ab0 rbp 0000008004246ac0 rsi 000000000000c60f rdi 0000000000000000
rip 0000008004215726 r8 00000080042469c8  r9 000000800421bb65 r10 0000000000000000
r11 0000000000000206 r12 000000800421da95 r13 0000010424617000 r14 00001f0424600000
r15 0000800422bd6800 rflags 00000006
es: 0010 ds: 0010 cs: 0008 ss: 0010
Kernel PANIC recursion at ../../userprog/exception.c:100 in kill().
Timer: 100 ticks
Thread: 37 idle ticks, 50 kernel ticks, 13 user ticks
hd0:0: 0 reads, 0 writes
hd0:1: 117 reads, 260 writes
hd1:0: 111 reads, 0 writes
hd1:1: 0 reads, 0 writes
Console: 2562 characters output
Keyperl -I../.. ../../tests/userprog/fork-read.ck tests/userprog/fork-read tests/userprog/fork-read.result
FAIL tests/userprog/fork-read
Kernel panic in run: PANIC at ../../userprog/exception.c:100 in kill(): Kernel bug - unexpected interrupt in kernel
Call stack: 0x80042186ec 0x800421d75f 0x80042095ea 0x8004209a08 0x8004218e9e 0x800420c5be 0x80042229c0 0x80042227ff 0x800421acba 0x800422283e 0x800421c8a3 0x800421c871 0x8004207296 0x800421e3bb 0x800421db01 0x800421d9d4 0x400fed
Translation of call stack:
0x00000080042186ec: debug_panic (lib/kernel/debug.c:32)
0x000000800421d75f: kill (userprog/exception.c:106)
0x00000080042095ea: intr_handler (threads/interrupt.c:352)
0x0000008004209a08: intr_entry (threads/intr-stubs.o:?)
0x0000008004218e9e: list_push_front (lib/kernel/list.c:197)
0x000000800420c5be: free (threads/malloc.c:222)
0x00000080042229c0: uninit_destroy (vm/uninit.c:103)
0x00000080042227ff: page_destroy (vm/vm.c:395)
0x000000800421acba: hash_clear (lib/kernel/hash.c:60)
0x000000800422283e: supplemental_page_table_kill (vm/vm.c:408)
0x000000800421c8a3: process_cleanup (userprog/process.c:430)
0x000000800421c871: process_exit (userprog/process.c:415)
0x0000008004207296: thread_exit (threads/thread.c:374)
0x000000800421e3bb: sys_write (userprog/syscall.c:348)
0x000000800421db01: syscall_handler (userprog/syscall.c:88)
0x000000800421d9d4: no_sti (userprog/syscall-entry.o:?)
0x0000000000400fed: (unknown)

테스트 케이스가 한개 더 통과된다. 아마 어제 fork-read를 고치면서 바뀐 것 같다.
아직 해결해야될 테스트 케이스가 많다. 근데, 어떤 코드를 바꿔도 해결되지 않는다…
문제가 될 요인이 여러가지라서 가늠하기 힘든것도 있는 것 같다. userprog의 inode, file, syscall, process, exception 모두 불안정한 상태라서 고려할 사항이 많은 것 같다. 일단 제쳐두고 스택 그로우를 이해하고, mmap를 구현해야할 것 같다.

pass tests/userprog/args-none
pass tests/userprog/args-single
pass tests/userprog/args-multiple
pass tests/userprog/args-many
pass tests/userprog/args-dbl-space
pass tests/userprog/halt
pass tests/userprog/exit
pass tests/userprog/create-normal
pass tests/userprog/create-empty
pass tests/userprog/create-null
pass tests/userprog/create-bad-ptr
pass tests/userprog/create-long
pass tests/userprog/create-exists
pass tests/userprog/create-bound
pass tests/userprog/open-normal
pass tests/userprog/open-missing
pass tests/userprog/open-boundary
pass tests/userprog/open-empty
pass tests/userprog/open-null
pass tests/userprog/open-bad-ptr
pass tests/userprog/open-twice
pass tests/userprog/close-normal
pass tests/userprog/close-twice
pass tests/userprog/close-bad-fd
pass tests/userprog/read-normal
pass tests/userprog/read-bad-ptr
pass tests/userprog/read-boundary
pass tests/userprog/read-zero
pass tests/userprog/read-stdout
pass tests/userprog/read-bad-fd
pass tests/userprog/write-normal
pass tests/userprog/write-bad-ptr
pass tests/userprog/write-boundary
pass tests/userprog/write-zero
pass tests/userprog/write-stdin
pass tests/userprog/write-bad-fd
pass tests/userprog/fork-once
pass tests/userprog/fork-multiple
pass tests/userprog/fork-recursive
FAIL tests/userprog/fork-read
FAIL tests/userprog/fork-close
FAIL tests/userprog/fork-boundary
pass tests/userprog/exec-once
pass tests/userprog/exec-arg
pass tests/userprog/exec-boundary
pass tests/userprog/exec-missing
pass tests/userprog/exec-bad-ptr
FAIL tests/userprog/exec-read
pass tests/userprog/wait-simple
pass tests/userprog/wait-twice
pass tests/userprog/wait-killed
pass tests/userprog/wait-bad-pid
pass tests/userprog/multi-recurse
pass tests/userprog/multi-child-fd
pass tests/userprog/rox-simple
pass tests/userprog/rox-child
pass tests/userprog/rox-multichild
pass tests/userprog/bad-read
pass tests/userprog/bad-write
pass tests/userprog/bad-read2
pass tests/userprog/bad-write2
pass tests/userprog/bad-jump
pass tests/userprog/bad-jump2
pass tests/vm/pt-grow-stack
pass tests/vm/pt-grow-bad
pass tests/vm/pt-big-stk-obj
pass tests/vm/pt-bad-addr
pass tests/vm/pt-bad-read
pass tests/vm/pt-write-code
FAIL tests/vm/pt-write-code2
FAIL tests/vm/pt-grow-stk-sc
pass tests/vm/page-linear
pass tests/vm/page-parallel
FAIL tests/vm/page-merge-seq
FAIL tests/vm/page-merge-par
FAIL tests/vm/page-merge-stk
FAIL tests/vm/page-merge-mm
pass tests/vm/page-shuffle
FAIL tests/vm/mmap-read
FAIL tests/vm/mmap-close
FAIL tests/vm/mmap-unmap
FAIL tests/vm/mmap-overlap
FAIL tests/vm/mmap-twice
FAIL tests/vm/mmap-write
FAIL tests/vm/mmap-ro
FAIL tests/vm/mmap-exit
FAIL tests/vm/mmap-shuffle
pass tests/vm/mmap-bad-fd
FAIL tests/vm/mmap-clean
FAIL tests/vm/mmap-inherit
FAIL tests/vm/mmap-misalign
FAIL tests/vm/mmap-null
FAIL tests/vm/mmap-over-code
FAIL tests/vm/mmap-over-data
FAIL tests/vm/mmap-over-stk
FAIL tests/vm/mmap-remove
pass tests/vm/mmap-zero
pass tests/vm/mmap-bad-fd2
pass tests/vm/mmap-bad-fd3
pass tests/vm/mmap-zero-len
FAIL tests/vm/mmap-off
FAIL tests/vm/mmap-bad-off
FAIL tests/vm/mmap-kernel
FAIL tests/vm/lazy-file
pass tests/vm/lazy-anon
FAIL tests/vm/swap-file
FAIL tests/vm/swap-anon
FAIL tests/vm/swap-iter
FAIL tests/vm/swap-fork
pass tests/filesys/base/lg-create
pass tests/filesys/base/lg-full
pass tests/filesys/base/lg-random
pass tests/filesys/base/lg-seq-block
pass tests/filesys/base/lg-seq-random
pass tests/filesys/base/sm-create
pass tests/filesys/base/sm-full
pass tests/filesys/base/sm-random
pass tests/filesys/base/sm-seq-block
pass tests/filesys/base/sm-seq-random
FAIL tests/filesys/base/syn-read
pass tests/filesys/base/syn-remove
FAIL tests/filesys/base/syn-write
pass tests/threads/alarm-single
pass tests/threads/alarm-multiple
pass tests/threads/alarm-simultaneous
pass tests/threads/alarm-priority
pass tests/threads/alarm-zero
pass tests/threads/alarm-negative
pass tests/threads/priority-change
pass tests/threads/priority-donate-one
pass tests/threads/priority-donate-multiple
pass tests/threads/priority-donate-multiple2
pass tests/threads/priority-donate-nest
pass tests/threads/priority-donate-sema
pass tests/threads/priority-donate-lower
pass tests/threads/priority-fifo
pass tests/threads/priority-preempt
pass tests/threads/priority-sema
pass tests/threads/priority-condvar
pass tests/threads/priority-donate-chain
FAIL tests/vm/cow/cow-simple
38 of 141 tests failed.

16:30 ~ 18:00

운동을 했습니다.

18:00 ~ 19:00

식사를 하고 휴식을 가졌습니다.

코어타임 (19:00 ~ 22:30)

본 그룹 코어 타임을 진행했습니다.

채호형 코드 트러블슈팅

채호형 코드를 하나하나씩 확인해보며 어나니머스 페이지의 fork 테스트 이전까지 모든 코드 함수를 확인했습니다.
vm_alloc_page_with_initializer 함수에 문제가 있어서 항상 false를 리턴하는 것이 주요 문제점 이었습니다.

첫번째에는 모든 함수를 저의 코드와 비교해보며 틀린 것들을 수정했는데, 되지 않았습니다.
두번째에는 init 함수에 문제가 있다는 로그를 보고 해당 관련된 함수를 수정을 시도 했으나 되지 않았습니다.
세번째에는 printf 를 직접 찍어보면서 어디에서 문제가 생기는지 정확히 찍어보기로 했고, 최종적으로 process_exec의 load 함수가 문제가 있음을 알게되었습니다. load 함수에는 load_segment가 문제가 있었고, 그안에 있는 vm_alloc_page_with_initializer가 문제가 있음을 알았습니다…

이 과정에 있는 모든 함수는 문제가 없는데, 무엇이 false를 반환하나 확인을 해보니 한줄의 bool sucess = false; 가 무조건적으로 반환은 false만 하게끔 만들고 있었습니다.
해당 함수를 지우니 거짓말 처럼 make check가 문제없이 가능했습니다. 거의 2시간 넘게 남의 코드를 분석하며 디버깅해보니 쉽지 않음을 알게되었습니다… 권호형의 마음을 역지사지해보는 아주 뜻깊은 시간이었습니다.

결론적으로 팀원의 코드가 문제없이 fork를 제외한 테스트 케이스가 통과되니 다행이었습니다.

그러나 다른 팀원께 남아있지만, 코드 돌리는데 문제가 생겨서 내일할 예정입니다.
오늘 머지도 한번 하기로 해서 mmap를 할 수 있을지… 의문입니다 ㅋㅋㅋ, 머지도 내일하기로 했습니다.

22:30 ~ 24:00

코어 타임내용을 대강 정리했습니다. merge를 위한 본인 파일 백업을 실시했습니다. 물리 백업과 본인 페이지 백업도 해야겠습니다.
머지 전 본인 페이지 복제는 완료했습니다. 깃허브에는 커밋까지 완료했습니다.

24:00 ~ 03:30

스택 성장과 pt-grow-stk-sc 테스트 케이스 트러블 슈팅을 하는 중입니다. 밑에와 같이 memcmp에서 문제가 생깁니다.

Acceptable output:
  (pt-grow-stk-sc) begin
  (pt-grow-stk-sc) create "sample.txt"
  (pt-grow-stk-sc) open "sample.txt"
  (pt-grow-stk-sc) write "sample.txt"
  (pt-grow-stk-sc) 2nd open "sample.txt"
  (pt-grow-stk-sc) read "sample.txt"
  (pt-grow-stk-sc) compare written data against read data
  (pt-grow-stk-sc) end
Differences in `diff -u' format:
  (pt-grow-stk-sc) begin
  (pt-grow-stk-sc) create "sample.txt"
  (pt-grow-stk-sc) open "sample.txt"
  (pt-grow-stk-sc) write "sample.txt"
  (pt-grow-stk-sc) 2nd open "sample.txt"
  (pt-grow-stk-sc) read "sample.txt"
- (pt-grow-stk-sc) compare written data against read data
- (pt-grow-stk-sc) end

일단 스택 그로우 전에 현재 스레드의 rsp 포인터를 저장하기 위해 syscall.c에 syscall_handler에 해당 코드를 추가합니다.

/* Project3 VM : stack growth를 위한 추가 */
	thread_current() -> user_rsp = f->rsp;

물론 스레드 구조체에도 저장해야하므로 스레드 구조체에 #ifdef VM 안에 다음과 같이 코드를 추가합니다.

void *user_rsp;     // 유저 스택 포인터를 저장

마지막으로 vm_try_handle_fault 함수에 다음과 같이 수정합니다.

void *user_rsp = thread_current()->user_rsp; 추가.

스택 그로우 if문을 다음과 같이 수정해줍니다. addr >= user_rsp - 32

bool
vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
		bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
	struct supplemental_page_table *spt UNUSED = &thread_current ()->spt;
	struct page *page = spt_find_page(spt, addr);  /* valid address인지 확인 */

	void *user_rsp = thread_current()->user_rsp;  // 시스템 콜 중이라도 유저 %rsp

	void *va = pg_round_down(addr);  // 주소를 정렬에 맞춰준다.
	/* TODO: Validate the fault */
	/* TODO: Your code goes here */

	// case 1: page가 없음 → stack growth 고려
	// 스택 자동확장이 필요한 상황일 때, vm_stack_growth 함수로 확장해줍니다.
	if(page == NULL) {
		// 유저 모드에서 발생한 폴트만 처리, 유저 스택 포인터 바로 아래에 접근할 시 허용, 접근한 주소가 전체 유저 스택 범위 안에 있는지 확인. -> 모든 조건일시 스택 확장
		// 스택확장 부분이 왜 if문에 있는지 의아할 수 있는데, 이는 스택확장이 무조건 성공하지 않으므로 실패시 spt_find_page를 실행하지 않도록 방지한다.
		if (is_user_vaddr(addr) && user && addr >= user_rsp - 32 && addr < USER_STACK && vm_stack_growth(addr)) {
			page = spt_find_page(spt, addr);   // addr이 현재 스택 영역 바로 아래에 접근한 경우에만 새 페이지를 할당
		}
		else
			return false;
	}

	// case 2: 접근 권한 확인 (write 접근인데 read-only면 실패)
	if (write && !page->writable)  // 쓰기 접근인데 페이지가 읽기 전용이라면, false 반환
    	return false;
	
	// 페이지 적재 (uninit이든 아니든 모두 처리)
	return vm_do_claim_page (page);
}

해당 과정 이후에 파일 시스템 락관련 코드들도 file.c, syscall.c 등 모두 수정해봤지만, 개선되지 않았다. 그나마 addr >= f->rsp - 32 으로 바꿔야 tests/vm/pt-grow-bad 가 통과한다.

일단 최종적으로 스택 그로우는 다음과 같은 코드로 마감했다.

vm_try_handle_fault

bool
vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
		bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
	struct supplemental_page_table *spt UNUSED = &thread_current ()->spt;
	struct page *page = spt_find_page(spt, addr);  /* valid address인지 확인 */

	void *user_rsp = thread_current()->user_rsp;  // 시스템 콜 중이라도 유저 %rsp
	// void *user_rsp = user ? thread_current()->user_rsp : f->rsp;

	void *va = pg_round_down(addr);  // 주소를 정렬에 맞춰준다.
	/* TODO: Validate the fault */
	/* TODO: Your code goes here */

	// case 1: page가 없음 → stack growth 고려
	// 스택 자동확장이 필요한 상황일 때, vm_stack_growth 함수로 확장해줍니다.
	if(page == NULL) {
		// 유저 모드에서 발생한 폴트만 처리, 유저 스택 포인터 바로 아래에 접근할 시 허용, 접근한 주소가 전체 유저 스택 범위 안에 있는지 확인. -> 모든 조건일시 스택 확장
		// 스택확장 부분이 왜 if문에 있는지 의아할 수 있는데, 이는 스택확장이 무조건 성공하지 않으므로 실패시 spt_find_page를 실행하지 않도록 방지한다.
		if (is_user_vaddr(addr) && user && addr >= f->rsp - 32 && addr < USER_STACK && vm_stack_growth(addr)) {
			page = spt_find_page(spt, addr);   // addr이 현재 스택 영역 바로 아래에 접근한 경우에만 새 페이지를 할당
		}
		else
			return false;
	}

	// case 2: 접근 권한 확인 (write 접근인데 read-only면 실패)
	if (write && !page->writable)  // 쓰기 접근인데 페이지가 읽기 전용이라면, false 반환
    	return false;
	
	// 페이지 적재 (uninit이든 아니든 모두 처리)
	return vm_do_claim_page (page);
}

vm_stack_growth

static bool
vm_stack_growth (void *addr UNUSED) {
	struct thread *t = thread_current();
	void *va = pg_round_down(addr);  // 주소를 정렬에 맞춰준다.

	// 실제로 할당 요청
    return vm_alloc_page(VM_ANON | VM_MARKER_0, va, true);  // VM_MARKER_0: 스택에서 생성된 익명 페이지를 구별하기 위함
}

근데 위의 코드가 100퍼센트 완벽한 코드는 아니다. 나중에 와서 보충해야겠다.

profile
모든걸 기록하며 성장하고 싶은 개발자입니다. 현재 크래프톤 정글 8기를 수료하고 구직활동 중입니다.

0개의 댓글