일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 개발
- node.js
- 스프링부트
- Python
- 보안
- rabbitmq
- Spring
- 스프링 부트
- 스프링
- python3
- mysql
- 데이터베이스
- bytecode
- HTTP
- 안드로이드
- 디자인 패턴
- BCI
- 자료구조
- django
- ORM
- Spring Boot
- 웹
- 파이썬
- JPA
- 웹 개발
- db
- java
- 파이썬3
- 장고
- 자바
- Today
- Total
semtax의 개발 일지
인텔 메모리 관리 기법 분석 본문
개요
해당 포스팅은, 1년전에 학교 과제로 정리했던 내용을 그냥 묵혀두기에는 아까워서 블로그에 포스팅을 하는 내용입니다.
따라서, 틀린 부분이 많을 수도 있으니 너그럽게 봐주시면 감사하겠습니다.
1. 인텔 메모리 관리기법 분석
1-1. Address Model in Intel
1-1-1. 주요 특징들
-
인텔의 Address Model은 3가지로 나누어 지는데, 각각 Flat Memory Model, Segmentation Memory Model, Real Address Model로 나눌 수 있다.
-
첫번째로는, Flat Memory Model이다. Flat Model과 같은 경우 코드나 데이터 영역이(세그멘테이션 같이) 따로 영역이 나누어지지 않아서, 32bit 기준으로, 0~2^31-1 byte 에 해당하는 영역을 별 다른 제한 없이 모든 메모리 영역을 가리킬 수 있다. 여담으로, 해당 메모리모델에서의 주소를 선형 주소라고 부른다.
-
두번째로는, Segmentation Memory Model인데 해당 메모리 모델과 같은 경우, 이름 그대로 세그먼테이션 방식을 사용해서, 코드, 데이터 와 같은 영역들을 구분한다. IA-32에서는 보통 코드영역을 가리키는 세그먼트를 CS(Code Segment)라고 부르며, 점프 명령어 또는 인터럽트 명령어를 사용해야만 제어 할 수 있다(mov, lea 와 같은 데이터 이동 명령어로는 제어가 불가능 하다) 데이터 영역을 가리키는 세그먼트를 DS~FS, 스택영역을 가리키는 세그먼트를 SS 등으로 구분을 하고 있다. DS~FS, SS 세그먼트는 데이터 이동 명령어를 통해 제어가 가능하다. 또한 IA-32에서는 세그먼트 디스크립터들의 모음을 GDT라고 해서, 각 세그먼트 영역의 크기(limit) 뿐만이 아닌, R/W/X 와 같은 권한, 보통 OS시간에 배웠던 Ring0~Ring3 와 같은 특권 레벨 등 도 지정이 가능하다. IA-32에서는 이러한 GDT 영역을 통째로 레지스터에 저장하지 않고, 메모리의 임의의 영역에 저장을 해놓고 해당영역의 시작주소를 레지스터에 저장하는 방식을 채택하고 있으며, GDTR 레지스터에 해당 주소를 저장해놓는다. 그리고, lgdt 라는 명령어를 이용해서 해당 레지스터에 주소를 로딩한다. 세그멘테이션 모델에서는 만약, 특정 세그먼트 영역을 벗어나는 접근을 하는 경우에는 예외가 발생하며, IA-32에서는, GP(General Protection)예외를 발생시키게 된다. 내부적으로는, 위에서 언급한 선형주소로 변환이 되어서 메모리에 접근을 하게 된다.
-
세번째로, 인텔 CPU와 같은 경우에는 다른 CPU와는 다르게 하위 호환성을 최대한 지원을 하려고 노력을 하기 때문에, 16비트 영역만을 가리킬수 있는 통칭 Real Address 라고 불리는 16비트 주소 모델도 지원을 한다. 그리고, POST과정이 끝난뒤, CPU의 기본 Address 모드가 바로 이 Real Address 메모리 모델 이다. 보통 "(segment selector) *16 + Offset" 과 같은 방식으로 계산이 된다.
-
아래의 그림이, 위에서 언급한 3가지 메모리 모델을 간략하게 요약한 그림 이다.
1-2. 메모리 관리와 관련된 레지스터, 관련된 자료구조들의 구조
기본적인 레지스터에는 범용 레지스터, 세그먼트 레지스터, 컨트롤 레지스터, 인스트럭션 포인터 레지스터가 있다.
-
범용 레지스터(General Purpose Register) – 32bit / 8개
<특징>
운영 모드와 가장 깊은 관계가 있는 레지스터이다. 계산, 메모리 어드레스 지정, 임시 저장 공간 등의 목적이나 특수한 용도로 사용한다. 범용 레지스터의 크기는 운영 모드 앞에 붙는 숫자와 대체로 일치하지만, 범용 레지스터의 수는 프로세서가 지원하는 운영 모드에 따라 다르다. (16비트와 32비트 모드를 지원하는 x86 계열 – 8개 / x86-64계열 – 16개 )
<장점>
수행 속도를 개선할 수 있다. 관련된 값을 레지스터에 올려서 계산하여 메모리에 접근하는 시간을 줄일 수 있다. 이와 마찬가지로 호출되는 함수의 작업 처리에 필요한 정보를 호출하는 쪽에서 파라미터를 통해 넘겨줄 때 다수의 범용 레지스터에 넣어 넘겨줌으로써 스택 영역의 메모리에 접근하는 시간과 스택을 정리하는 시간을 줄일 수 있다.
<종류>
(EAX : 32bit / AX : EAX의 하위 16bit / AH : AX의 상위 8bit / AL : AX의 하위 8bit)
-
산술 연산 레지스터(4개)
변수 저장 용도로 많이 사용한다
특정 Assembly 명령어(MUL, DIV, LODS, etc)에서는 특정 레지스터를 직접 조작하기도 한다.
EAX |
피연산자 및 결과 데이터 누산기(Accumulator for operands and results data) (+ 일반적으로 함수 리턴 값에 사용) |
EBX |
DS 세그먼트의 데이터에 대한 포인터(Pointer to data in the DS) |
ECX |
문자열 및 루프 연산 카운터(Counter for string and loop operations) (+ 반복문 명령어에서 참조 카운트로 사용된다.(루프를 돌 때마다 ECX를 -1)) |
EDX |
I/O 포인터 |
-
인덱스 레지스터
특정 명령어들(LODS, STOS, REP MOVS등)과 함께 주로 메모리 복사에 사용한다.
ESI |
문자열 연산을 위한 소스 포인터(source pointer for string operations) |
EDI |
문자열 연산을 위한 대상 포인터(destination pointer for string operations) |
-
포인터 레지스터
ESP |
스택 포인터(Stack pointer (in the SS segment)) (스택 메모리 관리는 프로그램에서 매우 중요하기 때문에 다른 용도로 사용하면 안된다.) |
EBP |
스택의 데이터 포인터(Pointer to data on the stack (in the SS segment)) (함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가 , 함수가 리턴하기 직전에 다시 ESP에 값을 되돌려줘서 스택이 깨지지 않도록 해야한다.) |
-
세그먼트 레지스터(Segment Register) – 16bit / 6개
<특징>
주소 공간을 목적에 따라 다양한 크기로 구분한다. 또한 접근 권한(Privilege Level), 세그먼트의 시작 어드레스와 크기 등을 지정하는데 사용하기도 한다. SDT(Segment Descriptor Table)의 index를 가지고있다.
<종류>
CS |
코드 영역을 가리키는 레지스터(Code Segment) (데이터 이동 명령으로 값을 변경할 수 없으며, 점프 명령이나 인터럽트 관련 명령으로 변경 가능하다.) |
|
DS |
데이터 영역을 가리키는 레지스터(Data segment) (데이터 이동 명령으로 값을 변경할 수 있다.) (데이터 영역에 접근하면서 DS레지스터 이외의 세그먼트 레지스터 접두사 사용한다.) |
데이터 영역에 접근할 때 암시적으로 사용된다. |
ES |
+Extra Segment (문자열과 관련된 작업을 처리할 때 암시적으로 사용한다.) |
|
FS |
|
|
GS |
|
|
SS |
스택 영역을 가리키는 레지스터(Stack Segment) (데이터 이동 명령으로 값을 변경할 수 있음) |
-
컨트롤 레지스터(Control Register) – 64bit / 1개
<특징>
운영 모드를 변경, 현재 운영 중인 모드의 특정 기능을 제어하는 레지스터이다.
각 필드는 저마다 특정 기능을 활성화 / 비활성화 하며 현재 운영 모드에 따라 필수 필드와 옵션 필드가 달라진다.
<종류>
CR0 |
운영 모드를 제어하는 레지스터 (리얼 모드에서 보호 모드로 전환하는 역할과 캐시, 페이징 기능 등을 활성화시킴) |
CR1 |
프로세서에 의해 예약된 레지스터 |
CR2 |
페이지 폴트 발생 시 페이지 폴트가 발생한 선형 주소가 저장되는 레지스터 (페이징 기법을 활성화한 후에는 페이지 폴트 발생 시만 유효한 값을 가짐) |
CR3 |
페이지 디렉터리의 물리 주소와 페이지 캐시에 관련된 기능을 설정하는 레지스터 |
CR4 |
프로세서에서 지원하는 각종 확장 기능을 제어하는 레지스터 (페이지 크기 확장이나 메모리 영역 확장 등의 기능을 활성화시킴) |
CR8 |
태스크 우선순위 레지스터의 값을 제어하는 레지스터 (프로세스 외부에서 발생하는 인터럽트를 걸러주는 필터의 역할) (IA-32e 모드에서만 접근 가능) |
-
인스트럭션 포인터 레지스터(Instruction Pointer Register) – 32bit / 1개
CPU가 처리할 명령어의 주소를 나타내는 레지스터이다. CPU가 하나의 명령을 처리하고 난 후 자동으로 그 명령어 길이만큼 EIP를 증가시킨다.범용레지스터들과는 다르게 EIP는 그 값을 직접 변경할 수 없도록 되어 있다. EIP를 변경하고 싶을 때는 특정 명령어(JMP, Jcc, CALL ,RET), 인터럽트(interrupt), 예외(exception)을 발생시켜야 한다
1-3. IA-32에서의 페이징 관리 기법
-
페이징은 linear 주소를 물리 주소로 페이지 단위로 메모리는 나눠서 변환한다. IA-32 구조에서는 페이지 크기를 4KB이나 4MB를 허용한다. 4KB 페이지인 경우 아래와 같이 2단계 페이징 체계를 사용한다(page number / page offset (32-bit linear address를 2개로 나눈 것)
(# linear 주소란, 페이지 유닛에 의해 물리 주소로 바뀌고 32비트의 부호없는 정수로 이루어져 있다. 총 4GB 크기이며 주소 공간을 하나의 큰 바이트 배열로 본다.(0x00000000 ~ 0xFFFFFFFF))
-
페이징에서 관여하는 레지스터
세가지 레지스터가 관여한다
-
CRO : WP(16 bits), PG(31 bits) 플래그
-
CR4 : PAE(5 bits), PGE(7 bits), PCIDE(17 bits), SMEP(20 bits)
-
IA32_EFER MSR : LME(8 bits), LMA(10 bits) NEX(11 bits)
CR0.WP |
- Write Protect - set : 페이지들은 supervisor-mode 쓰기로 부터 페이지를 보호 받음. - clear : supervisor-mode 쓰기는 읽기 전용 페이지에 쓸 수 있음. (이때 읽기 전용 선형주소에 user-mode 쓰기는 CR0.WP의 비트값과 상관 없이 허용되지 않는다.) |
CR0.PG |
- Paging - set : 페이징을 사용. - clear : 페이징을 사용하지 않음. (보호 모드에서만 의미를 가지고 CPU 동작에 아무런 영향을 주지 않는다.) - 이때 모든 선형주소는 물리주소로 취급된다. |
CR4.PAE |
- Physical Address Extension - set : 32bit 보다 큰 물리주소를 사용할 수 있음. - clear: 물리주소 32-bits 로 제한. - IA-32e mode 로 진입 전에 반드시 세팅 되어야 한다. |
CR4.PGE |
- Page Global Enable - set : Global Page Feature 사용할 수 있음. / 주소 공간에 대한 특정 변환이 공유될 수 있음. (set하기 위해선 CR0.PG가 먼저 세팅 되어야 한다.) - clear : Global Page Feature 사용할 수 없음. / 주소 공간에 대해서 공유되는 변환은 없음. - Global Page Feature : 빈번하게 사용되는 페이지나 공유 페이지를 모든 유저에 대해 Global로 표시한다. |
CR4.PCIDE |
- Process-Context Identifiers(PCDIs) Enable - set : PCIDs 를 사용 할 수 있음. - IA-32e 에서만 세팅될 수 있다. (IA32_EFER.LMA가 1일 때) - PCIDs : 논리 프로세서(logical processor)가 복수의 선형 주소 공간에 대한 정보를 캐싱할 수 있도록 해줌. |
CR4.SMEP |
- Supervisor Mode Execution Prevention Enable - set : SMEP 를 사용할 수 있음. / supervisor-mode 에서의 소프트웨어 동작은 선형주소로 부터 인스트럭션을 펫치할 수 없다 / user-mode 에서는 선형주소로의 접근이 가능 |
IA32_EFER MSR.LME |
- IA-32e Mode Enable을 세팅할 수 있는 플래그 - set : 오퍼레이션 사이즈가 변하면서 IA-32e 모드로 진입한다. |
IA32_EFER MSR.LMA |
- Logical Processor 가 IA-32e 모드에 있고 IA-32e 페이징을 사용하고 있음을 나타냄. (이 프로세서는 IA32_EFER.LMA, CR0.PG, IA32_EFER.LME 이 반드시 세팅 되어 있어야 한다.) - 소프트웨어는 IA32_EFER.LMA 플래그를 직접적으로 수정할 수 없다. |
IA32_EFER MSR.NEX |
- Execute Disable Bit Enable - set : PAE 페이징 또는 IA32-e 페이징에서 특정 선형 주소로 부터 인스트럭션이 펫치되는 것을 막아 페이지 접근 제한을 가능하게 함. (세팅이 되더라도 데이터 읽기는 해당 어드레스에서 가능하다.) |
-
동작
선형 주소의 상위 10 bits와 CR3는 현재 프로세스의 페이지 디렉토리를 가리키고 하위 비트 0-11는 페이지 테이블에서 가리키는 4KB 페이지의 offset을 나타낸다. 페이지 디렉토리의 엔트리는 페이지 테이블의 시작 주소를 가지고 있고 한 엔트리는 페이지 크기 플래그이다.
플래그가 설정된 경우, 페이지 디렉토리가 내부 페이지 테이블을 우회하여 직접 4MB페이지 프레임을 가리킨다. 또한, 선형 주소의 22비트는 4MB페이지 프레임의 offset을 나타낸다.
(여기서 페이지 프레임은 페이지와 마찬가지로 고정 크기의 연속적인 메모리 블럭이다. 페이지는 가상 메모리를 사용하는 최소 크기 단위이지만 페이지 프레임은 물리 메모리를 사용하는 최소 크기 단위이다.)
1-3-1. 페이지 테이블이 여러 단계가 존재하는 이유
-
페이지 테이블을 분산해서 저장할 수 있어 연속적인 메모리가 필요없다.
-
쓰이지 않는 페이지에 대한 테이블을 저장할 필요가 없어 메모리 절약의 효과가 있다.
1-4. PAE(Physical Address Extension) 기능
-
개요
-
본래 32bit 주소 공간 에서는 4GB에 해당하는 영역만을 가리킬 수 있지만, IA-32에서는 더 큰 영역을 가리키는 기능이 존재하는데 바로 Physical Address Extension 이라는 기능이다.
-
동작원리
-
먼저, PAE를 설명하기 전에, 다시 한번 IA-32 에서의 PAE가 적용되지 않은 페이지 테이블의 구조와 PAE가 적용된 페이지 테이블의 구조를 비교해 보도록 하겠다. 아래 그림은 기존 IA-32(32 bit)에서의 페이지 테이블, CR3 레지스터의 구조이다.
다음으로는, PAE가 적용된 페이지 테이블과 CR3레지스터의 구조이다.
그림에서 보다시피, PAE 옵션을 활성화 하는 경우, PDPTE(Page Directory pointer) 라는 자료구조가 새로 생겨난걸 볼 수 있고, page directory, page table, page frame을 가리키는 주소의 크기가 늘어 난 것을 볼 수 있다. 이러한 트릭을 이용해서, 변환 되기전의 주소가 32비트 밖에 되지 않아도, 페이징 테이블로 인해 변환된 주소가 32비트 이상이 되기 때문에, 4GB가 넘는 영역을 가리킬 수 있게 된다. (간단히 말해서, 32bit -> (32bit 보다 큰 주소크기) 로 변환되는 함수를 생각하면 될것 같다.) 또한, 저 PDPTE영역 역시 레지스터에 저장하는것이 아니고, 임의의 메모리 공간에 저장을 한뒤, CR3 레지스터를 이용해서 가리키는 방식을 채택하고 있다. PAE확장 옵션이 활성화 되었을때의 CR3 레지스터의 값들은 아래의 표와 같이 사용된다.
3) 주의사항 & 보충설명
-
PAE 옵션이 활성화 된 경우, 선형 주소 변환시, CPU에서 CR3 레지스터를 사용하지 않는다. 즉, 선형 주소 변환 중에는, PDPTE(Page Directory pointer)에 접근을 하지 않는다.
-
위의 페이지 테이블을 서술한 그림에서 M이 의미하는 것은 MAXPHYADDR(물리 메모리의 최대 상한치)를 의미한다.
-
위의 페이지 테이블을 서술한 그림에서 reversed 영역은 반드시 0으로 세팅되어 있어야 한다.
1-5. IA-32 캐시 구조 및 정책
-
IA-32 캐시 구조
인텔 IA-32 아키텍처에서의 캐시 구조는 아래 그림과 같다.
위 그림에서 보다시피, IA-32환경에서의 캐시는 Instruction Cache, I/D TLB(Instruction/Data TLB), Trace 캐시, L1,L2,L3 캐시 등이 있으며, L1 캐시와 같은 경우 Instruction/Data 캐시가 분리되어 있으며, L2,L3 캐시는 Instruction 과 Data 캐시가 따로 분리되어 있지 않다. 그리고 저러한 캐시/메모리들을 관리해주는 통합 메모리 컨트롤러(IMC)와, IA-32에서의 버스컨트롤러인 QPI(Quick Path Interconnect) 등으로 구성 되어 있다. 이러한 각 모듈들의 주요 특징을 아래 표에 자세히 서술하였다. 또한, TLB도 2단계로 구성되어 있다.
주요 모듈 |
주요 특징 & CPU별 특성 |
L1 I-Cache |
Intel Core i7 : 32-KByte, 4-way 셋 연관 캐시 Intel Core 2 Duo : 8-way 셋 연관 캐시 |
L1 D-Cache |
Intel Core i7, Intel Core 2 Duo : 8-way 셋 연관캐시, 캐시라인 : 64바이트 |
L2 통합캐시 |
Intel Core i7, i5, i3 processors: 256KBbyte, 8-way 셋 연관캐시, 캐시라인 : 64바이트 Intel Core 2 Duo : 24-way 셋 연관 방식, 캐시라인 : 64바이트 |
L3 통합캐시 |
Intel Core i7 processor, Intel Xeon processor 5500: Up to 8MByte, 16-way 셋 연관 방식, 캐시라인 : 64바이트 |
Instruction TLB |
Intel Core i7, i5, i3 processors: 스레드당 64 엔트리 (코어당 128 엔트리), 4-way 셋 연관방식. |
Data TLB |
Intel Core i7, i5, i3 processors, DTLB0: 32-entries, 4-way 셋 연관방식 |
L2 통합 TLB(STLB) |
Intel Core i7, i5, i3 processor, STLB: 512-entries, 4-way 셋 연관방식 |
Store Buffer |
Intel Core i7, i5, i3 processors: 32entries Intel Core 2 Duo processors: 20 entries |
Write Combining Buffer |
Intel Core 2 Duo processors: 8 entries |
또한, 추가적으로 IA-32의 경우 캐시 일관성 관리같은 일을위해 PREFETCHh, CLFLUSH와 같은 L1~L3 캐시를 제어하는 명령어를 제공한다. |
2) IA-32 캐시 정책
-
IA-32 캐시 타입(메모리 타입)
-
IA-32 에서 메모리를 관리할때, 메모리의 내용에 따라서, 캐시 정책을 다르게 해주어야 할 떄가 존재한다(예를 들어, 캐시가 불가능한 경우라던가, 캐시를 비울때 따로 파일에 써줘야 한다거나, Memory Ordering과 같이 물리메모리와 논리메모리가 1:1 대응인지, 1:1 대응이 아닌지와 같은 경우) IA-32에서는 이러한 메모리 내용들의 속성에 따라 타입/정책을 분류해놓았다. 분류한 타입은 Strong Uncacheable, Uncacheable, Write Combining, Write Through, Write Back, Write Protected 이고 주요 특징들은 아래 표와 같다.
메모리 타입 |
캐시가능 |
Write-Back |
Data/Inst reordering |
Memory ordering |
Strong Uncacheable(UC) |
불가능 |
불가능 |
불가능 |
Strong Ordering |
Uncachable(UC) |
불가능 |
불가능 |
불가능 |
Strong Ordering |
Write Combining(WC) |
불가능 |
불가능 |
가능 |
Weak Ordering |
Write Through(WT) |
가능 |
불가능 |
가능 |
Speculative Processor Ordering |
Write Back(WB) |
가능 |
가능 |
가능 |
Speculative Processor Ordering |
Write Protected(WP) |
읽기만 가능 |
불가능 |
가능 |
Speculative Processor Ordering |
-
각 타입별 특징
-
Strong Uncachable(UC)
-
무슨 경우가 있어도 메모리의 내용이 캐시가 되지 않음. 또한, Branch Prediction, memory reordring도 수행되지 않는다.
-
Uncachable (UC)
-
위의 Strong Uncachable과 동일하다. 하지만, WC(Write Combining) 상태로 변화가 될 수 있다는게 유일한 차이점이다.
-
Write Combining (WC)
-
해당 캐시 정책은, 위에서 언급한 Uncachable 정책 처럼 따로 메모리의 내용이 캐시가 되지는 않는다 하지만 쓰기연산이 발생한 경우, 따로 마련된 Write Buffer(Write Combining Buffer)에 임시로 변경된 내용을 쓰고, 추후에 CPU에서 LOCK명령어나, 메모리 베리어 명령어들(아래의 메모리 오더링(memory ordering) 항목에서 설명) 혹은, 인터럽트나, CPUID와 같은 명령어가 실행될때 버퍼의 내용의 Flush 된다.
-
Write Through (WT)
-
읽기에 대해서는, 일반적인 캐시와 동일하게 작동을 하고(캐시가 hit인 경우 캐시의 내용을 가져오고 miss 인경우 cache에 값을 쓴다) 쓰기연산이 발생하여 내용이 변경된 경우에 대해서는, 바로바로 메모리에 값을 갱신해 주는(Flush) 방식으로 작동한다(OS시간의 Write Through와 동일)
-
Write Back (WB)
-
읽기에 대해서는, 일반적인 캐시와 동일하게 작동을 하고(캐시가 hit인 경우 캐시의 내용을 가져오고 miss 인경우 cache에 값을 쓴다) 쓰기연산이 발생하여 내용이 변경된 경우에 대해서는, 최대한 실제 메모리에 쓰는것(Flushing)을 지연하다가(변경된 내용만 체크해놓음) 한꺼번에 쓰는 방식으로 작동한다(OS시간의 Write Back과 동일)
-
Write Protected (WP)
-
읽기에 대해서는, 일반적인 캐시와 동일하게 작동을 하고(캐시가 hit인 경우 캐시의 내용을 가져오고 miss 인경우 cache에 값을 쓴다) 쓰기연산이 발생하는 경우 무조건 invalidate 처리가 된다.
-
2) IA-32 캐시 일관성 관리
-
IA-32 와 같은 경우, 캐시 일관성 관리를 MESI 프로토콜을 이용해서 수행을 한다.
-
MESI Protocol 은 Modified, Exclusive, Shared, Invalid 이렇게 4가지의 상태를 가지고있는 finite state machine 이라고 설명할 수 있다. 각 상태별 동작은 아래의 표와 같이 정의 할 수 있다.
캐시라인 상태 |
Modified(M) |
Exclusive(E) |
Shared(S) |
Invalid(I) |
캐시라인이 유효한가? |
유효 |
유효 |
유효 |
오염 |
실제 메모리 내용 차이 |
불일치 |
일치 |
일치 |
- |
다른 코어에 복사본이 존재하는가? |
존재안함 |
존재안함 |
존재가능 |
존재가능 |
캐시라인에 쓰기가 발생한 경우 |
실제 메모리에는 아직 안쓰임 |
실제 메모리에는 아직 안쓰임 |
해당 프로세스의 코어만 유효함 (해당 코어만 ownership을 가짐) |
실제 메모리에 쓰기 내용 반영 |
1-6. 동기화 및 Memory Ordering
-
Memory Ordering은 무엇인가?
-
메모리 오더링(Memory Ordering)은 단어에서 유추할 수 있다시피, CPU가 메모리에 접근하는 순서를 말한다. 보통 컴파일타임에서 명령어를 뒤섞는(Instruction reordering) 경우나, CPU의 파이프라인과 같은곳에서 병렬실행을 하면서 명령어 순서가 바뀌는 경우 이렇게 2가지의 경우가 존재한다. 하지만 이러한 방식으로 실행을 하다가 에러가 발생하는 경우 Out-Of-Order Execution 과 같은 에러/예외가 발생하게 된다.
-
문제가 발생하는 경우
-
아래의 그림에 나온 프로그램을 예시로 들어보도록 하겠습니다.
위 그림에서 보다시피, 특정 프로그램을 실행하는데 3개의 프로세스가 개입을 한다고 가정을 해보자.(구체 적인 예를 들자면 Atomic 연산같은 걸 예로 들 수 있겠습니다., 아니면 DB의 트랜잭션과 같은 경우) 이때, 프로그램을 실행하는데 "Write A,1 -> Write B,1 -> Write C,1" 순으로 무조건 실행순서가 보장 되어야 하는데, 파이프라인과 같은 명령어 레벨의 병렬처리에 의한 최적화나, 실행중인 프로세서가 변경되는 경우와 같이 , 실행순서가 바뀌어 버리는 경우가 있는데, 이때 race condition과 같은 문제가 발생 할 수 있습니다. 이러한 문제를 해결하기 위해 지켜줘야 하는것들을 보통 Memory Ordering 문제라고 부릅니다.
3) IA-32 에서의 Memory Ordering 매커니즘
-
IA-32에서는 위와 같이 특정 영역에 대해 Re-Ordering을 수행하지말라고 명시적으로 지시할 수 있는 lfense, mfense, sfense와 같은 명령어들을 지원한다.
-
lfense 명령어와 같은 경우, 메모리 로드(load)와 관련된 연산들의 순서들을 지키도록 강제하는 명령어이고, sfense와 같은 경우 메모리 저장(store)와 관련된 연산들의 순서들을 지키도록 강제하는 명령어이고, mfense와 같은 경우는 sfense와 lfense 두 가지 경우를 모두 포함하는 명령어이다.
1-7. 메모리 보안정책
-
페이징/세그멘테이션 레벨에서의 보안정책
-
위에서 언급한, 페이지 테이블과 세그먼트 디스크립터의 구조를 다시한번 확인해 보자.
[Segment Descriptor 구조]
[Page Table Entry 구조]
위 구조들을 보면, 세그멘테이션 같은 경우에는, limit 필드를 이용해서 세그먼트의 영역의 크기를 제한하고 있으며, R/W 비트를 통해서 각 세그먼트 별로 읽기/쓰기 권한을 부여할 수 있다. 또한, DPL 필드를 통해서 유저 레벨에서만 접근이 가능한지, 커널레벨에서만 접근이 가능한지 구분이 가능하다. 페이징과 같은 경우에도, R/W bit와 U/S bit를 통해서 읽기/쓰기 권한이나, 유저/커널 레벨에서만 접근 가능한지 각각 구분할 수 있다. 또한, 페이징과 같은 경우 XD bit를 이용해서 특정 페이지에만 코드실행을 할 수있는 권한을 부여할 수 있다.
-
CET
-
2016년에 새로 도입된 기능 으로써, 하드웨어 레벨에서 함수 호출 명령어(Call)나, jmp명령어가 발생할때, 복귀주소, 프레임 포인터 주소 등을 복사해서 일반 사용자가 접근할 수 없는 영역에 저장해놓은 뒤에, 함수 호출이 끝난뒤 복귀주소와, 이전에 복사한 값과 비교해서 같지 않은 경우 예외를 발생시키면서 프로그램을 종료시킨다. 사실 해당기능은 원래 CFI 라는 이름으로 윈도우에 적용된 기술이지만(리눅스도 비슷한 기술이 존재함), 하드웨어 레벨에서 지원해 주지않아서 해당 방어기법들이 계속 우회가 되었기 때문에, CET와 같이 하드웨어 레벨에서 해당 기능을 지원해 줌으로써 ROP와 같은 공격기법들을 거의 원천적으로 차단이 가능해지게 되었다.
1-8. 메모리 관련된 취약점들
-
Uninitialized Padding Attack
-
아래 그림과 같은 구조체가 있다고 가정을 해보자
그리고 아래와 같이 구조체를 초기화했다고 가정을 해보자
위와 같이 초기화 한 경우 해당 변수의 메모리는 다음과 같이 구성된다
이때 저 초기화 되지않은 padding 영역(그림에서의 “????” 부분)에 민감한 데이터가 유출될 수 있는 취약한 부분이 존재한다(실제로 Intel SGX와 같은곳에서 일반적으로 허가되지 않은 접근을 할 수 없지만 이러한 취약점을 이용해서 접근 할 수 있는 취약점이 발생했었다. 지금은 패치가 된것으로 알고 있음)
-
Rowhammer Attack
-
위에서 언급했던 보안 정책들은 사실 물리적 레벨에서 보안을 완벽하게 보장해주지 않습니다. 해당 공격도 그러한 헛점을 이용한 공격 기법입니다. Rowhammer 공격은 램(RAM) 메모리의 특정 영역에 많은 횟수로 반복적으로 접근을 하는 경우 인접한 메모리 셀이 전자기적으로 간섭을 일으켜서 인접한 메모리 셀의 bit가 뒤집히는 버그가 발생하게 됩니다. 해당 취약점을 이용해서 공격자가 메모리를 적절하게 재배치 시킨뒤에 OS의 중요한 자료구조들(R/W/X 권한 비트와 같은것들)의 내용을 변경시켜서 악의적인 행동을 수행 할 수 있습니다.
'보안 > 시스템 해킹' 카테고리의 다른 글
운영체제론 : 가상메모리 내용 간략 정리 (0) | 2020.04.23 |
---|---|
ARM 메모리 관리기법 분석 (1) | 2020.04.23 |
pwnable.kr fd 풀이 (0) | 2020.04.15 |