CPU의 진화
CPU 관점의 프로그램
- 포토샵, 크롬, helloWorld 출력 등 cpu 입장에서는 기계 명령어일 뿐이다. 명령어의 양이 다를 뿐
- 기계어는 실행 파일에 저장된다. 프로그램이 실행되면 실행 파일에 있는 명령어들을 메모리에 적재하고 CPU가 실행한다.
- 프로그래머 관점에서 CPU의 역활은 아주 단순하다고 생각해볼 수 있습니다.
CPU에게 명령어 집합이란?
- CPU에게 명령어 집합(ISA: Instruction Set Architecture)은 CPU가 이해하고 실행할 수 있는 모든 기본 명령어들의 목록입니다. 요리사에게 레시피북이 있듯이, CPU에게는 수행 가능한 모든 동작이 정의된 명령어 집합이 있습니다.
- 예를 들어, CPU는 다음과 같은 기본 명령어들을 가지고 있습니다:
- ADD: 두 숫자를 더하기
- MOV: 데이터를 이동시키기
- JMP: 다른 위치로 점프하기
- CMP: 두 값을 비교하기
- 프로그래머는 이러한 단순한 명령어들을 조합하여 복잡한 프로그램을 만듭니다. 최초의 프로그래머들은 이 명령어 집합 문서를 직접 보며 어셈블리어로 프로그래밍했습니다.
- 사람마다 모국어가 다르듯이, CPU 제조사와 설계 철학에 따라 서로 다른 명령어 집합을 사용합니다:
- x86/x64 (Intel, AMD): 복잡하지만 강력한 CISC 방식
- ARM (스마트폰, 태블릿): 전력 효율적인 RISC 방식
- RISC-V: 오픈소스 명령어 집합
명령어 집합의 변천사
최근에 나온 기술인 AI Assistant와 AI Editer를 신뢰하지 않아 대중적으로 활용하지 않는 것처럼 컴파일러도 처음에 나왔을 때에는 신뢰하는 사람이 적어 많은 프로그램이 어셈블리어로 작성되었습니다.
1. 초기: 단순한 명령어 집합 (1950-60년대)
초기 CPU: "더하기, 빼기, 이동하기만 할 수 있어요"
프로그래머: "모든 걸 이 단순한 명령어로 만들어야 해... 😭"
2. CISC의 등장: "더 똑똑한 CPU를 만들자!" (1970-80년대)
개발자들의 요구:
- "배열 접근을 한 명령어로!"
- "함수 호출도 한 번에!"
- "문자열 복사도 명령어 하나로!"
결과: x86 같은 복잡한 명령어 집합 탄생
장점: 프로그래밍이 쉬워짐
단점: CPU가 복잡하고 비용이 비싸짐
3. RISC의 반격: "단순한 게 최고야!" (1980년대~)
RISC 철학:
- "복잡한 명령어 100개보다 단순한 명령어 30개가 낫다"
- "컴파일러가 똑똑해지면 되잖아?"
결과: ARM, RISC-V 등장
장점: CPU가 단순하고 빠름
단점: 컴파일러가 더 열심히 일해야 함
4. 현재: 추상화의 시대
1980년: 프로그래머가 어셈블리어로 직접 코딩
2025년: print("Hello World") → 컴파일러가 알아서 처리
폰 노이만 구조의 핵심

폰 노이만의 혁신적 아이디어:
"프로그램도 데이터다!"
이전: 프로그램은 배선으로 고정 (계산기처럼)
이후: 프로그램도 메모리에 저장 (USB처럼 교체 가능)
메모리에서 프로그램과 데이터의 공존
메모리 상태:
┌─────────────┐
│ 게임 코드 │ ← "마리오가 점프하는 명령어"
├─────────────┤
│ 게임 데이터 │ ← "마리오의 현재 위치, 점수"
├─────────────┤
│ 운영체제 │
└─────────────┘
둘 다 똑같은 0과 1의 나열!
메모리 크기의 변화
1985년 슈퍼마리오: 40KB (현재 카톡 이모티콘 하나 크기)
2025년 일반 PC: 16GB (40만 배 증가!)
당시 개발자의 고민:
"40KB에 전체 게임을 넣어야 해...
한 바이트도 아껴야 한다!"
메모리 절약을 위한 명령어 설계
메모리 아끼는 방법:
1. 복잡한 명령어 = 많은 일을 한 번에
예: "MOVS" → 문자열 전체 복사 (한 명령어로!)
2. 가변 길이 명령어
자주 쓰는 명령어: 1바이트 (예: ADD)
가끔 쓰는 명령어: 3바이트 (예: 특수 연산)
→ 모스 부호처럼 자주 쓰는 건 짧게!
마이크로 코드의 탄생
1. 초기 CPU: 하드웨어로 때려박기
- ADD가 실행되면 전용 회로가 실행되어 덧셈을 수행
- 장점: 빠르고 단순함
- 단점: 고치려면 하드웨어를 수정해야함;;
2. 문제 상황
- 예시로 엔지니어가 "이거 명령어 추가해주세요"라고 요청하면 "하드웨어 회로를 직접 수정해야되기 때문에 6개월 걸립니다." 답변하는 상황 발생
3. 마이크로 코드 탄생
- "복잡한 명령어를 직접 구현하지 말고 간단한 명령어의 집합으로 만들자!"라는 아이디어로 탄생
- 곱셈 회로를 ADD로 여러번 반복한 뒤 SHIFT로 자리수 이동하면 곱셈 코드 완성!
4. 소프트웨어처럼 유연한 하드웨어 활용
- 하드웨어 회로 변경 -> ROM 업데이트로 바뀌면서 유연하게 하드웨어로 활용 가능
5. 마이크로 코드의 한계
- 😈 디버깅의 어려움
- print 없음 (CPU 내부라서...)
- 디버거 없음 (너무 저수준이라...)
- 한 번 실수하면 CPU 전체 먹통
- 테스트하려면 실제 칩 제작 필요
- 💸 트랜지스터의 남용
- 전체 트랜지스터의 3분의 1을 차지
- 간단한 ADD 명령어도 마이크로코드 거쳐감
- 직접 연결하면 1 사이클 → 마이크로코드 거치면 3~4 사이클
축소 명령어 집합의 탄생
1. 시대 배경: "더 이상 메모리 아낄 필요 없어!"
1970년: 메모리 1KB = $1,000 😱
1985년: 메모리 1KB = $1 😊
개발자들의 깨달음:
"메모리 아끼려고 복잡한 명령어 만들었는데...
이제 메모리 싸니까 그럴 필요 없잖아?"
2. 컴파일러의 진화
1970년대: "컴파일러 못 믿어... 어셈블리로 직접 짜자"
1980년대: "어? 컴파일러가 나보다 최적화 잘하네?"
프로그래머의 변화:
Before: MOV AX, BX (어셈블리)
After: int x = y; (C 언어)
3. RISC의 핵심 아이디어: "단순함의 미학"
CISC (복잡한 명령어)
MULT [A], [B], [C] // 메모리에서 읽고, 곱하고, 저장까지 한방에!
└→ 마이크로코드가 10단계로 분해 실행 (느림...)
RISC (단순한 명령어)
LOAD R1, [A] // 1. 메모리 → 레지스터
LOAD R2, [B] // 2. 메모리 → 레지스터
MULT R3, R1, R2 // 3. 곱셈
STORE R3, [C] // 4. 레지스터 → 메모리
장점: 각 명령어가 1 사이클! (마이크로코드 없이 직접 실행)
4. 메모리 접근의 엄격한 통제
RISC의 철학: "메모리는 LOAD/STORE로만!"
❌ 금지:
ADD [메모리], [메모리] // 메모리끼리 직접 연산? 안돼!
✅ 허용:
LOAD R1, [메모리] // 1. 일단 가져와
ADD R1, R2 // 2. 레지스터에서 연산
STORE R1, [메모리] // 3. 다시 저장
이유: "단순하고 예측 가능한 동작 = 더 빠른 속도"
5. 결과: 단순함의 승리
CISC CPU(x86)
- 🧶 복잡한 회로
- 🐌 마이크로 코드를 통해 실행하여 느린 속도
RSIC CPU(ARM)
- 🗒️ 단순한 회로
- 🚀 직접 실행하여 빠른 속도
현실 예시:
- CISC = 맥가이버칼 (만능이지만 복잡하고 사용하기 어려움)
- RISC = 일반 드라이버 (한 가지만 잘하지만 극도로 효율적)
명령어 파이프라인 - CPU의 조립 라인
1. RISC의 선물: "남은 트랜지스터로 뭘 할까?"
CISC 시절:
┌─────────────────┐
│ 30% 마이크로코드 │ ← "이제 필요 없어!"
│ 70% 기타 회로 │
└─────────────────┘
RISC 이후:
┌─────────────────┐
│ 30% 여유 공간! │ ← "파이프라인 만들자!"
│ 70% 기타 회로 │
└─────────────────┘
2. 명령어 실행 시간의 균일화
CISC의 문제:
ADD: 1 클럭 ⚡
MUL: 5 클럭 ⚡⚡⚡⚡⚡
STRLEN: 50 클럭 ⚡⚡⚡... (문자열 길이 계산)
→ "예측 불가능... 스케줄링 어려워!"
RISC의 장점:
ADD: 1 클럭 ⚡
SUB: 1 클럭 ⚡
AND: 1 클럭 ⚡
→ "모든 명령어가 1 클럭! 완벽해!"
3. 파이프라인: 세탁소의 지혜
파이프라인 없이 (전통 방식):
빨래 1: 세탁(30분) → 건조(30분) → 개기(30분) = 90분
빨래 2: 세탁 → 건조 → 개기
총 시간: 180분 😴
파이프라인 사용:
시간: 30분 60분 90분 120분
빨래 1: [세탁] → [건조] → [개기] → 완료!
빨래 2: [세탁] → [건조] → [개기]
빨래 3: [세탁] → [건조]
총 시간: 120분! (33% 단축) 🚀
4. CPU 파이프라인의 실제 동작
클럭 1: [명령어1: Fetch]
클럭 2: [명령어1: Decode] [명령어2: Fetch]
클럭 3: [명령어1: Execute][명령어2: Decode] [명령어3: Fetch]
매 클럭마다 명령어 하나씩 완료!
(실제로는 3배 빠른 효과)
5. 성능 비교: CISC vs RISC
CISC (복잡한 명령어):
STRLEN 명령어:
- 마이크로코드 해석: 2 클럭
- 실제 실행: 8 클럭
- 총: 10 클럭 😵
- 파이프라인? "명령어마다 시간이 달라서 불가능..."
RISC (단순한 명령어 + 파이프라인):
문자열 길이 계산:
LOAD → 1 클럭
CMP → 1 클럭 } 파이프라인으로
JUMP → 1 클럭 } 동시 처리!
...
총: 실질적으로 1~2 클럭만에 처리! 🚀
현실과 비유한다면?
- CISC: 요리사 한 명이 전체 요리 완성 (느림)
- RISC + 파이프라인: 조립 라인처럼 각자 한 가지씩 (빠름)
- 똑같은 트랜지스터 수로 5~10배 성능 향상!
x86의 위기와 부활 - 공룡의 진화
1. 1990년대 초: x86의 위기
시장 상황:
ARM/RISC: "우리가 5배 빨라요! 🚀"
x86: "하지만... 기존 소프트웨어가..." 😰
주가 그래프:
Intel 📉 (곤두박질)
ARM 📈 (급상승)
업계 예측: "x86은 5년 안에 사라질 것"
2. Intel의 천재적 발상: "겉은 CISC, 속은 RISC"
문제: x86 명령어는 너무 복잡해서 파이프라인 불가능
해결책: "복잡한 명령어를 내부에서 쪼개자!"
프로그래밍 비유:
public void 복잡한함수() { // x86 명령어 (겉모습)
내부함수1(); // 마이크로 명령어
내부함수2(); // 마이크로 명령어
내부함수3(); // 마이크로 명령어
}
3. 마이크로 명령어의 탄생
예시: x86의 STRLEN (문자열 길이) 명령어
겉모습 (프로그래머가 보는 것):
STRLEN dest, source // 복잡한 한 줄
내부 동작 (CPU가 실제로 하는 것):
1. LOAD R1, [source] // 마이크로 명령어
2. CMP R1, 0 // 마이크로 명령어
3. JZ end // 마이크로 명령어
4. INC counter // 마이크로 명령어
5. JMP loop // 마이크로 명령어
각 마이크로 명령어 = 1 클럭 = 파이프라인 가능! ✨
4. 하이브리드 아키텍처의 성공
변환 과정:
x86 명령어 → [디코더] → 마이크로 명령어들 → [파이프라인]
Before (순수 CISC):
복잡한 명령어 → 마이크로코드 → 느린 실행 (10 클럭)
After (하이브리드):
복잡한 명령어 → 마이크로 명령어 5개 → 파이프라인 → 빠른 실행 (2 클럭)
5. 결과: 대역전극
2000년대:
- x86: "이제 우리도 RISC만큼 빨라!" 💪
- ARM: "어? 우리 장점이..." 😨
장단점 비교:
x86: ✅ 하위 호환성 + ✅ RISC급 성능
ARM: ✅ 전력 효율 + ✅ 단순한 설계
6. 현재 상황
데스크톱/서버: x86 지배 (Intel, AMD)
모바일: ARM 지배 (Apple M1, Snapdragon)
아이러니:
- Intel x86: 내부는 사실상 RISC
- Apple M1: 복잡한 명령어도 추가 중
결론: "둘 다 서로의 장점을 훔쳐서 비슷해졌다!" 🤝
실생활과 비유한다면
- 초기 x86: 맥가이버칼 (다재 다능하지만 사용하기 어려움)
- RISC: 공구함 (각각의 공구들이 사용하기 편리함)
- 현재 x86: 교체형 전동 드라이버 (두 장점 결합)
하이퍼스레딩 - Intel의 필살기
1. 하이퍼스레딩이란? "1개 코어로 2개인 척하기"
일반 CPU (하이퍼스레딩 X):
CPU 코어 상황:
시간 1: [작업 A 실행 중]
시간 2: [메모리 기다림...😴] ← 50% 시간 낭비!
시간 3: [작업 A 계속]
하이퍼스레딩 CPU:
CPU 코어 상황:
시간 1: [작업 A 실행 중]
시간 2: [작업 A 대기 → 작업 B 실행!] ← 놀지 않고 일해!
시간 3: [작업 B 대기 → 작업 A 실행!]
결과: 1개 코어가 2개처럼 동작! 💪
2. 실생활과 비유한다면: 요리사의 멀티태스킹
일반 요리사:
1. 파스타 삶기 (10분)
2. 기다림... 😴
3. 소스 만들기 (10분)
총 시간: 20분
하이퍼스레딩 요리사:
1. 파스타 물 올리고
2. 기다리는 동안 소스 만들기!
3. 동시에 완성!
총 시간: 12분 (40% 단축!)
3. CISC vs RISC: 끝나지 않는 전쟁
각자의 진화:
CISC (Intel x86)의 무기:
- 하이퍼스레딩 ✨
- 막강한 하위 호환성 💪
- "기존 프로그램 다 돌아가요!"
RISC (ARM)의 무기:
- 극강의 전력 효율 🔋
- 단순한 설계 = 저렴한 가격 💰
- "배터리 10시간 써요!"
4. 서로의 장점을 훔치다
Intel: "ARM처럼 전력 효율 높여야지"
→ 모바일용 Atom 프로세서 개발
ARM: "Intel처럼 성능 높여야지"
→ 복잡한 명령어 추가, 고성능 코어 개발
결과: 점점 비슷해지는 중... 🤝
5. 현재의 전장(戰場)
PC/서버 시장:
Intel/AMD (x86) 👑 - "아직은 우리가 왕!"
모바일/태블릿:
ARM 👑 - "여기는 우리 왕국!"
새로운 전장 - 노트북:
Apple M1 (ARM) ⚔️ Intel
"이제 노트북도 ARM 시대?"
서버 시장의 변화:
Amazon Graviton (ARM) 등장
"전력 효율로 서버 비용 절감!"
6. 하이퍼스레딩의 한계
장점:
- 30% 성능 향상 (공짜로!)
- 추가 코어 없이 멀티태스킹
단점:
- 보안 취약점 (Spectre 등)
- 실제 2개 코어보다는 느림
- 모든 작업에 효과적이지 않음
프로그램과 운영체제의 대화법 - System Call
1. 핵심 개념: "직접 못하니까 부탁해요!"
일상 비유:
나: "편의점에서 담배 사고 싶어요"
점원: "신분증 보여주세요" (권한 확인)
점원: "확인됐습니다. 여기 담배요" (대신 구매)
프로그램도 마찬가지!
프로그램: "파일 읽고 싶어요"
운영체제: "권한 확인하고... 읽어드릴게요"
왜 직접 못하는지?
위험한 상황 예시:
- 게임이 다른 프로그램 메모리 읽기? ❌
- 메모장이 시스템 파일 삭제? ❌
- 브라우저가 하드웨어 직접 제어? ❌
→ 운영체제만 이런 위험한 일을 할 수 있어요!
2. 두 개의 세계: 사용자 모드 vs 커널 모드
🏠 사용자 모드 (User Mode)
- 일반 프로그램들의 놀이터
- 위험한 명령어 사용 금지
- 제한된 권한만 가짐
🏛️ 커널 모드 (Kernel Mode)
- 운영체제의 영역
- 모든 권한 보유
- 하드웨어 직접 제어 가능
비유:
사용자 모드 = 일반 병사 (제한된 권한)
커널 모드 = 사단장 (모든 권한)
3. 시스템 호출의 5단계 여행
1단계: "도와주세요!" (요청)
# 프로그램 코드
file = open("data.txt", "r") # 이 순간!
내부적으로:
1. open() 함수 호출
2. CPU에게 특별한 신호 전송 (int 0x80)
3. "운영체제님, 파일 좀 열어주세요!"
2단계: "잠깐만요, 상황 저장할께요!" (전환)
CPU의 동작:
1. 🚨 트랩 발생! "시스템 호출이다!"
2. 모드 전환: 사용자 → 커널
3. 현재 상황 스냅샷 찍기:
- 어디까지 실행했나? ✓
- 변수 값들은? ✓
- 다음 실행할 명령은? ✓
이 모든 정보를 '커널 스택'에 안전하게 보관!
3단계: "작업 처리 중..." (실행)
운영체제의 작업:
1. "어떤 요청이지?" → "파일 열기구나"
2. 권한 체크: "이 프로그램이 열어도 되나?"
3. 파일 시스템 접근
4. 파일 열기 작업 수행
5. 결과 준비 (성공/실패)
4단계: "원상복구!" (복원)
되돌리기 작업:
1. 커널 스택에서 저장된 정보 꺼내기
2. CPU 상태를 원래대로 복원
3. 모드 전환: 커널 → 사용자
4. "자, 다시 당신 차례예요"
5단계: "그동안 무슨 일이 있었는지 모르겠지만 시키니 일할께요" (재개)
# 프로그램 입장에서는
file = open("data.txt", "r") # 완료!
content = file.read() # 계속 진행
마치 일반 함수 호출처럼 자연스럽게!
4. 시스템 호출 vs 인터럽트
시스템 호출 (자발적):
"제가 부탁드립니다"
- 언제: 코드에서 요청할 때
- 예시: 파일 열기, 네트워크 통신
인터럽트 (강제적):
"갑자기 끼어들기!"
- 언제: 예측 불가능
- 예시: 키보드 입력, 타이머
공통점: 둘 다 같은 방식으로 처리됨
(상황 저장 → 처리 → 복원)
5. 멀티태스킹의 비밀: 스레드 전환
실제로는 초고속 교대 근무!
시간 ────────────────────────→
CPU: A─B─C─A─B─C─A─B─C─A─B─C
└─┘ └─┘ └─┘
10ms씩 교대
우리 눈엔: "와! 동시에 돌아간다!"
실제로는: "엄청 빠르게 교대 중"
스레드 전환 과정:
1. 타이머: "띵동! A의 시간 끝!"
2. OS: "A야 잠깐 쉬어" (상태 저장)
3. OS: "B야 일어나" (상태 복원)
4. B 실행 시작
5. 10ms 후 다시 반복...
운영체제 정리
- 프로그램은 중요한 일을 직접 못해요 → OS에게 부탁
- 두 개의 모드가 있어요 → 사용자/커널
- 전환 과정은 복잡하지만 자동이에요 → 개발자는 몰라도 됨
- 멀티태스킹은 환상이에요 → 실제론 초고속 교대
이 모든 과정이 초당 수천~수만 번 일어나면서 우리의 컴퓨터가 작동합니다!
'CS' 카테고리의 다른 글
[컴퓨터 밑바닥의 비밀] 메모리 장벽과 잠금 프로그래밍 (0) | 2025.07.13 |
---|---|
[컴퓨터 밑바닥의 비밀] 프로그래밍 개념 파헤치기 (2) | 2025.06.01 |
[컴퓨터 밑바닥의 비밀 스터디] 링커에 대하여 (0) | 2025.05.27 |
[컴퓨터 밑바닥의 비밀] 소스 코드의 역사 (0) | 2025.05.11 |
비개발자를 위한 마이크로 서비스 아키텍처(MSA) 안내서 (2) | 2024.01.21 |