[CS] 컴퓨터 구조 - 명령어

wbnh·2024년 10월 2일

CS 지식

목록 보기
1/2

개발자가 작성한 소스코드에서 명령어로

컴퓨터는 Java나 C/C++, Python, JavaScript 등의 언어로 작성된 소스코드를 바로 이해해서 실행하는 것이 아니다.
컴퓨터는 데이터를 재료삼아 명령어를 이해한다.
따라서 소스코드는 실행되기 전에 명령어 + 데이터로 변환되어 실행된다.

소스코드: 사람(개발자)가 이해하기 편한 언어 -> 고급 언어
명령어 & 데이터: 컴퓨터가 이해하기 편한 언어 -> 저급 언어

즉, 고급 언어로 작성된 소스코드는 내부적으로 저급 언어로 구성된 명령어와 데이터로 변환된다.

저급 언어의 두 종류

  1. 기계어: 컴퓨터가 직접 이해하는 언어

  2. 어셈블리어

C/C++, Java로 작성한 동일한 코드를 기계어나 어셈블리어로 변환했을 때 CPU의 종류나, 컴파일러 종류에 따라 다르게 변환될 수 있다.

고급 언어에서 저급 언어로 변환되는 대표적인 방식

  • 컴파일
  • 인터프리트

(모든 언어가 위와 같은 방식으로 변환되는 것은 아니다)

컴파일(대표적인 언어: C/C++, Rust)

소스코드 전체가 컴파일러에 의해 검사되고, 목적코드(object code)로 변환된다.
컴파일러의 종류: gcc, clang, Visual Studio 등..

인터프리트(대표적인 언어: Python, JavaScript)

인터프리터에 의해서 소스코드 한 줄씩 검사되고, 목적코드(object code)로 변환된다.

컴파일 방식과 인터프리트 방식의 차이

한줄 한줄 인터프리터에 의해서 검사하는 것보다 한번에 쭉 검사해서 기계어로 바꾼다면 소스코드 실행 시 컴퓨터가 직접적으로 이해할 수 있기에 더 빠르다.
즉, 소스코드가 컴파일이 된 상태라면 컴파일 방식이 빠르다.
하지만 컴파일러는 n번째 줄에 오류가 있다면 처음부터 실행이 되지 않으며, '컴파일 에러'가 발생한다.

컴파일 방식과 인터프리트 방식은 소스코드가 저급 언어로 변환되는 대표적인 방식일 뿐 딱 떨어지게 구분되는 개념은 아니다.
컴파일 언어의 특성과 인터프리트 언어의 특성을 모두 갖춘 언어도 있다. (ex. Java, Kotlin 등..)


명령어의 구조

프로그램을 이루는 두 정보(0과 1로 이루어진 정보)는 두 가지로 구성되어 있다.

  • 명령어: 컴퓨터를 동작시키는 실질적인 정보
  • 데이터: 명령어의 대상

명령어의 구성

  • 무엇을 대상(명령의 대상)으로 무엇을 수행(명령의 동작)해라
  • 오퍼랜드(Operand, 피연산자): 명령어를 수행할 대상
    • 대상(데이터)이 직접 명시되기도 하고, 대상의 위치(레지스터의 이름 혹은 메모리 주소)가 명시되기도 한다.
    • 레지스터: CPU에 있는 작은 임시 저장 장치
  • 연산코드(Op-code): 오퍼랜드로 수행할 동작

즉, 오퍼랜드로 연산 코드를 수행해라.

명령의 동작명령의 대상명령의 대상
더해라100과120을
빼라메모리 32번지 값과메모리 33번지 값을
저장해라무엇을메모리 128번지에
출력해라무엇을모니터에

오퍼랜드가 없는 경우(0-주소 명령어)

연산코드(Op-code)

오퍼랜드가 1개인 경우(1-주소 명령어)

연산코드(Op-code) | 피연산자(Operand)

오퍼랜드가 2개인 경우(2-주소 명령어)

연산코드(Op-code) | 피연산자(Operand) | 피연산자(Operand)

오퍼랜드가 3개인 경우(3-주소 명령어)

연산코드(Op-code) | 피연산자(Operand) | 피연산자(Operand) | 피연산자(Operand)

피연산자(Operand)의 갯수는 유동적일 수 있다.

같은 코드라고 해도 오퍼랜드의 갯수에 따라 실행되는 명령어의 갯수가 달라질 수 있다

ex1) 2-주소 명령어로 "X = (A + B) * C" 계산하기

  1. R1 <- M(A): 메모리(M)의 A번지 값을 연산을 진행할 레지스터 R1으로 옯긴다.
  2. R1 <- R1 + M(B): 메모리(M)의 B번지 값을 R1 값에 더하고, 더한 값은 R1에 저장한다.
  3. R1 <- R1 * M(C): 메모리(M)의 C번지 값을 R1 값에 곱하고, 곱한 값은 R1에 저장한다.
  4. M(X) <- R1: 계산이 끝난 R1의 값을 메모리(M)의 X번지로 옮긴다.
연산코드(Op-code)피연산자(Operand)피연산자(Operand)
옮겨라A를R1으로
더해라B를R1과
곱해라C를R1과
옮겨라R1을X로

ex2) 3-주소 명령어로 "X = (A + B) * C" 계산하기

  1. R1 <- M(A) + M(B): 메모리(M)의 A번지, B번지의 값을 더해 레지스터 R1에 저장한다.
  2. M(X) <- R1 * M(C): 메모리의 C번지 값을 R1과 곱해 메모리 X번지에 저장한다.
연산코드(Op-code)피연산자(Operand)피연산자(Operand)피연산자(Operand)
더해라결과 R1에 저장AB
곱해라결과 X에 저장R1C

이처럼 명령어에서 사용되는 Operand가 몇 개 있는지에 따라서, CPU가 얼마나 복잡한 명령어를 지원하는 지에 따라서 명령어의 갯수가 달라질 수 있다.

연산코드(Op-code)

연산코드의 종류는 CPU마다 다를 수 있다. CPU의 종류와 관계없이 대표적으로, 공통적으로 사용되는 연산코드의 종류는 정해져 있다.

데이터 전송에 관여하는 Op-code

연산코드설명
MOVE데이터를 옮겨라(레지스터에서 레지스터로 ~ )
STORE메모리에 저장해라
LOAD(FETCH)메모리에서 가져와라(CPU 내부의 레지스터로 ~ )
PUSH스택 최상단에 데이터를 저장해라
POP스택 최상단의 데이터를 가져와라

산술/논리 연산에 관여하는 Op-code

연산코드설명
ADD / SUBTRACT / MULTIPLY / DIVIDE덧셈 / 뺄셈 / 곱셈 / 나눗셈을 수행해라
INCREMENT / DECREMENT1 증가 / 감소 시켜라
ADD / OR / NOTAND / OR / NOT 연산을 수행해라
COMPARE두 숫자 또는 TRUE / FALSE 값을 비교해라

제어흐름 변경에 관여하는 Op-code

연산코드설명
JUMP특정 주소로 실행 순서를 옮겨라(ex. JUMP 메모리 주소)
CONDITIONAL JUMP조건에 부합할 경우 특정 주소로 실행 순서를 옮겨라
HALT프로그램 실행을 멈춰라
CALL되돌아올 주소를 저장한 채 특정 주소로 실행 순서를 옯겨라(함수호출, 반환 시 사용됨)
RETURNCALL 호출 시 지정했던 주소로 돌아가라(함수호출, 반환 시 사용됨)

입출력 제어에 관여하는 Op-code

연산코드설명
READ(INPUT)특정 입출력 장치로부터 데이터를 읽어라
WRITE(OUTPUT)특정 입출력 장치로 데이터를 써라
START IO입출력 장치를 시작해라
TEST IO입출력 장치의 상태를 확인해라

주소지정

주소지정이란 명령어의 연산코드의 대상이 되는 데이터를 찾아가는 방법이다.
주소지정은 CPU마다 조금씩 차이가 있다. 그리고 주소지정에는 다양한 방식이 있다.

오퍼랜드(Operand): 명령어를 수행할 대상
오퍼랜드가 담기는 오퍼랜드 필드에는 연산의 대상(데이터)이 직접 명시되기도 하고, 대상의 위치(레지스터 이름, 메모리 주소)가 명시되기도 한다.

Q) 왜 데이터를 직접 명시하지 않고 위치를 명시하는 것일까?

연산코드오퍼랜드1오퍼랜드2
더해라100과120을
빼라메모리 32번지 값과메모리 33번지 값을
저장해라10을메모리 128번지에

A) 명령어의 길이는 한정되어 있기 때문에

명령어는 연산코드와 오퍼랜드로 구성되어 있고, 오퍼랜드는 여러개 있을 수 있다.
이 때, 명령어의 총 길이가 한정되어 있다면, 연산코드를 명시하기 위한 공간과 오퍼랜드를 명시하기 위한 공간이 한정되어있게 된다.

예를 들어 명령어의 길이가 16bit라고 가정한다면

  • 오퍼랜드가 2개인 경우(2-주소 명령어)
    연산코드(4bit) | 오퍼랜드(6bit) | 오퍼랜드(6bit)
    ==> 하나의 오퍼랜드 필드로 표현할 수 있는 데이터 크기: 2^6(64)

  • 오퍼랜드가 3개인 경우(3-주소 명령어)
    연산코드(4bit) | 오퍼랜드(4bit) | 오퍼랜드(4bit) | 오퍼랜드(4bit)
    ==> 하나의 오퍼랜드 필드로 표현할 수 있는 데이터 크기: 2^4(16)

하나의 오퍼랜드에 2500이라는 데이터를 표현해야 한다면, 이를 직접 명시할 수는 없다.
최대 표현가능한 데이터가 16밖에 안되니까

위와 같은 문제를 해결하기 위해 오퍼랜드 필드에 연산코드의 대상이 되는 데이터를 직접 명시하기 보다는,
데이터가 저장되어 있는 공간(레지스터 이름, 메모리 주소)을 명시

  • 메모리의 경우(유효주소 = 10번지)
    메모리(유효주소 = 10번지)

  • 레지스터의 경우(유효주소 = R1)
    레지스터(유효주소 = R1)

유효주소: 연산코드에 사용할 데이터가 저장된 위치. 즉, 연산의 대상이 되는 데이터가 저장된 위치
주소지정: 유효주소를 찾는 방법

주소지정의 방식

1. 즉시 주소 지정(가장 간단, 많이 사용되지는 않음)

연산코드 | 연산코드에 사용될 데이터

특징)

  • 연산에 사용할 데이터를 오퍼랜드 필드에 직접적으로 명시
  • 가장 빠른 주소지정(이론상) + 데이터 크기에 제한됨

2. 직접 주소 지정

직접 주소 지정

특징)

  • 오퍼랜드 필드에 유효주소(연산에 사용될 데이터가 저장된 메모리 주소) 명시
  • 오퍼랜드 필드로 표현가능한 메모리 주소 크기에 제한됨(시간이 지날 수록 메모리 용량이 커지면서 메모리 주소의 크기가 커지고 있음) -> 간접 주소 지정의 등장 배경

CPU가 레지스터에 접근하는 속도보다 메모리에 접근하는 속도가 훨씬 느리다.
레지스터에 접근함으로써 처리할 수 있는 작업은 레지스터에 접근해서 처리하는 것이 훨씬 더 빠른 성능을 보장한다.

3. 간접 주소 지정

간접 주소 지정

특징)

  • 오퍼랜드 필드에 유효주소의 주소를 명시
  • 유효주소 크기에 제한은 없으나, 속도가 비교적 느림

4. 레지스터 주소 지정

레지스터 주소 지정

특징)

  • 연산에 사용할 데이터를 저장한 레지스터의 이름을 오퍼랜드 필드에 직접 명시
  • 레지스터 접근은 메모리보다 훨씬 빠르다.

5. 레지스터 간접 주소 지정

레지스터 간접 주소 지정

특징)

  • 연산에 사용할 데이터를 메모리에 저장하고, 그 주소(유효주소)를 저장한 레지스터를 오퍼랜드 필드에 명시
  • 메모리 접근은 한 번

6. 그 외 여러 방식

profile
안드로이드 개발 공부중입니다

0개의 댓글