08:56 입실
핀토스 미완이지만.. 오늘 최대한 해결해보고
발표자료 준비
메모리 매핑된 페이지가 매핑 해제되거나 교체(스왑 아웃)되면 콘텐츠의 모든 변경 사항이 파일에 반영
처음 호출되면 페이지 폴트가 발생하고, 그때 이 함수 호출.
이 함수는 실제로 디스크의 내용을 메모리에 적재하는 로직이 실행됨.
load_segment로 우선 가짜로 메모리를 할당하고, 페이지 폴트가 발생하면 lozy_load_segment가 동작하면서 실제 할당을 하는 방식.
// 파일을 읽어서 메모리에 로딩하는데 필요한 정보
// 1. 파일 포인터
// 2. 읽어올 위치(오프셋)
// 3. 오프셋부터 어느정도 길이까지 읽어올지(바이트 길이)
// 4. 제로 바이트 수(파일이 페이지보다 작을 경우 나머지 부분은 0으로 채움)
struct lazy_load_arg
{
struct file *file;
off_t ofs;
uint32_t read_bytes;
uint32_t zero_bytes;
};
bool lazy_load_segment(struct page *page, void *aux)
{
// aux 포인터를 필요한 정보 구조체로 변환
struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg *)aux;
// aux에서 파일 위치와 오프셋을 기준으로 파일을 찾음.
// 이후 해당 file의 pos를 조정함.
file_seek(lazy_load_arg->file, lazy_load_arg->ofs);
// 이 시점에서 file_read를 통해 aux정보를 바탕으로 디스크의 파일을 메모리에 적재한다.
// 하지만 기대하던 파일 읽기 길이와 일치하지 않으면 페이지를 해제하고 false를 리턴(뭔가 문제가 생겼음)
if (file_read(lazy_load_arg->file, page->frame->kva, lazy_load_arg->read_bytes) != (int)(lazy_load_arg->read_bytes))
{
palloc_free_page(page->frame->kva);
return false;
}
// (파일 읽기가 정상적이면) aux값을 기반으로 나머지 부분을 0으로 초기화
memset(page->frame->kva + lazy_load_arg->read_bytes, 0, lazy_load_arg->zero_bytes);
return true;
}
aux에 파일 내용이 올라와 있는 게 아님. aux에는 단순히 길이 정보 등만 담겨있고 실제로 file_read로 파일 내용을 읽어오고, memset을 통해서 나머지 페이지 여백을 0으로 안전하게 세팅함.
memset의 dst에 해당하는 kva는 실제 물리 메모리 위치를 나타냄.(커널 가상 주소)
메모리 페이지를 예약하고 지연 로딩 정보를 설정
이후 페이지 폴트가 발생하면 lazy_load_segment()를 통해 실제 할당 실행
static bool
load_segment(struct file *file, off_t ofs, uint8_t *upage,
uint32_t read_bytes, uint32_t zero_bytes, bool writable)
{
ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT(pg_ofs(upage) == 0);
ASSERT(ofs % PGSIZE == 0)
// 주어진 바이트를 설정할 때까지 반복
while (read_bytes > 0 || zero_bytes > 0)
{
// 로딩할 메타 데이터를 저장할 공간 확보
struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg *)malloc(sizeof(struct lazy_load_arg));
lazy_load_arg->file = file; // 파일 정보 저장
lazy_load_arg->ofs = ofs; // 오프셋 저장
lazy_load_arg->read_bytes = page_read_bytes; // 로드할 데이터 길이 저장
lazy_load_arg->zero_bytes = page_zero_bytes; // 0으로 채울 길이 저장
// 가상 메모리 페이지 할당 실패 시 false return
if (!vm_alloc_page_with_initializer(VM_ANON, upage,
writable, lazy_load_segment, lazy_load_arg))
return false;
// 남은 작업 현황 최신화
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes;
}
return true;
}
잘못 생각한 점.
왜 매번 arg를 계속 생성하지? 마지막에 한 번만 생성하면 되는거 아닌가?
아님, 왜냐면 페이지끼리는 사실상 독립적으로 할당되고 작동됨. 그래서 페이지 마다 고유의 aux 정보가 있어야 함.
struct file file: 로드할 파일 구조체
off_t ofs: 파일 내에서 읽기 시작할 오프셋
uint8_t upage: 사용자 공간에 할당될 페이지 시작 주소(데이터 로드 위치)
uint32_t read_bytes: 파일에서 읽어야 하는 바이트 길이
uint32_t zero_bytes: 메모리 페이지 내에서 0으로 채워야 하는 길이
bool writable: 해당 페이지 쓰기 가능 상태 설정(페이지 보호용)
가상 익명 메모리 초기화
void vm_anon_init(void)
{
// 스왑 디스크를 세팅한다. 익명 페이지는 백업 공간이 필요하다.
swap_disk = disk_get(1, 1);
list_init(&swap_table);
lock_init(&swap_table_lock);
// 1섹터는 512바이트. 8섹터가 4kb로 1페이지 분량
for (disk_sector_t i = 0; i < swap_size; i++)
{
struct slot *slot = (struct slot *)malloc(sizeof(struct slot));
slot->page = NULL;
slot->slot_no = i;
lock_acquire(&swap_table_lock);
list_push_back(&swap_table, &slot->swap_elem);
lock_release(&swap_table_lock);
}
}
가상 익명 메모리 초기화 함수
bool anon_initializer(struct page *page, enum vm_type type, void *kva)
{
// 페이지의 연산 집합을 익명 페이지용으로 설정.
// 이는 익명 페이지 관련 작업을 정의하는 함수 포인터들을 포함한다.
page->operations = &anon_ops;
// 익명 페이지에 대한 메타데이터를 페이지 구조체 내에 설정.
// 이 메타데이터는 익명 페이지의 관리에 필요하다.
struct anon_page *anon_page = &page->anon;
// 초기 상태에서는 이 익명 페이지가 스왑 영역에 적재되지 않았음을 나타냄.
// slot_no가 -1이면, 이 페이지는 아직 스왑 영역에 저장되지 않은 상태임을 의미한다.
// 이는 페이지가 아직 사용되지 않았거나 메모리에 적재되지 않았음을 표시한다.
anon_page->slot_no = -1;
return true;
}
static bool
anon_swap_in(struct page *page, void *kva)
{
// 현재 페이지와 연관된 익명 페이지 구조체에 접근
struct anon_page *anon_page = &page->anon;
// 현재 페이지가 저장된 스왑 슬롯의 번호를 얻음
disk_sector_t page_slot_no = anon_page->slot_no;
// 스왑 테이블의 리스트 요소를 순회하기 위한 변수
struct list_elem *e;
// 스왑 테이블 내의 슬롯을 나타내는 구조체
struct slot *slot;
// 스왑 테이블에 대한 접근을 동기화하기 위해 락을 획득
lock_acquire(&swap_table_lock);
// 스왑 테이블 전체를 순회
for (e = list_begin(&swap_table); e != list_end(&swap_table); e = list_next(e))
{
// 현재 요소를 슬롯 구조체로 변환
slot = list_entry(e, struct slot, swap_elem);
// 현재 슬롯이 페이지에 대응되는 슬롯인지 확인
if (slot->slot_no == page_slot_no)
{
// 페이지에 대응하는 디스크 섹터들을 읽어, kva에 지정된 메모리 주소로 로드
for (int i = 0; i < 8; i++)
{
disk_read(swap_disk, page_slot_no * 8 + i, kva + DISK_SECTOR_SIZE * i);
}
// 페이지와 슬롯의 연결을 해제
slot->page = NULL;
// 스왑 슬롯 번호를 초기화하여 페이지가 더 이상 스왑에 있지 않음을 표시
anon_page->slot_no = -1;
// 작업이 완료되었으므로 락을 해제하고 함수를 종료
lock_release(&swap_table_lock);
return true;
}
}
// 스왑 테이블에서 해당 페이지를 찾지 못한 경우 락을 해제하고 false 반환
lock_release(&swap_table_lock);
return false;
}
/* Swap out the page by writing contents to the swap disk. */
static bool
anon_swap_out(struct page *page)
{
// 페이지가 NULL이면 함수를 종료하고 false 반환
if (page == NULL)
return false;
// 익명 페이지에 대한 메타데이터 접근
struct anon_page *anon_page = &page->anon;
// 스왑 테이블의 리스트 요소를 순회하기 위한 변수
struct list_elem *e;
// 스왑 테이블 내의 슬롯을 나타내는 구조체
struct slot *slot;
// 스왑 테이블에 대한 접근을 동기화하기 위해 락 획득
lock_acquire(&swap_table_lock);
// 스왑 테이블을 순회하면서 빈 슬롯을 찾음
for (e = list_begin(&swap_table); e != list_end(&swap_table); e = list_next(e))
{
slot = list_entry(e, struct slot, swap_elem);
// 빈 슬롯을 찾았을 경우
if (slot->page == NULL)
{
// 페이지의 내용을 스왑 디스크의 해당 슬롯에 저장
for (int i = 0; i < 8; i++)
{
disk_write(swap_disk, slot->slot_no * 8 + i, page->va + DISK_SECTOR_SIZE * i);
}
// 슬롯 번호 업데이트 및 슬롯에 페이지 연결
anon_page->slot_no = slot->slot_no;
slot->page = page;
// 페이지와 프레임 간의 연결 해제
page->frame->page = NULL;
page->frame = NULL;
// PML4 페이지 테이블에서 페이지 엔트리 제거
pml4_clear_page(thread_current()->pml4, page->va);
// 락 해제 후 함수 종료
lock_release(&swap_table_lock);
return true;
}
}
// 스왑 테이블에서 빈 슬롯을 찾지 못한 경우, 시스템 패닉 발생
lock_release(&swap_table_lock);
PANIC("insufficient swap space");
}
파일 맵핑 메모리 초기화
bool file_backed_initializer(struct page *page, enum vm_type type, void *kva)
{
// 파일 백언 관련 연산 정의(파일 기반의 스왑 인, 아웃 연산 연결됨)
page->operations = &file_ops;
// 페이지 구조체의 파일 정보 설정
struct file_page *file_page = &page->file;
// 지연 로딩할 부가 정보 추출
struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg *)page->uninit.aux;
// 파일 구조체 정보 업데이트
file_page->file = lazy_load_arg->file;
file_page->ofs = lazy_load_arg->ofs;
file_page->read_bytes = lazy_load_arg->read_bytes;
file_page->zero_bytes = lazy_load_arg->zero_bytes;
return true;
}
이 함수는 실제 파일을 아직 적재한 단계는 아님. 파일을 적재하기 위한 페이지를 생성하고 메타 데이터를 설정하는 것. 실제 스왑 인이 발생하면 물리 메모리(프레임)에 적재되는 것!
struct page page : 초기화할 페이지 정보 구조체
enum vm_type type : 가상메모리 타입(익명, 파일 맵핑)
void kva : 커널 가상 주소
static bool
file_backed_swap_in(struct page *page, void *kva)
{
struct file_page *file_page = &page->file;
return lazy_load_segment(page, file_page);
}
해당 페이지 구조체에 명시된 파일로 file_page 구조체 생성
page에 file_page 부가 정보를 바탕으로 프레임 연결하면서 실제 물리 메모리로 적재.
static bool
file_backed_swap_out(struct page *page)
{
// 파일 기반 페이지 보조 자료
struct file_page *file_page = &page->file;
// 만약 Pml4가 수정 이력이 있으면(파일 동기화 필요 시)
if (pml4_is_dirty(thread_current()->pml4, page->va))
{
// 파일 쓰기
file_write_at(file_page->file, page->va, file_page->read_bytes, file_page->ofs);
// 더티 값 0으로 바꾸기
pml4_set_dirty(thread_current()->pml4, page->va, 0);
}
// 페이지와 프레임 연결 끊기
page->frame->page = NULL;
page->frame = NULL;
// pml4 페이지 테이블에서 현재 메모리 적재 되지 않았음을 표시
pml4_clear_page(thread_current()->pml4, page->va);
return true;
}
LLM은 수학 계산은 못할 거라고 생각해서 물어봤는데,
해당 수식을 내부적으로 파이썬으로 계산해서 알려줌;;
즉, 계산하지 못하지만 텍스트를 분석해서 파이썬 코드로 바꾸고, 그걸 바탕으로 계산을 해내는 식으로 작동.
놀랐다..
'함께 자라기' 책 샀음! 오늘은 1시간 일찍 퇴근하고 기숙사에서 책 읽어보기!
https://www.yes24.com/Product/Goods/67350256