(TIL)munmap() 디버깅에 집중하기

낚시하는 곰·2025년 6월 7일
1

jungle TIL

목록 보기
11/20

void munmap(void *addr);

목표

매핑된 addr을 받아서 매핑을 해제한다.

구상

  • 매핑이 file backed page인지 anon page인지 check
  • fault stack growth case인지 && anon + lazy load인지 check

flow

mmap()한 걸 해제할 때

→ munmap(void *addr)

참고

중요: vm_alloc_page_with_initializer에서 말하는 initializer는 anon_initializer, file_backed_initializer같은 페이지 이니셜라이저고, lazy_load_segment 같은 vm_initializer와는 다르다.Page initialize 과정은 “지정“과 “실행“으로 나눠 볼 수 있다.

  1. 지정: vm_alloc_page_with_initializer
    1. 타입 지정
    2. 지정된 타입으로 page initializer 지정
    3. 만약 주어졌다면 vm_initializer 지정
  2. 수행: 첫 페이지 폴트 발생 시
    1. uninit_initialize가 수행되며 아래 루틴들 수행
    2. page initializer 수행 → page operation 지정
    3. vm_initializer 존재한다면 수행(lazy_load_segment 등)

do_munmap()

목표

전달받은 addr을 참조하여 실제 메모리 매핑 해제 작업을 수행한다.

구상

  • 모든 page를 정리한다
    • mmap list 전체를 순회하면서 page 해제를 수행한다.
      • for()에서 mm list의 mm file→list elem로 순회하면서 file별로 addr값을 찾는다.
      • addr위치부터 file length까지 offset만큼 높은 주소로 이동하면서
      • spt find page()를 이용해서 addr과 매핑된 page를 찾는다.
      • file backed destroy()를 이용해서 file backed과의 매핑을 해제한다.

flow

→ vlidation()

→ file backed destroy()
-> ...

debug

void
test_main (void)
{
  int handle;
  void *map;

  CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
  CHECK ((map = mmap (ACTUAL, 0x2000, 0, handle, 0)) != MAP_FAILED, "mmap \"sample.txt\"");
  msg ("memory is readable %d", *(int *) ACTUAL);
  msg ("memory is readable %d", *(int *) ACTUAL + 0x1000);

  munmap (map);

  fail ("unmapped memory is readable (%d)", *(int *) (ACTUAL + 0x1000));
  fail ("unmapped memory is readable (%d)", *(int *) (ACTUAL));
}

설명 : 이 테스트는 mmap()에 의해 매핑된 메모리가 실제로 읽을 수 있는지와, munmap() 이후에 해당 메모리에 접근하면 실패(즉, 페이지 폴트)가 발생해야 하는지를 검증하는 테스트.

(mmap-unmap) begin
(mmap-unmap) open "sample.txt"
(mmap-unmap) mmap "sample.txt"
Kernel PANIC at ../../lib/kernel/list.c:158 in list_insert(): assertion `is_interior (before) || is_tail (before)' failed.

0x000000800421802e: debug_panic (lib/kernel/debug.c:32)
0x0000008004218577: list_insert (lib/kernel/list.c:159)
0x000000800421881f: list_push_back (lib/kernel/list.c:204)
0x0000008004221b43: do_mmap (vm/file.c:127)
0x000000800421d55d: mmap (userprog/syscall.c:286)
0x000000800421d8c5: syscall_handler (userprog/syscall.c:376)
0x000000800421ce7c: no_sti (userprog/syscall-entry.o:?)
mmap_file: 0x8004248138
mmap_file->elem.prev: 0xcccccccccccccccc
mmap_file->elem.next: 0xcccccccccccccccc

before가 잘못된 주소를 참조하고 있었음.

#ifdef VM
	list_init(&t->mmap_list); // feat: do_munmap
#endif

해결

mmap_file: 0x8004248138
mmap_file->elem.prev: 0xcccccccccccccccc
mmap_file->elem.next: 0xcccccccccccccccc
[do_mmap] list_push_back() 이후
mmap-unmap: exit(-1)
mmap-unmap: exit(-1)

mmap()으로 매핑된 file을 읽지 못하고 있음.

000000800422199b: file_backed_destroy (vm/file.c:83)
0x0000008004221178: vm_dealloc_page (vm/vm.c:367)
0x00000080042215fb: spt_destructor (vm/vm.c:505)
0x000000800421a613: hash_clear (lib/kernel/hash.c:59)
0x00000080042215cd: supplemental_page_table_kill (vm/vm.c:499)
0x000000800421c107: process_cleanup (userprog/process.c:458)
0x000000800421c0a3: process_exit (userprog/process.c:441)
0x0000008004207240: thread_exit (threads/thread.c:328)
0x000000800421d009: write (userprog/syscall.c:98)
0x000000800421ce1e: page_fault (userprog/exception.c:152)

문제 정의

  1. mmap()에서 매핑해준 file이 적절하게 해제되지 않아서?
  2. file backed destroy()가 중복으로 호출되었기 때문에? 이때는 munmap()이 호출되지 않아서 이 가능성을 배제했었음. process_exit()에서 밖에 호출되지 않는데 중복 호출될리 없잖아..
    또한 reopen()에서 같은 file을 여러 page에서 매핑하고 있기 때문에 앞에서 삭제된 경우가 있을 수도 있지 않나? 에 대한 부분도 생각했었음.
  3. spt remove page()에서 이미 제거된 page를 다시 참조하는 경우 - 뒤에서 깨달았지만 이게 정답이였다…
mmap-unmap: exit(-1)
mmap-unmap: exit(-1)

위에서 file backed destroy()가 중복되는 문제 해결

if(spt_find_page(spt, page->va) != NULL && page->operations != NULL && page->frame != NULL){
		if (pml4_is_dirty(pml4, page->va))
		{		
			file_write_at(file_page->file, page->va, file_page->size, file_page->file_ofs); // Writes SIZE bytes만큼 쓴다.
		}
		file_close(file_page->file);
		dprintfg("[file_backed_destroy] spt remove page()할 때 문제가 발생한 것 같아\n");
		// spt_remove_page(spt, page); // spt 제거 -> spt에서 지우면 pml4에서 계속 업데이트가 된다?
	}	
}
(mmap-unmap) begin
(mmap-unmap) open "sample.txt"
(mmap-unmap) mmap "sample.txt"
[do_mmap] length: 4096, offset: 4096, addr: 0x10001000
[do_mmap] length: 0, offset: 8192, addr: 0x10002000
[do_mmap] list_push_back() 이전
mmap_file: 0x8004248138
mmap_file->elem.prev: 0xcccccccccccccccc
mmap_file->elem.next: 0xcccccccccccccccc
mmap_file->elem.prev: 0x8004243070
mmap_file->elem.next: 0x8004243080
[do_mmap] list_push_back() 이후
[lazy_load_file_backed] routine start. page: 0x8004246498, page->va: 0x10000000
[lazy_load_file_backed] reading file
mmap-unmap: exit(-1)
[file backed destroy] dirty bit를 확인하기 전
Execution of 'mmap-unmap' complete.

한참 찾았는데 file backed destroy()에서 spt_remove_page()을 호출하게 되면 spt_remove_page()에서 다시 destroy()를 호출하게 되면서 이미 해제된 메모리를 다시 접근하는 문제가 발생한다.

구현

static void
file_backed_destroy(struct page *page)
{
	// 	- 파일 기반 페이지를 제거하는 함수
	// - 페이지가 **dirty 상태**면, 변경 사항을 파일에 기록(write-back)해야 함
	// - 여기서 `page` 구조체 자체를 `free`할 필요는 없음 → 호출자가 해제함
	//    - 호출자 = spt_remove_page → vm_dealloc_page
	//   	- 여기서 destroy 호출 후 구조체 free까지 해줌
	// - destroy에서 구현할 로직?
	//    - 매핑된 프레임 해제?
	//    - spt_remove_page에서 구현하는것이 좋을듯하다
	//   - write-back 구현
	struct file_page *file_page = &page->file; 
	struct pml4 *pml4 = thread_current()->pml4;
	struct supplemental_page_table *spt = &thread_current()->spt;
	
	dprintfg("[file backed destroy] dirty bit를 확인하기 전\n");
	
	if(spt_find_page(spt, page->va) != NULL && page->operations != NULL && page->frame != NULL){
		if (pml4_is_dirty(pml4, page->va))
		{		
			file_write_at(file_page->file, page->va, file_page->size, file_page->file_ofs); // Writes SIZE bytes만큼 쓴다.
		}
		file_close(file_page->file);
		dprintfg("[file_backed_destroy] spt remove page()할 때 문제가 발생한 것 같아\n");
		// spt_remove_page(spt, page); // spt 제거 -> spt에서 지우면 pml4에서 계속 업데이트가 된다?
	}	
}

spt_remove_page()은 do_munmap()에서 호출해야 한다.


profile
취업 준비생 낚곰입니다!! 반갑습니다!!

0개의 댓글