'5kyc1ad's OS'에 해당되는 글 1건



[한빛미디어 출판, 64비트 멀티코어 OS 원리와 구조]


 이전에 이 책을 이용하여 OS 개발을 해보려고 생각했다가 이미 cygwin이나 binutils등의 버전이 달라져서 제대로 개발 환경을 구현하지 못해 버그가 무척 많이 발생했기 때문에 반쯤 포기하고 있었습니다. 그러던 중 이 책의 저자이신 한승훈님께서 개발 환경이 모두 구축된 VM을 세팅하여 공유하고 계셨다는 사실을 알게 되었습니다.



[오오 그저 빛 오오]


 해당 원글의 주소는 여기입니다. http://jsandroidapp.cafe24.com/xe/8920


 압축을 푼 이후 VirtualBox를 이용하여 실행 가능하며, 기본적으로 1-Core RAM 2GB로 설정되어 있어서 좀 더 원활한 개발 환경을 위해 저는 2-Core에 RAM 4GB로 늘려주었습니다. OS는 Windows XP였습니다.


 마침 책도 회사에서 생일에 지원받은 상품권을 이용해 두권 모두 사두었기 때문에 차근차근 진행해 나가면서 정리하고 진행상황을 작성해보려고 합니다. 블로그에 글을 남기는것도 이렇게 공개된 곳에 작성하면 나태해지지 않고 계속할 수 있을 것이라고 생각했기 때문입니다. :)


 어쨌든 이 VM 이미지 하나로 모든 개발환경의 준비는 끝났지만, 위 링크의 글에도 나와있다시피 QEMU의 경우 가상머신 내에서 동작시키면 상당히 속도가 느리고 키보드/마우스 입력이 제대로 되지 않는 문제가 있으니 메인 OS에 설치해두는게 좋겠습니다. QEMU는 여기에서 다운로드 및 설치할 수 있습니다. 이제 진행한 내용들을 정리하겠습니다.




 [운영 모드]


 x86-64 프로세서에는 크게 다섯 가지 운영 모드가 있고, 각각은 다음과 같습니다.


- 리얼 모드 : 최대 1MB의 주소 공간을 지원하며 16비트 모드로 동작함. 전원이 켜지거나 리셋되면 항상 이 모드로 진입하며, BIOS의 여러 기능들을 사용할 수 있음.

- 보호 모드 : 최대 4GB의 주소 공간을 지원하며 32비트로 동작함. 세그먼트, 페이징, 보호, 멀티태스킹 등의 기능을 하드웨어적으로 제공함. 

- IA-32e 모드 : 최대 16EB의 주소 공간을 지원하며 32비트 호환 모드, 64비트 모드의 두 가지 서브모드로 동작함.

- 시스템 관리 모드 : 전원 관리, 하드웨어 제어 등 특수 기능을 제공함.

- 가상 8086 모드 : 보호 모드 내부에서 가상의 환경을 설정하여 리얼 모드처럼 동작함.


 여기서 리얼 모드 및 보호 모드에 관해서는 '성공과 실패를 결정하는 1%의 Windows 구조와 원리' 책을 읽으면서 공부하여 정리했던 내용이 있었습니다. GDT와 LDT 관련하여 의문점이 남은 상태로 찝찝하게 글을 마무리했었는데 진행하면서 이 부분에 대해서도 이해가 가능하면 좋겠네요.


[운영 모드 전환 다이어그램]


 각각의 운영 모드는 위와 같은 과정을 통해 다른 모드로 전환될 수 있습니다.


 [레지스터]


운영 모드에 따라 사용하는 레지스터의 종류도 달라지는데, 크게 16비트 레지스터, 32비트 레지스터, 64비트 레지스터로 나타낼 수 있습니다. 다만 GDTR(Global Descriptor Table Register)이나 IDTR(Interrupt Descriptor Table Register) 등 특수한 목적을 지닌 레지스터는 다른 크기를 갖습니다. 범용 레지스터의 용도와 크기, 명칭 등은 이미 대체로 알고 있으므로 다른 블로그의 링크로 대체합니다.


※ PC에 연결된 디바이스를 제어할 때 I/O 어드레스를 지정할 수 있는 어드레스는 DX 레지스터 뿐입니다. 이처럼 범용 레지스터가 특수한 용도로 사용되기도 합니다.


 특이한 점은 IA-32e 시스템의 64비트 서브모드의 경우 64비트 모드이므로 어드레스의 크기가 64비트라 Insturction Pointer인 RIP 레지스터는 64비트 크기이지만, 기본 오퍼랜드 크기는 32비트로 고정이기 때문에 RIP 레지스터에 대한 상대 어드레스로의 접근은 RIP±2GB밖에 되지 않고 그를 초과하는 범위에 접근할 때는 무조건 jmp 명령어를 사용해야만 접근이 가능하다는 점입니다. 32비트 오퍼랜드로는 최대 4GB까지 표현이 가능하므로 음수/양수로 각각 2GB씩밖에 상대 참조가 가능한 것입니다.


 세그먼트 레지스터(Segment Register)는 16비트 레지스터로 어드레스 영역을 다양한 크기로 구분하는 데에 사용됩니다. 운영 모드에 따라 역할이 조금씩 달라지는데, 리얼 모드에서는 단순히 고정된 크기의 영역을 지정하기만 하지만 보호 모드와 IA-32e 모드에서는 접근 권한, 세그먼트의 시작 주소와 크기 등을 지정하는데에 사용되기도 합니다. 각 세그먼트 레지스터의 역할은 다음과 같습니다.


- CS : 코드 영역을 가리키는 레지스터로 점프 명령이나 인터럽트 관련 명령으로만 값을 변경 가능

- DS : 데이터 영역에 접근할 때 암시적으로 사용됨

- ES : 문자열과 관련된 작업을 처리할 때 암시적으로 사용됨

- FS/GS : DS/ES 레지스터와 함께 데이터 영역을 가리키는 레지스터로, 데이터 영역에 접근할 때 DS 레지스터 이외의 세그먼트 레지스터를 사용할 시 각각의 세그먼트 레지스터 접두사를 사용해야 함

- SS : 스택 영역을 가리키는 레지스터로, SP/BP 등 스택 관련 레지스터를 통해 스택에 접근할 때 암시적으로 사용됨


 컨트롤 레지스터(Control Register)는 운영 모드를 변경하고 현재 운영 중인 모드의 특정 기능을 제어하는 레지스터입니다. 각 컨트롤 레지스터의 역할은 다음과 같습니다.


- CR0 : 운영 모드를 제어하는 레지스터. 리얼 모드에서 보호 모드로 전환하는 역할 및 캐시, 페이징 기능 등을 활성화시키는 역할을 함

- CR1 : 프로세서에 의해 예약된 레지스터

- CR2 : 페이지 폴트 발생 시 페이지 폴트가 발생한 선형 주소가 저장되는 레지스터

- CR3 : 페이지 디렉터리의 물리 주소와 페이지 캐시에 관련된 기능을 설정하는 레지스터

- CR4 : 페이지 크기 확장, 메모리 영역 확장 등의 기능을 활성화하고 프로세서에서 지원하는 각종 확장 기능을 제어하는 레지스터

- CR8 : 태스크 우선순위 레지스터의 값을 제어하는 레지스터. 프로세스 외부에서 발생하는 인터럽트를 걸러주는 필터 역할을 함. IA-32e 모드에서만 접근 가능.



 [메모리 관리 기법]

- 세그먼테이션(Segmentation) : 세그먼트 레지스터를 이용하여 원하는 크기만큼 메모리를 나누어 사용

- 페이징(Paging) : CR3 레지스터에 페이지 디렉터리라는 자료구조의 물리 주소를 설정하여 일정 크기만큼 메모리를 나누어 사용


 리얼 모드에서의 메모리 관리 기법

 위에 작성한 바와 같이 리얼 모드는 최대 1MB까지 주소 공간을 사용합니다. 이 모드에서는 세그먼테이션만 지원하며 크기는 64K로 고정입니다. 메모리에 접근할 때 사용하는 범용 레지스터와 세그먼트 레지스터의 크기는 모두 16비트인데 주소 공간을 1MB까지 지원하는 이유는, 세그먼트 레지스터의 값에 0x10을 곱한 값에 범용 레지스터의 값을 더하여 접근하기 때문입니다. 

 예를 들어, FS가 0x1000이고 범용 레지스터가 0x1234라고 한다면 FS의 값인 0x1000에 0x10을 곱하여 0x10000에 범용 레지스터의 값인 0x1234를 더합니다. 최종적으로 0x11234 주소에 접근하게 됩니다.  16비트의 최댓값은 (2^16)-1이고 여기에 0x10을 곱한 후 범용 레지스터의 값까지 더하면 정확히 1MB 크기 내의 주소에 접근이 가능하게 되는 것입니다.


 보호 모드에서의 메모리 관리 기법

- 디스크립터 : 메모리 영역의 정보를 저장하는 자료구조. 세그먼트에 대한 정보를 나타내는 디스크립터를 '세그먼트 디스크립터' 라고 함.

[세그먼트 디스크립터 (출처 : https://en.wikipedia.org/wiki/Segment_descriptor)]


 보호 모드에서는 세그먼테이션과 페이징 기법을 모두 사용합니다. 다만, 여기에서는 세그먼트 레지스터가 직접적으로 메모리 주소를 가리키지 않고 세그먼트 디스크립터 자료구조의 Offset을 가리킵니다. 이를 따라서 레지스터의 명칭도 세그먼트 레지스터에서 세그먼트 셀렉터로 변경되었습니다. 세그먼트 디스크립터들은 메모리상에 위치하는 자료구조의 일종으로 GDT(Global Descriptor Table)이라고 불리는 곳에 모여 있습니다. GDT는 연속된 디스크립터의 집합으로, 최대 8192개의 디스크립터를 포함할 수 있는 테이블 형태의 자료구조입니다. 프로세서는 원하는 디스크립터를 참조하기 위해 GDT의 시작 주소와 크기를 저장하는 구조체의 주소를 담는 GDTR 레지스터를 이용합니다. 선형 주소는 리얼 모드에서와 같은 방식으로 세그먼트 디스크립터의 베이스 어드레스에 범용 레지스터의 값을 더하여 구하지만, 범용 레지스터의 값이 세그먼트 디스크립터에 정의되어 있는 세그먼트의 크기를 넘어설 경우에는 예외를 발생시켜 오류가 발생했음을 알립니다.


 위의 세그먼테이션을 통해 찾아낸 선형 주소의 경우 페이징 기법이 사용되지 않는다면 리얼 모드에서와 같이 물리 메모리와 1대 1로 매칭됩니다. 페이징은 물리 메모리를 페이지(Page)라고 불리는 일정한 크기로 나누고, 선형 주소화 실제 물리 주소를 나눠 놓은 페이지로 연결하는 방식을 말합니다. 페이징을 이용하면 실제 물리 메모리 크기보다 더 큰 영역의 선형 주소도 물리 페이지를 통해 연결하기만 하면 사용 가능하므로 주소 공간을 훨씬 더 넓게 사용할 수 있는 장점이 있습니다(* 가상 메모리의 원리). 또한 프로세스끼리 공유하는 메모리도 한 물리 페이지를 여러 선형 주소에 연결하는 것으로 간단하게 해결이 가능합니다.


[페이징 사용 시 선형 주소] 


 4KB 크기의 페이지로 나누는 3단계 페이징을 사용할 경우 선형 주소는 디렉터리/테이블/오프셋으로 이루어지며, 물리 메모리를 4KB 페이지로 나누어서 관리합니다. 4바이트인 선형 주소 내에서 0~11비트는 4KB 크기인 페이지 내부의 물리 주소를 나타내기 위해서 사용되며, 나머지 디렉터리와 테이블 값은 페이지 디렉터리와 페이지 테이블에 있는 엔트리의 Offset을 가리킵니다. 페이지 디렉터리의 시작 주소는 위에서 언급했던 CR3 컨트롤 레지스터에 저장되어 있습니다.



[페이지 디렉터리 엔트리 (출처 : https://wiki.osdev.org/Paging)]



[페이지 테이블 엔트리 (출처 : https://wiki.osdev.org/Paging)



 실제 메모리 주소를 찾는 과정은 다음과 같이 이루어집니다.


[페이징 주소 참조 (출처 : https://wiki.osdev.org/Paging)]


1. CR3 레지스터를 참조하여 페이지 디렉터리의 시작 주소를 찾습니다.

2. 선형 주소의 페이지 디렉터리 인덱스를 이용해 원하는 페이지 디렉터리를 찾고, 내부의 디렉터리 엔트리 값을 찾습니다.

3. 디렉터리 엔트리를 이용해 페이지 테이블의 시작 주소를 찾습니다.

4. 선형 주소의 페이지 엔트리 인덱스를 이용해 원하는 페이지 테이블을 찾고, 내부의 테이블 엔트리 값을 찾습니다.

5. 테이블 엔트리를 이용해 4KB 크기 페이지의 물리 어드레스를 구하고, 그곳에 선형 주소의 오프셋 값을 더해 실제 물리 주소로 변환합니다.


 IA-32e 모드의 메모리 관리

 IA-32e 모드는 32비트 호환 모드 및 64비트 모드로 동작하는데, 32비트 호환 모드의 경우는 보호 모드와 똑같이 작동합니다. 64비트 모드로 동작할 경우 말 그대로 64비트이므로 사용 가능한 최대 어드레스는 2^64인 16EB나 되는 크기의 주소까지 사용이 가능합니다. 64비트 모드로 동작한다고 하더라도 보호 모드와는 큰 차이는 없고, 주소 공간이 확장되면서 약간의 차이가 생겼을 뿐입니다.  


 첫 번째 차이는 세그먼트 디스크립터에 설정된 베이스 어드레스와 그 크기에 관계 없이 모든 세그먼트가 베이스 어드레스는 0, 크기는 64비트 메모리 전체로 설정된다는 점입니다. 즉, 사실상 64비트 모드에서는 세그먼테이션 기법을 사용하지 않게 되었다고 봐도 무방합니다. 따라서 기존에 개발했던 32비트 OS가 세그먼테이션을 이용해 커널 및 유저 영역을 구분하고 있었다면 이는 IA-32e 모드에서는 유효하지 않게 되었으므로 페이징이나 다른 방식을 고려해야 합니다.

 두 번째 차이는 호환 모드와 64비트 모드의 구분을 위해 코드 세그먼트 디스크립터에 L 필드라는 값이 추가된 것입니다(위의 세그먼트 디스크립터 그림 참조). 이 필드를 1로 설정하면 64비트 모드로, 0으로 설정하면 32비트 호환 모드로 동작합니다. 


 IA-32e 모드의 페이징은 보호 모드와는 달리 주소 공간이 64비트로 늘어났기 때문에 물리 주소 확장(PAE: Physical Address Extension)이 기본으로 활성화됩니다. 또한 기존의 4KB 크기의 3단계 페이징이 5단계로 늘어납니다. 이를 위해 새로 페이지 맵 레벨 4 테이블(PML4)과 페이지 디렉터리 포인터 테이블(PDPT)이 새로 생겼고, 이는 보호 모드에서 설명했던 다른 테이블과 같은 방식으로 참조가 이루어집니다.


[IA-32e 모드의 페이징 (출처 : http://egloos.zum.com/miooim/v/57175)]


  선형 주소의 크기가 64비트로 늘어난 만큼 늘어난 공간에 PML4와 디렉터리 포인터 값을 추가로 넣었습니다. 기존과 마찬가지로 CR3→PML4->디렉터리 포인터 엔트리→페이지 디렉터리 엔트리→페이지 테이블 엔트리→물리 주소 순서대로 참조를 진행합니다. 여기서 선형 주소의 48~63번째 비트가 부호 확장으로 채워진 것을 볼 수 있는데, 그렇다고 2^48의 주소를 사용할 수 있는것이 아니라 프로세서에 따라 최대 2^40까지의 물리 메모리만을 사용 가능합니다.


[IA-32e 모드의 페이지 엔트리 (출처 http://egloos.zum.com/miooim/v/57175)]


 64비트로 늘어난 어드레스로 인해 IA-32e 모드의 페이지 엔트리의 크기는 8바이트로 늘어났습니다. 하위 4바이트는 보호 모드와 같은 구조이며, 상위 4바이트에는 베이스 어드레스와 예약된 영역, 임의 사용 가능한 영역, EXB라는 값으로 구성됩니다. 이중 EXB 필드는 해당 페이지에서 명령어가 실행되는 것을 막을 수 있는 필드로, 이 값을 이용하여 데이터 영역에서 명령어가 실행되는 것을 막을 수 있어 더욱 안전한 OS를 만들 수 있습니다. (Windows의 DEP나 Linux 계열의 NX와 같은 메모리 권한 계열 미티게이션 비슷한 모양)



여기까지 '64비트 멀티코어 OS 원리와 구조' 3장의 내용이었습니다. 다행히도 이전에 메모리 세그먼테이션이나 페이징에 대해 약간 공부한 적이 있어서 이해하기 그렇게 어렵지는 않았습니다. 틈나는대로 한번씩 읽으면서 자신의 지식으로 만들 필요가 있어 보입니다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,