Virtual Memory 전체적인 큰 그림 그리기
저번 Project2에서 유저 프로그램 실행과 시스템 콜에 대해 다뤘다면, 이번 Project3에서는 Virtual Memory와 Page Fault 처리를 구현하게 되는데요. 그에 앞서 Virtual Memory가 도대체 왜 필요한지, 무슨 일을 하는지, 그리고 Page Fault는 어떻게 처리되는지에 대한 전체적인 큰 그림을 그려 봅니다.
기존 Pintos의 문제점 및 VM의 필요성
1. 물리 메모리 용량의 한계
- 실행 중 프로세스, 스레드 수가 많아지면 메모리가 부족해지고, 이는 실행 거부(OOM) 및 성능 급락으로 이어짐
- 물리 메모리보다 큰 프로그램은 실행 불가
2. 프로세스 간 보호 및 격리의 부족
- 한 프로세스가 다른 프로세스 메모리를 덮어쓰는 경우 치명적인 오류/보안 문제 발생 가능
3. 주소 공간 충돌
- 서로 다른 프로그램이 정적 링크, 공유 라이브러리 등에 대해 동일한 절대 주소를 요구할 수 있음
4. 비효율적인 디스크 I/O
- 시스템 콜 기반의 read/write는 user-kernel 간 복사와 모드 전환(system call overhead)이 반복되어 성능 저하 발생
- 커널 수준의 페이지 캐시는 존재하지만, 사용자 프로그램 입장에서 직접적인 캐시 활용이 어렵고 반복 접근 시 효율성이 떨어짐
5. 실행 파일/라이브러리 적재 비용 과다
- 프로세스 시작 시 전체 코드/데이터를 한 번에 RAM에 올려야 하므로, 초기 실행이 지연됨
- 실제로 프로그램이 사용하는 부분은 일부에 불과
VM을 통한 문제점 개선
주소 추상화와 동적 메모리 관리를 제공하는 가상 메모리(Virtual Memory, VM)를 도입함으로써 위 문제들을 해결할 수 있습니다.
1. 프로세스별 독립적인 가상 주소 공간 제공
- 각 프로세스 메모리 보호 및 주소 충돌 문제 해소
2. 지연 할당(Lazy Allocation) 기반의 동적 페이지-프레임 할당
- Page Fault 발생 시점에만 물리 메모리를 할당
- 전체 메모리 효율 증가, 실행 속도 개선
3. 스왑(Swap) 및 파일 매핑(Memory-Mapped File)을 통한 디스크 연계
- RAM 부족 상황에서도 디스크를 활용해 실행 지속 가능
4. 페이지 캐시(Page Cache)를 통한 디스크 I/O 최적화
- 파일 내용을 미리 메모리에 캐싱해 디스크 접근 최소화, 성능 향상
- 단, 페이지 캐시는 직접 구현하는 기능이 아니라 VM이 제공하는 인프라 위에서 동작하는 커널 수준의 고급 캐싱 메커니즘
- 즉, VM이 제공하는 페이지 단위 주소 관리와 메모리 보호 인프라를 기반으로 구현됨
Page fault 발생 시의 처리 과정
1. 사용자 프로세스 가상 주소 접근 및 Page Fault 발생
- 사용자 프로그램이 실행 중 가상 주소(VA)를 참조
- 새 코드, 데이터, 스택 영역 등에 처음으로 접근
- 하드웨어가 매핑 부재를 발견
- 페이지 테이블(PML4~PTE) 어디에서도 Present 비트가 0인 경우, 즉시 Page Fault 예외 발생
2. 커널의 문제 접수
- 예외 진입 → Page Fault Handler 호출
- 커널 모드로 전환되며, fault VA, 에러 코드, 스택 포인터 등의 컨텍스트가 저장됨
- 해당 VA가 사용자가 접근 가능한 유효한 주소인지 1차적으로 판별
- 커널 영역이거나 NULL 등 금지 주소인 경우 곧바로 해당 프로세스를 종료
- 사용자 스택 근처라면 Stack Growth 후보로 간주하고 계속해서 진행
3. Lazy Loading 여부 확인
- 프로세스의 보조 페이지 테이블(SPT) 조사
- 해당 VA에 대한 정보가 있는지 확인
- 정보가 있다면 Lazy Loading 대상
- 정보가 없다면 Stack Growth 조건에 해당하는지를 따져본 뒤, 아닌 경우 세그폴트 처리
4. 실제 메모리(프레임) 확보
- 빈 프레임 탐색
- 남는 프레임이 있으면 즉시 할당
- 없으면 Eviction(퇴출 작업)을 통해 Victim(희생될) 프레임 하나를 골라 내보내 공간을 만듦
- 이때 내보내지는 프레임은 페이지 유형에 따라 다르게 처리됨
- File Page면서 Clean(수정되지 않은 상태)한 경우 → 그대로 폐기
- File Page면서 Dirty(수정된 상태)한 경우 또는 Anonymous Page → 원본 파일/스왑 파티션에 저장 후 폐기
5. 데이터 채우기
- 할당받은 프레임에 내용 적재
- ELF 코드/데이터: 실행 파일에서 필요한 바이트만 읽어 옴
- Memory-Mapped File: mmap 대상 파일에서 읽어 옴
- Anonymous: 스왑 슬롯에서 읽어 오거나, 처음이라면 Zero-Fill(0으로 채움)
- Stack Growth: 무조건 Zero-Fill
6. 새 매핑 추가 및 복귀
- VA-PA 매핑
- 페이지 테이블에 매핑을 기록하고, 접근 권한(R/W)을 설정
- 커널 → 사용자 모드로 복귀
- 저장해 둔 레지스터 및 스택을 복원하고, Fault가 발생했던 명령어를 다시 실행
- 이번에는 정상적으로 메모리에서 데이터를 읽어 오면서 실행을 지속할 수 있게 됨
'크래프톤 정글 > Code 정글(C언어)' 카테고리의 다른 글
[Pintos] OSTEP 기반 Virtual Memory 배경지식 정리: 왜 VM이 필요한가 (0) | 2025.05.31 |
---|---|
[Pintos] Virtual Memory Layout 정리 (0) | 2025.05.30 |
[Pintos] Pintos-Kaist Project2 User Programs Solution 및 Docker 기반 프로젝트 실습 템플릿 (0) | 2025.05.27 |
[Pintos] User Programs 테스트 및 디버깅 정보 공유 (0) | 2025.05.24 |
[Pintos] 트러블슈팅: CRLF로 인해 맞는 코드의 테스트가 실패하는 현상 해결하기 (0) | 2025.05.23 |