IT_Programming/Assembly

[전광성의 어셈블리어 이해하기] IA-32 프로세서 아키텍쳐란?

JJun ™ 2007. 7. 2. 10:28
LONG
  • IA-32 프로세서 아키텍쳐(Processor Architecture)

    IA-32란 인텔386에서 시작하여 최근의 32비트 프로세서인 펜티엄4에 이르는 프로세서'군' 의미한다. 물론 펜티엄 4에 이르러가면서 많은 변화가 있었지만, 프로그래머의 관점에서 보면 크게 바뀐것이 없다고 할 수 있다. 왜냐하면 프로세서의 수행속도의 향상과 구현방법의 변화로 프로세서가 진화해 갔기 때문이다. 굳이 다른 것이 있다면 멀티미디어 처리에 대한 명령의 성능 향상을 꼽을 수 있다.

    참고로, 앞으로 우리는 CPU가 프로텍티드 모드(Protected Mode)라는 가정 하에서 공부할 것이다. 이 단어에 대해 간단히 말하면 단어가 뜻하는 대로, 한 프로그램에 주어진 일정한 메모리의 밖으로 벗어나는 메모리 참조를 프로세서가 감지해 그렇게 하지 못하도록 해주는 모드이다. 좀 더 자세한 사항은 인텔社의 IA-32프로세서 아키텍쳐를 참고하기 바란다.

  • 레지스터 메모리

    < 그림3 : 레지스터 메모리 >

    레지스터 메모리(이하 레지스터)는 CPU안에 위치한 고속의 저장장치이다. 보통은 CPU가 특정 연산을 수행할 때 피연산자를 레지스터 메모리에 올려놓고 수행하게 된다. 하드디스크가 메모리보다 느리다는 것은 알고 있을 것이다. 실제로 우리 눈에 레지스터 메모리는 직접 보이지 않으니까 실감하지 못하겠지만, 메모리는 레지스터보다 느리다.

    비유하자면, 하드디스크가 자료를 저장하는 책상서랍이라 하면 메모리는 자료를 올려놓는 책상 위라고 생각할 수 있으며, 레지스터는 자료를 직접 들고 있는 손이라고 생각할 수 있다. 레지스터는 그림3과 같이 8개의 일반 레지스터(General-Purpose Registers)와 6개의 세그먼트 레지스터(Segment Registers), 프로세서의 상태를 나타내는 플래그인 EFLAGS, 명령 포인터(instruction pointer)인 EIP로 이루어져있다.

    일반 레지스터(general-purpose register)는 주로 자료 전송이나 산술연산에 쓰이며, eax, ebx, ecx, edx는 그림4와 같이 나뉠 수 있다.

    < 그림4 : eax, ax, ah, al >

    그림에서 보이는 것과 같이 eax의 하위 16비트는 ax라고 이름붙여져 있고, ax의 상위 8비트는 ah라고 이름붙여져 있으며, ax의 하위 8비트는 al이라고 이름붙여져 있다. eax, ax, ah, al이 각각 다른 공간을 의미한다고 착각하지 않길 바란다. 32비트의 공간안에서 각각의 위치에 해당 이름이 붙여져 있는 것이다. 따라서 eax에다가 0000 0000 .... 0000 0001 을 집어넣으면 ax도 1, al도 1이 된다는 사실을 잊지 말기 바란다.

    이해를 돕자면, 본래 IA-32이전의 인텔 CPU는 32비트 레지스터가 없었다. 위의 그림에서 세그먼트 레지스터를 제외한 레지스터들의 앞에 붙은 E가 모두 없었고, 이들은 모두 16비트 레지스터였다. 하지만 IA-32에서 32비트 레지스터가 등장하였고, 이전과의 호환성을 유지하기 위해 앞에 Extended를 의미하는 E를 붙인 것이다. 덧붙여 이야기 하자면 ah, al에서 h는 high, l은 low를 뜻한다. 아래의 표를 참고하면 이해가 빠를 것이다.

    32-bit

    16-bit

    8-bit(high)

    8-bit(low)

    EAX

    AX

    AH

    AL

    EBX

    BX

    BH

    BL

    ECX

    CX

    CH

    CL

    EDX

    DX

    DH

    DL

    < 표1 : EAX, EBX, ECX, EDX >

    마찬가지로 나머지의 네 개의 일반 레지스터도 16bit의 다른 이름을 갖고 있다.

     32-bit

    16-bit

     ESI

     SI

     EDI

     DI

     EBP

    BP

     ESP

     SP

  • 각 레지스터의 용도

    EAX, EBX, ECX, EDX, ESI, EDI레지스터는 우리 마음대로 쓸 수 있는 레지스터이다. 반면 다른 레지스터들은 함부로 사용했다가는 프로그램이 종료되어버릴 수 있으니 주의하기 바란다.

    EAX : 곱셈, 나눗셈 명령에 쓰이게 된다.
    ECX : 루프 카운터로 쓰인다.
    ESI 와 EDI : 고속의 메모리 복사에 쓰인다. S는 source, D는 destination의 약자이다.
    EBP : 고급언어에서 사용하는 함수 파라미터와 지역변수를 가리키는 데 쓰일 것이다. 일반적인 산술이나 데이터 전송에 쓰여서는 안된다.
    ESP : Stack Pointer의 약자이다. 스택의 탑을 가리키고 있다. 즉, 주소를 갖고 있을 것이다.(스택이라는 자료구조를 아는가? 혹시 모른다면 그냥 스택이라는 메모리 공간에 데이터를 쌓아놓는데 쌓아두는 곳의 맨 윗부분이라고 생각하길 바란다.)
    세그먼트 레지스터(Segment Register) : 프로그램 수행에 필요한 내용들이 미리 메모리에 할당되어있는데 그곳의 주소를 갖고 있다.(우리는 이 레지스터를 사용할 일이 거의 없을 것이다.)
    명령 포인터(Instruction Pointer) : 다음에 수행되어야 할 명령문의 주소를 갖고 있게 된다.
    EFLAGS Register : 흔히 그냥 Flags라고 부른다. flag의 뜻은 다들 알다시피 '깃발'이다. 깃발로 어떤 정보를 전달 할 수 있을까? 깃발을 들고 내리고를 통하여 on/Off를 표시할 수 있을 것이다. EFLAGS의 각각의 비트는 1(set)이 되거나 0(clear)이 될 수 있다. 그리고 그 각각은 다른 의미를 갖고 있으며, 비트마다 다른 이름이 부여되어있다. 쉬운 예를 들면 Sign Flag라는 비트는 계산 결과가 음수인지 아닌지를 표시해 준다. 이런 게 무슨 의미를 가질까 의심할 수 있겠지만, 이는 다른 플래그와 결합하여 수의 크기를 비교하는 등 중요한 의미를 만들어 낼 수 있다는 것을 기억해주기 바란다. 각각의 플래그는 나올 때 마다 설명해 주기로 하겠다.

    앞으로 레지스터는 메모리에서 값을 불러와 잠깐 사용하는데 쓰이거나, 특정 연산을 하기 이전에 피연산자를 미리 불러오는 데 쓰일 것이다. 레지스터를 잘만 사용한다면 우리가 다른언어보다 뛰어난 퍼포먼스를 낼 수 있다는 점은 어셈블리어를 사용하는 데 있어 큰 매력이 된다. 지금 언급 한 것 이외에도 IA-32에는 부동소수점 연산장치(FPU : Floating-point unit)가 있는데 그다지 본 강좌의 목적에 부합한다고는 생각하지 않으므로, 따로 설명하지 않겠다.

    지금까지 레지스터에 대해 숨가쁘게 알아 보았다. 사실 매번 하는 이야기이지만 실전에 들어가기 이전에 배우는 배경지식은 모르는 부분은 한번 쯤 의심을 해보고 이해가 가지 않을 때는 체크해두고 넘어가면 되는 것이다. 나중에 뒷부분을 어느정도 본 후에 다시 앞부분을 보면 무슨소리를 하는 것이었는지 이해할 수 있을 것이다. 여기를 읽으면서 독자가 한번이라도 의심을 가져본다면 나로서는 대만족이다.

  •  
    =============================================================================================
     
  • 시스크(CISC)와 리스크(RISC)

    명령어는 크게 CISC(Complex Instruction Set Computer) 방식과 RISC(Reduced Instruction Set Computer)방식으로 구분할 수 있다. 인텔의 초기 프로세서들은 시스크(CISC : Complex Instruction Set)접근 방식으로 성능 향상시키려고 시도 하였다.

    CISC 방식은 기본 명령어가 있고 필요에 따라서 뒤에 명령어의 길이를 늘리는 방식으로 명령어를 확장한다. 따라서 하위 호환성을 갖게 된다.

    예를 들어 펜티엄용 프로그램은 펜티엄 이상의 프로세서에서는 모두 사용이 가능하다. 하지만, 명령어의 개수가 많고 명령어와 데이터의 길이가 일정하지 않고 가변적으로 구성된다는 문제점이 있다. 따라서 프로그램의 실행 위치는 실제 프로그램이 순차적으로 실행되기 전에는 알 수 없게 된다. 또한, CISC 방식은 명령어의 수준과 길이에 따라서 명령어 변환 과정에 소요되는 클럭 수가 달라지게 되어 프로그램의 실행 타이밍을 정확히 추적하는 것도 어렵게 되며, 실행 타이밍을 정하기도 어렵다.

    반면 RISC 방식은 프로세서에 따라서 필요한 명령어 세트만을 제한적으로 지원하고 명령어의 길이가 일정하게 구성이 된다. 따라서 하위 프로세서와 호환이 되지 않으며 전용 운용 시스템에 전용 소프트웨어를 필요로 한다. 이 경우에 명령어의 길이가 일정하고 명령어의 실행 위치가 미리 확연하게 정해져 있기 때문에 프로그램을 기계어로 변환하는 과정에서 프로그램이 최적화되게 된다.

    또한 명령어의 개수가 제한적이고 적기 때문에 프로그램의 최적의 실행을 위한 재배열도 손쉽게 이루어진다. 또한, 기계어를 다른 코드로 변환하지 않고 직접 실행 유니트에서 실행되기 때문에 대부분의 명령어를 1클럭으로 실행하게 된다. 따라서 실행 타이밍은 명령어의 개수를 통해서 쉽게 알 수 있기 때문에 여러 개의 명령어를 동시에 실행할 때 순서를 정하는 스케쥴링이 쉽다.

    하지만 왜 일반적으로 CISC를 사용하고 있을까? 그것은 CISC 명령어는 호환성이 뛰어나기 때문이라고 한다. 즉, 상위 프로세서는 하위 프로세서용으로 제작된 프로그램도 모두 사용이 가능하다. 실제로 RISC 계열의 컴퓨터에 비하여 CISC 계열의 컴퓨터용 소프트웨어가 엄청나게 많은 주된 이유가 바로 이것 때문이다. 궁극적으로 사용자는 소프트웨어를 운용하게 되며 많은 소프트웨어가 있을 수록 컴퓨터의 사용 환경은 당연히 더욱 유리해진다. 따라서 인텔 계열에서는 CISC를 포기하지 못하고 있는 것이다.

    CISC와 RISC 참고자료 :
    http://www.technoa.co.kr/content/View.asp?pPageID=50668

  • 마치는 글

    이번회에서 배운 내용 중 핵심은 레지스터 메모리이다. 다른 내용은 대충 이런가보다 하면 대 만족이다. 레지스터 메모리의 역할을 좁게 보았을 때 당장 필요한 어떤 연산에 대한 피연산자를 올려놓을 고속의 임시공간이며, 결과가 들어갈 수도 있는 공간이라는 것만 알아둔다면 크게 문제될 것 없다.

    다음 회에서는 본격적으로 어셈블리어를 코딩해 볼 것이며, 지금까지 이론적으로 설명했던 것들이 서서히 구현되어 갈 것이다. 다음 장부터 배우는 어셈의 부분적인 것들은 스스로 고급언어에 어떻게 적용될 수 있는 것인지 생각해 볼 수 있길 바라며 이번 강좌를 마치겠다.

  • ARTICLE
  • 시작하기에 앞서...

    컴퓨터는 어느새 우리의 생활과 너무나도 밀접한 연관을 갖게 되었다. 또한 수많은 사람들의 노력으로 컴퓨터에서 계산기 하나만 실행시켜주면 각종 연산을 해내고, 사람과 친숙한 언어를 이용하여 프로그램을 짤 수도 있게 되었다. 하지만 그 기반을 이루기 위해 어떤 일들이 이루어 졌는지 한번 생각해 보았는가? 0과 1로만 이루어진 것들을 어떤 방법을 통해 친숙해 보이도록 만들었을까?

    필자가 언급한 질문에 대한 대답은 정말 광범위하다. 그러나 그 대답의 일부분은 중앙처리장치(CPU:Central Processor Unit, 이하 CPU)와 이에 직접 명령을 내리는 언어에 대한 공부를 하면서 얻을 수 있을 것이다. 이전에 언급했듯이 어셈블리언어는 컴퓨터구조에 대해 공부할 수 있는 가장좋은 도구중의 하나이다.

    연산을 담당하는 CPU에서 하는 일이란 무엇이며 어떤 구조일까. 차근차근 하나씩 알아보자. 그리고 우리가 가장 많이 쓰고 있는 인텔 계열의 CPU구조에 대해 알아볼 것이다.

  • 기초 마이크로컴퓨터 구성

    사실 지금 필자가 강좌를 통해 알리려는 것은 인텔계열의 CPU에서 쓰이는 어셈블리어에 대한 것이다. 그러나 좀 더 일반적인 개념에 대해 공부한다면 더욱 더 이득이 되리라 생각한다. 즉, 인텔 이외의 CPU들에서도 공통적인 개념에 대해 이해하고 있다면 훗날 이와 관련된 공부를 할 때 더 도움이 될 것이다. 그래서 인텔 CPU에 대해 알아보기 이전에 CPU의 전반적인 개념 및 특징에 대해 알아보고자 한다.

    <그림1 : 마이크로컴퓨터의 개략적인 구성도>

    그림1은 마이크로컴퓨터에 대한 개략적인 그림을 그려놓은 것이다. CPU는 모든 계산과 논리 연산이 이루어 지는 곳이다. CPU는 레지스터(register)라는 매우 작은 기억공간, 클럭(clock), 콘트롤 유닛(CU:Control Unit), 그리고 연산 장치(ALU:Arithmetic Logic Unit)를 포함하고 있다. 클럭은 다른 시스템 구성요소와 CPU의 내부 연산들을 동기화 시켜준다.

    동기화란 무엇일까? 정확한 개념의 정의는 여러 상황에 따라 조금씩 틀려지지만, 이해하기 쉽게 예를 들어 보이겠다. A라는 일과 B라는 일이 있는데, A와 B는 어떤 연관관계가 있어서 한쪽이 너무 빨라서는 안된다. 그럴 때 빠른쪽이 일을 끝낸 후 다른 쪽을 기다린다든가 하면서 두 가지 일이 따로 놀지 않도록 해주는 것이 바로 동기화라고 할 수 있다.

    콘트롤 유닛은 기계어를 수행하는 데에 있어 그 순서를 조정해준다. ALU는 산술 연산(+,-등)과 논리 연산(AND OR NOT등)을 담당한다. CPU는 컴퓨터에 여러 핀을 통해 꽂혀있는데, 이러한 핀들은 데이터 버스(data bus), 콘트롤 버스(control bus), 어드레스 버스(address bus)에 연결되어있다.

    메모리 저장장치(memory storage unit)은 컴퓨터가 작동하는 동안 명령이나 자료가 임시로 들어가는 곳이다. 이것은 CPU의 데이터 요청을 받아들이고, 데이터를 전송해준다. 또는 그 반대의 일도 하며, 일반적으로 RAM을 지칭한다. 버스는 병렬선의 한 그룹인데, 컴퓨터의 한 부분에서 다른부분으로 데이터를 전송해준다. 버스에는 크게 세 가지가 있는데, 그것은 바로 데이터 버스, 콘트롤 버스, 어드레스 버스이다.

    데이터 버스(data bus)는 명령이나 자료를 CPU와 메모리 사이에서 전송해준다. 콘트롤 버스(control bus)는 컴퓨터의 모든 장치에 이진 신호를 보내 동기화시켜준다. 어드레스 버스(address bus)는 CPU와 메모리 사이에서 데이터를 전송할 때 명령의 주소 또는 자료의 주소를 갖고 있다.

    클럭은 컴퓨터가 내부에서 하는 일의 일정한 싸이클을 의미한다. 쉽게 말하면 사람의 심장이 일정한 시간 간격으로 뛰는 것에 비유할 수 있다. 이러한 클럭을 이용하여 컴퓨터는 동기화하게 된다.

    마이크로컴퓨터가 갖는 기초적인 구조에 대해 설명해 보았는데, 간단히 설명하려다 보니 현실과는 조금 차이가 있을 수 있다. 하지만 그 개념만은 비슷하니 걱정할 필요 없다. 다소 모호하게 설명한 감이 없지 않지만 이 부분은 깊게 들어가면 한도 끝도 없으니 여기서 맺도록 하자.

  • 명령 수행 사이클

    CPU에서 한 명령이 수행되는데는 여러 연산이 필요한데, 이를 명령 수행 사이클이라고 한다. 프로그램이 수행되기 이전에 프로그램 자체는 메모리에 올려지게 된다. 다음 그림을 참고하기 바란다.

     
    <그림2 : 명령 수행 사이클(Instruction Execution Cycle)>

    프로그램에는 필요한 데이터들의 초기값이나 수행되어야 할 명령어들이 들어있을 것이고, 이것이 메모리에 올라가게 된다. 그림2의 맨 위에 보이는 PC(Program Counter)는 다음에 수행될 명령어를 가리키게 된다. (참고로 I-1, I-2와 같은 글자에서 I는 Instruction의 약자로 생각할 수 있다.)

    다시말해서 PC는 다음에 수행될 명령어의 주소를 갖고 있을 것이다. 쉽게 설명하면, GW-BASIC에서 줄번호가 있는데 그 줄번호를 기억하고 있는 것이 PC라고 생각하면된다. 또 goto문같은 제어문을 만나지 않는 이상 PC는 계속 바로 다음의 명령을 가리키게 될 것이다. Instruction Register는 명령어가 다음 차례를 기다리는 곳으로 생각하면 이해하기 쉬울 것이다.

    이제 명령은 해독과정을 거쳐 ALU(Arithmetic Logic Unit)로 들어가게 된다. 여기서부터는 명령의 종류에 따라 행동 양식이 달라질 수 있다. 명령은 피연산자가 있느냐 없느냐에 따라, 피연산자가 있다면 그것에 일반 메모리(이하 '메모리')가 있느냐 없느냐에 따라, 또 몇개냐에 따라 달라질 수 있다.

    그림에서 보다시피 ALU에서는 레지스터나 간접적으로 메모리에서 뭔가를 읽어들이고 출력을 레지스터나 메모리에 쓴다. 또한 flags에도 영향을 미칠 수 있다. 메모리레지스터, 플래그를 눈여겨 봐 두어라.(특히, 메모리와 레지스터의 차이에 대해서는 한번만 의심해보길 바란다. 바로 뒤에 나온다.) 지금은 저 그림이 이해되지 않을 수 있지만 앞으로 올라올 강좌들을 읽다 보면 충분히 이해할 수 있다. 그때가서 다시 언급하도록 하겠다.

    독자들은 위의 내용을 잃으면서 뭔가 모호하다는 느낌을 받았을 받았을 수도 있다. 사실 위에 나온 저 그림은 일종의 모델이고, 실제 CPU는 저 틀에서 변형되어있다. 위의 내용이 이해가지 않더라도 실망할 필요 없다. 오히려 위의 내용을 달달 외운다 하더라도 도움될 것이 하나도 없다.다만, 잠시나마 한번 생각해 보고 의심을 가져보는 독자가 있길 빈다. 그런 의도에서 이 내용을 기사에 실었으니, 부담없이 봐주었으면 한다.