[TIL/크래프톤 정글] DAY 94

배재준·2025년 6월 11일

크래프톤 정글 - TIL

목록 보기
86/93
post-thumbnail

2025.06.11

TIL(TODAY I LEARN)


  • 오늘한 내용 : PintOS - Project3: Virtual Memory - mmap 구현 완료

  • WEEK 13 : 정글 끝까지(PintOS) - Virtual Memory


mmap 테스트 추가 완성하기

  • 같은 조 예찬 형님의 코드를 흡수했다.
    • 이전의 구조체를 다 지우고, spt를 통해 페이지를 순회한다.
    • 병합 및 수정 후 현 상황 9 of 141 tests failed.

fork_read()

  • lazy_load_segment()에 return true 직전 free()를 주석처리 → fork_read 통과
  • lazy_load_mmap() 또한 같은 처리
  • gpt의 설명

    lazy_load_segment() 안에서 aux를 해제하면, 그 load_info 구조체가 초기화 함수(initializer) 역할이 아닌 페이지 폴트 핸들러(swap-in)에서도 재사용된다는 점에서 문제가 생깁니다.

    구체적으로:

    1. 초기화 vs. 페이로드(load) 시점

      • vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, aux)
        • 초기화 함수(initializer)lazy_load_segment를 넘긴 것이 아니라, 첫 페이지 폴트 시 호출될 콜백으로 aux와 함께 등록하는 겁니다.
      • 즉, 초기화 단계가 따로 있고, 그 다음 첫 접근(페이로드) 단계에서 lazy_load_segment가 호출됩니다.
    2. aux 메모리의 생명 주기

      • aux 로 넘긴 struct load_info * 는,
        • 초기화 함수에서 page->uninit.aux 에 보관하고,
        • swap-in 콜백(lazy_load_segment)가 호출될 때 인자로 전달됩니다.
      • 따라서 이 aux 구조체는 swap_in 단계가 끝난 뒤(첫 페이지 폴트 처리 후)에도,
        • *언매핑(destroy)**이나
        • 스왑아웃(swap_out) 시점까지 page->uninit.aux 또는 page->file 등 내부 자료구조에서 읽혀야 할 수도 있습니다.
    3. free(info) 를 여기서 하면 안 되는 이유

      • lazy_load_segment첫 페이지 폴트에만 호출되는 게 아니라, 페이지가 스왑아웃됐다가 다시 스왑인되는 여러 차례 호출될 수 있습니다.
      • aux를 즉시 해제해 버리면, 이후 스왑인/언매핑 시에 page->uninit.aux 를 참조할 수 없게 되어 힙 오염이나 읽기 실패를 일으킵니다.
      • fork_read 테스트는 자식 프로세스가 부모로부터 load_info를 물려받고, 여러 번 페이지 폴트를 발생시키면서 코드를 읽어야 통과하는데, aux가 사라져 버리면 그 다음 폴트에서 로딩에 실패해 버립니다.

      올바른 패턴

      • 초기화 함수(file_backed_initializer 또는 anon_initializer) 에서만 aux를 꺼내서 내장 필드(page->file 또는 page->anon 등)에 복사한 뒤, 그때 free(aux) 해 줍니다.
      • swap-in 콜백(file_backed_swap_in 또는 lazy_load_segment) 에서는 절대 aux를 해제하지 않습니다.
      • 언매핑(destroy) 시점이나 스왑아웃(swap_out) 시점에서 필요한 모든 정보가 이미 page 구조체 내부에 복사되어 있고, 그때 aux를 해제할 필요도 없습니다.
  • aux 구조체는 다른데서도 쓰이기 때문에 해제 해주면 안된다고 한다.
static bool
lazy_load_segment(struct page *page, void *aux)
{
	...
	/* 2) 파일 위치 설정 후, read_bytes만큼 읽어서 kva에 복사 */
	file_seek(file, offset);
	if (file_read(file, kva, read_bytes) != (int)read_bytes)
	{
		free(info);
		return false;
	}

	/* 3) 나머지 부분(zero_bytes)만큼 0으로 채움 */
	memset(kva + read_bytes, 0, zero_bytes);

	// free(info); <-------------------------여기 주석처리!
	return true;
}

tests/vm/mmap-bad-off

  • do_mmap() 인자 유효성 검사 추가
size_t page_cnt = (length + PGSIZE - 1) / PGSIZE;
if (is_kernel_vaddr(addr + (page_cnt * PGSIZE) - 1)) // mmap-kernel 테스트
		return NULL;

tests/vm/mmap-kernel

  • do_mmap() 인자 유효성 검사 추가
if (offset < 0 || file_length(file) < offset || offset % PGSIZE != 0)
		return NULL;

test/vm/mmap-exit

  • process_exit() 수정

void process_exit(void)
{		...
// TODO: fd_table 순회하여 file_close()
	...

#ifdef VM
	/* 커널 스레드가 아닌, 사용자 프로세스 페이지만 */ 
	if (curr->pml4 != NULL) /* 또는 SPT가 초기화된 스레드만 */
	{
		/* 1) SPT 해시 전체 순회하며 file-backed 페이지만 골라 do_munmap 호출 */
		void **starts = NULL;
		size_t nstarts = 0;

		struct hash_iterator hi;
		hash_first(&hi, &curr->spt.spt_hash);
		while (hash_next(&hi))
		{
			struct page *p = hash_entry(hash_cur(&hi), struct page, hash_elem);

			/* 2) 파일 매핑된 페이지만 처리 (VM_FILE 타입) */
			if (p->operations->type == VM_FILE)
			{
				struct file_page *aux = p->uninit.aux;

				/* 3) 매핑의 첫 페이지(start_addr)에서만 수집 */
				if (p->va == aux->start_addr)
				{

					/* 4) 중복 저장 방지: 이미 수집된 주소인지 확인 */
					bool seen = false;
					for (size_t i = 0; i < nstarts; i++)
						if (starts[i] == aux->start_addr)
						{
							seen = true;
							break;
						}

					/* 5) 새로 발견된 시작 주소이면 배열에 추가 */
					if (!seen)
					{
						void **tmp = realloc(starts, sizeof(void *) * (nstarts + 1));
						if (tmp)
							starts = tmp;
						else
							break; /* OOM—give up early */
						starts[nstarts++] = aux->start_addr;
					}
				}
			}
		}
		/* 2) Now unmap each mapping (this mutates the hash, but it’s safe because
	   we’re no longer iterating it) */
		for (size_t i = 0; i < nstarts; i++)
			do_munmap(starts[i]);

		free(starts);
	}
#endif

process_cleanup();
}
  • file_page 구조체 인자 추가
struct file_page
{
	struct file *file;	 /* file_reopen() 으로 얻은 파일 핸들 */
	off_t offset;		 /* 이 페이지가 파일에서 읽어올 시작 오프셋 */
	uint32_t read_bytes; /* 이 페이지에 실제로 읽어들일 바이트 수 */
	uint32_t zero_bytes; /* 페이지의 나머지 부분(읽을 데이터 이후)을 0으로 채울 바이트 수 */
	bool writable;		 /* 쓰기 권한 */

------------------------------------- 추가
	void *start_addr; /* 이 매핑의 시작 가상주소 */
	size_t length;	  /* 매핑 전체 길이 (바이트) */
};
  • do_mmap() 에 aux 받는 인자 위 구조체 인자 추가한거 추가
struct file_page *aux = (struct file_page *)malloc(sizeof(*aux));
		if (aux == NULL)
			return NULL;

		aux->file = file_cp;
		aux->offset = offset;
		aux->read_bytes = page_read_bytes;
		aux->zero_bytes = page_zero_bytes;
		aux->writable = writable;

---------------------------------- 추가
		aux->start_addr = addr;
		aux->length = length;
  • do_munmap() 수정
/* Do the munmap */
void do_munmap(void *addr)
{
	struct thread *cur = thread_current();

	addr = pg_round_down(addr);

	/* 첫 페이지에서 file 핸들 꺼내기 */
	struct page *page = spt_find_page(&cur->spt, addr);
	if (!page)
		return;
	struct file_page *first_aux = (struct file_page *)page->uninit.aux;
	struct file *file = first_aux->file;

	while (true)
	{
		/* 매핑 정보 조회*/
		struct page *page = spt_find_page(&thread_current()->spt, addr);
		if (page == NULL)
			break;

		struct file_page *aux = (struct file_page *)page->uninit.aux;

		/* 수정된 페이지(dirty bit == 1)는 파일에 업데이트해놓는다. 이후에 dirty bit을 0으로 만든다. */
		if (pml4_is_dirty(thread_current()->pml4, page->va)) //	pml4_is_dirty함수는 페이지의 dirty bit이 1이면 true를, 0이면 false를 리턴한다.
		{
			/* 물리 프레임에 변경된 데이터를 다시 디스크 파일에 업데이트해주는 함수. buffer에 있는 데이터를 size만큼, file의 file_ofs부터 써준다 */
			file_write_at(aux->file, page->frame->kva, aux->read_bytes, aux->offset);
			/* 인자로 받은 dirty의 값이 1이면 page의 dirty bit을 1로, 0이면 0으로 변경해준다. */
			pml4_set_dirty(thread_current()->pml4, page->va, 0);
		}
		/* pml4 페이지 안에서 va와 매핑된거 지우는 함수 */
		pml4_clear_page(thread_current()->pml4, page->va);

		/* c) physical frame 해제 */
		if (page->frame)
		{
			palloc_free_page(page->frame->kva);
			free(page->frame);
		}

		/* d) SPT 엔트리 & aux 해제 */
		spt_remove_page(&cur->spt, page);
		free(aux);

		addr += PGSIZE;
	}
	/* 4) 파일 닫기 */
	file_close(file);
}

test/vm/page-merge-mm

  • process-exit() 내의 함수 순서 변경 - 위의 순서에서 변경이 있다.
/* Exit the process. This function is called by thread_exit (). */
void process_exit(void)
{
	/* TODO: Your code goes here.
	 * TODO: Implement process termination message (see
	 * TODO: project2/process_termination.html).
	 * TODO: We recommend you to implement process resource cleanup here. */
	struct thread *curr = thread_current();
	struct thread *parent;
	struct list_elem *e;
	struct child_status *c;

	/* --- USERPROG 에서만 종료메시지 찍게 설정하기 --- */
	if (curr->parent_tid != TID_ERROR)
	{
		/* 1) 종료 메시지 출력 */
		printf("%s: exit(%d)\n", curr->name, curr->exit_status);

#ifdef VM
		/* 커널 스레드가 아닌, 사용자 프로세스 페이지만 */
		if (curr->pml4 != NULL) /* 또는 SPT가 초기화된 스레드만 */
		{
			/* 1) SPT 해시 전체 순회하며 file-backed 페이지만 골라 do_munmap 호출 */
			void **starts = NULL;
			size_t nstarts = 0;

			struct hash_iterator hi;
			hash_first(&hi, &curr->spt.spt_hash);
			while (hash_next(&hi))
			{
				struct page *p = hash_entry(hash_cur(&hi), struct page, hash_elem);

				/* 2) 파일 매핑된 페이지만 처리 (VM_FILE 타입) */
				if (p->operations->type == VM_FILE)
				{
					struct file_page *aux = p->uninit.aux;

					/* 3) 매핑의 첫 페이지(start_addr)에서만 수집 */
					if (p->va == aux->start_addr)
					{
						/* 4) 중복 저장 방지: 이미 수집된 주소인지 확인 */
						bool seen = false;
						for (size_t i = 0; i < nstarts; i++)
							if (starts[i] == aux->start_addr)
							{
								seen = true;
								break;
							}

						/* 5) 새로 발견된 시작 주소이면 배열에 추가 */
						if (!seen)
						{
							void **tmp = realloc(starts, sizeof(void *) * (nstarts + 1));
							if (!tmp)
								break;
							starts = tmp;
							starts[nstarts++] = aux->start_addr;
						}
					}
				}
			}
			/* 2) Now unmap each mapping (this mutates the hash, but it’s safe because
		   we’re no longer iterating it) */
			for (size_t i = 0; i < nstarts; i++)
				do_munmap(starts[i]);

			free(starts);
		}
#endif
		/* 2) 부모에게 exit 상태 전달 및 sema_up() */
		if (curr->parent_tid != TID_ERROR)
		{
			parent = thread_by_tid(curr->parent_tid);
			if (parent != NULL)
			{
				for (e = list_begin(&parent->children);
					 e != list_end(&parent->children);
					 e = list_next(e))
				{
					c = list_entry(e, struct child_status, elem);
					if (c->tid == curr->tid)
					{
						c->exit_status = curr->exit_status;
						c->has_exited = true;
						sema_up(&c->sema);
						break;
					}
				}
			}
		}

		// TODO: fd_table 순회하여 file_close()
		for (int fd = 0; fd < MAX_FD; fd++)
		{
			struct file *f = curr->fd_table[fd];
			if (f != NULL && f != &console_in /* stdin 예외 */
				&& f != &console_out)		  /* stdout 예외 */
			{
				file_close(curr->fd_table[fd]);
				curr->fd_table[fd] = NULL;
			}
		}
	}
	process_cleanup();
}

  • 테스트 결과
FAIL tests/vm/cow/cow-simple
run: two phys addrs should be the same.: FAILED
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
pass tests/userprog/fork-read
pass tests/userprog/fork-close
pass 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
pass 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
pass tests/vm/pt-write-code2
pass tests/vm/pt-grow-stk-sc
pass tests/vm/page-linear
pass tests/vm/page-parallel
pass tests/vm/page-merge-seq
pass tests/vm/page-merge-par
pass tests/vm/page-merge-stk
pass tests/vm/page-merge-mm
pass tests/vm/page-shuffle
pass tests/vm/mmap-read
pass tests/vm/mmap-close
pass tests/vm/mmap-unmap
pass tests/vm/mmap-overlap
pass tests/vm/mmap-twice
pass tests/vm/mmap-write
pass tests/vm/mmap-ro
pass tests/vm/mmap-exit
pass tests/vm/mmap-shuffle
pass tests/vm/mmap-bad-fd
pass tests/vm/mmap-clean
pass tests/vm/mmap-inherit
pass tests/vm/mmap-misalign
pass tests/vm/mmap-null
pass tests/vm/mmap-over-code
pass tests/vm/mmap-over-data
pass tests/vm/mmap-over-stk
pass 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
pass tests/vm/mmap-off
pass tests/vm/mmap-bad-off
pass tests/vm/mmap-kernel
pass tests/vm/lazy-file
pass tests/vm/lazy-anon
FAIL tests/vm/swap-file
FAIL tests/vm/swap-anon
FAIL tests/vm/swap-iter
pass 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
pass tests/filesys/base/syn-read
pass tests/filesys/base/syn-remove
pass 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
4 of 141 tests failed.

  • 디스크 스왑 남았다.
    근데 내일 발표다.
    핀토스 주차가 마무리 되었고 난 기간 내에 끝내지 못했다. 따로 더 해야지...
    지금 오전 3시 19분인데 사실 아직 발표자료도 못 만들었다. 얼른 해야지..

  • 6.12 추가) 같은 조 예찬 형님 코드를 들으면서 2/ 141 까지 발전 시켰다.

FAIL tests/vm/swap-iter
FAIL tests/vm/cow/cow-simple

2 of 141 tests failed.

0개의 댓글