크래프톤정글/운영체제

[OSTEP][가상화] CH23 완전한 가상 메모리 시스템 +대화

아람2 2024. 11. 15. 16:02
반응형

발표 날짜 24.11.16 SAT 10시a.m. 

CH23 완전한 가상 메모리 시스템

완전한 가상 메모리 시스템을 구현하기 위해서는 성능, 기능성, 보안을 위한 다양한 특징들이 있다

핵심 질문 - 완전한 VM 시스템을 구현하는 방법
완전한 가상 메모리 시스템을 구현하기 위해서 필요한 특징은 무엇인가?
이러한 특징들은 어떻게 성능을 향상시키거나, 보안을 강화하거나, 혹은 시스템을 개선하는가?

우리가 살펴볼 시스템은 1) VAX/ VMS 운영체제와 2) Linux 의 가상 메모리 시스템이다 

VAX/ VMS 시스템에서 사용된 기법과 접근법들은 현재에도 사용 중이며 공부할 충분한 가치가 있고 

(+ VAX/VMS 운영체제: VAX = 컴퓨터, VMS = 운영체제)

Linux 는 가장 확장성이 뛰어난 다중 코어 시스템에서 효과적으로 실행된다

23.1 VAX/ VMS 가상 메모리 

VMS 는 다양한 종류의 기계들에서 실행되어야 한다는 문제를 알고 있어서, 다양한 종류의 시스템들에서 잘 동작하는 기법들과 정책들을 필요로 했고, 그래서 컴퓨터의 구조적 결함을 소프트웨어로 보완한 훌륭한 사례이다 

메모리 관리 하드웨어

VAX-11 은 페이징과 세그멘테이션의 하이브리드 구조를 가지고 있고, 주소 공간의 하위 절반은 "프로세스 공간"으로, 각 프로세스마다 다르게 할당된다, 주소 공간의 상위 절반은 그 중 반만 사용되며 운영체제의 보호된 코드와 데이터가 이곳에 존재한다 

VAX 선형 페이지 테이블의 크기가 지나치게 커진다는 근본적인 문제를 해결하기 위해 (메모리 압박의 정도를 경감시키기 위해) 아래의 두 가지 방법을 사용하였다 

1) 사용자 주소 공간을 두 개의 세그먼트로 나누어 프로세스마다 각 영역을 위한 페이지 테이블을 가지도록 하였다 

2) 사용자 페이지 테이블들을 커널의 가상 메모리에 배치하여 메모리 압박을 더 줄일 수 있었다 

커널 가상 메모리에 페이지 테이블을 넣으면 주소 변환 과정이 훨씬 더 복잡해지지만, 다행히 VAX 의 하드웨어 TLB 에서 빠르게 처리된다

실제 주소 공간 

사실, 실제 주소 공간은 훨씬 복잡하다 

코드 세그먼트는 절대로 페이지 0 에서 시작하지 않는다, 대신 이 페이지는 접근 불가능 페이지로 마킹되어 있으며 널-포인터 Null-pointer 접근을 검출할 수 있게 한다 

그리고 커널의 가상 주소 공간이 사용자 주소 공간의 일부이다 < 이 부분 이해 안 감 

몇 가지 이유로 커널은 여러 주소 공간들로 매핑되는데, 그러한 구조를 택하면 커널 동작이 쉬워진다, 커널이 물리 메모리에 전부 존재한다면 스왑하는 등의 작업이 상당히 어려웠을 것이다 

응용 프로그램은 운영체제의 데이터나 코드를 읽거나 쓰지 않아야 하기 때문에, 운영체제의 자료를 보호하기 위해서는 하드웨어가 페이지 별로 보호 수준을 다르게 설정할 수 있어야 한다, 이를 위해 VAX 는 페이지 테이블의 Protection Bit(s) 에 보호 수준을 지정하고, 특정 페이지를 접근하기 위해서 필요한 CPU 의 권한 수준을 기록한다 

사용자 코드가 더 높은 보호 수준의 자료를 접근하려고 시도하면 운영체제로 트랩이 걸리고, 트랩을 일으킨 프로세스는 종료된다 

페이지 교체 

VAX 의 페이지 테이블 항목 PTE 에는 참조 비트 Reference bit 가 없다, VMS 교체 알고리즘은 어떤 페이지가 자주 사용 중인지 하드웨어 지원 없이 판단해야 한다 

메모리 호그 Memory Hog 는 메모리를 너무 많이 사용하는 프로그램을 의미한다 

위의 두 가지 문제를 해결하기 위해 세그먼트된 FIFO 교체 정책이 나왔다 

각 프로세스를 상주 집합 크기 Resident Set Size, RSS 라고 불리는 최대 페이지 개수를 지정받고, 페이지 개수가 RSS 보다 커지면 "제일 먼저 들어왔던" 페이지가 쫓겨난다, FIFO 는 하드웨어의 지원이 필요 없기 때문에 구현이 간단하지만, 성능은 그리 좋지는 않다 

 

FIFO 의 성능을 개선하기 위해 전역 클린-페이지 프리 리스트 Global Clean-Page Free List 와 더티-페이지 리스트 Ditry-Page List 라고 하는 두 개의 Second-Chance List 를 도입하였고, 이 리스트는 메모리에서 제거되기 전에 페이지가 보관되는 리스트이다 

+ 스터디 내용 추가 - ‘전역 클린 리스트 global clean-page free list’‘더티 페이지 리스트 dirty-page list’를 도입해주었다. 이 두 리스트를 second-chance list라고 부르는데, 그 이유는 다음과 같다. RSS를 초과하면 해당 페이지는 프로세스의 FIFO 리스트에서 제거돼서 두 리스트 중 하나의 리스트로 들어간다. 2. 다른 프로세스가 빈 페이지를 요청하면 먼저 클린 리스트에서 페이지 하나를 빼간다. 만약 클린 리스트에 적절한 페이지가 없다면 더티 리스트에서 뺀다. 3. 만약 second-chance list에 있는 페이지가 리스트에서 제거되기 전에 참조가 일어나서 페이지 폴트가 발생하면, 리스트에서 해당 페이지를 빼가서 다시 FIFO리스트로 들어간다. 4. second-chance list로 인해 디스크 접근을 최대한 피할 수 있어 LRU 정책과 비슷하게 동작하게 되고, 이는 second-chance list 크기가 클수록 더 유사해진다.

 

또다른 최적화 기법으로, 스왑할 때 I/O 의 효율을 개선하기 위해 클러스터링 Clustrering 기법을 도입하여, 전역 더티 리스트에 있는 페이지들을 작업 묶음을 만들어서 한 번에 디스크로 보내면, 쓰기 횟수는 줄이고 한 번에 쓰는 양은 늘려서 성능을 향상 시킬 수 있다 

그 외의 기법들

요청 시 0 으로 채우기 Demand Zeroing

1) 페이지가 주소 공간에 추가되는 시점에는 페이지 테이블에 접근 불가능 페이지라고 표기하고 항목을 추가한다 (페이지에만 표기, 메모리에 할당하지는 않은 상태)

2) 프로세스가 추가된 페이지를 읽거나 쓸 때 운영체제로 트랩이 발생한다 

3) 트랩을 처리하면서 운영체제는 Demand Zeroing 할 페이지라는 것을 알게 되고

4) 이 시점에서 운영체제는 물리 페이지를 0 으로 채우고 프로세스의 주소 공간으로 매핑하는 등 필요한 작업을 한다 

🐣 트랩을 처리하면서 페이지를 물리 메모리에 매핑하고 페이지의 내용을 모두 0 으로 초기화한다

그리고 물리 메모리 주소가 페이지 테이블에 기록되고, 해당 페이지는 이제 접근 가능한 상태가 된다

즉, 메모리에 물리 페이지가 실제로 할당되는 시점은 프로세스가 페이지에 처음으로 읽기 또는 쓰기를 시도할 때이다 🐣

프로세스가 해당 페이지를 전혀 접근하지 않는다면 이 모든 작업을 피할 수 있다 

쓰기-시-복사 Copy-On-Write, COW

한 주소 공간에서 다른 공간으로 페이지를 복사할 필요가 있을 때, 복사하지 않고

해당 페이지를 대상 주소 공간으로 매핑하고, 해당 페이지의 페이지 테이블 엔트리를 양쪽 주소 공간에서 읽기 전용으로 표시한다 

양쪽 주소 공간이 페이지를 읽기만 한다면 더 이상의 조치는 필요 없고, 데이터 이동 없이 빠르게 복사할 수 있다 (메모리 공간도 절약된다)

만약 둘 중 하나가 페이지 쓰기를 시도한다면, 트랩을 발생시키고, COW 페이지라는 것을 파악하고, 새로운 페이지를 할당하고, 데이터로 채우고, 이 새로운 페이지 폴트를 일으킨 페이지의 주소 공간에 매핑하여 독자적인 페이지의 사본을 가지게 된다 

 

UNIX 시스템에서는 fork() 와 exec() 의 시맨틱 때문에 COW 는 훨씬 더 중요하다 

상당한 불필요한 복사를 피할 수 있으며, 성능을 개선하면서 정확한 시맨틱을 유지할 수 있다

23.2 Linux 가상 메모리 시스템

여기에서는 Intel x86 용 Linux 에 초점을 맞출 것이다 

Linux 주소 공간

Linux 가 다른 운영 체제가 다른 점 중에 하나는 커널 가상 주소의 유형이 두 가지라는 것이다

첫번째는 커널 논리 주소 Kernel Logical Addresses 이고, 일반적인 커널의 가상 주소 공간이다, kmalloc 으로 호출되며, 페이지 테이블, 프로세스 별 커너 스택 등 대부분의 커널 데이터 구조가 존재한다, 이 부분의 논리 메모리는 디스크로 스왑할 수 없다 

커널 논리 주소는 물리 메모리의 첫 부분에 직접 매핑한다 (ex. 커널 논리 주소 0xC00000000 은 물리 주소 0x00000000 이런 방식으로) 이 부분을 통해 메모리 청크가 커널 논리 주소 공간에서 연속적이면 물리 메모리에서도 연속적인 사실을 알 수 있고, 이러한 작업에는 직접 메모리 접근 방식 Direct Memory Access, DMA 를 사용하여 장치와 메모리 사이에 입출력 전송을 하는 경우가 가능하다 

다른 주소 유형은 커널 가상 주소 Kernel Virtual Address 이다, vmalloc 으로 호출되며, 커널 논리 메모리와 달리 커널 가상 메모리는 보통 연속적이지 않기 때문에 DMA 에는 적합하지 않다, 하지만 결과적으로 더 쉽게 할당 가능하기 때문에 대용량 버퍼 할당에 사용된다 

페이지 테이블 구조

32-bit 에서 64-bit 로 전환되면서 x86 은 다중 레벨 페이지 테이블을 사용하므로 64-bit 시스템은 4레벨 테이블을 사용한다 

아직 전체 가상 주소 공간은 사용되지 않고 하위 48비트만 사용된다 

+ 페이지 테이블을 페이지 크기로 나눠서, 페이지 테이블을 페이지 테이블화 해서 쓰는 페이지만 물리 메모리에 올리기 기법 

(페이지 디렉토리가 페이지 테이블의 페이지 테이블을 말한다)

물리 메모리가 페이지 크기니까 페이지 크기로 나눈다 

크기가 큰 페이지 지원

페이지가 클수록 페이지 테이블에 필요한 매핑 개수는 더 적어진다 

TLB 가 더 효과적으로 작동하고 그에 따른 성능의 이득이 주된 이유이다 

 

거대한 페이지를 사용하면 TLB 의 더 적은 슬롯을 사용하더라도 TLB 미스 없이 매우 큰 메모리 공간에 접근할 수 있다

🐣 페이지 크기가 커지면 

TLB 미스가 발생했을 때 더 빠르게 처리될 수 있고, 특정한 시나리오에서는 할당이 매우 빠를 수도 있다 

하지만 내부 단편화 Internal Fragmentation 이라는 큰 단점이 있다 🐣 페이지가 활성화 중이라면 스와핑도 통하지 않는다 🐣

페이지 캐시

Linux 페이지 캐시 Page Cache 는 아래 소스로부터 온 페이지를 메모리에 유지할 수 있도록 통합된다

1. 메모리 맵 파일 Memory-mapped Files - 메모리에 매핑된 파일 데이터

2. 파일 데이터와 자이의 메타 데이터 (통상 파일 시스템에 read() 와 write() 를 호출하여 액세스 되는)

3. Anonymous memory - 힙과 각 프로세스를 구성하는 스택 페이지 🐣 주로 스왑 공간과 연결 

이러한 개채들은 페이지 캐시 해시 테이블 Page Cache Hash Table 에 보관되어, 빠른 검색이 가능하다

 

경우에 따라 시스템의 메모리가 부족하기 때문에 축출할 페이지를 결정할 때 2Q 교체 알고리즘의 변형된 형태를 사용한다

2Q 교체 알고리즘의 Linux 버전은 두 개의 리스트를 유지하여 메모리를 두 부분으로 나누었다 

처음으로 액세스되면 페이지는 비활동 리스트 Inactive List 로 들어가고, 다시 참조되면 활동 리스트 Active List 로 승격된다

교체가 필요하면 교체 후보는 Inactive List 에서 가져오며, LRU 근사 알고리즘을 사용하여 리스트를 관리한다 

🐣 대규모 파일의 순환적 접근의 경우 Inactive List에 존재하도록 제한한다

보안과 버퍼 오버 플로 공격

VM 시스템에서 주요 위협 중 하나는 버퍼 오버 플로 Buffer Overflow 공격이다

버퍼에 자신의 코드를 주입하고 시스템을 장악하여 자신이 원하는 일을 할 수 있게 만드는 공격이다 

🐣 버퍼 주위를 덮어 씌우는 식으로 공격한다고 한다, 버퍼 크기를 정해도 넘어서는 놈들도 있대 🐣

가장 단순한 방어는 주소 공간의 특정 영역에 탑재된 어떤 코드로 실행할 수 없게 만드는 것이다 

Return-Oriented Programming ROP 라는 공격은 악의적인 코드를 이용하여 임의의 명령 시퀀스를 실행하게 만드는 공격이다 

ROP 공격에 방어하기 위해 address Space Layout Randomization ASLR 라는 방어책이 있는데 사용자 수준 프로그램을 위한 유용한 방어 수준이기 때문에 Kernel Address Space Layout Randomization KASLR 라는 기능으로 커널에 통합되었다

 

+ 스터디 내용 추가

3-1. NX 비트 (No-eXecute bit)

  • 동작 원리:
    • 페이지 테이블의 NX 비트를 설정하여 **특정 메모리 영역(예: 스택)**에서 코드 실행을 금지.
    • 공격자가 스택에 삽입한 악성 코드는 실행되지 않음.
  • 장점:
    • 간단하고 효과적인 방어.
    • 스택 기반 버퍼 오버플로우를 방지.
  • 한계:
    • 공격자는 리턴 지향 프로그래밍(ROP) 같은 기술로 우회 가능.

3-2. 리턴 지향 프로그래밍 (ROP: Return-Oriented Programming)

  • ROP 공격:
    • 프로그램이나 라이브러리에 이미 존재하는 코드 조각(ROP gadgets)을 이용해 임의의 작업을 수행.
    • 스택을 덮어써서 리턴 주소를 바꾸고, 공격자가 원하는 명령어 시퀀스를 실행.
  • 방어 기술:
    • ASLR(Address Space Layout Randomization)을 통해 ROP를 어렵게 만듦

3-3. ASLR (Address Space Layout Randomization, 주소 공간 레이아웃 난수화)

  • 동작 원리:
    • 코드, 스택, 힙가상 메모리 주소난수화하여 공격자가 정확한 메모리 주소를 예측하지 못하게 함.
  • 효과:
    • 버퍼 오버플로우 공격이 성공하기 어려워짐.
    • 대부분의 공격은 프로그램 충돌로 끝남.
  • 한계:
    • ASLR이 비활성화되거나 난수화가 충분히 강하지 않으면 우회 가능.
  • 실제 사용:
    • 리눅스 사용자 프로그램뿐 아니라 커널에도 적용됨(KASLR: Kernel ASLR).

다른 보안 관련 문제들 Meltdown and Spectre

커널 보호를 강화하는 한 가지 방법은 각 사용자 프로세서에서 커널 주소 공간을 최대한 많이 제거하고

대신 대부분의 커널 데이터에 대해 별도의 커널 페이지 테이블을 사용하는 것이다 

(커널 페이지 테이블 격리 Kernel Page Table Isolation, KPTI 라고도 불린다)

하지만 이 방법은 성능 저하라는 비용을 지불해야 한다 

23.3 요약

Linux 는 fork() 시 페이지의 지연 쓰기-시-복사를 수행하여 오버헤드를 줄인다

또한 /dev/zero 장치를 메모리 매핑하여 페이지를 요청 시 0으로 채우기한다

메모리 부담을 줄이기 위해 페이지를 디스크로 스왑하는 백그라운드 스왑 데몬 swapd 을 가지고 있다 

 

CH24 메모리 가상화를 정리하는 대화

TLB 는 주소 변환을 위한 적은 하드웨어 캐시를 갖는 시스템을 지원한다 

페이지 테이블은 상당히 크기 때문에 크고 느린 메모리에 존재하고 있다 

가상 메모리를 실제적으로 가능하게 만드는 것은 TLB 라고 볼 수 있다 

 

페이지 테이블은 배열과 같은 선형 페이지 테이블로 시작해서, 트리처럼 보이는 멀티레벨 테이블로 발전하였다 

주소 변환 자료 구조는 프로그래머가 자신의 주소 공간으로 무엇이든 할 수 있도록 충분히 유연해야 할 필요가 있다 

 

반응형