x86
1. 개요
x86은 인텔에서 1978년에 개발한 '''인텔 8086'''(80386...80486...80586....)에 적용된 아키텍쳐이자, 그 호환 프로세서 또는 후속작을 이르는 말이다. 1978년에 출시되어 40년이 지난 굉장히 오래된 아키텍쳐이지만, 이후에 출시된 프로세서들은 8086의 명령어 세트를 기반으로 하여 확장된 것이다. 이러한 이유로 x32라는 용어로 32비트 cpu를 표시하지 않고 x86 이라고 표기한다.
'x64'와 대비하여 '32비트 아키텍처'의 관습적 명칭으로도 쓰인다. 일반인이 설명서에서 본 것은 보통 이 의미. IBM PC 호환기종에 더 자세한 설명이 있다.
2. 역사
1970년 출시된 Computer Terminal Corporation(CTC)사의 Datapoint 2200이 기원이다. 이는 인텔 8080을 비롯하여 인텔의 명령어 세트와 레지스터 구조에 많은 영향을 주었다.
1978년 출시된 인텔 8086으로 x86의 역사가 시작된다. 당시 8086은 너무 비쌌기에 원가절감 모델로 8088을 출시했는데 이는 IBM의 개인용 컴퓨터의 프로세서로 사용되면서 대박을 치게 된다. 그 후 80186/80188로 확장되었다.
1982년 인텔 80286이 개발된다. 실제 모드와 보호 모드가 추가되며 실제 모드에서는 이전 프로세서와 같은 환경을 제공했으나 보호 모드에서는 호환성을 일부 포기한 대신 메모리 보호와 24비트의 넓은 주소 공간을 지원했다. 명령어 세트 자체는 80186에 메모리 보호와 멀티태스킹을 위한 명령어들을 추가한 것이 전부지만 내부 구조가 개선되어 같은 클럭이어도 두 배는 빠르게 작동했다.
1985년 인텔 80386이 개발되었고 32비트 시대의 문이 열렸다. 명령어 세트와 내부 구조가 32비트로 확장되었고 더 많은 명령어를 제공했다. 메모리도 32비트로 확장되었다.
1989년 인텔 80486이 개발되었다. 더 많은 명령어와 부동소수점 연산 장치의 통합이 이루어졌다.
1999년 경쟁사인 AMD에서 x86의 64비트 확장인 AMD64를 발표한다. 2003년 AMD64를 지원하는 옵테론이 출시되고 이어서 2004년에 인텔이 프레스캇으로 EM64T를 지원하면서 AMD64는 사실상 x86의 표준이 된다.
2.1. 16비트: IA-16(x86-16)
레지스터 목록
- AX (Accmulator)
- AH
- AL
- CX (Count)
- CH
- CL
- DX (Data)
- DH
- DL
- BX (Base)
- BH
- BL
- SP (Stack Pointer)
- BP (Base Pointer)
- SI (Source Index)
- DI (Destination Index)
- ES (Extra Segment)
- CS (Code Segment)
- SS (Stack Segment)
- DS (Data Segment)
- IP (Instruction Pointer)
- FLAG (x x x x [O] [D] [I] [T] | [S] [Z] x [A] x [P] x [C] )
주소용 인덱스 레지스터로 SI와 DI 두 개의 16비트 레지스터를 가지고 있다. 범용 레지스터 보다는 특수 목적 레지스터에 가깝다. 보통 주소를 담고 있으며 문자열 연산 명령어에 사용된다. 스택 포인터로 SP와 BP를 가지고 있다. 완전한 특수 목적 레지스터로 사용된다. 현재 스택의 꼭대기와 스택 프레임을 담고 있기 때문에 일반적인 용도로(계산 등) 사용할 수는 있지만 설계 목적 상 실제 사용은 하지 않는다. 함수에서 지역 변수 공간을 만들기 위해 스택 포인터의 값을 증가 또는 감소시키거나 베이스 포인터는 이전의 스택 프레임의 위치를 기억하기 위해 PUSH, POP 하기는 한다.
20비트의 주소 지정을 위한 ES, CS, SS, DS 4개의 16비트 세그먼트 레지스터를 가지고 있다. 완전한 특수 목적 레지스터이며 연산은 물론이고 이동조차 자유롭지 못한다. 주소 지정에 대해서는 주소 지정 문단을 참고
프로세서의 상태나 연산 결과의 상태를 기록하기 위해 플래그 레지스터를 가지고 있다. 하위 8비트는 8080에서 사용되는 레지스터와 동일하며 상위 8비트로 새로 확장되었다.
x86은 20비트의 주소 범위를 지원하며 최대 1 MB의 메모리에 접근할 수 있다. 하지만 x86-16은 레지스터의 최대 크기가 16비트 이므로 세그먼트 레지스터를 이용하여 16비트보다 많은 메모리에 접근할 수 있도록 했다. 이를 세그먼테이션이라고 하며 작동 원리는 다음과 같다.
먼저 세그먼트 레지스터를 16으로 곱하거나 왼쪽으로 4비트 시프팅한다. 즉 16진수 기준으로 한자리수가 올라가며 0x0b000 는 0xb0000이 된다. 이 값에 레지스터 또는 포인터의 값을 더한다. 만약 명령어 포인터의 값이 0x13ff라면 0xb13ff다. 이 주소값을 이용해 메모리에 접근한다.세그먼테이션은 콜 스택의 데이터 입출력을 깔끔하게 했다는 장점이 있다. 스택은 현재 프로세서의 워드 단위를 따라야 하는데 이는 스택이라는 공간은 항상 정렬되어 있기 때문이다. 지역 변수는 스택에 할당되는데 1바이트의 변수를 선언하더라도 스택 포인터는 항상 2바이트(32비트 시스템에서는 4바이트) 단위로 증가하거나 감소한다. 만약 레지스터가 16비트가 아니고 20비트였다면 16비트 단위를 벗어나기 때문에 사용이 힘들었을 것이다.
하지만 이는 프로그래밍을 어렵게 했는데 포인터 관리가 힘들었기 때문이다. 16비트로는 20비트에 주소에 접근할 수 없으니 현재 16비트 주소로 바로 접근이 안 되는 코드나 데이터는 세그먼트 레지스터 값을 변경해야 했다. 바로 이 때문에 Near와 Far라는 구분이 생기게 된다.
2.2. 32비트: IA-32(x86-32)
일반적으로 말하는 32비트 PC 아키텍처로, i386이라고도 부른다.
인텔 80386 및 호환 프로세서부터 지원하며, 인텔 넷버스트 마이크로아키텍처 및 AMD K7 마이크로아키텍처를 마지막으로 더 이상 사용되지 않는다. 이후 나오는 아키텍처부터는 후술할 AMD64로 넘어가게 된다.
레지스터는 32비트로 확장되었고 앞에 'E'가 붙는다. 32비트가 기본 연산 단위가 되었고 32비트의 주소 범위를 지원한다.(16비트 단위로 사용할려면 특수한 접두사를 이용해야 한다.)
x86의 역사에서 가장 중요한 버전 중 하나이며 후에 64비트까지 영향을 끼치게 된다.
2.3. 64비트: AMD64(x86-64)
3. 문제점
3.1. 중구난방으로 확장된 명령어 세트
8080의 구조를 최대한 옮겨오면서도 16비트 명령어를 추가하려다 보니 8086 때부터도 복잡한 명령어가 많았다. 이런 명령어들은 고속화에 악영향을 준다. AX(AL)을 16비트 상수값 포인터가 가리키는 메모리 주소로 쓰거나 읽는 명령어가 있었다. 또 비슷한 기능을 하는 ADD, SUB 등 9개의 명령어가 있다. 이는 다른 명령어를 사용하는 것보다 고작 1바이트만 줄였을 뿐이다. AH 레지스터는 이용하지 못한다. 심지어 반복문을 처리하는 명령어(LOOP, LOOPZ, LOOPNZ)도 있을 정도였다. 완전히 같은 기능을 하지만 다른 명령어(SHL과 SAL)도 있다.
가장 쓸데없던 명령어는 PUSH, POP, INC, DEC 16비트 레지스터 명령어로 다른 명령어와 고작 1바이트 차이였을 뿐 하는 기능은 완전히 같았다. 얼마나 쓸모없었는지 INC는 64비트 모드에서는 버려졌다. 왜 문제되냐 하면 확장성에 문제가 된다. 단독으로 32개의 추가적인 명령어 확장을 다 잡아먹는다. 이제는 남은 공간이 얼마 없어 추가적인 명령어 확장은 POP CS를 이용해서 확장한다. 0x0f
1978년 당시에는 전반적인 CPU 성능과 메모리 크기가 열악했기 때문에 때문에 명령어 크기의 1바이트 차이가 큰 영향을 주었을지도 모른다. 하지만 64비트 시스템이 보편화된 오늘날에는 더 이상 1바이트의 차이가 성능을 결정짓지 않는다.
3.2. 부족한 레지스터
자유롭게 사용할 수 있는 레지스터가 4개밖에 없다. 이를 극복하기 위해 레지스터와 메모리 간의 연산을 지원해서 부족한 공간을 커버할 수 있을 것 같지만 이는 많은 메모리 접근을 필요로 하기 때문에 많이 느려진다.
지금은 캐시 메모리 도입 등으로 속도를 개선했지만, 아무리 캐시 메모리라도 레지스터에서 바로 연산하는 것 보다는 조금 느리다.
AMD64에서는 이걸 염두에 뒀는지 범용 레지스터 수를 16개로 늘렸다.
4. 기타
- 인텔이 x86 아키텍처를 대체하려고 했던 모든 노력은 실패했다고 한다. 첫 번째 노력은 iAPX432로 인텔 80286의 4분의 1에 불과한 성능으로 사장되었다. 두 번째 노력은 32비트의 황혼기에 차세대 64비트 아키텍처인 IA-64(아이태니엄)으로 단절적인 이행을 하려고 했지만 AMD에서 AMD64를 만들어내는 바람에 탄탄한 기둥을 갖추지 못하고 지금은 특수 목적 서버용으로만 쓰이는 듯 하고 그마저도 잘 쓰이지 않는다. 사실 지금까지 x86에 덤볐던 CPU들이 골로 갔던 결정적인 이유는 프로그램이 없어서였다. IA-64는 처음 예상과 달리 VLIW 아키텍처의 한계로 개발도 복잡했을 뿐더러 결정적으로 x86과의 호환성을 에뮬레이션으로 확보하려다가 시궁창스러운 성능이 나온 덕에 결국 골로 갔다.
- 부동소수점 연산 명령인 x87 명령어 셋도 있다. 하지만 명령어 셋이라고 하기도 뭐한 게 그냥 소수점 연산밖에 없다. 이것 만으로는 프로그램을 못 짠다는 뜻. 이는 SSE 명령어로 대체되었으며, 현재는 AVX로 대체되었다. 다만 소수점 연산을 정말 많이 해야 한다면 CUDA, OpenCL 같은 GPGPU가 훨씬 효율이 좋다. AMD64에서도 이 명령어를 하위 호환용으로 유지만 하고, 확장은 하지 않고 x86과 마찬가지로 SSE, AVX 명령어로 대체되었다.
왜 이런 명령어 셋이 따로 있는가 하면 초기의 x86(8086~80386)에는 부동소수점 연산 유닛(FPU)이 없었고 정수형 ALU만 달려있었다. 필요시 별도로 부동소수점 연산 보조프로세서인 x87을 설치하도록 되어 있는 설계였는데, 이런 설계로 만든 이유는 당시엔 FPU의 하드웨어 비용이 꽤 비싼데 비해 빠른 부동소수점 연산 기능은 과학기술 및 그래픽 쪽 용도가 아니면 그다지 필요가 없었기 때문에[1] x86 CPU의 단가를 낮추기 위해서 별도로 뺀 것이다. FPU가 없어도 소프트웨어적으로 부동소수점 연산은 가능했으므로 크게 불편한 점은 없었다. 다만 부동소수점 연산 속도가 느리고 정밀도가 떨어졌을 뿐인데 어차피 자주 쓰는 기능도 아니라서 당시에도 대부분의 일반사용자는 x87 보조프로세서 장착 없이도 문제없이 잘만 사용했다.[2] 심지어 대부분의 사용자가 x87이라는 것의 존재를 모를 정도. 80286이면 80287, 80386이면 80387로 짝이 있었는데, 80386+80287처럼 하위 버전의 x87 보조프로세서를 사용하는 것도 가능했다. 참고로 FPU가 x86 CPU 내부로 들어온 것은 80486DX부터.[3]
5. 관련 문서
[O] Overflow Flag, 오버플로 플래그. 연산 결과가 연산 범위를 초과하였을 때 켜지는 플래그이다. 음수 플래그와는 다르다. 이 플래그는 덧셈을 할 때 연산 전의 결과가 양수고 연산 후의 결과가 음수일 때 켜진다. 또는 뺄셈을 할때 위와 반대 경우면 켜진다. 대신 0이면 켜지지 않는다.[D] Direction Flag, 방향 플래그. 문자열 연산(명령어 뒤에 S가 붙는 명령어들, 예를 들어 MOVS 같은 것)은 연산후에 주소값이 자동으로 더해지는데(더해지는 값은 사용하는 자료형의 바이트의 크기를 따른다.) 이 방향을 정하는 비트다. 1이면 역방향으로 더해진다.[I] Interrupt Mask Bit, 인터럽트 무시 비트. 외부 인터럽트를 받을지 말지 결정하는 플래그이다. 0이면 외부 인터럽트를 받지 않는다. 다만 무시 불가 인터럽트(NMI)까지 거부할 수 없다.[T] Trap Flag, 트랩 플래그. 이 플래그가 켜지면 명령어가 실행될 때마다 인터럽트가 걸린다. 디버거에서 주로 사용된다.[S] Sign Flag, 음수 플래그. 연산 결과가 음수일 때 켜진다. 오버플로 플래그와 다른 점은 연산 전의 값은 상관없다는 것이다.[Z] Zero Flag, 제로 플래그. 연산 결과가 0일 때 켜진다. 비교 후 결과가 서로 같음을 기록하는데 쓰인다.[A] Auxiliary Carry Flag, 보조 캐리(올림) 플래그. 8비트 중에서 하위 4비트의 올림을 기록하는 데 사용된다. 정확한 용도는 두 값의 연산 후에 BCD 결과로 보정하는 용도로 쓰인다. x86은 BCD 연산을 지원하지 않기 때문에 일단 계산하고 결과를 원래 BCD 연산의 결과에 맞게 보정한다. 8비트 연산에서만 효과가 있고 16비트 이상의 연산에서는 하위 8비트의 결과만 보정해 준다.[P] Parity Flag, 패리티 플래그. 8비트 연산 결과의 1의 개수가 짝수거나 0개면 켜진다. 예를 들어 0xb7, 10110111이 결과일 경우 켜지지 않지만 1이 증가하면 1의 개수가 짝수가 되므로 켜진다.[C] Carry Flag, 캐리(올림) 플래그. 올림 또는 빌림이 발생하면 켜진다. ADC, SBB 등의 올림(빌림)을 같이 더해 주는 연산을 해서 16비트라는 적은 크기의 자료형을 32, 64비트로 확장할 수 있다.)[1] 실제로는 3D 관련 기술용으로 많이 쓰였다. 대표적으로 AutoCAD와 3D MAX[2] AutoCAD와 3D MAX는 코프로세서라 불리던 x87이 없어도 실행은 되었다. 물론 x87 없이 업무용으로 사용 가능한 수준의 성능은 얻을 수 없었다.[3] x87의 소형화에 성공하여 CPU 유닛에 집적이 가능해졌기 때문이다. 다만 이로 인해 안 그래도 비쌌던 x86 프로세서의 가격이 확 뛰긴 했다. 그래서 코프로세서를 달지 않은 486SX도 출시는 했지만, 의외로 시장에서는 그리 환영받지 못하고 다들 486DX를 구매했다. 아마도 386시절에 SX와 DX의 차이로 받아들인 소비자들이 DX를 선호했기 때문이 아닐까 추측된다. 사실 같은 클럭의 486SX와 486DX는 성능 차이가 그리 크지 않았다.