우리는 디스크에 영구적으로 저장해놓은 데이터를 사용할 때 디스크에서 직접 작업하지 않는다.
왜일까?
파일 I/O 등의 작업을 디스크에서 직접 처리하면 일단 너무 느리다. 따라서 우리는 RAM과 같은 메인 메모리에 올린 후 매핑해서 사용하면 작업을 좀 더 빠르게 처리할 수 있다.
anonymous page 같은 경우 각 프로세스는 자신만의 독립적인 익명 메모리 공간을 가지며, 이 영역은 프로세스 간에 공유되지 않는다.
fild backed page는 주로 디스크에 저장된 파일의 내용을 매핑하므로 여러 프로세스가 하나의 파일 메모리 매핑을 공유할 수 있기에 동시에 읽고 쓰는 것이 가능하다.
파일 메모리를 수정하면 변경 사항이 원본 파일에도 적용되어야 한다. 따라서 여러 프로세스가 동시에 파일 메모리를 수정하면 모든 변경 사항이 동기화 되어야 한다.
void *mmap(void *addr, size_t length, int writable, int fd, off_t offset);
fd값에 있는 파일을 offset부터 length 까지 써준 후 남은 페이지를 0으로 채워준다.
하지만 우리는
이처럼 palloc을 받을 때 PAL_ZERO를 사용하여 0으로 초기화 된 페이지를 할당 받을 것이기 때문에 파일에 내용만 써주었다.
mmap을 system call로 호출하는데 위와 같은 예외처리를 해주었다.
addr과 offset이 page-aligned 인지 체크해주었다.
그리고 length의 타입이 size_t로 되어있는데 이는 unsigned long이라서 길이가 음수로 들어올 때 어마무시하게 큰 수로 입력 받아지는 것을 확인했다.
그래서 long으로 형변환을 해준 뒤 예외처리를 하니 이상한 length를 처리할 수 있었다.
예외처리를 무사히 넘기면 do_mmap 함수를 호출한다.
do_mmap 함수는 시스템 콜(mmap)에서 fd에 해당하는 파일을 찾아서 넘겨주기 때문에 인자로 fd 대신 file을 받는다.
매핑 과정에서 해당 파일이 닫힐 수 있기 때문에 안전하게 매핑이 완료될 수 있도록 mmap이 사용할 파일을 reopen 해주어야 한다.
나중에 파일이 매핑된 시작 주소를 반환해야하므로 origin_addr
을 저장해주었다.
그리고 이 파일에 해당하는 페이지가 얼마나 연속되어있는지 알기 위해 인자로 받은 총 length (origin_length
)를 따로 저장하여 페이지 수 (page_cnt
)를 계산해주었다.
munmap 할 때 page_cnt 만큼 페이지를 찾아야하므로 꼭 필요!
우리는 lazy_loading을 구현해야하므로 실제 file을 읽을 때 필요한 정보를 load_segment의 aux와 같은 buffer에 담아준 후, vm_alloc_page_with_initializer()
를 호출한다.
이 때, FILE 페이지 타입에 맞는 초기화 함수인 lazy_load_file()
을 만들어 파일을 읽을 수 있도록 했다.
받아온 buffer(aux) 에서 실제 파일의 정보를 꺼내 file_read_at()
을 통해 kva에 내용을 기록한다.
munmap을 할 때, 파일이 수정되었을 경우 디스크에 반영해줘야 하므로 해당 페이지에 파일에 대한 정보를 저장해둔다.
이 과정이 완료되면 aux는 본인의 역할을 다 했으므로 free .. 메모리를 해제해준다.
mummap은 연결된 물리 프레임과의 연결을 끊어주는 함수로, mmap의 반대 함수로 생각하면 쉬울 것 같다.
mmap에서 메모리에 파일을 매핑한 후, 해당 페이지에 수정사항이 있을 경우 그 내용을 디스크에 있는 파일에 반영해줘야 한다.
dirty bit가 1이면 file_write_at()
을 통해 디스크의 file에 write 해준다.
정석대로라면 pml4_set_dirty()를 사용해서 dirty bit를 0으로 돌려줘야하지만, 어차피 clear 될 녀석이므로 생략했다.
그 후, 페이지는 수정 여부와 관계없이 페이지 테이블에서 현재 페이지의 매핑을 제거하고 페이지 프레임을 할당 해제한다.
페이지 테이블과 관련된 해시 테이블에서 현재 페이지를 삭제하고, 할당된 메모리를 해제시키면 해당 페이지는 더 이상 메모리에 매핑되지 않게 된다.