PintOS/Project 3 : VIRTUAL MEMORY

[PintOS] Project 3 : VM - Anonymous Page

넌뭐가그렇게중요해 2025. 6. 7. 02:01

PintOS의 Virtual Memory(Project 3)에서는 여러 유형의 페이지를 지원합니다. 그중 Anonymous Page는 디스크 파일과 연결되지 않은 메모리 페이지를 의미합니다.

Anonymous Page는 파일과 연결되어 있지 않기 때문에 데이터를 임시로 저장할 필요가 있을 경우 Swap 영역에 저장합니다. 이 말은 즉, Anonymous Page는 다음과 같은 고유 기능을 갖추어야 함을 의미합니다:

  • Swap In (디스크 → 메모리)
  • Swap Out (메모리 → 디스크)
  • 초기화 (anon_initializer)
  • 해제 (anon_destroy)

전체 흐름도

[ vm_alloc_page_with_initializer ]
          │
          ▼
[ Page Struct 생성 (VM_UNINIT) ]
          │
          ▼
[ User Program 실행 중 ]
          │
          ▼
[ Page Fault 발생 ]  <-- ❗ Lazy Loading trigger
          │
          ▼
[ page_fault() → vm_try_handle_fault() ]
          │
          ▼
[ spt_find_page()로 해당 page 찾기 ]
          │
          ▼
[ page->operations->swap_in 호출 ]
          │
          ▼
[ uninit_initialize() ]
          │
          ▼
[ initializer(page, type, kva) 실행 ]
        (e.g. anon_initializer)
          │
          ▼
[ page->operations 설정 (anon_ops) ]
          │
          ▼
[ anon_page 내부 초기화, frame 할당 ]
          │
          ▼
[ 페이지 실제 메모리에 매핑됨 ]
          │
          ▼
[ 이후 Swap Out 또는 Destroy 될 수 있음 ]
          │
          ├────────────┐
          ▼            ▼
[ swap_out() 호출 ]  [ destroy() 호출 ]
          │             │
    (page->frame 해제)  │
          ▼            ▼
   [ anon_swap_out() ]  [ anon_destroy() ]

0.1 struct anon_page - Anonymous Page 전용 데이터 구조

Anonymous Page는 기본적으로 struct page 구조체 안에 포함된 union 영역 중 하나인 anon_page에 정의됩니다. 즉, 페이지 타입이 VM_ANON일 경우에만 이 구조체가 실제로 사용됩니다.

// ..include/vm/vm.h
struct anon_page
{
    /* 음수이면 스왑 아웃 상태가 아님 */
    int swap_idx;
};

0.1.1 설명

  • swap_idx는 해당 Anonymous Page가 스왑 디스크에 저장된 위치(인덱스)를 나타냅니다.
  • swap_idx == -1이면 아직 스왑 아웃되지 않았다는 의미입니다.
  • 이 값은 swap_out() 시에 설정되고, swap_in() 시 다시 -1로 초기화됩니다.
  • 이 인덱스는 이후 스왑 디스크에 접근할 때 필요한 참조값 역할을 합니다.
참고로, 이 구조체는 struct page 내부에 포함되어 있으며, page->anon.swap_idx 형태로 접근합니다.

0.2. struct lazy_load_info - Lazy Loading 전용 데이터 구조

struct lazy_load_info는 PintOS의 Virtual Memory에서 Lazy Loading(지연 로딩) 기능을 구현할 때 사용되는 보조 데이터 구조체입니다.

// ...include/vm/vm.h
struct lazy_load_info
{
	struct file *file;
	off_t offset;
	size_t readbyte;
	size_t zerobyte;
};

0.2.1. 설명

  • file
    해당 페이지가 실제로 데이터를 읽어올 파일 객체의 포인터입니다.
    → 비유: "책" 자체(파일)
    → 여러 페이지가 같은 파일을 참조할 수 있으므로, 보통 file_reopen()을 통해 핸들을 복사해서 저장합니다.
  • offset
    파일에서 데이터를 읽기 시작할 위치(오프셋)입니다.
    → 비유: "책에서 몇 쪽부터 복사할지
    → ELF 세그먼트 로딩 시, 각 페이지별로 읽기 시작할 위치가 다를 수 있습니다.
  • readbyte
    파일에서 실제로 읽어올 바이트 수입니다.
    → 비유: "책에서 몇 장을 복사할지
    → 보통 한 페이지 크기(PGSIZE) 이내이며, 나머지는 0으로 채웁니다.
  • zerobyte
    읽어온 뒤 남은 부분을 0으로 채울 바이트 수입니다.
    → 비유: "복사 후 남은 빈칸에 0을 채움
    → 예를 들어, .bss 영역 등 초기화되지 않은 데이터 공간에 해당합니다.

1. vm_alloc_page_with_initializer() – 페이지를 생성하고 초기화 함수 등록하기

// ...vm/vm.c
/* 이 함수는 가상 주소(upage)에 해당하는 '페이지 객체'를 생성하고,
   초기화 함수(init)와 보조 데이터(aux)를 등록
   반드시 이 함수나 vm_alloc_page()를 통해서만 페이지를 생성해야 합니다.
   생성된 페이지는 SPT에 등록
*/
bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
                                    vm_initializer *init, void *aux)
{
   // type이 VM_UNINIT(아직 초기화되지 않은 타입)이면 안 됨을 보장
   ASSERT(VM_TYPE(type) != VM_UNINIT)

   // 현재 스레드의 SPT(보조 페이지 테이블) 포인터를 가져옴
   struct supplemental_page_table *spt = &thread_current()->spt;

   /* 이미 해당 page가 SPT에 존재하는지 확인합니다 */
   if (spt_find_page(spt, upage) == NULL)
   {
      /* TODO: VM 타입에 따라 페이지를 생성하고, 초기화 함수를 가져온 뒤,
       * TODO: uninit_new를 호출하여 "uninit" 페이지 구조체를 생성하세요.
       * TODO: uninit_new 호출 후에는 필요한 필드를 수정해야 합니다. */
      // 페이지 타입에 따라 적절한 초기화 함수(페이지 이니셜라이저)를 선택
      bool (*page_initializer)(struct page *, enum vm_type, void *kva);
      struct page *page = malloc(sizeof(struct page));
      
      // 메모리 할당 실패 시 에러 처리(메모리 부족 등)
      if (page == NULL)
      {
         goto err;
      }

      // VM_TYPE에 따른 초기화 방법
      switch (VM_TYPE(type))
      {
      case VM_ANON:
         page_initializer = anon_initializer;         // 익명 메모리
         break;
      case VM_MMAP:
      case VM_FILE:
         page_initializer = file_backed_initializer;  // 파일 기반 메모리
         break;
      default:
         free(page);                                  // 지원하지 않는 타입이면 메모리 해제 후 에러 처리
         goto err;
         break;
      }

      // 미초기화 페이지 생성 : 실제 데이터는 아직 없고, 초기화 정보만 등록
      uninit_new(page, upage, init, type, aux, page_initializer);
      // 페이지의 쓰기 권한 설정
      page->writable = writable;

      /* TODO: 생성한 페이지를 spt에 삽입하세요. */
      // SPT에 새 페이지 등록
      if (!spt_insert_page(spt, page))
      {
         // 실패 시 메모리 누수 방지 위해 free
         free(page);
         // 실패 했으니까 에러로 가야겠지?
         goto err;
      }

      return true;
   }

err:
   return false;
}

1.1. vm_alloc_page_with_initializer() 핵심 요약

  • 이 함수는 VM 타입에 맞는 초기화 함수(anon_initializer, file_backed_initializer)를 연결하고,
  • 해당 가상 주소에 대한 struct page 객체를 생성한 뒤,
  • SPT(Supplemental Page Table)에 등록합니다.
  • 이후 페이지가 실제로 필요할 때(vm_try_handle_fault) 초기화 함수가 호출됩니다.

1.2.  vm_alloc_page_with_initializer()를 이렇게 구현 한 이유

  • 공통 생성 루틴 통일: anon, file 등 모든 페이지 타입을 하나의 함수로 생성하기 위함입니다.
  • 초기화 함수 연결이 핵심: 이 단계에서 초기화 함수만 연결해 두고, 실제 물리 페이지 할당은 나중에 claim_page()에서 발생합니다.
  • SPT 관리: spt_insert_page()를 통해 보조 페이지 테이블에 해당 정보를 등록해 두면, 이후 페이지 폴트 처리나 페이지 복사(fork) 시 일관성 있게 관리할 수 있습니다.

2. uninit_initialize() – 미초기화(uninit) 페이지를 실제로 초기화하기

/* 첫 번째 page fault 발생 시 해당 페이지를 초기화하는 함수 */
static bool
uninit_initialize(struct page *page, void *kva)
{	
	/*	페이지 구조체의 uninit 멤버를 가져옴
		이 구조체에는 페이지 타입, 초기화 함수, aux 데이터가 저장되어 있음
	*/ 
	struct uninit_page *uninit = &page->uninit;

	/*	page_initialize에서 사용할 수 있도록 먼저 init 함수를 가져옴
		나중에 page_initializer가 이 값을 덮어쓸 수 있음
	*/
	vm_initializer *init = uninit->init;

	// 페이지 초기화에 필요한 부가 정보를 담고 있는 aux 포인터를 가져옴
	void *aux = uninit->aux;

	/*	만약 페이지 타입이 mmap 또는 file-backed (VM_MMAP or VM_FILE)인 경우
		aux에는 mmap_info 구조체가 들어 있으므로, 그 안의 info 필드를 사용함 
	*/
	if (uninit->type == VM_MMAP || uninit->type == VM_FILE)
	{
		struct mmap_info *mmap_info = (struct mmap_info *)aux;
		aux = mmap_info->info;
	}

	/* TODO: 이 함수를 수정해야 할 수도 있습니다. */
	return uninit->page_initializer(page, uninit->type, kva) &&
		   (init ? init(page, aux) : true);
}

2.1. uninit_initialize() 핵심 요약

  • 이 함수는 page fault가 처음 발생했을 때, uninit 타입의 페이지를 실제로 초기화합니다.
  • 초기화는 두 단계로 이루어집니다:
    1. page_initializer: anon_initializer 또는 file_backed_initializer 호출
    2. init: Lazy loading 등에서 실제 데이터를 로딩하는 역할
  • VM_FILE, VM_MMAP 타입일 경우 aux가 구조체이므로 내부의 info 필드만 전달해야 합니다.

2.2. uninit_initialize()를 이렇게 구현한 이유

  • Lazy Loading 구현의 핵심 함수: 아직 물리 메모리에 로드되지 않은 페이지를 실제로 초기화하는 시점.
  • 기본 + 부가 초기화 분리: 기본 생성(page_initializer)과 상황별 처리(init)를 분리해 유연한 동작 구현.
  • mmap/file 처리 예외 반영: 이들 타입은 aux 구조가 다르기 때문에 별도 분기 처리 필요.

3. anon_init() - 익명 페이지 초기화

/* Initialize the data for anonymous pages */
void vm_anon_init(void)
{
	/*	스왑 영역으로 사용할 디스크를 가져옵니다.
		디스크 번호(1, 1)은 PintOS에서 일반적으로 스왑 디스크로 설정된 위치입니다.
	*/
	swap_disk = disk_get(1, 1);

	// 예외 처리 : swap_disk가 없을 경우 커널 패닉 발생
	if (swap_disk == NULL)
	{
		PANIC("CAN'T FIND SWAP DISK!");
	}

	/** TODO: bitmap 자료구조로 스왑 테이블 만들기
		스왑 테이블은 각 스왑 슬록(swap slot)의 사용 여부를 추적하기 위한 비트맵
		- 스왑 슬롯 : 메모리의 한 페이지를 디스크에 저장할 수 있는 최소 단위(1 page = PGSIZE)
		- 각 스왑 슬롯은 여러 개의 디스크 섹터(sector)로 구성

		따라서 스왑 슬롯의 개수 = 전체 디스크 섹터 수 / 한 페이지를 구성하는 섹터 수
		- 디스크 전체 섹터 수 : disk_size(swap_disk)
		- 한 페이지당 섹터 수 : PGSIZE / DISK_SECTOR_SIZE

		이 값을 기반으로 비트맵을 생성
		- bitmap의 각 비트는 하나의 스왑 슬롯을 의미하며, 0이면 비어있고 1이면 사용중
	 */
	swap_table = bitmap_create(disk_size(swap_disk) / (PGSIZE / DISK_SECTOR_SIZE));
}

3.1. anon_init() 핵심 요약

  • 스왑 디스크 설정: disk_get(1, 1)로 스왑에 사용할 디스크를 가져옴
  • 예외 처리: 디스크가 없으면 커널 패닉
  • 비트맵 생성: 페이지 단위 스왑 슬롯 관리를 위한 bitmap 생성
    • 총 스왑 슬롯 개수 = 전체 섹터 수 / (페이지 크기 / 섹터 크기)

3.2 anon_init()를 이렇게 구현 이유

  • 익명 페이지는 디스크에 저장할 파일이 없으므로, 메모리에서 디스크로 스왑 될 수 있어야 함
  • 스왑 공간의 관리를 위해 bitmap을 사용 → 각 비트가 하나의 스왑 슬롯 사용 여부를 나타냄
  • 디스크 크기와 페이지/섹터 단위를 기반으로 정확한 슬롯 개수 계산

4. anon_initializer() - anon 페이지의 초기화 

/* Initialize the file mapping */
bool anon_initializer(struct page *page, enum vm_type type, void *kva)
{
	// 예외 처리 → 들어온 page가 null일 경우
	if (page == NULL)
	{
		return false;
	}

	/*	이페이지는 anonymous 페이지이므로, anon_ops로 설정
		anon_ops는 스왑 인/아웃 등을 포함한 함수 포인터 구조체
	*/
	page->operations = &anon_ops;

	/* uninit을 anon으로 변환 */
	struct anon_page *anon_page = &page->anon; // page->anon은 포인터가 아니라 구조체 자체여서 항상 유효한 주소를 반환함

	/* swap index 초기화 
		-1은 아직 스왑 아웃된 적이 없다는 것을 의미
	*/
	anon_page->swap_idx = -1;

	/*	페이지의 물리 주소(kva)가 유효하며,
		해당 페이지의 frame이 1개 이하로만 참조되고 있을 경우
		새로 할당된 물리 페이지라고 간주하고 초기화함
	*/
	if (kva != NULL && page->frame->ref_cnt <= 1)
	{
		// 물리 페이지 전체를 0으로 초기화(보안 및 예측 가능한 동작 보장)
		memset(kva, 0, PGSIZE);
	}

	return true;
}

4.1. anon_initializer() 핵심 요약

  • anon_ops 등록 → 익명 페이지 전용 스왑 핸들러 설정
  • swap_idx를 -1로 초기화 → 아직 스왑 된 적 없다는 의미
  • 물리 메모리가 새로 할당된 경우, 보안을 위해 zero-fill (memset)

4.2. anon_initializer()를 이렇게 구현한 이유

  • 익명 페이지는 파일 기반이 아니므로, 스왑 인/아웃 동작이 반드시 필요
    → 따라서 anon_ops 등록은 필수
  • swap_idx = -1로 초기화함으로써, 이후 스왑 아웃 시 새로운 슬롯을 할당할 수 있도록 함
  • 할당 직후 물리 페이지를 0으로 초기화함으로써 예측 가능하고 안전한 상태 유지
    (예: 이전 사용자 정보 노출 방지)

5. load_segment() – ELF Segment의 Lazy Loading 매핑

/* FILE의 OFS 오프셋에서 시작하여 UPAGE 주소에 세그먼트를 로드합니다.
 * 총 READ_BYTES + ZERO_BYTES 바이트의 가상 메모리가 다음과 같이 초기화됩니다:
 *
 * - UPAGE에서 READ_BYTES 바이트는 FILE에서 OFS 오프셋부터 읽어옵니다.
 *
 * - UPAGE + READ_BYTES 위치에서 ZERO_BYTES 바이트는 0으로 초기화됩니다.
 *
 * 이 함수로 초기화된 페이지는 WRITABLE이 true이면 사용자 프로세스가 수정할 수 있으며,
 * 아니라면 읽기 전용입니다.
 *
 * 이 함수는 Lazy Loading 기법을 사용하므로, 실제 파일 내용은 즉시 로딩되지 않고,
 * 페이지 폴트가 발생할 때 lazy_load_segment() 함수에 의해 로드됩니다.
 * 
 * 메모리 할당 오류나 디스크 읽기 오류가 발생하면 false를 반환하고,
 * 성공하면 true를 반환합니다.
 */
static bool
load_segment(struct file *file, off_t ofs, uint8_t *upage,
             uint32_t read_bytes, uint32_t zero_bytes, bool writable)
{
   // 전체 segment 크기는 페이지 크기(PGSIZE)의 배수여야 함
   ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
   // upage는 반드시 페이지 시작 주소여야함
   ASSERT(pg_ofs(upage) == 0);
   // 파일 오프셋도 페이지 단위 정렬되어 있어야 함
   ASSERT(ofs % PGSIZE == 0);

   // 세그먼트 끝날 때까지
   while (read_bytes > 0 || zero_bytes > 0)
   {
      /* 이 페이지를 어떻게 채울지 계산합니다.
       * FILE에서 PAGE_READ_BYTES 바이트를 읽고
       * 남은 PAGE_ZERO_BYTES 바이트는 0으로 초기화합니다. */
      size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE; // 4KB까지만 읽어라
      size_t page_zero_bytes = PGSIZE - page_read_bytes;                  // 0 패딩 사이즈는 4KB - read_byte

      /* TODO: Set up aux to pass information to the lazy_load_segment. */
      /* Lazy Loading을 위한 보조 정보(aux)를 준비
         이 구조체는 실제 로딩 시 필요한 파일 정보 등을 포함
      */
      struct lazy_load_info *aux = malloc(sizeof(struct lazy_load_info)); // 전달해야할 인자
      if (aux == NULL)
         return false;

      // Lazy Loading을 위해 필요한 정보 저장
      aux->file = file_reopen(file);         // 나중에 읽기 위해 파일 핸들 복사
      aux->offset = ofs;                     // 파일에서 읽기 시작할 위치
      aux->readbyte = page_read_bytes;       // 읽을 바이트 수
      aux->zerobyte = page_zero_bytes;       // 0으로 채울 바이트 수

      if (!vm_alloc_page_with_initializer(VM_ANON,             // 페이지 타입은 익명 
                                          upage,               // 가상 주소
                                          writable,            // 쓰기 권한
                                          lazy_load_segment,   // 페이지 폴트 발생 시 호출될 함수
                                          aux))                // Lazy_Loading_segment에 전달될 인자
         return false;

      /* Advance. */
      read_bytes -= page_read_bytes;      // read_bytes 갱신
      zero_bytes -= page_zero_bytes;      // zero_bytes 갱신
      ofs += page_read_bytes;             // 파일 오프셋도 읽은 만큼 이동
      upage += PGSIZE;                    // 가상 주소도 다음 페이지로 이동
   }
   return true;
}

5.1. load_segment() 핵심 요약

  • ELF 파일의 세그먼트를 가상 주소에 매핑하는 함수이며, 한 번에 한 페이지씩 처리함.
  • Lazy Loading 기법을 적용해, 실제 데이터는 나중에 페이지 폴트가 발생할 때 lazy_load_segment()에서 로딩됨.
  • 각 페이지마다 읽어올 바이트(read_bytes)와 0으로 채울 바이트(zero_bytes)를 계산하고,
    이를 담은 lazy_load_info 구조체를 생성하여 초기화 함수와 함께 vm_alloc_page_with_initializer()에 넘김.
  • file_reopen()으로 파일 핸들을 복사해 여러 페이지가 동시에 파일에 접근 가능하도록 처리.

5.2. load_segment()를 이렇게 구현한 이유

  • 메모리 절약: 프로그램 전체를 한 번에 메모리에 올리지 않고, 실제 접근 시에만 로드함으로써 메모리 낭비를 줄임.
  • 속도 최적화: 필요한 페이지만 읽으므로 프로세스 시작 속도가 빨라짐.
  • 유연성 확보: lazy_load_info에 필요한 정보를 담아 페이지별로 로딩 조건을 다르게 설정 가능.
  • 병렬 파일 접근 방지: file_reopen()으로 각 페이지가 독립적으로 파일에 접근할 수 있어 동기화 문제를 회피함.

6. lazy_load_segment() – 지연 로딩된 세그먼트 실제 로드

/* 여기부터 코드는 project 3 이후 사용됩니다.
 * project 2만을 위해 함수를 구현하려면 위쪽 블록에서 구현하세요. 
 *
 * lazy_load_segment() 함수는 해당 가상 주소(page->va)에서
 * 첫 페이지 폴트가 발생할 때 호출됩니다.
 * 즉, 실제 메모리에 페이지를 로드해야 할 때 동작합니다.
 *
 * 파라미터:
 * - page: 페이지 구조체, 로드 대상 페이지 정보 포함
 * - aux: lazy_load_info 구조체 포인터, 파일 핸들, 읽을 바이트 수, 오프셋 등 로딩 정보 포함
 *
 * 반환값:
 * - 성공 시 true 반환 (페이지 로드 완료)
 * - 실패 시 false 반환 (파일 읽기 실패 등)
 */
bool lazy_load_segment(struct page *page, void *aux)
{
   /* TODO: Load the segment from the file */
   /* TODO: 이 함수는 해당 VA(가상 주소)에서 첫 페이지 폴트가 발생할 때 호출됩니다. */
   /* TODO: 이 함수를 호출할 때 VA는 사용할 수 있습니다. */
   // kva는 page 안에 이미 있다
   // 타입별로 다른 초기화 작업을 거쳐야하나?

   // lazy loading에 필요한 정보를 가져옴
   struct lazy_load_info *lazy_info = (struct lazy_load_info *)aux;
   struct file *read_file = lazy_info->file;

   // 필요한 만큼의 read_byte를 가져옴
   off_t my_read_byte = file_read_at(read_file, 
                                    page->frame->kva,       // 실제 물리 메모리 주소 
                                    lazy_info->readbyte,    // 읽어야 할 바이트 수
                                    lazy_info->offset);     // 파일 내 오프셋

   // lazy_loading에 필요한 read_byte와 실제로 필요한 read_byte가 다르면
   if (my_read_byte != (off_t)lazy_info->readbyte)
   {
      return false;
   }
   // 읽은 데이터 뒤에 남는 영역을 0으로 초기화(제로 패딩)
   memset(page->frame->kva + lazy_info->readbyte, 0, lazy_info->zerobyte);

   // 동적 할당한 aux 구조체 메모리 해제
   free(lazy_info);
   return true;
}

6.1. lazy_load_segment() 핵심 요약

  • 페이지 폴트 발생 시 호출되어 해당 페이지에 실제 데이터를 파일에서 읽어오는 함수.
  • aux로 전달된 lazy_load_info 구조체에서 파일, 읽을 바이트 수, 오프셋 등 로딩 정보를 가져옴.
  • 파일에서 필요한 바이트만큼 page->frame->kva(물리 메모리)에 읽어들임.
  • 읽은 데이터 이후 남은 부분은 0으로 초기화(제로 패딩).
  • 동적 할당한 lazy_load_info 메모리를 해제.

6.2. lazy_load_segment()를 이렇게 구현한 이유

  • 필요할 때만 데이터 로드하여 메모리 사용 최적화 및 빠른 프로세스 시작 가능.
  • 안정성 확보를 위해 파일에서 실제 읽은 바이트 수와 기대 바이트 수를 비교해 오류 처리.
  • 제로 패딩으로 읽지 않은 영역을 명확히 초기화하여 예측 가능한 메모리 상태 유지.
  • 메모리 누수 방지를 위해 로딩 후 할당된 보조 정보 구조체를 반드시 해제.

7. supplemental_page_table_copy() – 부모의 보조 페이지 테이블을 자식에게 복사

/*
 * supplemental_page_table_copy - 보조 페이지 테이블(dst)에 src 페이지 테이블을 복사합니다.
 * 
 * src의 모든 페이지를 순회하면서 페이지 타입에 따라 적절히 복사 및 초기화 작업을 수행합니다.
 * 
 * 파라미터:
 * - dst: 복사 대상 보조 페이지 테이블
 * - src: 복사할 원본 보조 페이지 테이블
 * 
 * 반환값:
 * - 모든 페이지 복사 성공 시 true 반환
 * - 중간에 실패 발생 시 false 반환
 */
bool supplemental_page_table_copy(struct supplemental_page_table *dst UNUSED, struct supplemental_page_table *src UNUSED)
{
   struct hash_iterator i;
   // src의 해시 테이블 첫 번째 요소로 iterator 초기화
   hash_first(&i, &src->SPT_hash_list);
   struct thread *cur = thread_current();

   // src의 모든 페이지 엔트리를 순회
   while (hash_next(&i))
   {
      // src_page 정보
      struct SPT_entry *src_entry = hash_entry(hash_cur(&i), struct SPT_entry, elem);
      struct page *src_page = src_entry->page;
      enum vm_type type = src_page->operations->type;    // 페이지 타입 확인
      void *upage = src_page->va;                        // 가상 주소
      bool writable = src_page->writable;                // 쓰기 가능 여부

      /* 1) type이 uninit이면(초기화되지 않은 페이지) */
      if (type == VM_UNINIT)
      {  // 원본 페이지의 초기화 함수와 aux 정보를 복제하여 새로운 페이지 생성
         vm_initializer *init = src_page->uninit.init;
         void *aux = duplicate_aux(src_page);

         // dst에 익명(anon) 페이지를 초기화 함수와 aux와 함께 할당
         vm_alloc_page_with_initializer(VM_ANON, upage, writable, init, aux);
         continue;
      }

      /* 2) type이 file-backed이면(파일 기반 페이지) */
      if (type == VM_FILE)
      {
         // 원본 페이지의 파일 관련 정보 가져오기
         struct file_page *src_info = &src_page->file;
         // 파일 핸들을 복사하고 읽을 위치 및 바이트 수 정보를 포함한 lazy_load_info 생성
         struct lazy_load_info *info = make_info(file_reopen(src_info->file), src_info->offset, src_info->read_byte);
         // mmap 관련 정보 생성(복사할 때 매핑 카운트 등 포함)
         struct mmap_info *mmap_info = make_mmap_info(info, src_info->mapping_count);
         void *aux = mmap_info;

         // 부모 프로세스(src)에서 이미 초기화된 페이지 내용을 명시적으로 복사
         if (!vm_alloc_page_with_initializer(type, upage, writable, lazy_load_segment, aux))
            return false;

         /* 부모에서 이미 초기화된 페이지이기에 바로 명시적 초기화 호출 */
         if (!vm_copy_claim_page(upage, src_page, dst))
            return false;

         continue;
      }

      /* 3) type이 anon이면 */
      if (!vm_alloc_page_with_initializer(type, upage, writable, NULL, NULL)) // uninit page 생성 & 초기화
         // init(lazy_load_segment)는 page_fault가 발생할때 호출됨
         // 지금 만드는 페이지는 page_fault가 일어날 때까지 기다리지 않고 바로 내용을 넣어줘야 하므로 필요 없음
         return false;

      // vm_claim_page으로 요청해서 매핑 & 페이지 타입에 맞게 초기화
      // if (!vm_claim_page(upage))
      // return false;
      /* Project 3 extra : COW */ 
      if (!vm_copy_claim_page(upage, src_page, dst))
         return false;

      // 매핑된 프레임(실제 메모리)에 src 페이지 내용 복사
      struct page *dst_page = spt_find_page(dst, upage);
      memcpy(dst_page->frame->kva, src_page->frame->kva, PGSIZE);
   }
   return true;
}

 7.1. supplemental_page_table_copy() 핵심 요약

  • 부모 프로세스의 보조 페이지 테이블을 자식 프로세스에 복사.
  • src의 모든 페이지를 순회하며 dst에 페이지를 복사:
    • VM_UNINIT: 초기화 함수와 aux를 복사하여 uninit 페이지 생성.
    • VM_FILE: file을 reopen하고 lazy_load_segment를 사용해 초기화, 필요시 데이터 복사.
    • VM_ANON: 일반 익명 페이지 복사 및 메모리 내용 memcpy.
  • COW 미사용 시에는 실제 물리 페이지 내용을 그대로 복사

7.2. supplemental_page_table_copy()를 이렇게 구현한 이유

  • fork 구현의 핵심: 자식 프로세스가 부모와 동일한 주소 공간을 가지도록 함.
  • 페이지 타입별로 다르게 처리: lazy loading 유지(UNINIT), 파일 매핑 복사(FILE), 실제 데이터 복사(ANON).
  • VM_FILE의 경우 reopen 필요: 공유가 아닌 독립된 파일 핸들로 페이지를 관리하기 위함.
  • vm_copy_claim_page 사용 이유: lazy page라도 이미 메모리에 올라온 경우 데이터를 즉시 복사해야 함.
  • 예외 처리 중요: 중간에 실패하면 전체 복사 실패로 간주하여 false 반환.

 8. supplemental_page_table_kill() – 프로세스 종료 시 SPT 자원 정리

/* Free the resource hold by the supplemental page table */
void supplemental_page_table_kill(struct supplemental_page_table *spt UNUSED)
{
   /* TODO: 스레드가 보유한 모든 supplemental_page_table을 제거하고,
    * TODO: 수정된 내용을 스토리지에 기록(writeback)하세요. */
   struct thread *curr = thread_current();
   hash_clear(&curr->spt, hash_spt_entry_kill);
}

9. anon_destroy() – 익명 페이지 제거 및 스왑 슬롯 정리

/* 
 * anon_destroy - 익명(anonymous) 페이지를 소멸시킵니다.
 * 
 * 이 함수는 주어진 페이지가 메모리에서 제거될 때 호출되며,
 * 해당 페이지가 스왑 공간을 사용 중이었다면 해당 스왑 슬롯을 해제합니다.
 * 
 * 매개변수:
 * - page: 제거할 대상 페이지 (호출자가 page 자체 메모리 해제는 수행함)
 */
static void
anon_destroy(struct page *page)
{
	// 페이지의 anon_page 구조체 접근 (swap 관련 정보 포함)
	struct anon_page *anon_page = &page->anon;

	// 현재 스레드의 pml4에서 이 페이지에 대한 매핑을 제거 (VA -> PA 연결 해제)
	pml4_clear_page(thread_current()->pml4, page->va);

	// 스왑 아웃된 적이 없거나, 이미 스왑에서 복구되어 유효하지 않은 스왑 슬롯이면 아무 작업도 하지 않음
	if (anon_page->swap_idx < 0)
	{
		return;
	}

	// 스왑 테이블에서 해당 스왑 슬롯을 비어있는 상태로 표시
	bitmap_reset(swap_table, anon_page->swap_idx);
}

10. uninit_destroy() – 사용되지 않은 uninit 페이지의 리소스 해제

/* 
 * uninit_destroy - 초기화되지 않은(uninit) 페이지가 제거될 때 호출되는 함수입니다.
 * 
 * 대부분의 uninit 페이지는 실제로 페이지 폴트가 발생하면서 
 * 익명(anon) 페이지나 파일(file) 페이지로 전환되어 사용됩니다.
 * 
 * 그러나 프로세스 실행 중 참조되지 않은 uninit 페이지는 실제 메모리에 로드되지 않고,
 * 종료 시점에까지 초기화되지 않은 상태로 남아있을 수 있습니다.
 * 
 * 이 함수는 그러한 경우에 대비해, uninit 페이지가 가지고 있는
 * 부가 정보(auxiliary data)를 정리합니다.
 * 
 * 매개변수:
 * - page: 제거 대상 페이지. page 객체 자체는 호출자가 해제함.
 */
static void
uninit_destroy(struct page *page)
{
	// uninit 페이지 구조체를 가져옵니다. (현재는 사용되지 않음)
	struct uninit_page *uninit UNUSED = &page->uninit;

	/* auxiliary data 해제
	 * aux는 lazy loading 등 페이지 초기화를 위해 미리 저장해둔 데이터입니다.
	 * 예: lazy_load_segment에서 사용하는 lazy_load_info 구조체 등.
	 * 
	 * 초기화되지 않고 남은 페이지라면 이 aux도 사용되지 않았기 때문에,
	 * 지금 해제해줘야 메모리 누수가 발생하지 않습니다.
	 */
	free(uninit->aux);
}