프레임은 page로 구성되어 있고, 실제 데이터를 저장할 수 있다.
struct frame{
void *kernel_VA
struct page *page;
}
하나의 frame에는 하나의 page를 할당하고 있다.
그리고 물리 메모리와 매핑되어 있는 커널 가상 주소를 담고 있다.
근데 이 frame 정보를 어떤 식으로 사용하는 거지?
struct list frame_table; // 프레임 테이블
struct lock frame_table_lock; // 동기화를 위한 락
struct list_elem elem;
lock에 대한 개념이 잘 안 잡혀 있는 것 같다…
void frame_table_init(void) {
list_init(&frame_table);
lock_init(&frame_table_lock);
}
frame table에 사용될 list와 lock을 초기화
// 1. 물리 페이지 하나를 할당받고 (사용자 프로세스를 위한 데이터 공간)
void *kernel_VA = palloc_get_page(PAL_USER);
// 2. frame 구조체를 위한 메타데이터를 malloc으로 할당
struct frame *fr = malloc(sizeof(struct frame));
// 3. 해당 frame에 물리 주소와 연결된 page를 연결
fr->kernel_VA = kernel_VA;
fr->page = page;
// 4. frame_table에 추가 (락을 사용하여 동기화)
list_push_back(&frame_table, &fr->elem);
page 공간 할당하면 malloc으로 물리 메모리의 스택 영역에 frame을 생성 → 프레임 테이블에 추가하는 순서인 것 같은데…
malloc은 동적 메모리 할당 함수로 힙 영역에 공간을 할당한다. 그런데 우리가 현재 배우고 있는 pintos환경에서는 커널 힙이 존재하지 않고, 커널 스택에 쌓는 것 같다.
struct frame *fr = malloc(sizeof(struct frame));
이 코드의 이해가 틀렸는데 위 코드는 커널 힙에 frame 구조체를 저장할 공간을 생성한다.
palloc_get_page() → 물리 메모리에 공간 생성
palloc_get_page()의 정확한 설명은 물리 메모리 공간에 하나의 page를 생성하고, 이 페이지를 참조하는 frame을 malloc으로 생성한다??
palloc_get_page(PAL_USER)
PAL_USER는 유저 모드의 주소인 것 같은데??
프레임 테이블에서 가장 중요한 작업은 사용되지 않은 프레임을 획득하는 것입니다. 이는 프레임이 free 상태라면 간단한 일입니다. 하지만 free 상태인 프레임이 없다면, 몇몇 페이지들을 프레임에서 쫓아내어 그 프레임을 free 상태로 만들어주어야 합니다.
위 내용을 곰곰히 생각해봤다. 사용되지 않는 프레임을 어떻게 찾는걸까? free 상태인 프레임을 확인하고 쫓아내는 코드는 어떻게 구현해야 될까?
struct frame *frame_get_page(enum palloc_flags flags, struct page *page) {
void *kva = palloc_get_page(flags);
struct frame *f = NULL;
if (kva != NULL) {
// Free frame이 있다면 바로 할당
f = malloc(sizeof(struct frame));
f->kva = kva;
f->page = page;
list_push_back(&frame_table, &f->elem);
return f;
}
// Free frame이 없다면 → Eviction 시도
struct frame *victim = select_victim_frame(); // 교체할 프레임 선택
if (victim == NULL)
PANIC("No frame available and cannot evict any!");
// Evict 현재 페이지 → 스왑 or 파일에 저장
if (!swap_out(victim->page)) {
PANIC("Eviction failed: Cannot swap out page.");
}
// victim 프레임을 재사용
f = victim;
f->page = page;
return f;
}
세부적으로 보면
if (kva != NULL) {
// Free frame이 있다면 바로 할당
f = malloc(sizeof(struct frame));
f->kva = kva;
f->page = page;
list_push_back(&frame_table, &f->elem);
return f;
}
위 코드에서 중요한 부분은 이 부분인 것 같고…
이 부분을 어떻게 보완할 수 있는 지 생각해봐야 되는데…
struct frame *select_victim_frame(void) {
struct list_elem *e;
for (e = list_begin(&frame_table); e != list_end(&frame_table); e = list_next(e)) {
struct frame *f = list_entry(e, struct frame, elem);
if (!page_is_recently_accessed(f->page)) {
return f;
}
page_reset_accessed(f->page);
}
// 다 돌았으면 그냥 첫 번째 프레임 리턴
return list_entry(list_front(&frame_table), struct frame, elem);
}
frame table의 첫 frame부터 end frame까지 순회를 한다.
다음 frame으로 넘어갈 때는 frame의 next 포인터를 이용함.
현재 가리키고 있는 frame의 entry 정보를 받아와서 해당 entry의 페이지가 비어있는 지를 확인하고 비어있다면 해당 frame을 반환.
page_reset_accessed(f->page); 이 함수의 역할은 잘 모르겠다. ← 여기서 frame이 비어있지 않다면 reset 시키는 건가?? 왜??
frame table을 전체를 순회해도 비어있는 frame이 없다면 첫 번째 프레임을 return한다?
bool swap_out(struct page *p) {
if (p == NULL)
return false;
// 페이지가 더티하고, 백업 대상이 없다면 스왑 필요
if (page_is_dirty(p) || page_get_type(p) == VM_ANON) {
size_t slot = swap_out_to_disk(p); // 스왑 슬롯에 저장
p->frame = NULL;
p->swap_slot = slot;
return true;
}
// 파일 매핑 페이지라면 파일에 다시 저장 (optional)
return true;
}
스왑 아웃에 대해서 고민해봐야 되겠는데?
size_t slot = swap_out_to_disk(p); // 스왑 슬롯에 저장
스왑 아웃을 하겠다는 말인데 사용되지 않는 일부 프로세스를 스왑 영역으로 보낸다는 거니까… 근데 스왑 영역이 어딘줄 알고??
page가 dirty하다는 개념은 밑에 더 나온다.