Krafton Jungle/5. PintOS

[PintOS 4주차] Day 7

munsik22 2025. 6. 4. 11:11

오늘의 랜덤런치

함수 수정: spt_find_page()

❎ Before: 모든 프로그램이 exit(-1)로 비정상적으로 종료됨

struct page *
spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {
	struct page *page = NULL;
	if(spt == NULL || va == NULL) return NULL;
	int key = hash_bytes(&va, sizeof(va)) % spt->spt_hash->bucket_cnt;
	struct list *bucket = &spt->spt_hash->buckets[key];
	for (struct list_elem *e = list_begin(bucket); e != list_end(bucket); e = list_next(e)) {
		struct hash_elem *he = list_elem_to_hash_elem(e);
		struct page *p = hash_entry(he, struct page, hash_elem);
		if (p->va == va)
			return p;
	}
	return page;
}
  • int key = hash_bytes(&va, sizeof(va)) % spt->spt_hash->bucket_cnt;
    • 해시 인덱스를 계산할 때 va의 주소 자체를 &va로 해시한다.
      → 실제 원하는 가상 주소의 값이 아니라, va라는 포인터의 스택 상 주소를 해싱함
    • 즉, va의 "값"을 해싱한 게 아니라, va 변수가 저장된 "주소"를 해싱한 것이다.
      → 설계했던 의도와 전혀 다른 값을 key로 만듦
  • if (p->va == va)
    • 비교 자체는 정확하지만, 해당 bucket 자체가 잘못된 곳을 가리키고 있기 때문에 루프에 들어가도 못 찾음
    • p->vava를 비교할 때는 pg_round_down(va)으로 정규화된 주소로 비교해야 함
  • 해시 테이블에서 잘못된 방식으로 버킷을 찾기 때문에 NULL을 리턴한다.
    • SPT에서 페이지를 항상 못 찾음
    • vm_try_handle_fault()에서 페이지가 없음
    • fault 처리 실패 → exit(-1)

✅ After: 상당 수의 테스트가 pass됨 (58 failed)

struct page *
spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {
	struct page *page = NULL;
	if(spt == NULL || va == NULL) return NULL;
	page = malloc(sizeof(struct page));
	page->va = pg_round_down(va);
	struct hash_elem *e = hash_find(&spt->spt_hash, &page->hash_elem);
	if (e != NULL) return hash_entry(e, struct page, hash_elem);
	else return NULL;
}
  • va를 페이지 크기 단위로 정규화한 후 page->va에 넣고 해시 테이블에서 찾는다.
  • hash_find()는 내부적으로 h->hash(e)를 이용해 정확히 일치하는 hash bucket을 선택하고, 내부의 less 함수 등을 이용해 비교한다.
  • struct pagehash_elem 위치를 기준으로 정확한 해시 연산이 가능하므로 정확히 원하는 요소를 찾을 수 있다.
struct hash_elem *
hash_find (struct hash *h, struct hash_elem *e) {
	return find_elem (h, find_bucket (h, e), e);
}
  • 이미 hash_find()에서 해시 테이블에서 특정 요소 e가 속해야 할 bucket을 찾아서 반환하는 함수(find_bucket())를 사용하고 있었다.
static struct list *
find_bucket (struct hash *h, struct hash_elem *e) {
	size_t bucket_idx = h->hash (e, h->aux) & (h->bucket_cnt - 1);
	return &h->buckets[bucket_idx];
}
  • h->hash(e, h->aux)
    • 해시 테이블에서 사용하는 해시 함수를 호출해서 요소 e에 대한 해시 값을 계산함
    • h->aux는 사용자 정의 보조 데이터로 해시 계산 시 필요하면 함께 사용됨 (하지만 여기서는 NULL이 들어감)
  • &(h->bucket_cnt - 1)
    • 버킷 개수는 항상 2의 제곱수이므로, 모듈러 연산을 빠르게 수행하기 위해 비트 연산을 사용함
    • hash_val % bucket_cnt는 느릴 수 있기 때문에 (hash_val & (bucket_cnt - 1))을 사용해 더 빠르게 계산함
    • (예) bucket_cnt = 8일 때 bucket_idx = hash_val & 0b0111 → 0~7 범위
  • return &h->buckets[bucket_idx];
    • 해당 인덱스의 버킷을 포인터로 반환함
    • 버킷은 struct list 타입으로 그 안에는 해당 해시값을 공유하는 요소들이 연결 리스트 형태로 저장됨

🤝 코어 타임

💭 palloc_get_pagemalloc의 차이가 뭐지?

 

🔹 palloc_get_page (enum palloc_flags flags)

  • 역할: 페이지 단위(4KB)로 커널 또는 유저 풀의 물리 메모리에서 메모리를 할당
  • 특징
    • 정확히 1개의 페이지(4KB) 크기로 할당됨
    • 물리 메모리의 프레임을 직접 반환함
    • 보통 페이지 테이블 설정, 프레임 할당, 커널 스택/유저 스택 등에 사용
    • 인자로 PAL_USER, PAL_ZERO 등을 줄 수 있음
      • PAL_USER: 사용자 영역 메모리 풀에서 할당
      • PAL_ZERO: 할당한 페이지를 0으로 초기화
  • 사용 예시: void *page = palloc_get_page(PAL_USER | PAL_ZERO);
  • 할당 대상: 실제 물리 메모리 프레임

🔹 malloc (size_t size)

  • 역할: 크기 size만큼의 임의 크기 메모리 블록을 커널 힙에서 동적으로 할당
  • 특징
    • 내부적으로는 메타데이터 관리, free list, chunk 병합 등을 수행하는 메모리 관리자를 사용
    • 보통 sizeof(struct page) 같은 구조체 또는 작은 메모리 블록 할당에 사용
    • 연속된 바이트 단위 메모리 할당이 가능하며, 페이지 경계나 크기와는 무관함
  • 사용 예시: struct page *p = malloc(sizeof(struct page));
  • 할당 대상: 커널 힙 영역 (page allocator 위에 구축된 layer)
항목 palloc_get_page() malloc()
메모리 크기 고정: 1 페이지(= 4KB) 가변 크기 (임의의 byte 수)
단위 페이지 단위 바이트 단위
내부 동작 페이지 풀에서 직접 물리 프레임 할당 힙에서 메타데이터 기반으로 동적 관리
주 사용처 가상 메모리, 페이지 테이블, 프레임 매핑 등 구조체, 버퍼 등 일반적인 커널 동적 메모리
반환 위치 커널/유저 페이지 풀 (물리 메모리) 커널 힙 영역 (page allocator 위 abstraction)
초기화 옵션 PAL_ZERO로 0으로 초기화 가능 없음 (직접 memset 필요)
▪  페이지 단위로 물리 메모리가 필요한 경우 (프레임 할당, 페이지 테이블용 등) → palloc_get_page()
▪  작은 구조체나 버퍼 등 일반 동적 메모리 필요 시 → malloc()

💭 malloc을 하면 kernel pool에서 가져오는 걸까, user pool에서 가져오는 걸까?
▪  malloc (palloc_get_page(0)): kernel pool
▪  palloc_get_page(PAL_ZERO): kernel pool (+ 0으로 초기화)
▪  palloc_get_page(PAL_USER): user pool

💭 어떤 것들이 user pool 또는 kernel pool에서 물리 메모리를 가져와야 할까?

 

핀토스에서 user pool과 kernel pool은 서로 다른 목적을 가진 메모리 풀이다. 어떤 상황에서 어떤 풀을 사용해야 하는지 정확히 구분해야 메모리 부족 문제나 보안/격리 문제를 피할 수 있다.

 

🔹 User Pool에서 가져와야 하는 경우

  • 유저 프로세스의 주소 공간에 매핑될 물리 메모리가 필요한 상황
  • 유저 프로세스의 가상 주소(< KERN_BASE)에 매핑되는 페이지는 모두 User Pool에서 할당해야 함
사용 대상 예시 설명
사용자 스택 / 힙 setup_stack(), vm_stack_growth() 등에서 유저 주소 공간에 대응되는 페이지
유저 가상 주소에 대응되는 프레임 vm_alloc_page(VM_ANON | VM_MARKER_0, stack_bottom, true)
파일을 lazy loading 하는 페이지 lazy_load_segment() 등에서 할당되는 유저 페이지
argument passing, 유저 버퍼 복사 process_exec(), syscall.c 내 유저 주소 접근 시 프레임 매핑용
유저 코드 segment ELF 로드 중 코드/데이터 segment를 매핑할 때

 

🔹 Kernel Pool에서 가져와야 하는 경우

  • 커널이 내부적으로 사용하는 자료구조, 스택, 임시 버퍼 등을 위한 메모리가 필요한 경우
  • 커널 전용 주소 공간(≥ KERN_BASE)에서 동작하거나, OS 내부 자료구조에 쓰일 페이지는 Kernel Pool에서 할당해야 함
사용 대상 예시 설명
커널 스레드의 커널 스택 thread_create()palloc_get_page(0) 사용
malloc()으로 할당되는 arena 등 malloc() 내부에서 palloc_get_page(0)
커널 자료구조용 페이지 예: file descriptor, struct thread, struct frame
페이지 프레임 관리용 구조체들 예: frame_table, supplemental_page_table 구조체 등
유저 버퍼 복사 시 임시 커널 버퍼용 syscall.c 등에서 커널 주소 공간에 임시로 버퍼 만들 때
SPT나 Frame Table 등을 위한 해시 테이블 malloc() 또는 palloc_get_page()로 메모리 확보 시 사용

 

🔹 요약 정리

항목 User Pool Kernel Pool
언제 사용하는가? 유저 프로세스의 가상 주소 매핑용 커널 내부 스택, 자료구조, 임시 버퍼 등
가상 주소 < KERN_BASE (유저 주소 공간) KERN_BASE (커널 주소 공간)
대표 함수 사용 예시 palloc_get_page(PAL_USER) palloc_get_page(0), malloc()
예시 대상 스택, 힙, 코드/데이터 세그먼트, mmap 등 thread 구조체, frame table, 커널 malloc 등

 

🔸 잘못된 사용 예시

  • 유저 주소 공간인데 palloc_get_page(0) 사용 → 유저 풀이 아닌 커널 풀 침범, 메모리 부족 가능
  • 내부 자료구조에 palloc_get_page(PAL_USER) 사용 → 유저 풀 낭비, 보안 위험 (사용자 접근 가능 주소에 매핑 가능)
잘못 사용하면? 무엇이 문제인가?
커널 공간 자료구조에 PAL_USER 사용 유저 풀 낭비 + 리소스 부족 가능성
유저 공간 페이지에 PAL_USER 없이 할당 커널 풀 침범 → 사용자 메모리 부족 발생 가능
malloc()을 유저 데이터에 사용 보안 위협 (커널 heap에 유저 데이터 섞임)

 

⭐ VM에서의 메모리 풀 사용 구분

종류 사용 대상 대표 함수 설명
User Pool 유저 가상 주소에 대응되는 페이지 vm_alloc_page_with_initializer() 내부에서 palloc_get_page(PAL_USER) 유저 공간에 매핑되는 실제 물리 페이지 프레임
User Pool Lazy Loading 대상 페이지 (ELF, mmap) lazy_load_segment() 파일에서 읽어 메모리에 올릴 때 유저 공간에 대응되는 페이지
User Pool setup_stack() 시 스택용 페이지 vm_stack_growth() 유저 스택이 페이지 폴트를 유발할 때 새로운 페이지 생성
Kernel Pool struct page, struct frame, struct hash_elem 등 메타데이터 구조체 malloc() 또는 palloc_get_page(0) 커널 내부 자료구조용, 유저와 무관
Kernel Pool SPT 구조체 supplemental_page_table_copy(), spt_insert_page() 해시 테이블 및 엔트리 정보 저장용
Kernel Pool Frame Table, Swap Table 등 전역 테이블 vm_get_victim(), vm_evict_frame() 등에서 사용 커널이 관리하는 물리 메모리 추적 테이블
Kernel Pool 페이지 클레임 시 내부 처리용 버퍼 vm_do_claim_page() → 페이지 맵핑 전에 필요한 임시 메모리 -
Kernel Pool 페이지 교체 시 swap slot 관리 구조체 swap_out(), swap_in() 유저 페이지를 디스크로 내릴 때 관리용

 

🔹 결론

종류 어디서? 예시 함수들
유저 주소 매핑용 페이지 User Pool (PAL_USER) vm_alloc_page_with_initializer, setup_stack, lazy_load_segment
커널 자료구조용 구조체, 버퍼 등 Kernel Pool (기본) malloc(), supplemental_page_table_copy, frame.c
▪  실제 data (예: kva와 같은 물리페이지(프레임)) → palloc
▪  메타 data (예: struct page, struct frame 등…) → malloc