깃북을 읽자
마르고 닳도록 읽다 보면 뭐라도 되겠지
익명 페이지
이 프로젝트의 이 부분에서는 익명 페이지(anonymous page)라는 디스크 기반이 아닌 이미지를 구현할 것입니다.
익명 매핑에는 백업 파일이나 장치가 없습니다. 이는 이름이 지정된 파일 소스가 없기 때문에 익명으로 불리며(파일 지원 페이지와 달리), 실행 파일에서 스택과 힙과 같은 용도로 사용됩니다.
익명 페이지를 설명하기 위한 구조체는 include/vm/anon.h
에 있는 anon_page
입니다. 현재는 비어 있지만 구현하면서 익명 페이지의 필요한 정보나 상태를 저장하기 위해 멤버를 추가할 수 있습니다. 또한 페이지의 일반적인 정보를 포함하는 include/vm/page.h
에 있는 struct page
를 참조하십시오. 익명 페이지의 경우, struct anon_page
가 페이지 구조체에 포함됩니다.
게으른 로딩으로 페이지 초기화
게으른 로딩(lazy loading)은 메모리 로딩이 필요할 때까지 지연되는 디자인입니다. 페이지가 할당되어 해당 페이지 구조체가 존재하지만, 물리적 프레임이 없고 페이지의 실제 콘텐츠도 아직 로드되지 않은 상태입니다. 페이지 내용은 실제로 필요할 때, 즉 페이지 폴트가 발생했을 때 로드됩니다.
페이지 타입이 세 가지
enum vm_type
{
/* page not initialized */
VM_UNINIT = 0,
/* page not related to the file, aka anonymous page */
VM_ANON = 1,
/* page that realated to the file */
VM_FILE = 2,
...
}
이므로, 초기화 절차는 각 페이지마다 다릅니다. 여기서는 페이지 초기화 흐름에 대한 개요를 제공합니다.
첫째, 커널이 새로운 페이지 요청을 받으면 vm_alloc_page_with_initializer
가 호출됩니다.
초기화 함수는 새로운 페이지를 할당하고 페이지 타입에 따라 적절한 초기화 함수를 설정한 후 사용자 프로그램에 제어를 반환합니다.
사용자 프로그램이 실행되다가 페이지 폴트가 발생하면, uninit_initialize
가 호출되어 이전에 설정한 초기화 함수를 호출합니다.
익명 페이지의 경우 anon_initializer
,
파일 지원 페이지의 경우 file_backed_initializer
가 호출됩니다.
페이지는 initialize->(page_fault->lazy-load->swap-in->swap-out->...)->destroy
의 생명 주기를 가질 수 있습니다. 각 전환 과정은 페이지 타입에 따라 절차가 다릅니다. 이 프로젝트에서는 각 페이지 타입에 대한 이러한 전환 과정을 구현해야 합니다.
실행 파일의 게으른 로딩
게으른 로딩에서는 프로세스가 시작될 때, 즉시 필요한 메모리 부분만 메인 메모리에 로드됩니다. 이는 바이너리 이미지를 한 번에 메모리에 로드하는 적극적 로딩(eager loading)과 비교하여 오버헤드를 줄일 수 있습니다.
게으른 로딩을 지원하기 위해 include/vm/vm.h
에 VM_UNINIT
이라는 페이지 타입을 도입합니다. 모든 페이지는 처음에 VM_UNINIT
페이지로 생성됩니다. 초기화되지 않은 페이지를 위한 페이지 구조체인 struct uninit_page
는 include/vm/uninit.h
에 있습니다. 초기화되지 않은 페이지를 생성, 초기화, 소멸하는 함수들은 include/vm/uninit.c
에서 찾을 수 있습니다.
페이지 폴트 시, 페이지 폴트 핸들러(userprog/exception.c
의 page_fault
)는 제어를 vm/vm.c
의 vm_try_handle_fault
로 전달하여 유효한 페이지 폴트인지 확인합니다. 유효하지 않은 페이지 폴트라면, 일부 콘텐츠를 페이지에 로드하고 사용자 프로그램에 제어를 반환합니다.
세 가지 유형의 유효하지 않은 페이지 폴트가 있습니다: 게으르게 로드된 페이지, 스왑아웃된 페이지, 쓰기 보호된 페이지(복사-온-라이트 참고). 현재는 첫 번째 유형인 게으르게 로드된 페이지에 대해서만 고려합니다. 게으르게 로드된 페이지에 대한 페이지 폴트라면, 커널은 이전에 설정한 초기화 함수 중 하나를 호출하여 세그먼트를 게으르게 로드합니다. userprog/process.c
에서 lazy_load_segment
를 구현해야 합니다.
vm_alloc_page_with_initializer() 구현
vm_alloc_page_with_initializer
함수는 전달된 vm_type
에 따라 적절한 초기화 함수를 가져오고 uninit_new
를 호출합니다.
bool vm_alloc_page_with_initializer (enum vm_type type, void *va,
bool writable, vm_initializer *init, void *aux);
주어진 타입으로 초기화되지 않은 페이지를 생성합니다. uninit
페이지의 스왑인 핸들러는 타입에 따라 페이지를 자동으로 초기화하고, 주어진 AUX
와 함께 INIT
을 호출합니다. 페이지 구조체를 얻은 후, 해당 페이지를 프로세스의 보조 페이지 테이블에 삽입합니다. vm.h
에 정의된 VM_TYPE
매크로를 사용하면 편리합니다.
/* 초기화 함수로 대기 페이지 객체를 생성합니다. 페이지를 생성하려면 직접 생성하지 말고
* 이 함수나 `vm_alloc_page`를 통해 생성하십시오. */
bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux)
{
ASSERT(VM_TYPE(type) != VM_UNINIT)
struct supplemental_page_table *spt = &thread_current()->spt;
/* upage가 이미 사용 중인지 확인합니다. */
if (spt_find_page(spt, upage) == NULL)
{
/* TODO: 페이지를 생성하고, VM 타입에 따라 초기화 함수를 가져온 다음,
* TODO: uninit_new를 호출하여 "uninit" 페이지 구조체를 생성하십시오.
* TODO: uninit_new를 호출한 후 필드를 수정해야 합니다. */
/* TODO: 페이지를 spt에 삽입하십시오. */
}
err:
return false;
}
- upage가 뭐임?
upage는 사용자 공간에서의 가상 주소를 의미합니다. 이는 보통 사용자 프로그램에서 접근하려는 페이지의 가상 주소를 나타냅니다.
해당 함수에서는 주어진 upage가 이미 보조 페이지 테이블(supplemental page table)에 있는지 확인하고, 만약 그렇지 않다면 새로운 페이지를 생성하여 초기화하는 작업을 수행합니다.
구체적으로 upage의 역할은 다음과 같습니다:
- 확인: 주어진 가상 주소 upage가 이미 보조 페이지 테이블에 있는지 확인합니다.
- 생성: upage가 보조 페이지 테이블에 없다면, 새로운 페이지를 생성하고 초기화합니다.
- 삽입: 새로 생성한 페이지를 보조 페이지 테이블에 삽입합니다.
즉, upage는 사용자 공간에서의 특정 가상 주소를 가리키며, 이 주소에 해당하는 페이지를 관리하는 데 사용됩니다. 이는 페이지 폴트가 발생했을 때, 해당 주소에 대한 페이지를 올바르게 로드하고 처리하기 위해 필요합니다.
todo가 시키는대로 구현
/* 초기화 함수로 대기 페이지 객체를 생성합니다. 페이지를 생성하려면 직접 생성하지 말고
* 이 함수나 `vm_alloc_page`를 통해 생성하십시오. */
bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux)
{
ASSERT(VM_TYPE(type) != VM_UNINIT)
struct supplemental_page_table *spt = &thread_current()->spt;
/* upage가 이미 사용 중인지 확인합니다. */
if (spt_find_page(spt, upage) == NULL)
{
/* TODO: 페이지를 생성하고, VM 타입에 따라 초기화 함수를 가져온 다음,
* TODO: uninit_new를 호출하여 "uninit" 페이지 구조체를 생성하십시오.
* TODO: uninit_new를 호출한 후 필드를 수정해야 합니다. */
// 페이지를 생성하고
struct page *page = malloc(sizeof(struct page));
if (page == NULL) goto err;
// VM 타입에 따라 초기화 함수를 가져온 다음
bool (*initializer)(struct page *, enum vm_type, void *);
switch (VM_TYPE(type))
{
case VM_ANON:
initializer = anon_initializer;
break;
case VM_FILE:
initializer = file_backed_initializer;
break;
default:
goto err;
}
// uninit_new를 호출하여 "uninit" 페이지 구조체를 생성
uninit_new(page, upage, init, aux, type, initializer);
/* TODO: 페이지를 spt에 삽입하십시오. */
if (!spt_insert_page(spt, page))
{
free(page);
goto err;
}
return true;
}
err:
return false;
}
오케이 다음
lazy_load_segment 구현
load_segment
과 lazy_load_segment
를 userprog/process.c
에 구현합니다. 실행 파일에서 세그먼트를 로드하는 작업을 구현합니다. 모든 페이지는 커널이 페이지 폴트를 가로챌 때만 로드됩니다.
load_segment
의 루프에서 각 반복마다 vm_alloc_page_with_initializer
를 호출하여 대기 페이지 객체를 생성합니다. 페이지 폴트가 발생하면 이 때 파일에서 세그먼트가 실제로 로드됩니다.
static bool
lazy_load_segment(struct page *page, void *aux)
{
/* TODO: 파일에서 세그먼트를 로드합니다. */
/* TODO: 첫 번째 페이지 폴트가 주소 VA에서 발생할 때 호출됩니다. */
/* TODO: 이 함수를 호출할 때 VA가 사용 가능합니다. */
}
- 파일에서 세그먼트를 로드한다는 게 뭔 소리임?
"파일에서 세그먼트를 로드한다"는 의미는 파일로부터 특정 데이터를 읽어와서 메모리의 특정 위치에 해당 데이터를 적재하는 것을 의미합니다. 이는 일반적으로 프로그램 실행 시, 실행 파일의 특정 부분을 메모리에 적재하는 과정에서 이루어집니다. - 인자로 받는 게 사실상 page 포인터 뿐인데, 어떻게 파일에서 세그먼트를 로드한다는 거임?
lazy_load_segment 함수는 페이지 폴트가 발생했을 때 호출되어, 실제로 파일에서 데이터를 읽어와 해당 페이지에 로드하는 역할을 합니다. 이 함수는 페이지와 추가적인 정보를 담고 있는 aux 포인터를 인자로 받습니다. aux 포인터는 페이지를 로드할 때 필요한 파일 관련 정보를 포함하는 구조체로 사용됩니다. - 아.. 쓰지도 않을 aux는 왜 꼬박꼬박 인자에 박아두나 했더니 지금 쓰는구나.
*aux에 담길 정보는 lazy_load_segment를 호출할 때 담길 테고, lazy_load_segment는 load_segment에서 호출하니까 이 부분은 load_segment까지 다 구현하고 나서 다시 봐야겠다.
static bool
lazy_load_segment(struct page *page, void *aux)
{
// aux에서 필요한 정보를 가져옵니다.
struct aux_data {
struct file *file;
off_t offset;
size_t page_read_bytes;
};
struct aux_data *info = (struct aux_data *)aux;
// 페이지에 실제로 데이터를 로드할 주소를 가져옵니다.
void *kva = page->frame->kva;
// 파일에서 데이터를 읽어옵니다.
if (file_read_at(info->file, kva, info->page_read_bytes, info->offset) != (int)info->page_read_bytes)
{
return false;
}
// 나머지 부분은 0으로 채웁니다.
memset(kva + info->page_read_bytes, 0, PGSIZE - info->page_read_bytes);
return true;
}
- aux에서 필요한 정보 추출:
- aux는 페이지를 로드할 때 필요한 추가 정보를 담고 있습니다. 이 예에서는 struct aux_data라는 구조체를 사용하여 파일 포인터, 오프셋, 페이지에서 읽을 바이트 수를 저장합니다.
- 페이지의 커널 가상 주소 가져오기:
- page->frame->kva를 통해 페이지의 커널 가상 주소를 가져옵니다. 이 주소는 실제로 데이터를 로드할 메모리 위치입니다.
- 파일에서 데이터 읽기:
- file_read_at 함수를 사용하여 파일에서 지정된 오프셋부터 필요한 바이트 수만큼 읽어와 페이지에 로드합니다. 읽은 바이트 수가 기대한 바이트 수와 다르면 실패로 처리합니다.
- 나머지 부분 0으로 채우기:
- 페이지의 나머지 부분은 0으로 채웁니다. 이는 페이지가 부분적으로만 데이터로 채워지고 나머지 부분은 0으로 초기화해야 하기 때문입니다.
load_segment 구현
현재 코드는 파일에서 읽어야 할 바이트 수와 0으로 채울 바이트 수를 계산한 후 vm_alloc_page_with_initializer
를 호출하여 대기 객체를 생성합니다. vm_alloc_page_with_initializer
에 전달할 aux
인자를 설정해야 합니다. 바이너리 로딩에 필요한 정보를 포함하는 구조체를 생성할 수 있습니다.
static bool lazy_load_segment (struct page *page, void *aux)
{
...
void *aux = NULL; // aux가 NULL로 대충 퉁쳐져 있다. 여기를 수정해야 하는 듯
...
}
이 함수는 페이지 폴트 시 실행 파일의 페이지를 초기화하는 함수로, load_segment
에서 설정한 정보를 사용하여 파일에서 세그먼트를 찾아 메모리에 로드합니다.
/* 파일의 OFS 오프셋에서 시작하는 세그먼트를 주소 UPAGE에 로드합니다.
* 총 READ_BYTES + ZERO_BYTES 바이트의 가상 메모리가 다음과 같이 초기화됩니다:
*
* - UPAGE에서 READ_BYTES 바이트는 파일의 OFS에서 읽어야 합니다.
*
* - UPAGE + READ_BYTES에서 ZERO_BYTES 바이트는 0으로 채워야 합니다.
*
* 이 함수에 의해 초기화된 페이지는 WRITABLE이 true인 경우 사용자 프로세스에 의해
* 쓰기가 가능해야 하며, 그렇지 않은 경우 읽기 전용이어야 합니다.
*
* 성공하면 true를 반환하고, 메모리 할당 오류 또는 디스크 읽기 오류가 발생하면 false를 반환합니다. */
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)
{
/* 이 페이지를 채우는 방법을 계산합니다.
* FILE에서 PAGE_READ_BYTES 바이트를 읽고
* 마지막 PAGE_ZERO_BYTES 바이트를 0으로 채웁니다. */
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
/* TODO: lazy_load_segment에 정보를 전달하기 위해 aux를 설정합니다. */
struct aux_data *aux = malloc(sizeof(struct aux_data));
if (aux == NULL)
return false;
aux->file = file;
aux->offset = ofs;
aux->page_read_bytes = page_read_bytes;
if (!vm_alloc_page_with_initializer(VM_ANON, upage,
writable, lazy_load_segment, aux))
{
free(aux);
return false;
}
/* 진행 */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes;
}
return true;
}
- ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
- read_bytes와 zero_bytes의 합이 페이지 크기(PGSIZE)의 배수인지 확인합니다.
- 페이지 크기는 일반적으로 4096 바이트(4KB)입니다.
- read_bytes와 zero_bytes는 메모리에 로드할 데이터의 크기와 0으로 초기화할 데이터의 크기입니다. 이들의 합이 페이지 크기의 배수가 되어야 페이지 단위로 정확히 메모리를 할당할 수 있습니다.
- ASSERT(pg_ofs(upage) == 0);
- upage가 페이지 정렬된 주소인지 확인합니다.
- pg_ofs(upage)는 주어진 주소 upage의 페이지 오프셋을 반환합니다. 페이지 오프셋은 주소가 페이지의 시작 부분에서 얼마나 떨어져 있는지를 나타냅니다.
- 페이지 정렬된 주소는 페이지의 시작 부분(예: 0x1000, 0x2000 등)을 가리키며, 오프셋은 0이어야 합니다.
- ASSERT(ofs % PGSIZE == 0);
- 파일 오프셋 ofs가 페이지 크기의 배수인지 확인합니다.
- ofs는 파일에서 읽기를 시작할 오프셋입니다. 페이지 크기의 배수인 오프셋에서 읽기를 시작해야 페이지 경계에 맞게 데이터를 로드할 수 있습니다.
/* 이 함수에 의해 초기화된 페이지는 WRITABLE이 true인 경우 사용자 프로세스에 의해
* 쓰기가 가능해야 하며, 그렇지 않은 경우 읽기 전용이어야 합니다. */
이 부분이 완전히 무시당한다고 생각했다. struct page에도, vm_alloc_page_with_initializer에도 writable은 인자로 받기만 하지 뭘 하지는 않는다. 해서 struct page와 vm_alloc_page_with_initializer에 writable 내용을 추가했다.
struct page {
...
bool writable;
...
}
bool vm_alloc_page_with_initializer(enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux)
{
...
// uninit_new를 호출하여 "uninit" 페이지 구조체를 생성
uninit_new(page, upage, init, aux, type, initializer);
// 페이지의 쓰기 가능 여부 설정
page->writable = writable;
...
}
load_segment는 이미 writable 관련 내용이 있어서 추가적으로 수정하지는 않았다.
스택 설정 조정
userprog/process.c
의 setup_stack
을 수정하여 새로운 메모리 관리 시스템에 맞게 스택 할당을 조정합니다. 첫 번째 스택 페이지는 게으르게 할당할 필요가 없습니다. 명령줄 인수로 할당 및 초기화할 수 있으며, 페이지 폴트를 기다릴 필요가 없습니다. 스택을 식별할 방법을 제공해야 할 수도 있습니다. vm/vm.h
의 vm_type
의 보조 마커(예: VM_MARKER_0
)를 사용하여 페이지를 표시할 수 있습니다.
- 첫 번째 스택 페이지는 게으르게 할당할 필요가 없다는 게 뭔 소리?
- "첫 번째 스택 페이지는 게으르게 할당할 필요가 없다"는 의미는 첫 번째 스택 페이지는 프로그램 실행 시 즉시 할당하고 초기화해야 한다는 뜻입니다. 게으른 할당(lazy allocation)은 페이지 폴트가 발생할 때까지 실제 메모리 할당을 지연시키는 방법입니다. 그러나 스택의 첫 번째 페이지는 즉시 필요한 메모리이기 때문에 프로그램 시작 시 할당하고 초기화해야 합니다.
- setup_stack 함수 수정
/* USER_STACK에서 스택의 페이지를 생성합니다. 성공 시 true를 반환합니다. */
static bool
setup_stack(struct intr_frame *if_)
{
bool success = false;
void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);
/* TODO: stack_bottom에 스택을 매핑하고 페이지를 즉시 클레임합니다.
* TODO: 성공하면 rsp를 적절히 설정하십시오.
* TODO: 페이지가 스택임을 표시해야 합니다. */
/* TODO: 여기에 코드를 작성하십시오 */
// 스택 페이지를 즉시 할당하고 초기화합니다.
if (vm_alloc_page_with_initializer(VM_MARKER_0 | VM_ANON, stack_bottom, true, NULL, NULL))
{
success = vm_claim_page(stack_bottom);
if (success)
{
// 초기화 성공 시 스택 포인터 설정
if_->rsp = USER_STACK;
// 명령줄 인수를 스택에 할당 및 초기화하는 로직을 추가할 수 있습니다.
// 예: if_->rsp -= sizeof(arguments);
// 스택 페이지를 스택으로 표시
struct page *stack_page = spt_find_page(&thread_current()->spt, stack_bottom);
if (stack_page != NULL)
{
stack_page->type = VM_MARKER_0;
}
}
}
return success;
}
솔직히 뭔 소린지 잘 모르겠는데 일단 넘어가자.
vm_try_handle_fault 수정
vm_try_handle_fault
함수를 수정하여 오류 주소에 해당하는 페이지 구조체를 보조 페이지 테이블에서 찾아 해결합니다.
- '오류 주소에 해당하는 페이지 구조체'를 어케 찾음?
- 이렇게
bool vm_try_handle_fault(struct intr_frame *f UNUSED, void *addr,
bool user UNUSED, bool write UNUSED, bool not_present)
{
struct supplemental_page_table *spt = &thread_current()->spt;
struct page *page = NULL;
// 페이지 폴트가 유효한지 확인 (not_present는 페이지가 존재하지 않음을 의미)
if (not_present)
{
// 보조 페이지 테이블에서 주소에 해당하는 페이지 구조체를 찾습니다.
page = spt_find_page(spt, addr);
// 페이지가 보조 페이지 테이블에 존재하지 않는 경우, 잘못된 접근이므로 false를 반환합니다.
if (page == NULL)
{
return false;
}
// 페이지가 쓰기 요청인데 쓰기 보호된 페이지라면, 접근을 허용하지 않습니다.
if (write && !page->writable)
{
return false;
}
// 페이지를 실제로 메모리에 매핑합니다.
if (!vm_do_claim_page(page))
{
return false;
}
return true;
}
// 그 외의 경우는 잘못된 페이지 폴트이므로 false를 반환합니다.
return false;
}
- 보조 페이지 테이블에서 페이지 구조체 찾기:
- spt_find_page 함수를 사용하여 주소 addr에 해당하는 페이지 구조체를 보조 페이지 테이블에서 찾습니다.
- 페이지가 존재하지 않으면 잘못된 접근이므로 false를 반환합니다.
- 페이지가 쓰기 요청인데 쓰기 보호된 페이지인 경우 처리:
- 페이지 폴트가 쓰기 요청에 의한 것(write == true)인데, 페이지가 쓰기 가능하지 않은 경우(!page->writable), 접근을 허용하지 않으므로 false를 반환합니다.
- 페이지 클레임 (매핑):
- vm_do_claim_page 함수를 호출하여 페이지를 실제로 메모리에 매핑합니다.
- 매핑에 실패하면 false를 반환하고, 성공하면 true를 반환합니다.
보조 페이지 테이블 복사 및 제거
프로세스를 생성하거나 종료할 때 필요한 보조 페이지 테이블 인터페이스를 지원하기 위해 다시 방문합니다.
bool supplemental_page_table_copy (struct supplemental_page_table *dst,
struct supplemental_page_table *src);
src
의 보조 페이지 테이블을 dst
로 복사합니다. 이는 자식 프로세스가 부모의 실행 컨텍스트를 상속해야 할 때 사용됩니다. src
의 보조 페이지 테이블을 반복하여 dst
의 보조 페이지 테이블에 동일한 항목을 복사합니다. 초기화되지 않은 페이지를 할당하고 즉시 클레임해야 합니다.
/* Copy supplemental page table from src to dst */
bool supplemental_page_table_copy(struct supplemental_page_table *dst UNUSED,
struct supplemental_page_table *src UNUSED)
{
struct hash_iterator it;
hash_first(&it, &src->spt_hash);
while (hash_next(&it))
{
struct page *src_page = hash_entry(hash_cur(&it), struct page, hash_elem);
enum vm_type type = src_page->operations->type;
void *upage = src_page->va;
bool writable = src_page->writable;
void *kpage = palloc_get_page(PAL_USER);
if (kpage == NULL)
{
return false;
}
// 물리 메모리의 내용을 복사합니다.
memcpy(kpage, src_page->frame->kva, sizeof(struct page));
// 새로운 페이지를 할당하고 초기화합니다.
if (!vm_alloc_page_with_initializer(type, upage, writable, NULL, NULL))
{
palloc_free_page(kpage);
return false;
}
struct page *dst_page = spt_find_page(dst, upage);
if (dst_page == NULL)
{
palloc_free_page(kpage);
return false;
}
// 새로 할당된 페이지에 물리 메모리를 매핑합니다.
if (!vm_do_claim_page(dst_page))
{
palloc_free_page(kpage);
return false;
}
// 새 페이지에 실제 데이터를 복사합니다.
memcpy(dst_page->frame->kva, kpage, sizeof(struct page)); // 페이지 크기만큼 복사
palloc_free_page(kpage); // 임시 페이지 메모리 해제
}
return true;
}
void supplemental_page_table_kill (struct supplemental_page_table *spt);
보조 페이지 테이블이 보유한 모든 리소스를 해제합니다. 프로세스가 종료될 때(userprog/process.c
의 process_exit
) 호출됩니다. 페이지 테이블을 반복하여 페이지를 destroy(page)
로 호출합니다. 실제 페이지 테이블(pml4)과 물리 메모리(palloc-ed 메모리)에 대해 걱정할 필요는 없습니다. 보조 페이지 테이블이 정리된 후 호출자가 정리합니다.
/* Free the resource hold by the supplemental page table */
void supplemental_page_table_kill(struct supplemental_page_table *spt UNUSED)
{
/* TODO: Destroy all the supplemental_page_table hold by thread and
* TODO: writeback all the modified contents to the storage. */
struct hash_iterator it;
hash_first(&it, &spt->spt_hash);
while (hash_next(&it))
{
struct page *page = hash_entry(hash_cur(&it), struct page, hash_elem);
// 페이지 제거 로직 (예: 물리 메모리 해제)
if (page->frame)
{
palloc_free_page(page->frame->kva);
}
free(page);
}
hash_destroy(&spt->spt_hash, NULL);
}
복잡하다...
페이지 정리
vm/uninit.c
의 uninit_destroy
와 vm/anon.c
의 anon_destroy
를 구현합니다. 초기화되지 않은 페이지에 대한 소멸 핸들러입니다. 초기화되지 않은 페이지는 다른 페이지 객체로 변환되지만, 프로세스가 종료될 때 여전히 초기화되지 않은 페이지가 있을 수 있습니다.
/* uninit_page가 보유한 자원을 해제합니다. 대부분의 페이지가 다른 페이지 객체로 변환되지만,
* 프로세스 종료 시 실행 중에 한 번도 참조되지 않은 초기화되지 않은(uninit) 페이지가
* 존재할 수 있습니다.
* 페이지는 호출자에 의해 해제됩니다. */
static void
uninit_destroy (struct page *page) {
struct uninit_page *uninit UNUSED = &page->uninit;
// 페이지의 vm_type을 확인하여 익명 페이지인지 확인합니다.
if (VM_TYPE(page->operations->type) == VM_ANON)
{
// 익명 페이지의 자원을 해제합니다.
vm_dealloc_page(page);
// 현재 익명 페이지에 특별히 해제할 자원이 없으면 단순히 반환합니다.
return;
}
// 다른 페이지 타입에 대한 자원 해제 로직은 나중에 추가할 수 있습니다.
}
페이지 구조체가 보유한 리소스를 해제합니다. 페이지의 vm 타입을 확인하고 적절히 처리해야 할 수 있습니다.
현재는 익명 페이지만 처리할 수 있습니다. 나중에 이 함수를 다시 방문하여 파일 지원 페이지를 정리할 것입니다.
/* Destroy the anonymous page. PAGE will be freed by the caller. */
static void
anon_destroy(struct page *page)
{
struct anon_page *anon_page UNUSED = &page->anon;
// 페이지가 프레임을 가지고 있는 경우 이를 해제합니다.
if (page->frame != NULL)
{
palloc_free_page(page->frame->kva);
page->frame = NULL;
}
// 추가적으로 해제할 자원이 있으면 여기에 해제 로직을 추가합니다.
}
익명 페이지가 보유한 리소스를 해제합니다. 페이지 구조체를 명시적으로 해제할 필요는 없습니다. 호출자가 이를 수행합니다.
이제 프로젝트 2의 모든 테스트를 통과해야 합니다.
모든 테스트 통과! 할 리가 없지 이제부터 시작이다
자잘한 디버깅이 뭐 있었는데 기록하는 걸 깜박함
- vm_alloc_page_with_initializer에서 page type을 초기화 해주는데 , 굳이 저 부분이 또 필요한 이유가 무엇이지?
- 만약 vm_alloc_page_with_initializer 함수가 페이지 타입을 이미 적절히 초기화하고 있다면, setup_stack 함수 내에서 페이지 타입을 다시 설정할 필요는 없습니다.
- vm_alloc_page_with_initializer가 페이지 타입을 올바르게 설정하고 있고, type 필드를 수정할 필요가 없다면, 단순히 스택 페이지를 초기화하고 스택 포인터를 설정하는 부분만 남기면 됩니다.
- 그래서 지움
/* USER_STACK에서 스택의 페이지를 생성합니다. 성공 시 true를 반환합니다. */
static bool
setup_stack(struct intr_frame *if_)
{
bool success = false;
void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);
/* TODO: stack_bottom에 스택을 매핑하고 페이지를 즉시 클레임합니다.
* TODO: 성공하면 rsp를 적절히 설정하십시오.
* TODO: 페이지가 스택임을 표시해야 합니다. */
/* TODO: 여기에 코드를 작성하십시오 */
// 스택 페이지를 즉시 할당하고 초기화합니다.
if (vm_alloc_page_with_initializer(VM_MARKER_0 | VM_ANON, stack_bottom, true, NULL, NULL))
{
success = vm_claim_page(stack_bottom);
if (success)
{
// 초기화 성공 시 스택 포인터 설정
if_->rsp = USER_STACK;
// 명령줄 인수를 스택에 할당 및 초기화하는 로직을 추가할 수 있습니다.
// 예: if_->rsp -= sizeof(arguments);
}
}
return success;
}
이번엔 또 뭘까... aux가 뭔가 문제구나...
타입이 안맞는다고?
// vm.c
uninit_new(page, upage, init, aux, type, initializer);
// uninit.c
void
uninit_new (struct page *page, void *va, vm_initializer *init,
enum vm_type type, void *aux,
bool (*initializer)(struct page *, enum vm_type, void *))
이런 염병, type이랑 aux의 순서가 바뀌었네. gpt 정신 안채리나
커널 패닉이나 맞아라 얍~ ㅋㅋ
무려 2개나 pass했다 야호~
- gpt 왈
- 커널 패닉이 다시 발생하는 것으로 보아, 여전히 리스트 또는 해시 테이블을 순회하는 과정에서 문제가 있는 것 같습니다. 특히 supplemental_page_table_kill 함수에서 문제가 발생하는 것으로 보입니다.
- 그렇다면 수정해주지
/* Free the resource hold by the supplemental page table */
// SPT가 보유하고 있던 모든 리소스를 해제하는 함수 (process_exit(), process_cleanup()에서 호출)
void supplemental_page_table_kill(struct supplemental_page_table *spt UNUSED)
{
/* TODO: Destroy all the supplemental_page_table hold by thread and
* TODO: writeback all the modified contents to the storage. */
// todo: 페이지 항목들을 순회하며 테이블 내의 페이지들에 대해 destroy(page)를 호출
hash_clear(&spt->spt_hash, hash_page_destroy); // 해시 테이블의 모든 요소를 제거
/** hash_destroy가 아닌 hash_clear를 사용해야 하는 이유
* 여기서 hash_destroy 함수를 사용하면 hash가 사용하던 메모리(hash->bucket) 자체도 반환한다.
* process가 실행될 때 hash table을 생성한 이후에 process_clean()이 호출되는데,
* 이때는 hash table은 남겨두고 안의 요소들만 제거되어야 한다.
* 따라서, hash의 요소들만 제거하는 hash_clear를 사용해야 한다.
*/
}
void hash_page_destroy(struct hash_elem *e, void *aux)
{
struct page *page = hash_entry(e, struct page, hash_elem);
destroy(page);
free(page);
}
보니까 hash_destroy가 아니라 hash_clear를 해줘야 하는 모양이다.
커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 씨발 아싸리 씨발!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!! 커널 패닉 안난다!!!
테스트도 무려 어... 몇개 차이지.... 아 16개나 더 통과했다 ! ! !
project 3 시작한지 일주일만에 결과다운 결과창을 보게 되었다.
카이스트 학부생들 존경합니다.
'크래프톤 정글 일지' 카테고리의 다른 글
[PintOS] project 3.3.0 - stack growth (1) | 2024.06.07 |
---|---|
[PintOS] project 3.2.1 - anonymous 디버깅 (1) | 2024.06.07 |
[PintOS] project 3.1 - hash.h, memory management (0) | 2024.06.06 |
[PintOS] project 3.0 - vm.h (0) | 2024.06.03 |
[PintOS] union이 뭐임? What is union in C (0) | 2024.06.03 |