크래프톤 정글

Pintos Project3 - VA와 디스크 데이터 매핑 과정 이해하기

Jerry_K 2024. 12. 1. 18:46

프로그램이 실행될 때, ELF 파일은 어떻게 메모리에 로드될까?

 

특히 Lazy Loading 방식에서는 데이터가 즉시 메모리에 적재되지 않고,

실제로 필요할 때 디스크에서 로드된다.

이 과정에서 페이지 폴트(Page Fault) 발생하면

운영체제는 어떻게 VA(가상 주소)를 기반으로 디스크에서 데이터를 찾아 메모리에 매핑할까?

 

이번 포스팅에서는 ELF 파일의 구조Lazy Loading,

그리고 페이지 폴트 처리 과정을 살펴보며 VA와 디스크 데이터 매핑의 원리를 알아보자.

 

해당 이미지는 그냥 참고 !!


🚨 그 전에 먼저 ! 

ELF 파일에 대한 개념이 없으면 이해하기 힘들다.

혹시 ELF 개념이 헷갈리면 아래 포스팅 참고하자 !!

특히 프로그램 헤더 테이블 같은 경우 엄청 중요하니 꼭 알아두자

 

 

Pintos Project2 - ELF 파일

Pintos Project 2에 process.c 파일에 load하는 과정이 있다. 이 과정에서 ELF 개념이 나온다.Deep dive를 안하려고 했지만, 찾다보니 여기까지 와버렸다...ELF가 무엇인지를 알아보자 ! 🔗 ELF 형식의 파일 

jerry-k.site


 

궁금증❗

디스크 블록 상태에 있는 데이터가 아직 메모리에 로드되지도 않았는데,
어떻게 VA 주소를 알 수 있고,  ELF File을 메모리에 어떤식으로 적재할까 ?

 

struct ELF64_hdr {
    unsigned char e_ident[EI_NIDENT];
    uint16_t e_type;
    uint16_t e_machine;
    uint32_t e_version;
    uint64_t e_entry;
    uint64_t e_phoff;
    uint64_t e_shoff;
    uint32_t e_flags;
    uint16_t e_ehsize;
    uint16_t e_phentsize;
    uint16_t e_phnum;
    uint16_t e_shentsize;
    uint16_t e_shnum;
    uint16_t e_shstrndx;
};

struct ELF64_PHDR {
    uint32_t p_type;
    uint32_t p_flags;
    uint64_t p_offset;
    uint64_t p_vaddr;
    uint64_t p_paddr;
    uint64_t p_filesz;
    uint64_t p_memsz;
    uint64_t p_align;
};
  • PintOS에서 ELF 파일 헤더 & 프로그램 헤더 필드값
  • ELF헤더는 한개 뿐이고 프로그램 헤더는 세그먼트 개수별로 존재

 

✨ Linking time

  • 컴파일러링커프로그램의 코드와 데이터를 어떻게 배치할지 결정
  • Linking time에서 ELF 파일에서 각 세그먼트들의 VA 주소가 정해짐
    • 일부 동적 라이브러리 주소는 실행 시점에 조정

  • p_vaddr (각 세그먼트의 가상 주소)를 ELF 파일에 기록
    • p_paddr은 VM 사용하는 일반 OS에서 보통 무시함
  • 실행 시점에 OS가 세그먼트들의  VA를 기준으로 기준으로 메모리에 로드하도록 지시

 

+ @  ( 만일  한 프로세스가 여러개의 파일을 load했고, 두 파일이 동일한 VA를 사용할 경우 )

계속 같은 말이지만 실행 파일들은 링킹 과정에서 미리 정해진 가상 주소를 알고 있음 

  • 일반적으로 OS가 충돌하지 않는 빈 가상 주소 공간 찾아 세그먼트 배치
  • 동적 재배치 (Relocation)
    • 링커나 로더가 ELF 파일 p_vaddr 기준으로 새로운 VA를 할당
    • 충돌 감지되면 파일을 다른 VA로 매핑
  • ASLR(Address Space Layout Randomization) /  메모리 매핑 영역 분리 등의 방법 (이것까지는 잘 모르겠다.)
  • 결론 : ELF 파일 간 VA가 겹칠 수는 있지만 결국에 운영체제가 충돌을 방지  !! 

 

+ @  한 프로세스에 두개의 파일이 로드 될때 메모리 배치  방법들

1. 코드, 데이터, 힙, 스택 영역이 각 ELF 파일별로 연속적으로 묶여서 쌓이는 배치

[코드 A] → [데이터 A] → [힙 A] → [스택 A]
[코드 B] → [데이터 B] → [힙 B] → [스택 B]
  • 각 ELF 파일이 독립적인 메모리 구조를 가짐
  • 서로 충돌하지 않도록 가상 메모리 상에 A와 B의 메모리 영역이 떨어진 위치에 배치

 

2. 코드, 데이터, 힙, 스택 같은 유형별 영역을 모아 배치

[코드 A] → [코드 B] → [데이터 A] → [데이터 B] → [힙 A] → [힙 B] → [스택 A] → [스택 B]
  • 유사한 용도의 메모리 영역끼리 묶어서 배치 
  • ELF 로더가 각 세그먼트를 따로 처리해야 하며 참조 주소 수정
  • 구현이 어려움 (현대 운영체제는 이 방식 사용)

 

 Run time

  • 프로그램이 실행되면 loader(load 함수) 가 ELF 파일을 읽어 p_vaddr 기준으로 메모리에 배치
  • 페이지 폴트 발생 시, 디스크에서 데이터를 읽어와 해당 VA에 매핑 

 

 

그러면 이제 위에 궁금증에 대해 답변을 알 수 있다. 

요약하자면, ELF 파일 링크 과정에 링커가 각 세그먼트들의 VA 주소를 ELF 파일에 기록한다. 그리고 ELF 파일을 실제 로드할 때 각 세그먼트들의 VA 주소를 베이스로 데이터를 적재한다. 

궁금증❗

ELF파일의 데이터들이 메모리에 어떻게 적재되는지는 이제 알 것 같다. 

근데 Lazy Loading 방식 때문에, 처음에 데이터들이 메모리에 바로 적재되지 않는다.
그렇다면 Page Fault가 발생 했을 때, 어떻게 메모리와 디스크를 연결시킬 수 있을까 ? 
디스크에서 어떻게 VA 주소만 가지고 데이터를 가져오고 VA와 매핑 할 수 있을까 ?   

 

 

우선 Program Header Table에는 각 세그먼트에 대한 정보들이 아래와 같이 있음 

(참로고 Program Header는 세그먼트별로 존재  !! )

  • p_vaddr (세그먼트의 가상 주소 시작점)
  • p_offset (세그먼트의 디스크 상의 위치)
  • p_filesz (디스크에 실제 존재하는 크기)
  • p_memsz (메모리에서 필요한 총 크기)

 

이를 기반으로 OS는 VA로 디스크 찾을 수 있음

운영체제는 ELF 파일의 Program Header 정보를 기반으로 모든 VA가 디스크의 어디에 있는지 계산할 수 있음
따라서 페이지 폴트 발생 시, 필요한 데이터를 정확히 로드할 수 있음 !! 

 

 

아래 예시를 통해 좀 더 구체적으로 이해해보자. 

세그먼트	 p_vaddr	 크기		p_offset	p_filesz	p_memsz
.text		0x8048000	4KB		 0x1000		   4KB		   4KB
.data		0x8049000	2KB		 0x5000		   2KB		   2KB

 

그럼 페이지 폴트가 발생 했을 경우 

  1. mmu가 페이지 테이블에서 VA -> PA를 매핑 찾지 못함
  2. CPU는 페이지 폴트 예외 발생 시키고, OS의 페이지 폴트 핸들러 호출
  3. OS는 fault 가 발생한 VA 확인 
  4. VA가 적절한 주소인지 확인 (적절한 주소 아니면 Segmentation Fault)
  5. VA가 속한 페이지의 디스크 오프셋 계산
  6. 필요한 데이터를 디스크에서 읽어 물리 메모리로 로드
  7. 페이지 테이블을 업데이트하여 새로 매핑

 

5~6번을 구체화 해보자 !!  (VA로 디스크의 원하는 데이터 찾기)

ex)  가상 주소 0x8049000가 참조 되었다고 가정

  • p_vaddr : 0x8048000
  • p_offset : 0x1000
  • p_filesz : 0x3000
  • p_memsz : 0x4000

 

p_vaddr(세그먼트 시작주소)를 기준으로 세그먼트(0x8048000) 범위에 속하는지 확인

→ 세그먼트(0x8048000) 범위에 포함됨!

오프셋 = 요청된 VA - 세그먼트 시작 주소
    = 0x8049000 - 0x8048000
    = 0x1000
  • 요청된 VA가 세그먼트 시작 주소로부터 얼마나 떨어져 있는지 계산

 

파일 오프셋 = p_offset + 오프셋
    = 0x1000 + 0x1000
    = 0x2000
  • 이 오프셋을 ELF 파일의 시작 위치(p_offset)과 더해서 파일에서 데이터를 읽어올 위치 계산
  • 디스크의 0x2000 위치에서 한 페이지(4KB)를 읽음
  • 물리 메모리의 빈 페이지를 할당한 뒤, 데이터를 로드
  • 가상 주소 0x8049000과 물리 메모리 주소를 페이지 테이블에 매핑

이렇게 하면 완료  !! 

 

 

+ @  메모리 LOAD  배치

 

  • 가상 메모리: ELF 파일의 세그먼트는 지정된 가상 주소에 순차적으로 배치
  • 물리 메모리: 가상 주소가 매핑된 물리 메모리 주소는 불연속적일 수 있음. (페이지 단위로 매핑)

 

그리고 Lazy Loading에서는 가상 메모리에서 세그먼트가 순차적으로 매핑되지만,

물리 메모리에서는 참조 시점에 따라 비순차적으로 Load 됨

 

 

그러면 이제 위에 궁금증에 대해 답변을 알 수 있다. 

→ 운영체제는 ELF 파일의 Program Header 정보를 통해 모든 VA가 디스크 어디에 위치하는지 유추 가능하다 .
그렇기 때문에 실제 VA와 PA가 매핑되지 않은 상태에서도 디스크에서 원하는 데이터를 가져 올 수 있다.