태초의 프로그래밍 언어 어셈블리

assembly, 8086, x86

8086의 기본 구조

어셈블리 언어를 보통 가장 낮은 레벨의 언어라고 말합니다. 여기에서 높은 언어란 사람들끼리 사용하는 자연어를 말합니다. 그리고 낮을 수록 기계에 가깝다는 뜻입니다. 어셈블리 언어는 인간이 사용할 수 있는 언어중에서는 가장 기계에 가깝다는 말이 됩니다. 따라서 어셈블리 언어로 뭔가를 해보려면 기계를 먼저 알아야합니다. 어셈블리 언어보다 먼저 수와 기계를 설명하는게 그런 이유 때문입니다. 이번 글에서는 8086이 어떤 구조로 되어있고 어셈블리 언어로 조종할 수 있는 기능들이 뭐가 있나 알아볼 것입니다.

어셈블리 언어보다 더 기계에 가까운게 있을까요? 숫자가 있습니다. 모든 어셈블리 언어는 기계가 이해할 수 있는 또 기계를 직접 동작시키는 숫자로 변환됩니다. 그렇게 변환하는 것을 어셈블러 assembler 변환하는 작업 자체를 어셈블링 assembling 이라고 합니다. 숫자의 모음은 기계어라고도 합니다. 사람의 언어가 아니라는 것이지요. 그래서 인간이 사용할 수 있는 최소한의 언어가 어셈블리 언어입니다.

참고로 어셈블리 언어는 숫자와 1대1로 매칭됩니다. 아직 배우지는 않았지만 mov라는 명령어가 있는데 mov라는 명령이 나오면 이 명령은 항상 특정 숫자로 변환됩니다. 이 값을 123h라고 가정해보면 어셈블러는 코드에서 mov를 만나면 그대로 123h라고 변환하는 것입니다. 그리고 mov ax, bx라는 명령을 만나면 항상 12345h라고 변환한다고 해봅시다. 어셈블러는 컴파일러에 비하면 굉장히 간단한 프로그램입니다.

컴퓨터 구조

이 그림은 컴퓨터를 아주아주 많이 간단하게 표현한 것입니다. 메모리와 프로세서, 장치들이 버스로 연결되어있습니다. 우리는 파일을 하드디스크에 저장합니다. 그리고 더블클릭을 하면 프로세서가 더블클릭이라는 명령을 이해하고 파일을 하드디스크에서 메모리로 읽어옵니다. 그리고 메모리를 조금씩 읽어서 파일에 써있는 명령어들을 실행합니다. 그 명령어가 특정 이미지를 장치로 보내라는 명령이면 영상 출력이 되는 것이고 특정한 데이터를 스피커로 보내라는 것이면 음악 출력이 되는 것입니다.

프로세서

우리가 어셈블러로 우리 어셈블리 언어 코드를 어셈블링해서 저장한 실행파일이 있다면 이 파일은 하드디스크 장치에 저장될 것입니다. 그리고 프로세서는 실행파일을 메모리로 옮기고 메모리에서 명령들을 읽어서 실행할 것입니다. 8086 프로세서의 구조를 간단한게 생각해보면 이렇습니다. (주1)(주2)

8086구조

범용 레지스터

레지스터라는 것은 프로세서 안에 있는 16비트의 저장공간입니다. 메모리와 역할이 같지요. 하지만 메모리와 프로세서는 프로세서보다 훨씬 느린 버스라는 전선으로 연결되어있어서(주3) 메모리에 있는 데이터를 프로세서가 읽으려면 프로세서는 한참을 기다려야 합니다. 하지만 레지스터는 프로세서 내부에 있기 때문에 아주 빠르게 읽을 수 있습니다. 속도 차이는 최소한 만배 이상입니다. 마치 서울에서 대전에가서 물건을 사오는 것과 주머니 속에있는 물건을 꺼내오는 것의 차이와 같습니다. 대전에 가서 큰 물건을 사올 수는 장점이 있지만 시간이 오래 걸린다는 단점이 있지요. 주머니속 물건도 빨리 꺼낼 수는 있지만 큰 물건을 넣을 수는 없다는 단점이 있습니다.

그래서 계산할 때 최대한 많은 데이터를 메모리에서 레지스터로 읽어오고 최대한 레지스터만 가지고 계산을 합니다. 그리고 최종 데이터만 메모리에 기록하도록 하면 프로그램 속도를 높일 수 있습니다. 그렇게 계산 중간 중간에 사용할 임시 데이터를 저장하는 공간을 레지스터라고 생각하면 됩니다.

8086에는 8개의 범용 레지스터가 있습니다.

AX: 누산 레지스터라고 불리거나 연산 레지스터라고 불립니다. 계산에 주로 사용됩니다.

BX: 베이스 주소 레지스터라고 불립니다. 메모리 주소를 계산할 때 사용됩니다.

CX: 카운터 레지스터라고 불립니다. 반복문에서 지금 몇번째로 반복하고 있는지를 기억할 때 사용됩니다.

DX: 데이터 레지스터입니다. 계산의 결과 값을 저장하거나 메모리에서 읽어온 데이터를 저장합니다.

SI: 소스 인덱스 레지스터(Source Index Register)라고 불립니다. 메모리 복사 등에서 원본 데이터의 주소를 저장할 때 사용합니다.

DI: 목적지 인덱스 레지스터(Destination Index Register)라고 불립니다. 메모리 복사 등에서 목적지의 주소를 저장할 때 사용합니다.

BP: 베이스 포인터 레지스터(Base Pointer Register)입니다. 스택 주소를 보존하는데 사용됩니다.

SP: 스택 포인터 레지스터(Stack Pointer Register)입니다. 현재의 스택 주소가 저장됩니다.

각각의 레지스터의 기능을 간단히 설명했지만 지금은 아마 전혀 이해가 안되실 겁니다. 각각의 레지스터의 사용법을 emu8086으로 직접 실습해봐야 어떤 레지스터를 어디에 어떻게 사용하는 것인지 알게됩니다. 지금은 그냥 이런 이름들의 레지스터가 8개 있다는 것만 아시면 됩니다.

그리고 각각의 레지스터에 용도가 있다고 말씀드렸짐만 이건 그냥 일반적인 용도를 말한 것이고 사실은 프로그래머가 마음대로 사용해도 됩니다. CX를 계산에 사용해도 되고 BX에 루프 카운터를 저장해도 됩니다.

모든 레지스터는 16비트입니다. 그리고 특별히 AX, BX, CX, DX는 8비트로 나눠서 반씩만 사용할 수 있습니다. 어떤 계산을 할 때 이 계산이 굳이 16비트가 필요없는 계산이라면 8비트만 사용할 수 있습니다. 이것도 emu8086으로 실습해보겠습니다.

만약에 AX 레지스터에 1234h라고 값을 쓰면 어떻게 될까요? AH에 12h가 저장되고 AL에 34h가 저장될까요? 아니면 AL에 12h가 저장되고 AH에 34h가 저장될까요? 실습을 하면서 알아보겠습니다. 일단 지금은 16비트의 레지스터가 있고 8비트로 나눠서 사용할 수도 있다라는 것만 알고 넘어가겠습니다.

왜 레지스터는 작을까요? 빠르니까 크게 만들면 좋을 텐데요. 이유는 돈때문입니다.프로세서에 뭔가를 넣는다는 것은 돈이 많이 들지요. 컴퓨터를 조립해보면 캐시 메모리가 큰 프로세서가 더 비싼데 캐시 메모리가 프로세서 안에 있기 때문입니다. 레지스터도 크면 클수록 프로세서가 비싸집니다.

세그먼트 레지스터

아직 설명안한 나머지 레지스터들이 세그먼트 레지스터입니다. 마찬가지로 그냥 그런 것들이 있다라고만 이해하고 다음에 실습을 하면서 어떤 역할을 하는지 알아가기로 하겠습니다. 여기서는 소개만 하겠습니다.

CS: 현재 프로그램이 저장된 세그먼트의 주소가 저장됩니다.

DS: 현재 사용하는 데이터가 저장된 세그먼트의 주소가 저장됩니다.

ES: 별도의 용도는 없고 필요할 때마다 원하는 메모리 위치의 세그먼트 주소를 저장합니다.

SS: 스택이 있는 세그먼트의 주소가 저장됩니다.

세그먼트라는 것은 좀더 많은 메모리를 컴퓨터에 넣기 위해 생겨난 개념입니다. 예를 들어서 설명하면 더 쉬울것 같습니다.

8086에서는 모든 레지스터가 16비트입니다. 따라서 메모리 주소를 저장하는 레지스터도 16비트입니다. 16비트는 2^16이므로 2^10 * 2^6 = K * 64 = 64K가 됩니다. 지금 보통 컴퓨터 메모리가 1G나 2G를 쓰는데요 우리가 쓰는 프로세서의 레지스터 크기가 32비트이기때문에 4G까지 메모리 주소를 계산할 수 있기 때문입니다. 8086은 16비트이기때문에 64K가 한계이지요.

그런데 64K는 너무 작습니다. 그 옛날에도 메모리는 무조건 클 수록 좋았던 것이지요. 그래서 고민한 결과가 두개의 레지스터를 붙여서 좀더 메모리의 접근 범위를 넓혀보자입니다. 두개를 붙이면 32비트가 되겠지만 그당시 기술로는 그렇게까지 크게 만들지 못하고 단지 한 자리만 더 늘릴 수 있었습니다. 이렇게요.

DS 레지스터에 1230h을 저장하고 SI 레지스터에 45h를 저장합니다. 그러면 실제로 접근하려는 메모리의 주소가 1230h * 10h + 45h = 12300h + 45h = 12345h 가 됩니다.

즉 16비트 레지스터가 1234h같이 4자리 16비트의 주소만 표시할 수 있는데 세그먼트 레지스터 하나를 더 써서 5자리 20비트의 주소를 표시할 수 있게 만듭니다. 그러면 2^20이 되니까 1M의 메모리를 프로세서에 연결할 수 있게 됩니다.

세그먼트 레지스터의 역할이 이해가 되시나요? 주소 범위를 넓혀주는 것이라고 생각하시면 됩니다.

다시한번 예를 들면 반대로 12345h 라는 주소를 8086 레지스터에서 표현하려면 세그먼트 레지스터에 1230h를 저장하고 SI 레지스터에 45h를 저장하면 됩니다. 특이한 점은 세그먼트 레지스터에 1234h를 저장하고 SI 레지스터에 5h를 저장해도 같은 주소가 됩니다. 어떻게 하든지 프로그래머 마음입니다. 단지 두개의 레지스터의 합만 원하는 값이면 됩니다.

살짝 세그먼트 레지스터와 SI/DI 레지스터의 역할을 설명했습니다만 아직 잘 이해가 안되실 겁니다. 실습을 해봐야 이해가 되실 거니까 실습을 기다려 주세요.

지금은 메모리 주소가 20비트이고 하나의 레지스터가 16비트이므로 메모리 주소를 표현하기 위해서는 두개의 레지스터를 붙여서 표현해야한다는 것입니다. 그리고 그 때 사용하는 레지스터가 CS, DS, ES 라는 세그먼트 레지스터와 SI, DI라는 인덱스 레지스터라는 것만 알고 넘어가겠습니다.

특수 목적 레지스터

아직 설명안한 레지스터가 있습니다. 아무리 어셈블리 언어라고해도 직접 값을 쓰고 읽을 수 없는 특수한 레지스터입니다.

IP: 명령어 포인터 Instruction Pointer 레지스터입니다. 현재 수행할 명령어의 주소를 나타냅니다.

플래그 레지스터: 프로세서의 상태를 나타냅니다.

이 두개의 레지스터는 프로그래머가 마음대로 값을 바꿀 수 없습니다. 프로그램이 실행되면 프로세서가 자동으로 값을 바꾸는 레지스터입니다. 그래서 두개다 프로세서의 상태를 알려주는 레지스터입니다.

각 레지스터의 역할도 실습을 하면서 알아보겠습니다.

 

이번 글은 레지스터의 소개를 했습니다. 많은 레지스터가 소개되어서 어려우실 수 있습니다만 지금은 8086에 여러개의 레지스터가 있고 각 레지스터가 16비트라는 것, 그리고 메모리 주소를 나타내기위해 두개의 레지스터를 붙여서 20비트의 값을 사용한다는 것만 확실히 아시면 됩니다. 각 레지스터들은 어셈블리 언어로 프로그래밍을 하면서 하나씩 직접 써보면서 친해지도록 하겠습니다.

 

 


주1: 이 그림은 프로세서를 논리적인 기능들로 나타낸 것입니다. 물리적으로 어떻게 프로세서를 만드는지는 전혀 다른 이야기입니다.

주2: 8086이 최초의 현대적인 프로세서라고 불리는 이유는 이 그림에 나온 AX, BX 등의 범용 레지스터나 CS, IP 등의 제어 레지스터 등 8086에 있는 모든 것이 지금 현재의 i5같은 프로세서에도 그대로 있기 때문입니다. 8086 이후로 핵심적인 기능들은 변하지 않고 호환성을 유지하고 있기 때문에 우리가 8086을 배우는 것이기도 합니다.

주3: 메인보드의 판때기?를 보시면 녹색 선들이 있지요? 그것들이 사실은 전선입니다. 전기가 통하는 선인데 아주 얇게 만들어서 메인보드의 판속에 뭍어둔 것입니다.

댓글

댓글 본문
작성자
비밀번호
  1. gurugio
    제 홈페이지 개편하면서 제가 실수한게 있었습니다.
    고쳤으니까 잘 보이실거에요.
    알려주셔서 감사합니다.
    대화보기
    • ㅁㄴㅇㄹ
      그림이 엑박이에요
    • 세그먼트 레지스터 크기도
      범용 레지스터크기랑 똑같나여?
    • 보고있어요
    • TeacherK
      좋은 강의 감사합니다.
    • 워니요
      너무 쉽게 설명해주셔서 이해하는데 도움이 많이 되었습니다. 감사합니다.
    • 참치통조림
      좋은 강의 감사합니다.
    버전 관리
    gurugio
    현재 버전
    선택 버전
    graphittie 자세히 보기