Page Fault
- 실제로 가상주소와 물리주소의 매핑을 관리하게 되면서 페이지 폴트에 대해 더 명확하게 정리할 필요가 있었다.
- 책 컴퓨터 시스템의 자료를 참고했다.
- 책 그림 컴퓨터 시스템 책
- va(가상주소)로 페이지에 접근했을 때 실제 메인 메모리에 페이지가 없다면 page fault가 난다.
- MMU가 가상주소 A를 물리주소로 번역하려다가 페이지 폴트가 났다면 다음과 같은 단계를 수행한다
- 가상 주소 A가 영역 구조체로 정의된 영역 내에 위치하나? (위 그림에서 1번) (pintOS코드에서는 해당 주소가 페이지 테이블(supplement page table우리가 만들어준 가상 페이지) 가 있는지를 체크하는 방식으로 구현했다)
void check_address(void *addr) {
struct thread *cur = thread_current();
#ifdef VM
if (addr == NULL || is_kernel_vaddr(addr) || spt_find_page(&cur->spt, addr) == NULL)
exit(-1);
#else
if (addr == NULL || is_kernel_vaddr(addr) || pml4_get_page(cur->pml4, addr) == NULL)
exit(-1);
#endif
}
- 2번. 페이지의 권한을 확인한다. write요청이면 write 권한이 있는 지등
- 3번, 1,2를 통해서 합법적 접근인거 알았으니까, 여기서, 희생자 페이지(물리메모리에서 내쫓을 페이지) 선택해서 오류 처리, dirty하다면(물리메모리에 올라온 후 내용 수정이 있었다면) 희생자 페이지 내쫓고 새로 들어감. 그리고 페이지 테이블 갱신
- 페이지 오류 핸들러가 리턴할 때 cpu는 오류 인스트럭션을 재시작하고 그러면 해당 인스트럭션은 아까 요청한 a라는 가상 주소에 대해서 다시 요청을 할 것이고 다시 mmu로 가면 이번에는 페이지 폴트 없이 정상 작동된다.
--
학습내용 정리 - 설계시 고민했던 지점과 fork()
구현시 겪은 문제
구현시 고민했던 점
설계 고민
- 기존에 핀토스에서 페이지를 만드는 동시에바로바로 물리메모리에 매핑이 되었다면 이번에는 그를 막고 우리가 만들어준 struct page라는 구조체의 엘리먼트로 만들었다.
- 이는 커널의 스택영역에 저장된다 (pintOS에는 힙이 없다)
- struct page가 무엇인지, frame이라는 구조체는 어떤 용도로 쓸지 고민했다.
- struct page를 page table entry라고 생각을 했다.
struct page
엘리먼트 만들어서 가상 주소공간에 실제 페이지 관련된 정보를 만들어주고, 이는 해시 테이블로 관리했습니다. 가상 주소를 넣으면, 바로 페이지 엘리먼트를 찾을 수 있게 하기 위함
- 물리 메모리에 실제로 올라가면
struct frame
을 만들어주자고 결정.
- 이들을 연결리스트로 관리하면 물리 메모리에 올라온 애들을 순회하면서 뺄 애들 골라주고 그 때 매핑된 페이지도 삭제해주기 위해서 였습니다.
- 그를 위해 frame에 page도 필드로 설정하고, list-elem 도 필드로 주었다.
문제
pml4
를 고려 못하고 있었다. 아무리 우리가 만든 spt table(Supplemental Page Table)에 데이터 넣어주고 frame과 매핑해줘도 해당 VA를 참조하면 해당 물리 주소 매핑을 못하길래 누가 매핑하지? 누가 자꾸 페이지 폴트 내지? 하다가 다른 분들의 도움으로 pml4
가 프로젝트 3에서 사라진 것이 아니며 실제 페이지 테이블로서의 기능하고 가상주소와 물리주소 매핑은 여기서 해주며 개념상 이해하는 실제 페이지가 있는 곳이라고 이해 했다.
fork시에 발생한 문제 - fork() 시에 부모로 돌아오지 못하는 문제 발생
- 자식 fork()까지 잘하고 자식은 완벽하게 종료되는데 부모가 정상 종료가 되지 않았다.
- 왜 그런지 디버깅 해보니, 자식에서 부모 데이터를 변경해서 발생한 문제
fork()시에 자식 프로세스에게 어디까지 복사해줄까? 어디까지는 공유자원으로 사용할까
-
처음 생각은 어차피 물리 메모리에 똑같은 내용 올라오는데 뭐하러 두 번 올려주냐 한 번만 올려주자! 해서
-
-
1) 페이지만 따로 만들어주고 프레임은 동시에 가져가자고 결정했다. (**다른 페이지가 한 프레임을 참조하는 방식**
) 같은 프레임에는 같은 물리주소 있으니까
-
그러면 프레임에 있는 필드에 어떤 페이지를 할당해줄지 문제가 생겼다.
-
그래서 **그럼 프로세스마다 페이지, 프레임 각각 만들고 실제 물리적 주소에는 한 번만 올려서 같은 kva(커널에서 사용하는 실제 물리주소(커널에서는 가상주소)를 복사해주자 했는데**
→ 그렇다보니 계속 FAIL (부모가 정상 종료되지 않는 문제 발생)
-
(ex 스택 초기 페이지, write 명령 있는 페이지) 자식 프로세스에서 물리 메모리에 write을 할 경우 같은 물리 주소를 참조하고 있기 때문에 부모의 데이터도 자식도 변하게 되는 문제였다. 그래서 부모의 코드로 돌아오지 못했다
-
-
그래서 비록 같은 내용이어도 fork()
시에 물리 메모리에 2번 올리는 것으로 변경하고 테스트를 통과했습니다.
-
그렇기 때문에 자식이 수정해도 부모는 변함 없게 됩니다.
같은방식을 물리 메모리에 두 번 올리는 것은 낭비아닌가? 라고 생각해서 찾아보니 copy-on-write
방식을 사용한다고 합니다.
- 사진 출처: 공룡책
- 이런식으로 같은 공간을 참조하고 있다가 왼쪽 프로세스에서(ex 자식) 같이 참조하고 있는 페이지에 write를 하려고 하면 이 페이지를 그제야 복사해서 각기 다른 내용을 쓸 수 있게 하는 방식입니다.
어차피 exec 할 것인데 왜 굳이 원래 데이터를 복사해줄까?