swap in/out...
이 코드는 나는 에러때문에 돌아가지 않지만,
돌아가는 다른 사람의 코드도 에러가 뜨기 때문에,
나의 코드의 다른 곳에서 문제인 것같다...
또 정말 안 되어 완전히 다른 사람 코드로 바꾸었기때문에
내가 어떻게 구현했는지 말은 하겠지만...
.....
그렇다.
아무튼 swap의 골자는 이렇다.
내가 용량 부족해 -> 아무 페이지를 다른 곳에 데이터를 그대로 옮겨놓는다(swap out)-> 걔가 자리 차지하던 공간을 쓴다
아까 걔 어디갔어? -> 표시해놓은 곳에 데이터를 가져온다(swap in) -> 내가 찾던 애와 백년해로한다.
그래서 swap in, swap out에 빠지는 부분은 동일하지만
page 타입에 따라 데이터를 어디에다 적재했다가 다시 가져올지가 갈린다.
anon은 디스크 내 swap disk라는 공간에,
file backed은 디스크 내 본래 있던 file을 이용한다.
anon, file 순서로 보되
일단 swap in, out 자체가 언제 일어나는지 확인해보자.
swap out은
내가 더 이상 기존 메모리에서 공간을 할당받을 수 없을 때 일어난다.
즉, palloc을 했지만 NULL이 반환되는 상황 이라고 할수 있다.
언제 palloc을 하는지 기억 나는가?
맞다, page에서 get frame 당시,
frame 안의 kva를 배정할때 썼었다.
그때 아마 PANIC으로 설정했을 것이다.
이제는, 거기에 새로운 frame을 배정해주는 함수를 넣어준다.
본래는 palloc으로
frame을 Init 시켜 frame을 return 해주는 함수다.
그러나, 넣을 kva가 null이라면
vm evict frame으로 빠지게 해준다.
(vm에서 희생자가 될 frame을 가져오는 함수다!)
static struct frame *
vm_get_frame (void) {
struct frame *frame = NULL;
void *pg_ptr = palloc_get_page(PAL_USER);
if (pg_ptr == NULL)
{
return vm_evict_frame();
}
frame = (struct frame *)malloc(sizeof(struct frame));
frame->kva = pg_ptr;
frame->page = NULL;
ASSERT(frame != NULL);
ASSERT(frame->page == NULL);
return frame;
}
이 단계에서는
희생자가 될 fraem을 이미 vm get victim으로 가져왔다고 가정하고
swap out 시켜, 원래의 데이터는
해당 페이지의 방식대로 다른 곳에 저장해둔뒤,
여기서는 추가로 kva 안의 공간이 오염되었을 가능성을 염려해
0으로 모조리 memset을 해주고있다...
(근데 아마 다른 사람의 코드니 단순히 그런 이유보단
혹시? 여러 프로세스가? 다른 frame의 접근할수 있다면?
memset 해주는게 맞긴 한데(Protection목적으로)
자기 process면 정말 혹시 모를 오염...정도일듯.)
(일단 내 생각엔 필수는 아님.)
static struct frame *
vm_evict_frame (void) {
struct frame *victim = vm_get_victim();
if (!swap_out(victim->page)){
return NULL;
}
victim->page = NULL;
memset(victim->kva, 0, PGSIZE);
return victim;
}
이곳에선 LRU 방식으로 구현하고 있다.
내 방식이 안 되어 복붙했었고
확실히 이 부분 복붙하니까
모든 코드가 안되는 에러지점에 도달했던거같다
(즉 코드 자체가 돌아가긴했다는 뜻...내 코드적 문제가 있을뿐.)
최대한 흉내내어 내가 썼던 코드는 안되고
복붙되니 되었던건 정말 아직까지 봐도 차이점을 모르겠지만
뭐 오타가 있었나보지...
아무튼...
처음 쓸 당시에는 확실히 몰랐지만
지금은 대강 안다.
관리를 어떻게 하냐면...
어차피 2번에서 list move해서
헉!! 영원히 포함 안되는건가요 하고 걱정할 수 있지만
claim page 등 단계를 타면서 list에 포함되는 부분을...
그러니까 frame 자체가 어차피 frame list 에 관리되기 위해
포함되는 부분에서 다시 포함되므로 괜찮다!
static struct frame *
vm_get_victim (void) {
struct frame *victim = NULL;
lock_acquire(&swap_lock);
size_t swap_len = list_size(&swap);
struct list_elem *tmp = list_begin(&swap);
struct frame *tmp_frame;
struct list_elem *next_tmp;
for (size_t i = 0; i < swap_len; i++)
{
tmp_frame = list_entry(tmp, struct frame, frame_elem);
if (pml4_is_accessed(thread_current()->pml4, tmp_frame->page->va))
{
pml4_set_accessed(thread_current()->pml4, tmp_frame->page->va, false);
next_tmp = list_next(tmp);
list_remove(tmp);
list_push_back(&swap, tmp);
tmp = next_tmp;
continue;
}
if (victim == NULL)
{
victim = tmp_frame;
next_tmp = list_next(tmp);
list_remove(tmp);
tmp = next_tmp;
continue;
}
tmp = list_next(tmp);
}
if (victim == NULL)
victim = list_entry(list_pop_front(&swap), struct frame, frame_elem);
lock_release(&swap_lock);
return victim;
}
그렇게.... anon 타입의 page를 다루게 된다.
anon 타입은 swap disk에 데이터를 저장했다가 가져오기 때문에,
swap disk를 써야하고, disk 관련 함수를 쓰게 된다.
disk.c 파일을 전반적으로 확인하는게 좋으며,
여기서 확인하게 되는건...
대개 방법은 두 가지인데
하여 disk size를 구해,
disk size와 잘 협의해서 bit의 갯수를 정해
그 크기에 맞는 bitmap을 create한다면,
bitmap scan이라는 함수 자체가
원하는 갯수만큼 연속적인 비트를 찾아서
맨 앞 인덱스를 주기때문에
bitmap을 8개 찾든, 1개 찾든,
아무튼...
(그러니까 sector별로 bit를 설정한다면 8개씩 찾아야할거고)
(page 별로 bit를 설정한다면 1개씩 찾아야할 거다.)
(용량은 일정하니 sector 별이 page 별보다 bit 갯수가 많겠지.)
그래서 anon init 시 그에 따라 이렇게 설정한다.
void
vm_anon_init (void) {
swap_disk = disk_get(1,1); // swap disk
swap_bit = bitmap_create((size_t)disk_size(swap_disk));
lock_init(&swap_lock);
}
swap disk를 왜 1, 1로 get하냐면
disk 가 00 01 10 11이 있는데 11이 swap disk라고 하고 있다.
여기서는 swap disk의 size 만큼 swap bit를 만들고 있다.
...
깃북에서는 swap out이 일어난 직후에야 swap in이 일어나니
swap out을 먼저 짤 것을 권유하고 있다.
swap out의 순서는 이렇다.
다시 찾을수 있게 기존 page 구조체에 어느 sector에 썼는지 잘 기재하는게 중요하다.
아래에서는 아예 page의 anon page에
swap idx에 시작 idx를 저장하고 있다. (그래서 아래의 anon initializer도 이를 고려해야한다.)
static bool
anon_swap_out (struct page *page) {
struct anon_page *anon_page = &page->anon;
lock_acquire(&swap_lock);
disk_sector_t sec_no = (disk_sector_t)bitmap_scan_and_flip(swap_bit, 0, 8, false);
lock_release(&swap_lock);
if (sec_no == BITMAP_ERROR)
return false;
anon_page->swap_idx = sec_no;
for (int i = 0; i < 8; i++)
{
disk_write(swap_disk, sec_no + i, page->frame->kva + i * DISK_SECTOR_SIZE);
}
pml4_clear_page(anon_page->thread->pml4, page->va);
pml4_set_dirty(anon_page->thread->pml4, page->va, false);
page->frame = NULL;
return true;
}
disk write 시
disk는 반드시 sector 만큼 분리되어있으니
다음 sector로 넘어가는 것,
kva에서 주소를 write한 만큼 이동하는 것을
계산이 잘 되도록 유의하자.
나의 경우는
흠
dirty bit는 아직 고려하지 못했었고
pml4도... 애초에 나는 첫 구현에서는
swe라고 frame을 list로 관리하지 않고
frame 안에 swe 라고 swap table entry라며
구조체 선언해서 frame 가리키는 포인터...로
아예 새로운 노드로 썼었는데
이 과정에서 해봤던게
슬프다..
나에게도 열정이 있던 시기가 있었지...
근데 아직도 전자와 후자가 안 되는 이유를 잘 모르겠지만
복합적인 이유가 있긴 하겠지 git에는 commit으로 남아있겠지만.
swap index를 초기화해주는 모습.
또 어느 thread인지 알아야 pml4자체를 조정할수 있기때문에 추가해줘야한다.
(thread 단위로 pml4가 있으니까.)
bool
anon_initializer (struct page *page, enum vm_type type, void *kva) {
/* Set up the handler */
struct uninit_page *uninit = &page->uninit;
memset(uninit, 0, sizeof(struct uninit_page));
page->operations = &anon_ops;
struct anon_page *anon_page = &page->anon;
anon_page->swap_idx = UINT32_MAX;
anon_page->thread = thread_current();
return true;
}
이후 swap in을 한다.
anon page에 index가 잘 있는지 확인하고
disk에서 그 시작 index부터 8번 읽어
disk read를 전개하고
bitmap을 다시 원래로 돌려준다.
static bool
anon_swap_in (struct page *page, void *kva) {
struct anon_page *anon_page = &page->anon;
if (anon_page->swap_idx == SIZE_MAX)
return false;
lock_acquire(&swap_lock);
bool check = bitmap_contains(swap_bit, anon_page->swap_idx, 8, false);
lock_release(&swap_lock);
if (check)
{
return false;
}
for (int i = 0; i < 8; i++)
{
disk_read(swap_disk, anon_page->swap_idx + i, kva + i * DISK_SECTOR_SIZE);
}
lock_acquire(&swap_lock);
bitmap_set_multiple(swap_bit, anon_page->swap_idx, 8, false);
lock_release(&swap_lock);
return true;
}
이후 destroy도
frame 도 list에서 제해주고
bitmap도 리셋 시켜준다.
static void
anon_destroy (struct page *page) {
struct anon_page *anon_page = &page->anon;
if (page->frame != NULL)
{
lock_acquire(&swap_lock);
list_remove(&page->frame->frame_elem);
lock_release(&swap_lock);
free(page->frame);
}
if (anon_page->swap_idx != SIZE_MAX)
bitmap_set_multiple(swap_bit, anon_page->swap_idx, 8, false);
}
...
file swap in, out은
거의 mmap, munmap과 유사하다.
VM_FILE은 애초에 page에 aux로 관련 구조체를 할당받았으므로
거기서 가져와 하기만 하면 된다.
file back swap out 시에는 file에 쓰고,
swap int시에는 file read를 하면 된다.
destroy는 애초에 mmap 시 구현하였을 것이므로 생략.
bool
file_backed_initializer (struct page *page, enum vm_type type, void *kva) {
/* Set up the handler */
page->operations = &file_ops;
struct file_page *file_page = &page->file;
struct page_load_data *aux = (struct page_load_data *) page->uninit.aux;
file_page->file = aux->file;
file_page->ofs = aux->ofs;
file_page->read_bytes = aux->read_bytes;
file_page->zero_bytes = aux->zero_bytes;
}
static bool
file_backed_swap_in (struct page *page, void *kva) {
struct file_page *file_page UNUSED = &page->file;
struct page_load_data *aux_d = (struct page_load_data *)(page->uninit.aux);
file_seek(aux_d->file, aux_d->ofs);
if(file_read(aux_d->file, kva, aux_d->read_bytes) != (int)aux_d->read_bytes){
palloc_free_page(page->frame->kva);
return false;
}
memset((page->frame->kva)+(aux_d->read_bytes), 0, aux_d->zero_bytes);
return true;
}
static bool
file_backed_swap_out (struct page *page) {
struct file_page *file_page UNUSED = &page->file;
if(page == NULL){
return false;
}
struct page_load_data *aux_d = (struct page_load_data *)page->uninit.aux;
if(pml4_is_dirty(thread_current()->pml4, page->va)){
file_write_at(aux_d->file, page->va, aux_d->read_bytes, aux_d->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, 0);
}
pml4_clear_page(thread_current()->pml4, page->va);
}
static void
file_backed_destroy (struct page *page) {
struct file_page *file_page UNUSED = &page->file;
if(pml4_is_dirty(thread_current()->pml4, page->va)){
file_write_at(file_page->file, page->va, file_page->read_bytes, file_page->ofs);
pml4_set_dirty(thread_current()->pml4, page->va, 0);
}
pml4_clear_page(thread_current()->pml4, page->va);
}
....
내 코드는 어디서부터가 문제인걸까
흠.....
아무튼 끝.