Music for reading(spotify)

My Little Pony's trixie say 'real programmer use butterfly'

레지스터와 운영 모드

CPU를 모르고는 OS를 개발 할 수 없다고 생각한다. 또한 CPU의 모드나 레지스터가 제공하는 기능을 100% 활용할 수 있다면 강력한 OS를 만드는 데 많은 도움이 된다고 생각한다. 나 뿐만 아니라 대부분의 OS 개발 경험이 있는 사람은 똑같이 생각하지 않을까 싶다.

Registers

General Purpose Registers(GPRs)

CPU를 이용한 범용 프로그래밍 모델 (General-purpose programming model) 에는 범용 레지스터 (General-purpose registers, GPRs) 를 사용하게 되어 있다. 우리가 흔히 사용하는 amd64(x86-x64) CPU는 CISC (Complex Instruction Set Computer) 이며, 따라서 굉장히 많은 양의 마이크로코드를 내재하고 있다. 1

이를 지원하기 위해 많은 양의 GPR이 각각의 역할을 수행하기 위해 존재한다. GPR의 종류를 간략하게 알아보자.

Application Registers and Stack, by Operating Mode 2

먼저 각각의 운영 모드에 따른 GPR들이다. 레거시 모드(32비트 모드 호환모드 포함)에서는 EAX, EBX 등의 레지스터를 사용하는 것을 볼 수 있다. 롱 모드(IA-32e 모드)에서는 RAX, RBX등으로 E가 아닌 R을 사용하는 것을 유의깊게 보면 된다.

그래서 범용 레지스터는 무엇을 하는가 하면, 범용 레지스터는 계산, 메모리 어드레스 지정, 임시 저장 공간 등의 목적으로 사용한다.

운영 모드 앞에 붙는 숫자(16비트, 32비트 등)와 GPR의 크기는 대체로 일치하지만, GPR의 수는 프로세서가 지원하는 운영 모드에 따라 다르다. 예를 들어, 최대 32비트 모드까지만을 지원하는 x86계열(IA-32) 프로세서는 GPR을 8개 (AX, BX, CX, DX, BP, SI, DI, SP), 최대 64비트 모드까지 지원하는 amd64(x86-64, IA-32e)계열은 16개 (Legacy + R8 ~ R15) 가 존재한다.

GPR은 이름에서부터 나타나듯이, 용도가 고정되어 있지 않고 다양한 목적으로 사용할 수 있다. 하지만 특정 명령어는 특정 레지스터와 같이 사용해야만 동작하는 경우가 있다. GPR들에겐 각각 이름이 붙여져 있는데. 이름과 그에 따른 용도는 다음과 같다.

GPR 이름 용도
AX 산술 연산을 수행할 때 누산기로 사용
BX 데이터의 어드레스를 지정할 때 데이터 포인터로 사용
CX 루프 또는 문자열의 카운터로 사용
DX I/O 어드레스를 지칭할 때 사용되며, 산술 연산을 수행할 때 보조 레지스터로 사용
SI 문자열에 관련된 작업을 수행할 때 원본 문자열의 인덱스로 사용
DI 문자열에 관련된 작업을 수행할 때 목적지 문자열의 인덱스로 사용
SP 스택의 포인터로 사용
BP 스택의 데이터에 접근할 때 데이터의 포인터로 사용
R8~R15 롱 모드에서 지원하는 범용 레지스터로, 다양한 용도로 사용 가능

General-Purpose Registers and Segment Registers 2

좀더 자세한 GPR이다. 색칠되어 있는 부분을 잘 보면 운영 모드에 밀접하게 연관되어 있음을 알 수 있다. 색칠이 되어 있지 않은 부분은 16비트, 32비트일 때 활성화 되는 레지스터이며, 연한 색상의 부분은 64비트일 때 활성화 되는 부분이다. 앞에 붙는 접두사 r 은 롱 모드에서 부르는 명칭이며, (RAX, RBX…) 32비트는 접두사 e가 붙고, (EAX, EBX…) 16비트는 접두사가 없다.

16비트 레지스터는 다시 상위 8비트와 하위 8비트로 구분되는데, 상위 8비트는 끝자리가 H로 변경되며, (AH, BH…) 하위 8비트는 끝자리가 L로 변경된다. ( AL, BL…) 3

즉, 운영 모드에 따라 레지스터들의 가용 범위가 달라지며, 사용하는 레지스터는 같은 레지스터이다. 이는 모드에 맞춰 레지스터의 갯수를 늘리지 않아도 된다는 뜻이며, amd64계열의 프로세서는 총 16개의 범용 레지스터를 가지고 리얼 모드, 보호 모드, 롱 모드 등에 따라 레지스터 사용 갯수와 비트 크기를 정한다.

운영 모드에 따른 범용 레지스터의 크기와 이름

좀 더 자세하게 구분한 그림이다. 각 모드에서 접근할 수 잇는 레지스터의 크기를 색깔로 나타내었다.

운영 모드에 따라 접두사를 결합하는 방법에는 일정한 규칙이 있다. 사용한 접두사에 따라 명령어 (Instruction) 가 처리하는 오퍼랜드 (Operand) 4 나 주소의 크기가 달라진다.

접두사

여기서 간단히 알아 볼 접두사는 총 3가지다.

  1. REX 접두사
  2. Operand 크기 접두사
  3. Address 크기 접두사

REX 접두사부터 알아보자.

REX prefix

REX 접두사는 64비트 모드(롱 모드)에서만 다음과 같은 용도로 사용된다.

REX 접두사가 모든 명령셋에 필요한 건 아니다. 만약 롱 모드가 아닌 경우에 REX 접두사가 입력되어 REX접두사가 의미를 가지지 않는 경우, REX접두사는 무시된다.

만약 64비트 모드(롱 모드)에서 명령어가 두 그룹으로 이루어져 있고, 그 명령어의 Oprand가 디폴트 사이즈(32bit)인 경우에는 REX 접두사가 없어도 64비트로 인식한다. 그룹으로 인식하는 조건은 다음과 같다. 5

Operand 크기 접두사

64비트 모드에서, Operand의 기본 사이즈는 32비트이다. 소프트웨어는 레거시 Operand 크기 접두사 (Legacy Operand-size prefix) 를 이용해서 32비트와 16비트의 Operand 사이즈를 토글할 수 있다. REX prefix는 레거시 Operand 크기 접두사보다 우선한다.

Address 크기 접두사

64비트 모드에서, Address의 기본 사이즈는 64비트이다. 유효한 Address의 크기를 레거시 Address 크기 접두사 (Legacy Address-size prefix) 를 이용하여 32비트로 오버라이딩 가능하다. 64비트 모드에서는 16비트 오버라이딩이 불가능하고, 롱 모드의 서브모드인 호환 모드와 레거시 모드(리얼 모드, 보호 모드)에서는 16비트 오버라이딩이 가능하다.

RIP Register

RIP 레지스터는 현재 수행 중인 명령어의 어드레스를 가리키는 레지스터이다. RIP는 이름에서 나타나듯 (R이 붙었으니까) 64비트의 크기를 가진다. 64비트 모드에서는 새로운 주소 할당법을 사용하는데, 이를 RIP-상대 주소 할당 (RIP-Relative Addressing) 이라고 부른다.

RIP-Relative Addressing

RIP-Relative Addressing은 64비트 모드부터 삽입되었으며, 다음 명령어 분기를 나타내는 데에 RIP을 중심으로 상위 2G, 하위 2G, 총 4G(2^64)크기만큼을 이동할 수 있게 하는 어드레싱 방법이다.

Direct Control Transfer

하지만 여기서 문제가 발생하는데, 4G를 넘는 범위의 영역에 있는 메모리를 제어하기 위해서는 여러 번의 점프 (jump) 코드를 넣어 상대 RIP-Relative하게 가야 하는 문제가 생긴다. 이를 해결하기 위해 직접 제어 전송 (Direct Control Transfer) 라는 방법이 나타났다. 이는 소프트웨어가 콜 게이트를 사용하지 않고 RIP-Relative의 거리를 넘는 먼 (far) CALL혹은 JMP를 사용할 때 발생한다. Direct Control Transfer로써 생기는 결과인 접근 권한 체크, 접근 허용 타입 등은 RIP가 이동한 영역의 코드의 세그먼트에서 승인하거나 거절한다.

Segment Register

세그먼트 레지스터 (Segment Register) 는 16비트 레지스터로, 어드레스 영역을 다양한 크기로 구분하는 역할을 한다. 기본적인 역할은 어드레스 영역을 구분하여 각 영역을 침범하지 않게 하는 것이지만, 모드에 따라 조금씩 차이가 존재한다.

세그먼트 레지스터의 종류는 다음과 같다.

Segment Registers

각각의 세그먼트 레지스터의 역할은 다음과 같다.

세그먼트 레지스터 이름 설명
CS -코드 영역을 가리키는 레지스터
-데이터 이동 명령으로 값을 변경할 수 없으며, 점프 명령이나 인터럽트 관련 명령으로 변경 가능
DS, ES, FS, GS -데이터 영역을 가리키는 레지스터
-데이터 이동 명령으로 값을 변경할 수 있음.
-DS 레지스터는 데이터 영역에 접근할 때 암시적으로 사용.
-ES 레지스터는 문자열과 관련된 작업을 처리할 때 암시적으로 사용.
-데이터 영역에 접근하면서 DS 레지스터 이외의 세그먼트 레지스터를 사용하려면 세그먼트 레지스터 접두사 사용.
SS -스택 영역을 가리키는 레지스터.
-데이터 이동 명령으로 값을 변경할 수 있음.
-스택 관련 레지스터(SP,BP)를 통해 스택에 접근할 때 암시적으로 사용.

세그먼트 레지스터는 주소 공간을 목적에 따라 구분하는 것이며, 이는 메모리 관리 기법과 깊은 연관이 있다. 이는 추후 다룰 예정이다.

컨트롤 레지스터

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

컨트롤 레지스터 이름 설명
CR0 -운영 모드를 제어하는 레지스터
-리얼 모드에서 보호 모드로 전환하는 역할과 캐시, 페이징 기능 등을 활성화.
CR1 -프로세서에 의해 예약된 레지스터
CR2 -페이지 폴트 발생 시 페이지 폴트가 발생한 선형 주소가 저장되는 레지스터.
-페이징 기법을 활성화한 후에는 페이지 폴트 발생 시만 유효한 값을 가짐.
CR3 -페이지 디렉터리의 물리 주소와 페이지 캐시에 관련된 기능을 설정하는 레지스터.
CR4 -프로세서에 지원하는 각종 확장 기능을 제어하는 레지스터.
-페이지 크기 확장이나 메모리 영역 확장 등의 기능을 활성화.
CR8 -태스크 우선순위 레지스터의 값을 제어하는 레지스터.
-프로세스 외부에서 발생하는 인터럽트를 걸러주는 필터 역할.
-롱 모드에서만 접근 가능.

마치며

오늘은 각종 레지스터에 대해 알아보았다. 각 레지스터를 제어하는 방법과 더 자세한 내용은 다음에 설명하도록 하고. 오늘은 이만 마치도록 하겠다.

각주 및 레퍼런스

  1. RISC vs CISC
     

  2. AMD64 Architecture Programmer’s Manual, Volume 1: Application Programming
      2

  3. High, Low로 생각하면 쉽다.
     

  4. 피연산자, 명령어 세트 (Instruction set) 가 구성될 때, 옵코드(opcode)를 읽고 피연산자를 옵코드에 따라 연산한다. 

  5. AMD64 Architecture Programmer’s Manual, Volume 2: System Programming  2