정보의 표현
컴퓨터가 받아들이고 처리하는 정보의 종류로는 프로그램 코드(program code)와 데이터(data)가 있습니다. 컴퓨터에서 그러한 정보들은 모두 2진수(binary)를 나타내는 비트(bit)들의 조합으로 표현됩니다.
※ 2진수로 음수 표현하는 방법 → "2의 보수"를 이용
양수를 음수로 표현할 때 2진수의 모든 비트를 뒤집고(1→0, 0→1), 1을 더하는 방법.
11 = 01101(2)
-11 = 10010 + 1 = 10011(2)
※ 2진수로 문자 표현하는 방법 → "인코딩, 디코딩"을 이용
대표적인 인코딩 방법: UTF-8(모든 나라의 언어를 1~4 바이트로 표현하는 방법)
컴퓨터 프로그램은 C, Python 등과 같은 고급 언어를 이용해서 작성되며, 이를 컴파일러(Compiler)라고 부르는 소프트웨어에 의해 하드웨어가 이해할 수 있는 언어로 번역됩니다. 이렇게 번역된 언어를 기계어, 기계 코드라고 합니다.
어셈블리 언어
고급 언어들은 어느 컴퓨터에서 사용되든 거의 동일하지만, 기계어는 CPU마다 서로 다릅니다. 즉, CPU의 내부 구조에 따라 그 하드웨어가 이해할 수 있는 언어도 달라지는 것입니다. 이러한 언어상의 차이를 해결하기 위해 고급 언어와 기계 언어 사이에는 각 CPU 고유의 중간 언어가 존재하는데, 이 언어를 어셈블리 언어(Assembly Language)라고 부릅니다.
즉, 고급-언어 프로그램이 컴퓨터에서 처리되기 위해서는 먼저 어셈블리 프로그램으로 번역되고, 그 후 어셈블러(assembler)라는 프로그램을 통해 해당 CPU를 위한 기계어 프로그램으로 번역되어야 합니다.
여기서 LOAD, ADD, STOR을 니모닉스(mnemonics)라고 합니다. 각 어셈블리 명령어가 지정하는 동작을 개략적으로 표현합니다.
① LOAD A, X : 기억장치 X번지의 내용을 읽어서, 레지스터 A에 적재(load)
② ADD A, Y: 기억장치 Y번지 내용을 읽어서,레지스터A에 적재된 값과 더하고, 결과를 다시 A에 적재
③ STOR Z, A: A값을 기억장치 Z번지에 저장(store)
이때 LOAD A, X 명령어에 대한 기계어의 예를 들어보면 다음과 같습니다. 이 예시에서는 기계어가 두 개의 필드들로 구성되는 것으로 가정하겠습니다.
연산코드 필드: 001 / 오퍼랜드 필드: 00101
연산코드는 연산을 지정해주는 비트들이며, 오퍼랜드는 적재(load)될 데이터가 저장되어 있는 기억장치 주소를 가리킵니다.
즉, 이 기계어는 '기억장치 5번지의 내용을 읽어서 레지스터 A에 저장하라'는 명령을 나타내는 것입니다.
1. 연산 코드
명령어가 수행할 연산을 크게 4가지로 분류할 수 있습니다.
① 데이터 전송: MOVE, STORE, LOAD, PUSH, POP
② 산술/논리 연산: ADD/SUBSTRACT/MULTIPLY/DIVIDE, INCREMENT/DECREMENT: ±1, AND/OR/NOT, COMPARE: 참/거짓
③ 제어 흐름 변경: JUMP, CONDITIONAL JUMP: 조건에 부합할 때 특정 주소로 실행 순서를 옮겨라, HALT, CALL, RETURN
④ 입출력 제어: READ, WRITE, START IO, TEST IO
2. 오퍼랜드
"연산에 사용할 데이터" 또는 "연산에 사용할 데이터가 저장된 위치".
주소 지정 방식
"왜 오퍼랜드 필드에 실제 연산에 사용될 데이터가 아니라 메모리/레지스터의 주소를 담는 것인가?"
그 이유는 명령어 길이 때문입니다. 만약 명령어의 크기가 16비트인데, 연산 코드 필드가 4비트인 2-주소 명령어에서는 오퍼랜드 필드당 6비트 정도 밖에 남지 않습니다.. 정보의 가짓수가 32개밖에 되지 않는 것이죵
이처럼 연산의 대상이 되는 데이터가 저장된 위치를 유효 주소라고 합니다. 자아 그럼 이제 CPU가 메모리든 레지스터든 이 데이터의 위치를 찾을 수 있어야겠죠? 이것을 찾는 방법을 주소 지정 방식이라고 합니다. 현대 CPU는 다양한 주소 지정 방식을 사용합니다.
1. 즉시 주소 지정 방식
연산에 사용할 데이터를 오퍼랜드 필드에 직접 명시하는 방식. 빠르긴 하지만, 표현 가능한 데이터 크기가 작습니다.
2. 직접 주소 지정 방식
오퍼랜드 필드에 유효 주소를 직접적으로 명시하는 방식입니다. 이것도 유효 주소를 표현할 수 있는 크기가 연산 코드의 비트 수만큼 줄어들었다는 단점이 있습니다.
3. 간접 주소 지정 방식
유효 주소의 주소를 오퍼랜드 필드에 명시하는 방식입니다. 표현할 수 있는 유효 주소 범위가 더 넓어졌지만 일반적으로 다른 방식과 비교했을 때 느린 방식입니다. CPU가 메모리를 뒤적거리는 시간은 매우 속도가 느리기 때문에 메모리 접근을 최소화하는게 속도 면에서 중요!!
4. 레지스터 주소 지정 방식
연산에 사용할 데이터를 저장한 레지스터를 오퍼랜드 필드에 직접 명시하는 방식입니다. 그럼 왜 굳이 메모리가 아닌 레지스터를 사용해서 접근하는 방식을 정의했을까요? 바로 CPU가 메모리에 접근하는 속도보다 레지스터에 접근하는 속도가 훨씬 빠르기 때문입니다..!!
그러므로 직접 주소 지정 방식보다 빠르게 데이터에 접근할 수 있지만, 표현할 수 있는 레지스터 크기에 제한이 생길 수 있다는 단점이 있습니다.
5. 레지스터 간접 주소 지정 방식
연산에 사용할 데이터를 메모리에 저장하고, 그 유효 주소를 저장한 레지스터를 오퍼랜드 필드에 명시하는 방법입니다. 메모리 접근 횟수가 한번으로 줄어든다는 차이이자 장점이 있습니다. 메모리에 접근하는 것보다 레지스터에 접근하는 것이 더 빠르기 때문입니다.
고급 언어의 종류
1. 컴파일 언어
컴파일러에 의해 소스 코드 전체가 어셈블리어/기계어 등의 저급 언어로 변환되어 실행되는 고급언어
대표적인 컴파일 언어로는 C가 있습니다.
2. 인터프리터 언어
인터프리터에 의해 소스 코드가 한 줄씩 실행되는 고급언어
대표적인 인터프리터 언어로 Python이 있습니다.
물론 반드시 하나의 프로그래밍 언어가 반드시 둘 중 하나의 방식으로 작동한다고 생각하는 것은 오개념입니다. 그냥 '고급 언어가 저급 언어로 변환되는 방식에는 컴파일 방식과 인터프리터 방식이 있구나'정도로만 생각하면 👍
최종적으로 소스코드가 위 방식을 통해 목적 코드로 변경되는 것이죵
목적 파일과 실행 파일, 그리고 링킹
목적 파일: 목적 코드로 이루어진 파일
실행 파일: 실행 코드로 이루어진 파일(e.g. .exe 파일)
목적 코드가 실행 파일이 되기 위해서는 링킹이라는 작업을 거쳐야합니다. 그럼 링킹이란?
만약 young.c 파일의 특정 함수 F를 jun.c에서 호출하여 사용한다고 가정해보겠습니다. 단순히 컴파일해서 jun.c 파일을 jun.o라는 저급 언어로 변환된 목적 파일로 만들더라도 jun.o를 바로 실행할 수 없습니다. jun.c에는 없는 young.c의 F 함수를 알지 못하기 때문입니다. 따라서 jun.o가 실행되면 jun.o에 없는 외부 기능들, 즉 함수 F를 jun.o와 연결짓는 작업이 필요합니다.
'Computer Science > 컴퓨터 구조' 카테고리의 다른 글
RAID (0) | 2023.03.27 |
---|---|
입출력 장치 (0) | 2023.03.26 |
CPU - 명령어 사이클과 인터럽트 (0) | 2023.03.20 |
CPU - 구성 요소와 기능 (0) | 2023.03.18 |
컴퓨터의 기본 구조 (0) | 2023.03.13 |