카테고리 없음

컴퓨터 구조

whffu0123 2026. 6. 24. 17:26

컴퓨터 구조 정리 — 데이터 표현부터 성능까지

컴퓨터 구조 공부 시리즈 전체를 하나로 묶었다. 전체를 관통하는 한 문장: 컴퓨터는 명령어와 데이터를 메모리에 저장해두고, CPU가 하나씩 꺼내 실행하는 기계다. (폰 노이만 구조)

목차

  1. 데이터 표현 — 0과 1로 정수·음수·소수·문자를 표현
  2. 명령어 — CPU가 알아듣는 ISA와 fetch–decode–execute 사이클
  3. CPU 동작 — 제어장치·ALU·플래그, 그리고 파이프라인
  4. 메모리 계층 — 레지스터→캐시→RAM→디스크, 지역성, 가상 메모리
  5. 성능 — 무엇이 빠름을 결정하는가

1편 데이터 표현

컴퓨터 구조 (1) 데이터 표현 — 컴퓨터가 0과 1로 모든 걸 나타내는 법

컴퓨터 구조 공부 시리즈 1편. 컴퓨터가 정수, 음수, 소수, 문자를 어떻게 0과 1만으로 표현하는지 정리한다.


1. 양의 정수 — 2진수

10진수는 자리마다 10의 거듭제곱, 2진수는 2의 거듭제곱이다.

1011₂ = 1×2³ + 0×2² + 1×2¹ + 1×2⁰ = 8 + 0 + 2 + 1 = 11

16진수(hex)

4비트가 정확히 한 자리(0~F)로 떨어져서 비트를 짧게 적을 때 편하다. 4비트씩 묶으면 바로 변환된다.

1011 1010₂ = 0xBA

포너블에서 주소를 0x400000처럼 적는 그 hex가 이것이다.


2. 음수 — 2의 보수 (two's complement)

음수를 표현하는 가장 단순한 발상은 맨 앞 비트를 부호로 쓰는 것(부호-크기 방식)이지만, +0과 -0이 따로 생기고 덧셈 회로가 복잡해진다. 그래서 컴퓨터는 거의 항상 2의 보수를 쓴다.

만드는 법: 모든 비트를 뒤집고(1의 보수) +1

8비트로 -5 만들기:

   5  = 0000 0101
뒤집기 = 1111 1010   (1의 보수)
   +1 = 1111 1011   ← 이게 -5

뺄셈을 덧셈 회로 하나로 처리

7 + (-5)를 그냥 더한다.

  0000 0111  (7)
+ 1111 1011  (-5)
-----------
 10000 0010  → 맨 앞 자리 넘침(캐리) 버림 → 0000 0010 = 2  ✓

음수든 양수든 똑같이 더하기만 하면 되므로 하드웨어가 단순해진다.

  • 8비트 2의 보수 표현 범위: -128 ~ +127

3. 소수 — 부동소수점 (floating point, IEEE 754)

과학적 표기법(1.234 × 10³)을 2진수로 옮긴 것이 부동소수점이다. 32비트(float)는 세 부분으로 나뉜다.

부분 비트 수 역할

부호 1 양수 / 음수
지수 8 소수점 위치
가수 23 유효숫자

핵심 직관은 "소수점이 고정돼 있지 않고 지수에 따라 떠다닌다(floating)"는 것. 그래서 아주 큰 수와 아주 작은 수를 같은 형식으로 표현할 수 있다.

정밀도의 한계

0.1은 2진수로 딱 떨어지지 않아(무한 반복) 근사값으로 저장된다. 그 유명한 0.1 + 0.2 != 0.3 현상이 여기서 나온다.

>>> 0.1 + 0.2
0.30000000000000004

4. 문자 — 인코딩

문자도 결국 숫자다. 어떤 숫자가 어떤 글자인지 약속한 표가 인코딩이다.

  • ASCII: 영어와 기호를 7비트(0~127)로. A = 65, a = 97
  • 유니코드 / UTF-8: 한글·이모지까지. 실제 저장은 보통 UTF-8

정리

대상 표현 방법

양의 정수 2진수
음수 2의 보수
소수 부동소수점 (IEEE 754)
문자 인코딩 표 (ASCII, UTF-8)

셋 다 "0과 1만으로 표현하기 위한 약속"이라는 점이 공통이다.

 


2편 명령어

컴퓨터 구조 (2) 명령어 — CPU가 알아듣는 언어와 실행 사이클

 


1. ISA — CPU와 맺은 약속

ISA(Instruction Set Architecture) 는 CPU가 알아듣는 명령어 전체 목록과 규칙이다. 제조사가 정한 약속이고, 같은 ISA면 같은 기계어가 돈다.

ISA 쓰이는 곳

x86-64 인텔·AMD, PC/서버
ARM 폰, 맥, 라즈베리파이
RISC-V 교육·오픈소스

기계어 vs 어셈블리

같은 명령어의 두 표기일 뿐이고 1:1로 대응된다.

기계어:   01001000 10000011 11000000 00000001   ← CPU가 실제로 읽는 것
어셈블리:  add rax, 1                              ← 사람이 읽는 표기

포너블에서 디스어셈블러로 본 mov, push, ret이 전부 이 어셈블리다.


2. 명령어의 구조 — opcode + operand

add   rax, 1
─┬─   ─┬─  ┬
opcode  operand들
"더해라" "rax에"  "1을"
  • opcode: 무엇을 할지 (더해라, 옮겨라)
  • operand: 무엇을 대상으로 (어떤 레지스터, 어떤 값)

레지스터

CPU 안에 있는 극소수의 초고속 저장 공간. 메모리보다 압도적으로 빠르지만 개수가 적다. (x86-64 기준 rax, rbx, rsp, rip 등)

포너블에서 익숙한 두 개:

  • rsp — 스택 꼭대기를 가리킴
  • rip — 다음에 실행할 명령어 주소를 가리킴

3. 명령어 실행 사이클 — fetch / decode / execute

CPU는 명령어 하나를 처리할 때 항상 같은 3단계를 반복한다.

  1. Fetch (가져오기) — rip이 가리키는 주소에서 명령어를 읽어온다. 읽고 나면 rip은 자동으로 다음 명령어를 가리키도록 증가한다.
  2. Decode (해석하기) — 가져온 기계어 비트를 제어장치가 해석한다. opcode와 operand를 풀어낸다.
  3. Execute (실행하기) — 실제 동작을 수행한다. 연산이면 ALU가 계산하고 결과를 레지스터/메모리에 쓴다.

그리고 다시 ①로. 이 사이클이 초당 수십억 번 도는 게 "프로그램 실행"의 실체다.

예제로 보는 흐름

mov rax, 5      ; rax에 5를 넣어라
add rax, 3      ; rax에 3을 더해라  → rax = 8
push rax        ; rax(8)를 스택에 올려라  → rsp 감소
ret             ; 스택에서 주소를 꺼내 rip에 넣고 점프

각 줄마다 fetch→decode→execute가 한 바퀴씩 돌고 rip은 한 줄씩 내려간다. ret에서만 rip이 스택값으로 점프한다.

포너블과의 연결

ret 명령이 하는 일이 바로 스택에서 주소를 꺼내 rip에 넣는 것 이다.

  • 스택의 리턴 주소를 덮어쓰면 → 다음 fetch가 엉뚱한 주소에서 일어남 → BOF로 실행 흐름 탈취
  • ROP도 "ret이 rip을 스택값으로 계속 바꾼다"는 이 사이클을 악용한 것

명령어 실행 사이클을 이해하면 메모리 손상 익스플로잇의 원리가 그대로 보인다.


4. RISC vs CISC

ISA 설계 철학의 두 갈래.

CISC RISC

대표 x86 ARM, RISC-V
방식 복잡한 명령어를 많이 단순한 명령어 몇 개를 조합

요즘은 x86도 내부에서 복잡한 명령어를 잘게 쪼개 실행해 경계가 흐려졌지만, 폰·맥이 ARM(RISC)으로 간 흐름은 알아둘 만하다.


정리

  • CPU는 ISA라는 약속된 명령어 목록을 갖는다.
  • 각 명령어를 fetch–decode–execute 사이클로 처리한다.
  • 그 과정의 상태를 레지스터(특히 rip, rsp)에 들고 있다.
  • 어셈블리는 기계어에 사람이 읽을 이름을 붙인 것뿐이다.

3편 CPU 동작

컴퓨터 구조 (3) CPU 동작 — 제어장치, ALU, 그리고 파이프라인

컴퓨터 구조 공부 시리즈 3편. CPU 안에서 부품들이 어떻게 분업하는지, 그리고 명령어를 겹쳐 처리해 속도를 높이는 파이프라인을 정리한다.

들어가며

2편에서 명령어가 fetch–decode–execute 사이클로 처리된다고 했다. 이번엔 그 사이클을 CPU 안의 부품들이 실제로 어떻게 나눠 처리하는지, 그리고 어떻게 여러 명령어를 겹쳐 속도를 끌어올리는지 본다.


1. 세 부품의 분업

CPU 핵심 부품은 셋이다.

   
제어장치 (Control Unit) 명령어 해석 후 제어 신호를 뿌림. 직접 계산 안 함
ALU (산술논리연산장치) 실제 산술·논리 연산 수행
레지스터 연산할 값·결과를 잠깐 보관

플래그(flag)

ALU는 계산 결과와 함께 플래그도 내놓는다. "결과가 0이었나", "자리 넘침이 있었나", "음수였나" 같은 부가 정보를 플래그 레지스터에 기록한다.

이게 중요한 이유: 조건 분기(if문, 반복문)가 플래그로 작동한다.

cmp rax, rbx    ; rax와 rbx를 비교 (사실은 빼서 플래그만 갱신)
je  target      ; 결과가 0이면(같으면) target으로 점프

cmp는 두 값을 빼서 결과는 버리고 플래그만 갱신한다. je(jump if equal)는 그 플래그를 보고 점프를 결정한다. 우리가 쓰는 모든 if문·for문이 밑바닥에선 이 "비교 → 플래그 → 조건 점프"로 굴러간다.


2. 파이프라인 — 현대 CPU 성능의 핵심

명령어 하나를 끝까지 처리하고 다음 걸 시작하면 부품이 노는 시간이 많다. 그래서 명령어 처리를 여러 단계로 쪼개고 여러 명령어를 한 칸씩 어긋나게 겹쳐서 흘려보낸다.

 

5단계 파이프라인 타임라인

단계: IF(fetch) / ID(decode) / EX(execute) / MEM(메모리 접근) / WB(결과 기록)

사이클 1 2 3 4 5 6 7 8

명령어1 IF ID EX MEM WB      
명령어2   IF ID EX MEM WB    
명령어3     IF ID EX MEM WB  
명령어4       IF ID EX MEM WB

5번 사이클을 세로로 보면 다섯 단계가 동시에 가동된다. 부품이 노는 시간이 사라진다.

효과

  • 파이프라인 없음: 명령어당 5사이클 × 4개 = 20사이클
  • 파이프라인: 첫 명령어가 5사이클에 끝난 뒤로는 매 사이클마다 1개씩 완성 → 4개를 8사이클

명령어가 많아질수록 "거의 매 사이클당 1명령어"에 수렴한다.


3. 해저드(hazard) 

겹쳐 처리하기 때문에 생기는 문제.

데이터 해저드

앞 명령어의 결과를 뒤 명령어가 곧바로 써야 할 때.

add rax, rbx    ; rax를 계산 (결과는 WB 단계에서야 확정)
sub rcx, rax    ; 그 rax를 바로 써야 하는데 아직 안 끝남!

sub가 rax를 읽으려는 시점에 add 결과가 기록 전이라 옛날 값을 읽을 위험.

해결책: 멈춰서 기다리거나(stall), 계산 끝난 값을 기록 전에 바로 넘겨주는 포워딩(forwarding).

제어 해저드

분기(점프)에서 발생. je 같은 조건 점프는 "어디로 갈지"가 EX 단계에서야 정해지는데, 파이프라인은 이미 그 뒤 명령어들을 빨아들이고 있다. 점프가 일어나면 빨아들인 명령어들을 버려야 한다.

해결책: 분기 예측(branch prediction). "이 분기는 아마 점프할 것"이라고 미리 찍어서 진행하고, 맞으면 이득, 틀리면 비우고 다시 채운다.

보안과의 연결

분기 예측은 보안과도 이어진다. Spectre 같은 취약점이 "예측해서 미리 실행했다가 틀려서 되돌리는" 동작의 흔적(캐시 상태)을 악용한 것이다.


정리

  • CPU 안에서 제어장치가 지휘하고 ALU가 계산하며, 결과의 부가정보는 플래그에 남아 조건 분기를 좌우한다.
  • 명령어들을 단계별로 쪼개 파이프라인으로 겹쳐 처리해 속도를 끌어올린다.
  • 그 대가로 해저드가 생겨 포워딩·분기예측으로 메운다.

4편 메모리 계층

들어가며

메모리에는 근본적인 딜레마가 있다. 빠른 메모리는 비싸고 작고, 싼 메모리는 느리고 크다. 둘 다 가질 순 없다.

그래서 컴퓨터는 빠른 걸 조금, 느린 걸 잔뜩 두고 계층으로 쌓아서 마치 빠르면서도 큰 메모리가 있는 것처럼 보이게 만든다.


1. 계층 구조

위로 갈수록 빠르고 작고 비싸며, 아래로 갈수록 느리고 크고 싸다.

 

레지스터 1 사이클 수십~수백 바이트 CPU 안
캐시 (L1/L2/L3) 수~수십 사이클 KB~수십 MB CPU 안/근처
메인 메모리 (RAM) 수백 사이클 GB 메인보드
디스크 (SSD/HDD) 수십만~수백만 사이클 TB 저장장치

 


2. 캐시가 통하는 이유 — 지역성(locality)

빠른 캐시는 작아서 데이터를 조금밖에 못 담는데, 어떻게 대부분의 접근을 빠르게 처리할까? 답은 프로그램이 메모리를 무작위로 쓰지 않는다는 것. 이를 지역성이라 한다.

시간 지역성 (temporal locality)

한 번 쓴 데이터는 곧 또 쓸 가능성이 높다. (반복문 변수 i) → 한 번 접근한 데이터를 캐시에 남겨두면 다음 접근이 빨라진다.

공간 지역성 (spatial locality)

한 데이터를 쓰면 그 근처 데이터도 곧 쓸 가능성이 높다. (배열 순회) → 캐시는 데이터 하나만 가져오지 않고 그 주변을 한 덩어리(캐시 라인)로 통째로 가져온다.

히트와 미스

  • 캐시 히트(hit): 원하는 데이터가 캐시에 있음
  • 캐시 미스(miss): 없어서 아래 계층까지 내려가야 함

히트율이 높을수록 빨라진다.

코딩에의 영향

2차원 배열을 행 우선으로 도느냐 열 우선으로 도느냐에 따라 속도가 몇 배씩 차이 나는 게 공간 지역성 때문이다. (메모리에 연속으로 깔린 순서대로 접근하느냐)


3. 가상 메모리 — 포너블과 만나는 지점

프로그램이 보는 메모리 주소(0x400000 같은)는 사실 진짜 물리 주소가 아니다. 운영체제가 각 프로세스에게 "너만의 깨끗한 메모리 공간이 0번지부터 쭉 있어"라고 보여주는 가상 주소다. 실제로는 OS와 하드웨어(MMU)가 가상 주소를 그때그때 물리 주소로 번역한다.

왜 이렇게 하는가

  1. 격리 — 각 프로세스가 서로의 메모리를 못 건드림 (보안·안정성)
  2. 스왑 — 물리 메모리보다 큰 주소 공간 사용 (디스크를 메모리처럼 빌림)
  3. 일관성 — 프로그램마다 항상 같은 주소 배치를 가정하고 작성 가능

포너블과의 연결

  • 같은 바이너리를 여러 번 실행해도 스택·코드가 비슷한 주소에 뜨던 게 이 가상 주소라서다. (물리 위치는 매번 달라도 가상 주소는 일정)
  • ASLR은 이 가상 주소 배치를 일부러 매번 무작위로 흩뜨리는 방어 기법. 가상 메모리라는 토대가 있어야 가능하다.
  • NX도 "이 가상 메모리 페이지는 실행 금지"라고 페이지 단위로 표시하는 것.

정리

  • 메모리는 빠르고 작은 것(레지스터·캐시)부터 느리고 큰 것(RAM·디스크)까지 계층으로 쌓인다.
  • 지역성(시간·공간) 덕분에 작은 캐시로도 대부분의 접근을 빠르게 처리한다.
  • 프로그램이 보는 주소는 가상 메모리이며, 이게 격리·스왑·ASLR·NX의 토대다.

5편 성능

컴퓨터 구조 (5) 성능 — 무엇이 컴퓨터를 빠르게 만드는가


1. 클럭만 보면 안 되는 이유 — 성능 공식

프로그램 실행 시간은 세 가지의 곱으로 분해된다.

실행 시간 = 명령어 수 × CPI × 클럭 주기

요소 의미 영향을 주는 것

명령어 수 프로그램이 몇 개의 기계어로 이뤄졌나 컴파일러, 알고리즘
CPI (Cycles Per Instruction) 명령어 하나당 평균 사이클 수 파이프라인, 캐시 미스
클럭 주기 한 사이클이 얼마나 짧은가 "3GHz" = 초당 30억 사이클

핵심 통찰

클럭(GHz)은 셋 중 하나일 뿐이다. 클럭이 높아도 CPI가 나쁘거나(캐시 미스 폭발) 명령어 수가 많으면 느리다.

그래서 "A칩 3GHz, B칩 2.5GHz니까 A가 빠르다"는 단순 비교가 자주 틀린다. ARM 맥북이 더 낮은 클럭으로도 빠른 게 CPI와 명령어 효율 덕분이다.


2. 왜 클럭 경쟁이 멈췄나 — 멀티코어로 간 이유

2000년대 초까지 성능을 올리는 가장 쉬운 길은 클럭을 높이는 것이었다. 그런데 벽에 부딪혔다.

파워 월 (power wall)

클럭을 올릴수록 전력 소비와 발열이 폭증한다 (대략 클럭의 제곱 이상). 어느 선을 넘으니 칩이 녹을 지경이 돼서 클럭 경쟁이 사실상 멈췄다.

멀티코어로 전환

한 코어를 더 빠르게 만드는 대신 느리더라도 코어를 여러 개 두기로 했다. 요즘 CPU의 "8코어, 16코어"가 이 흐름의 결과다.

함정: 암달의 법칙 (Amdahl's law)

코어가 8개라고 프로그램이 자동으로 8배 빨라지지 않는다. 일을 8조각으로 나눠 동시에 시켜야 하는데, 나눌 수 없는 부분(순차적으로 해야만 하는 부분)이 발목을 잡는다.

전체 중 병렬화 가능한 비율이 최대 속도 향상의 한계를 정한다.

예: 프로그램의 90%만 병렬화되면, 코어를 무한히 늘려도 최대 10배까지밖에 못 빨라진다.


3. 성능을 어떻게 재나 — 벤치마크

공식만으로는 실사용 성능을 다 못 잡아서, 벤치마크(benchmark) 라는 표준 작업 묶음을 돌려 비교한다. (대표: SPEC)

벤치마크에만 최적화하면 실제 체감과 어긋날 수 있어서 여러 종류를 함께 본다.


정리

  • 성능은 명령어 수 × CPI × 클럭 주기로 분해되고, 클럭은 그중 하나일 뿐이다.
  • 클럭 경쟁은 발열(파워 월)로 멈췄고, 그래서 멀티코어로 갔다.
  • 멀티코어에는 암달의 법칙이라는 한계가 있다.
  • 측정은 벤치마크로 한다.