[TIL/크래프톤 정글] DAY 47

배재준·2025년 4월 25일

크래프톤 정글 - TIL

목록 보기
40/93
post-thumbnail

2025.04.25

TIL(TODAY I LEARN)


  • 오늘한 내용 : CS - 가상메모리 9.6 ~ 9.8

  • WEEK07: 시스템 콜, 데이터 세그먼트, 메모리 단편화, sbrk/mmap


9.6 주소의 번역

  • 가상 주소 구성
    • 가상 주소는 페이지 번호 + 페이지 오프셋(VPN + VPO) 두 부분으로 나뉨
    • 페이지 번호는 페이지 테이블 인덱스, 오프셋은 그 안의 바이트 위치
  • TLB (Translation Lookaside Buffer)
    • 가상 페이지 번호 → 물리 페이지 번호 매핑을 캐시하는 고속 저장소
    • TLB 히트 시 변환 빠르게 완료, TLB 미스 시 페이지 테이블로 이동
  • 페이지 테이블 (Page Table)
    • 전체 가상 주소 공간을 페이지 단위로 쪼개어 매핑
    • 각 항목(PTE)에 유효 비트, PFN, 접근 권한 포함
    • 다단계 페이지 테이블 구조로 메모리 낭비 최소화
  • 최종 주소 계산
    • 변환 결과인 PFN + 오프셋(VPO)를 합쳐 물리 주소 생성
    • 이후 DRAM에서 해당 위치의 데이터를 읽거나 씀

9.6.1 캐시와 VM의 통합

가상 메모리 시스템과 캐시 메모리 시스템함께 사용함.

이 둘이 올바르게 통합 되어야, 프로그램이 빠르고 안전하게 메모리에 접근할 수 있음.

  • CPU는 가상 주소로 메모리 접근을 시도하지만,
  • 캐시는 반드시 물리 주소로만 동작
  • TLB와 페이지 테이블을 통해 주소 변환을 먼저 수행해야 캐시에 접근할 수 있음
  • 가상 주소 기반 캐시는 alias 문제 등으로 인해 거의 사용되지 않음

9.6.2 TLB를 사용한 주소 번역 속도의 개선

주소 변환은 매우 자주 발생하는 연산이기 때문에, 빠르게 수행되어야 한다.

하지만 페이지 테이블을 매번 조회하는 건 느리다.

그래서 하드웨어는 TLB(Translation Lookaside Buffer)라는 고속 캐시를 사용해 최근 변환 결과를 저장한다.

필드설명
TLBI (Index)엔트리를 찾아볼 위치 (VPN의 하위 비트 일부)
TLBT (Tag)정확한 VPN 식별자 (VPN의 상위 비트)
PFN변환 결과: 매핑된 물리 프레임 번호
Valid엔트리 유효 여부
Perm (RWX)접근 권한 (읽기/쓰기/실행)
CPU → 가상 주소 (VA) 생성
     ↓
MMU → VPN 추출
     ↓
TLB 조회:
   → TLBI로 세트 선택
   → TLBT(Tag) 비교
     ↓
   • Hit → PFN 반환 → 물리 주소 계산
   • Miss → 페이지 테이블 탐색 → PFN 얻고 TLB 갱신

9.6.3 다중 레벨 페이지 테이블

가상 메모리의 전체 주소 공간을 페이지 단위로 나눈다면, 모든 페이지에 대해 PTE를 만들어야 하고 → 페이지 테이블이 너무 커짐!

그래서 운영체제는 “필요한 부분만 만들어 쓰는” 다중 레벨(계층형) 페이지 테이블을 사용.

항목
가상 주소 공간48비트 사용 (나머지 16비트는 unused or sign-extended)
페이지 크기4KB = 2¹² → 오프셋(VPO): 12비트
VPN 비트 수48 − 12 = 36비트
PTE 크기보통 8바이트 (64비트 시스템에서는 주소 + 보호 비트 등 포함)
  • VPN마다 4바이트 PTE 필요 → 2³⁶ × 8B = 2³⁹ bytes = 512GB테이블
+------+------+------+------+------------+
| PML4 | PDPT |  PD  | PT   |  Offset    |
|  9b  |  9b  | 9b   | 9b   |   12b      |
+------+------+------+------+------------+

9.6.4 종합 구현 : 종단 주소 번역

  1. CPU가 가상 주소 VA를 생성
  2. MMU가 VA의 상위 36비트에서 VPN 추출
  3. TLB에 VPN 조회:
    • TLB hit → PFN → 물리 주소 계산 완료
    • TLB miss → 다단계 페이지 테이블 탐색 시작
  4. VPN을 4개의 인덱스 (Ix1~Ix4)로 분할
  5. 페이지 테이블 단계별 탐색:
    • Ix1 → top-level page table
    • Ix2 → 2단계 테이블
    • Ix3 → 3단계 테이블
    • Ix4 → leaf-level page table
  6. 최종적으로 Page Table Entry(PTE) 도달 → PPN 추출
  7. PPN + Page Offset → 물리 주소 계산
  8. 물리 주소로 캐시(L1/L2) 또는 DRAM 접근

9.7 사례 연구 : 인텔 코어 i7/리눅스 메모리 시스템

Intel Core i7과 Linux는 48비트 가상 주소를 4단계 페이지 테이블로 변환하며, TLB를 통해 고속 주소 매핑을 실현한다.

9.7.1 코어 i7에서의 주소 번역

Core i7 전체 메모리 시스템 구조도

구성요소:

  • 4개의 코어
  • L1/L2/L3 캐시 계층 (전부 물리 주소 기반)
  • L1 i-TLB, d-TLB, L2 unified TLB (전부 가상 주소 기반)
  • MMU: 주소 변환을 담당
  • DDR3 메모리 컨트롤러QuickPath 인터커넥트

TLB는 가상 주소 기반,

캐시는 물리 주소 기반

9.7.2 리눅스 가상메모리 시스템

task_struct

  • 하나의 프로세스를 나타내는 구조체
  • 주요 필드:
    • PID, 실행 파일, 레지스터 값
    • → mm_struct mm(가상 메모리 상태 정보)

mm_struct

  • 그 프로세스의 가상 메모리 전체 상태를 담는 구조체
  • 주요 필드:
    • pgd: 최상위 페이지 테이블 (PGD) 주소
    • mmap: vm_area_struct의 연결 리스트 시작점

vm_area_struct

  • 가상 주소 영역(Area) 하나에 대한 정보
  • 예: 코드 영역, 데이터 영역, 힙, 라이브러리 등
  • 필드 설명:
필드명설명
vm_start영역의 시작 가상 주소
vm_end영역의 끝 가상 주소
vm_prot접근 권한 (r/w/x)
vm_flags공유/사적 매핑 여부 등
vm_next다음 영역을 가리키는 포인터

task_struct, mm_struct, vm_area_struct는 모두 커널 메모리 안에 존재하며,
프로세스의 가상 주소 공간을 추적하고 관리하는 “관리용 포인터와 메타데이터”일 뿐,
실제 유저의 가상 주소 공간에는 나타나지 않습니다.

  • 리눅스 페이지 오류 예외 처리
    1. 주소가 유효한 영역(vm_area_struct)에 속하는가?
      • 아니면: segmentation fault → 프로세스 종료
    2. 접근이 합법적인가? (읽기/쓰기/X 등)
      • 아니면: protection fault → 프로세스 종료
    3. 정상적인 페이지 폴트라면
      • 페이지 교체 알고리즘 → victim 페이지 선택
      • victim이 dirty면 → 디스크에 저장
      • 새 페이지 로딩 → PTE 갱신 → TLB도 갱신
      • faulting instruction 재실행

9.8 메모리 매핑

메모리 매핑은 디스크 상의 파일을 가상 주소 공간에 직접 매핑하여,

freadwrite 없이 메모리 접근만으로 파일을 읽고 쓸 수 있게 해주는 기법

  • 두 종류의 객체 중 하나로 매핑될 수 있음.
구분정규 파일 매핑익명 파일 매핑
지연 페이징O (접근 시 디스크에서 해당 파일 페이지 로딩)O (접근 시 0으로 초기화된 페이지 생성)
데이터 원천디스크에 존재하는 실제 파일존재하지 않는 "빈 파일" 같은 느낌
초기 페이지 로딩파일 내용으로 채움0으로 초기화(빈 파일처럼 작동)
페이지 폴트 발생 시디스크에서 읽어옴물리 페이지 확보 후 0으로 초기화
스왑 여부O (스왑 공간 사용)O (스왑 공간 사용)
예시실행 파일, mmap("foo.txt")malloc(), mmap(NULL, ..., MAP_ANONYMOUS, ...)

9.8.1 다시 보는 공유 객체

  • 문제점 다수의 프로세스가 동일한 읽기 전용 코드를 사용할 때 각자 메모리에 별도 복사본을 두는 것은 매우 비효율적이다. 예: bash, printf 같은 함수들은 대부분의 프로세스가 사용
  • 해결책: 메모리 매핑 기반의 공유 객체
    • 공유 객체(shared object)는 여러 프로세스가 동일한 물리 페이지를 공유하도록 매핑됨
    • 쓰기를 수행하면 해당 변경 사항은 다른 프로세스에도 반영, 디스크 파일에도 반영됨
    • 이런 영역을 shared area라고 부른다
  • 반대 개념: 사적 객체(private object)
    • 프로세스마다 독립된 접근을 제공하는 매핑
    • 처음에는 공유된 물리 페이지를 사용하지만, 쓰기 시점에 복사(copy-on-write)
    • 새로운 물리 페이지를 할당하고 페이지 테이블을 업데이트
    • 변경 사항은 디스크에도 반영되지 않음
  • Figure 9.29: 공유 객체 매핑 → 프로세스들이 동일 물리 페이지를 사용
  • Figure 9.30: 사적 객체 매핑 → 쓰기 시점에만 물리 페이지 복사

9.8.2 다시 보는 fork 함수

  • 기본 동작 fork() 호출 시,
    • 자식 프로세스의 mm_struct, vm_area_struct, 페이지 테이블 등을 부모로부터 복사
    • 하지만 모든 페이지는 읽기 전용 + copy-on-write으로 설정
  • 쓰기 발생 시
    • protection fault 발생
    • 새로운 물리 페이지를 할당하고, 기존 페이지를 복사
    • 이후에는 각 프로세스가 독립적인 페이지 사용
  • 효과
    • 초기 fork 시 거의 물리 메모리 소모 없이 복제 가능
    • 실제 쓰기할 때만 복사가 일어나므로 매우 효율적

9.8.3 다시 보는 execve 함수

  • execve() : 현재 프로세스의 메모리 내용을 싹 지우고, 지정한 프로그램으로 덮어쓰기.
  • execve("a.out", NULL, NULL) 호출 → 현재 프로세스를 a.out 실행 파일로 대체
    • 주요 단계
      1. 기존 유저 영역 삭제
      2. 새로운 private area 매핑:
        • 코드(.text), 데이터(.data): 파일 기반 private mapping
        • BSS, 힙, 스택: demand-zero 영역
      3. 공유 객체 매핑 (예: libc.so): shared area로 동적 링킹
      4. PC(프로그램 카운터)를 새 코드의 진입점으로 설정

9.8.4 함수를 이용한 사용자수준 메모리 매핑

커널에 새 가상메모리 영역을 생성해 줄 것을 요청하는 함수
#include <unistd.h>
#inlcude <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
  • 인자 설명
인자의미
start요청 가상 주소 (NULL이면 커널이 자동 결정)
length매핑할 바이트 수 (페이지 크기 배수)
prot접근 권한: PROT_READ, PROT_WRITE 등
flags매핑 유형: MAP_SHARED, MAP_PRIVATE, MAP_ANONYMOUS 등
fd파일 디스크립터 (익명 매핑일 경우 -1)
offset파일의 시작 오프셋
가상메모리의 영역들을 삭제하는 함수
#include <unistd.h>
#inlcude <sys/mman.h>
int munmap(void *start, size_t length);

0개의 댓글