강민철님의 "혼자 공부하는 컴퓨터 구조+운영체제"을 기반으로 학습한 게시물입니다.
📕 목차
1. 0과 1로 숫자 표현
2. 0과 1로 문자 표현
3. 소스코드와 명령어
4. 명령어 구조
1. 0과 1로 숫자 표현
📌 정보 단위
- 1bit는 0(off), 1(on) 2개의 정보를 나타낼 수 있다. → n-bit는 2^(n)개
- bit - byte - kB - MB - GB - TB (단, 1byte=8bit이고 나머지는 1:1000)
- 1kB는 1,024byte, 1MB는 1,024KB...로 표현하는 것은 잘못된 관습
- 1,024개 묶어 표현한 단위는 KiB, MiB, GiB, TiB이다.
- 워드(word) : CPU가 한 번에 처리할 수 있는 데이터 크기
- 현대 컴퓨터 워드 크기는 대부분 32비트 또는 64비트
- 인텔 x86 CPU는 32비트 워드 CPU, 인텔 x64 CPU는 64비트 워드 CPU에 해당한다
- 정의된 워드의 절반 크기(하프워드), 1배 크기(풀워드), 2배 크기(더블워드)로 나눌 수 있다.
📌 이진법 (binary)
- 모든 수를 0과 1만으로 숫자를 표현한다.
- Sign and magnitude, one's complement, two's complement 등의 부호 체계가 존재한다.
- 1의 보수는 비트 뒤집기, 2의 보수는 1의 보수에서 +1을 한 결과라고 보면 된다. (음수)
- 컴퓨터는 음수를 판단하기 위해 플래그(flag)를 사용한다.
- 11의 2의 보수(0101)와 십진수 5인 0101을 컴퓨터는 플래그로서 구분한다.
- 2의 보수 체계는 한계가 존재한다.
- 0000(2) → 1 0000(2) : carry값을 버린다
- 1000(2) → 1000(2) : 자기 자신으로 돌아오는 문제
- 즉, n비트로 -2^(n)과 2^(n) 수를 동시에 표현할 수 없다.
📌 십육진법
- 한 글자로 0~F의 정보를 표현하므로, 이진수에 지해 더 적은 자릿수로 많은 정보를 표현할 수 있다.
- 15(16)은 수학적 표기 방식이고, 0x15는 코드상 표기 방식이다.
- 이진수 ↔ 십육진수 사이의 변환히 쉽다.
- 16진수는 2^(4)bit가 필요.
- 즉, 십육진수 한 글자가 4비트 이진수로 취급할 수 있다.
- 0001 1010 0010 1011(2) ↔ 1A2B(16)
- 하드웨어와 밀접하게 맞닿아 있는 개발 분야에서는 코드에 십육진수를 직접 쓰는 경우도 많다.
#define BCM2711_PERL_BASE 0xFE000000
2. 0과 1로 문자 표현
📌 문자 집합과 인코딩
- 문자 집합 (character set)
- 컴퓨터가 인식하고 표현할 수 있는 문자의 모음
- 컴퓨터는 문자 집합에 속해 있지 않은 문자를 이해하지 못한다.
- 문자 인코딩 (character encoding)
- 문자 집합에 속한 문자를 0과 1로 변환하는 과정
- 0과 1로 이루어진 결과가 문자 코드가 된다.
- 문자 디코딩 (character decoding)
- 인코딩의 반대 과정, 0과 1을 사람이 이해할 수 있는 문자로 변환하는 과정
📌 아스키 코드 (ASCII code)
아스키 코드표(ASCII Table)
아스키 코드표다. 컴퓨터는 문자를 문자로 기억하지 않는다. 그대신 순자로 기억하고 표현한다. 예를 들면 문자 'A'는 65로, 'B'는 66으로 표현한다. 그런데 사람마다 규칙을 개인대로 정하면 자칫
stepbystep1.tistory.com
- 초창기 문자 집합 중 하나로, 영어 알파벳과 아라비아 숫자, 일부 특수 문자를 포함한다.
- 아스키 코드는 8비트 중 오류 검출을 위한 1비트를 제외한 7비트로 표현한다. (2^(7) = 128개 문자)
- 0~127까지의 숫자가 하나의 아스키 문자와 일대일 대응된다. (code point; 글자에 부여된 고유한 값)
- 8비트의 확장 아스키가 등장했지만, 모든 문자를 표현하는 것이 불가능하다.
📌 EUC-KR
- 한글 인코딩 방식은 영어와 다르다
- 한글은 각 음절 하나하나가 초성, 중성, 종성의 조합으로 구성된다.
- 완성형 인코딩 : 완성된 하나의 글자에 고유한 코드를 부여한다. ('가'는 1, '나'는 2, '다'는 3,...)
- 조합형 인코딩 : 초성, 중성, 종성을 위한 각각의 비트열을 할당하여 글자 코드를 완성
- EUC-KR은 완성형 인코딩 방식이며, 초성/중성/종성 모두 결합된 한글 단어에 2바이트 크기 코드 부여
- 한글은 한 글자에 2바이트 코드가 부여되며, 2,350개 정도의 한글 단어를 표현 가능 (모든 수 표현 불가능)
- EUD-KR 확장 버전인 마이크로소프트의 CP949 또한 한글 전체를 표현하기에 넉넉하지는 않다.
📌 유니코드와 UTF-8
- 유니코드 : 현대 문자를 표현하기 위한 표준 문자 집합
- UTF는 글자에 부여된 값 자체를 인코딩된 값으로 삼지 않는다.
- UTF-8
- 1~4 바이트까지의 인코딩 결과를 만들어낸다.
- 0~007F(16) 1바이트, 0080(16)~17FF(16) 2바이트, 0800(16)~FFFF(16) 3바이트, 10000(16)~10FFFF(16) 4바이트
- 한(D55G), 글(AE00)을 UTF-8로 인코딩하면 3바이트로 표현된다.
start 코드 포인트 | end 코드 포인트 | 1바이트 | 2바이트 | 3바이트 | 4바이트 |
0000 | 007F | 0XXXXXXX | - | - | - |
0080 | 07FF | 110XXXXX | 10XXXXXX | - | - |
0800 | FFFF | 1110XXXX | 10XXXXXX | 10XXXXXX | - |
10000 | 10FFFF | 11110XXX | 10XXXXXX | 10XXXXXX | 10XXXXXX |
3. 소스코드와 명령어
프로그래밍 언어로 만든 소스 코드는 컴퓨터 내부에서 명령어로 변환된다.
📌 고급 언어와 저급 언어
기계어, 어셈블리어, 고급 언어의 차이점이 뭐지?
기계어 (Machine Language) 기계어는 컴퓨터가 사용하는 언어 기계어는 컴퓨터가 사용하는 언어이다. 좀 ...
blog.naver.com
- 고급 언어(high-level programming language)
- 사람이 이해하고 작성하기 쉽게 만들어진 언어 (그나마 C언어가 low-level에 가깝다)
- 컴퓨터는 이해하지 못한다.
- 저급 언어(low-level programming language)
- 컴퓨터가 직접 이해하고 실행할 수 있는 언어
- 고급 언어로 작성된 소스코드는 반드시 저급 언어, 즉 명령어로 변환되어야 함
- 기계어(machine code)와 어셈블리어(assembly language) 두 가지 종류가 있다.
- 기계어 : 0과 1의 명령어 비트로 이루어진 언어
- 어셈블리어 : 기계어로 표현된 명령어를 읽기 편한 형태로 번역한 언어
어셈블리어란 '작성의 대상'일 뿐만 아니라 '관찰의 대상'이 되기도 한다.
프로그램이 어떤 절차로 작동하는지를 가장 근본적인 단계에서 추적할 수 있기 때문이다.
📌 컴파일 언어와 인터프리터 언어
고급 언어가 저급 언어로 변환되는 방식에는 컴파일 방식과 인터프리트 방식이 있다.
1️⃣ 컴파일 언어
- 컴파일러에 의해 소스 코드 전체가 저급 언어로 변환되어 실행되는 고급 언어
- 컴파일(compile) : 컴파일 언어로 작성된 소스 코드를 저급 언어로 변환하는 과정
- 컴파일러(compiler) : 컴파일을 수행해주는 도구
- 목적 코드(object code) : 컴파일을 통해 저급 언어로 변환된 코드
- 컴파일러가 소스 코드 내에서 오류를 하나라도 발견하면 해당 소스 코드는 컴파일에 실패한다.
2️⃣ 인터프리터 언어
- 인터프리터에 의해 소스 코드가 한 줄씩 실행되는 고급 언어
- 인터프리터(interpreter) : 소스 코드를 한 줄씩 저급 언어로 변환하여 실행해 주는 도구
- 소스 코드 전체를 저급 언어로 변환하는 시간을 기다릴 필요가 없다
- N번째 줄에 오류가 있어도, N-1번째 줄까지는 올바르게 수행된다.
- 컴파일 언어보다 느리다 (한 줄씩 저급 언어로 해석하고 실행하기 때문)
💡 대부분 고급 언어의 경우 칼로 자르듯이 컴파일/인터프리터 언어를 구분하기는 힘들다.
📌 목적 파일 vs 실행 파일
- 목적 파일: 목적 코드로 이루어진 파일
- 실행 파일: 실행 코드로 이루어진 파일
4. 명령어 구조
명령어의 종류와 생김새는 CPU마다 다르기 때문에 공통으로 이해하는 대표 몇 가지만 알아보자.
📌 연산 코드와 오퍼랜드
- 명령어는 연산 코드와 오퍼랜드로 구성되어 있다.
- "더해라" "a와" "b를"
- "빼라" "메모리 10번지의 값과" "메모리 30번지 값"
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 1
...
add eax, edx
...
pop rbp
ret
- 연산 코드(operation code)
- 명령어(CPU)가 수행할 연산 → 연산자
- 크게 4가지로 구성된다.
- 데이터 전송 : MOVE, STORE, LOAD, PUSH, POP, ...
- 산술/논리 연산 : ADD, SUBTRACT, MULTIPLY, DIVIDE, AND, OR, NOT, ...
- 제어 흐름 변경: JUMP, CONDITIONAL JUMP, HALT, CALL, ...
- 입출력 제어 : READ, WRITE, START IO, TEST IO, ...
- 오퍼랜드(operand) :
- 연산에 사용할 데이터, 또는 연산에 사용할 데이터가 저장된 위치 → 피연산자
- 오퍼랜드 필드는 데이터(숫자, 문자 등) 또는 주소(메모리, 레지스터)가 올라올 수 있다.
- 오퍼랜드 필드는 주소 필드라고도 부른다.
- 명령어 안에 하나도 없을 수도, 한 개 혹은 여러 개 있을 수도 있다.
- 오퍼랜드 개수(n)에 따라 "n-주소 명령어"라고 부른다.
- 오퍼랜드 필드에는 연산에 사용할 데이터를 직접 명시하기 보다, 데이터가 저장된 위치를 명시한다.
📌 주소 지정 방식
오퍼랜드 필드에 메모리나 레지스터의 주소를 담는 이유는 명령어 길이의 제약 때문이다.
- 명령어 크기가 16bit, 연산 코드 필드가 4bit인 2-주소 명령어에서 오퍼랜드 필드는 필드당 6bit 밖에 남지 않는다.
- 하나의 오퍼랜드 필드로 표현 가능한 정보 가짓수는 2^(6)개의 제한이 있다.
- 오퍼랜드 필드에 메모리 주소를 담는다면, 데이터 크기는 하나의 메모리 주소에 저장할 수 있는 공간만큼 커진다. 이를 주소 지정 방식(addressing mode)라고 한다.
- 유효 주소(effective address) : 연산의 대상이 되는 데이터가 저장된 위치
- 종류
- 즉시 주소 지정 방식 : 연산에 사용할 데이터를 오퍼랜드 필드에 직접 명시 (크기 제한)
- 직접 주소 지정 방식 : 유효 주소를 직접적으로 명시
- 간접 주소 지정 방식 : 유효 주소의 주소를 오퍼랜드 필드에 명시 (범위 확장, 속도 느림)
- 레지스터 주소 지정 방식 : 연산에 사용할 데이터를 저장한 레지스터를 직접 명시 (크기 제한)
- 레지스터 간접 주소 지정 방식 : 연산에 사용할 데이터를 메모리에 저장하고, 유효 주소를 저장한 레지스터를 명시