page 하나는 4KB로 공간을 차지한다. mmap-munmap test case를 보면 file length(8192)가 인자값으로 들어간다. 딱 page 2개의 크기이다. pintos의 요구사항을 보면 lazy load 방식을 사용하라고 나와있어서 요구사항대로 구현을 하게 되면 한 번에 8192bytes를 load하지 않는다. 필요한 data만 load하기 때문에 읽을 데이터를 따로 변수에 관리해야 한다.
page의 전체 크기는 4KB이다. 만약 읽어야 하는 데이터의 size가 page보다 작다면 남은 부분을 어떻게 해야할까? 그렇다. 0으로 채워줘야 한다. 이렇게 하는 이유가 있는데 그건 잘 모르겠고, 0으로 기존의 데이터를 덮어씌워주지 않으면 여전히 데이터가 남아있게 되니 문제가 생기는 것 같다.
그러면 구체적으로 어떻게 읽어야 할까? 처음 addr은 맨 하위 주소에 존재한다. 따라서 높은 주소로 8byte씩 올라가면서 데이터를 읽을 것이다. 읽어야 되는 데이터를 모두 읽고 나서 읽어야 되는 데이터가 더 존재 한다면(인자로 받게될 Length로 확인할 것이다) loop()를 통해 다시 page를 읽을 것이다. 그리고 page에 공간이 남게 되면 read bytes한 위치에서부터 memset()을 이용해 zero로 채워준다.
사실 이 함수를 구현하기 전에 page단위로만 addr을 이동시키면서 데이터를 읽었다. 그렇다보니 file의 데이터를 제대로 읽지 못하는 오류가 발생했고, 디버깅 하는데만 하루를 썼다.
void *
do_mmap(void *addr, size_t length, int writable, struct file *file, off_t offset)
{
// 1. addr로부터 페이지 생성
// 1-1. lazy_load, aux 초기화해서 넘겨주기.
// 1-2. 복사(length, offset, 등등) 이거 바로 해줘요? 그럼 또 lazy 아니잖아. -> 이 내용이 lazy_load에서 타입 체크후에 복사 바로 하면 되지 않겠나.
// 1-3. 나머자 내용은 0으로 채워야 함.
void *start_addr = addr;
size_t start_length = length;
while (length > 0)
{
size_t page_read_bytes = (length < PGSIZE) ? length : PGSIZE;
size_t file_left = file_length(file) - offset;
// read_bytes, zeor_bytes 초기화
size_t read_bytes = (file_left < page_read_bytes) ? file_left : page_read_bytes;
off_t zero_bytes = PGSIZE - read_bytes;
// aux 초기화
struct lazy_aux_file_backed *aux = malloc(sizeof(struct lazy_aux_file_backed));
aux->file = file_reopen(file);
aux->read_bytes = read_bytes;
aux->zero_bytes = zero_bytes;
aux->offset = offset;
dprintfg("[do_mmap] vm_alloc_page_with_initializer()실행하기 전. 만약 이 다음에 exit()된다면 이 함수에서 문제가 발생한 것임\n");
if (!vm_alloc_page_with_initializer(VM_FILE, addr, writable, lazy_load_file_backed, aux))
{
// page clean??
free(aux);
while(start_addr < addr){
dprintfg("[do_mmap] vm_dealloc_page() 실행 전\n");
vm_dealloc_page(spt_find_page(&thread_current()->spt, addr));
dprintfg("[do_mmap] vm_dealloc_page() 실행 후\n");
addr -= PGSIZE;
}
// file_close(file);
return NULL;
}
// 쓴 만큼 offset, length 업데이트.
length -= PGSIZE;
offset += PGSIZE;
addr += PGSIZE;
dprintfg("[do_mmap] length: %d, offset: %d, addr: %p\n", length, offset, addr);
}
struct mmap_file *mmap_file = malloc(sizeof(struct mmap_file));
mmap_file->start_addr = start_addr;
mmap_file->start_length = start_length;
mmap_file->file = file_reopen(file);
dprintfg("[do_mmap] list_push_back() 이전\n");
list_push_back(&thread_current()->mmap_list, &mmap_file->elem);
dprintfg("[do_mmap] list_push_back() 이후\n");
return start_addr;
}
bool lazy_load_file_backed(struct page *page, void *aux)
{
/* 파일에서 페이지 컨텐츠를 읽어옵니다. */
/* 이 함수는 주소 VA에서 첫 페이지 폴트가 발생했을 때 호출됩니다. */
/* 이 함수를 호출할 때 VA를 사용할 수 있습니다. */
dprintfg("[lazy_load_file_backed] routine start. page: %p, page->va: %p\n", page, page->va);
if (page->frame == NULL || page->frame->kva == NULL){
PANIC("lazy_load_file_backed이 allocate 되어 있지 않습니다!!");
}
/* Load this page. */
// aux 멤버 정의 필요.
// file page 업데이트
struct lazy_aux_file_backed *lazy_aux = (struct lazy_aux_file_backed *)aux;
// struct file_page *file_page = &page->file; //file_backed에 page 정보를 저장한다
struct file *file = lazy_aux->file;
size_t read_bytes = lazy_aux->read_bytes;
size_t zero_bytes = lazy_aux->zero_bytes;
off_t offset = lazy_aux->offset;
dprintfg("[lazy_load_file_backed] reading file\n");
if (file_read_at(file, page->frame->kva, read_bytes, offset) != read_bytes)
{
free(lazy_aux);
return false;
}
memset(page->frame->kva + read_bytes, 0, zero_bytes); // zero bytes 복사.
free(lazy_aux);
return true;
}
Here are the failed tests from the list:
1. tests/vm/pt-write-code
2. tests/vm/pt-write-code2
3. tests/vm/pt-grow-stk-sc
4. tests/vm/page-merge-stk
5. tests/vm/page-merge-mm
6. tests/vm/mmap-read
7. tests/vm/mmap-close
8. tests/vm/mmap-write
9. tests/vm/mmap-ro
10. tests/vm/mmap-exit
11. tests/vm/mmap-bad-fd // 해결
12. tests/vm/mmap-clean
13. tests/vm/mmap-over-stk
14. tests/vm/mmap-remove
15. tests/vm/mmap-off
16. tests/vm/mmap-bad-off
17. tests/vm/mmap-kernel
18. tests/vm/swap-file
19. tests/vm/swap-anon
20. tests/vm/swap-iter
21. tests/vm/swap-fork
22. tests/vm/cow/cow-simple
void
test_main (void)
{
char *actual = (char *) 0x10000000;
int handle;
void *map;
size_t i;
CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
CHECK ((map = mmap (actual, 4096, 0, handle, 0)) != MAP_FAILED, "mmap \"sample.txt\"");
/* Check that data is correct. */
if (memcmp (actual, sample, strlen (sample)))
fail ("read of mmap'd file reported bad data");
/* Verify that data is followed by zeros. */
for (i = strlen (sample); i < 4096; i++)
if (actual[i] != 0)
fail ("byte %zu of mmap'd region has value %02hhx (should be 0)",
i, actual[i]);
munmap (map);
close (handle);
}
✅ 1. 파일 내용이 메모리에 제대로 매핑되었는가?
if (memcmp (actual, sample, strlen (sample)))
fail ("read of mmap'd file reported bad data");
• sample.txt의 앞부분이 메모리 주소 0x10000000에 정확히 복사되었는지 확인합니다.
• memcmp()을 통해 매핑된 메모리 영역(actual)과 사전에 정의된 참조 데이터 sample을 비교합니다.
• 파일에서 읽은 내용이 기대한 문자열과 다르면 실패 처리합니다.
⸻
✅ 2. 매핑된 페이지의 나머지 영역이 0으로 초기화되었는가?
for (i = strlen (sample); i < 4096; i++)
if (actual[i] != 0)
fail ("byte %zu of mmap'd region has value %02hhx (should be 0)", i, actual[i]);
• strlen(sample) 이후의 메모리 공간이 전부 0으로 채워져 있어야 합니다.
• 이는 파일의 크기가 4096바이트보다 작을 경우, 남은 부분을 0으로 채워야 한다는 mmap의 요구사항을 테스트하는 것입니다.
• 예를 들어, 파일이 100바이트라면, 나머지 3996바이트는 0이 되어야 합니다.