도입
프로그램이 실제로 CPU에서 실행되려면, 프로세서는 프로그램이 담고 있는 명령을 해석할 수 있어야 합니다. 이때 CPU가 어떤 명령어를 지원하는지, 어떤 레지스터를 제공하는지, 메모리를 어떤 방식으로 다루는지, 예외나 분기를 어떻게 처리하는지를 정한 규칙이 바로 ISA (Instruction Set Architecture)입니다.
즉, ISA는 단순히 명령어 목록만 뜻하는 것이 아니라, 소프트웨어가 하드웨어를 어떻게 볼 수 있는지 정의하는 인터페이스입니다. 컴파일러는 이 규칙을 기준으로 기계어를 만들고, 운영체제는 이 규칙을 기준으로 프로세스를 관리하며, CPU는 이 규칙에 맞춰 명령을 실행합니다.
필요성
ISA는 컴파일러, 운영체제, 디버거, 바이너리 도구가 모두 의존하는 공통 기준입니다. 그래서 ISA를 이해하면 소스 코드가 기계어로 바뀌는 과정, 바이너리 호환성, 레지스터 기반 실행, 분기와 메모리 접근의 구조를 더 명확하게 볼 수 있습니다.
- 컴파일러와 어셈블러 동작 이해
- 운영체제와 시스템 콜 구조 이해
- 바이너리 호환성과 크로스 컴파일
- 리버스 엔지니어링과 보안 분석
- 성능 최적화와 저수준 디버깅
- 임베디드 시스템과 펌웨어 개발
정의
여기서 핵심은 ISA가 소프트웨어에서 보이는 CPU의 모습이라는 점입니다. 즉, 내부적으로 CPU가 얼마나 복잡하게 구현되었는지는 몰라도, 소프트웨어는 ISA가 약속한 명령어와 레지스터 모델을 기준으로 프로그램을 작성하고 컴파일합니다.
| 항목 | ISA가 정의하는가 | 예시 |
|---|---|---|
| 명령어 | 예 | ADD, LOAD, STORE, JMP |
| 레지스터 모델 | 예 | 범용 레지스터 개수와 이름 체계 |
| 메모리 접근 방식 | 예 | load/store 규칙, addressing mode |
| 예외 / 특권 명령 | 예 | trap, interrupt, privileged instruction |
| 파이프라인 깊이 | 아니오 | 구현마다 다름 |
| 캐시 크기 | 아니오 | 마이크로아키텍처 차이 |
왜 “계약”이라고 부르는가
컴파일러는 ISA 문서를 기준으로 “이 명령을 이런 바이너리로 인코딩하면 CPU가 이렇게 동작한다”고 믿고 코드를 생성합니다. CPU 제조사는 그 ISA를 구현해, 해당 바이너리를 실제로 약속된 의미대로 실행해야 합니다. 이 때문에 ISA는 소프트웨어와 하드웨어 사이의 공통 언어라고도 설명됩니다.
핵심 구성 요소
| 구성 요소 | 의미 | 왜 중요한가 |
|---|---|---|
| 명령어 집합 | CPU가 이해하는 연산 종류 | 컴파일러가 어떤 기계어를 만들 수 있는지 결정 |
| 레지스터 집합 | 소프트웨어가 사용할 수 있는 내부 저장소 | 코드 생성과 호출 규약에 직접 영향 |
| 데이터 크기와 형식 | 정수, 부동소수점, 벡터 연산 단위 | 연산 정확도와 성능 특성에 영향 |
| Addressing Mode | 피연산자를 어떻게 해석할지 정하는 방식 | 메모리 접근 표현력과 코드 밀도에 영향 |
| 메모리 규칙 | 정렬, 접근 규칙, 메모리 모델 등 | 멀티스레드와 시스템 코드 안정성에 중요 |
| 특권 / 예외 모델 | 운영체제 모드와 예외 처리 규칙 | 시스템 콜, 인터럽트, 보호 구조의 기반 |
| 확장 명령 | SIMD, 벡터, 암호화 같은 추가 기능 | 특정 워크로드에서 성능을 크게 좌우 |
명령어 형식
하나의 명령어는 보통 연산 종류를 나타내는 opcode와, 대상 레지스터나 메모리 위치를 나타내는 operand, 그리고 경우에 따라 상수값이나 오프셋 같은 추가 정보를 담습니다. ISA는 이 요소들을 어떤 규칙으로 배치할지 정의합니다.
[ opcode ][ register ][ register ][ immediate / offset ]
모든 ISA가 같은 형식을 쓰는 것은 아닙니다. 어떤 ISA는 고정 길이 명령어를 선호하고, 어떤 ISA는 가변 길이 명령어를 허용합니다. 이 차이는 디코딩 난이도, 코드 크기, 하드웨어 복잡도에 직접 영향을 줍니다.
주소 지정 방식
같은 명령이라도 피연산자를 즉시값으로 볼지, 레지스터 값으로 볼지, 메모리 주소로 볼지에 따라 의미가 달라집니다. 이 규칙을 ISA가 정의합니다.
| 방식 | 의미 | 개념 예시 |
|---|---|---|
| Immediate | 명령어 안에 값이 직접 들어 있음 | ADD R1, 5 |
| Register | 레지스터 값을 사용 | ADD R1, R2 |
| Register Indirect | 레지스터가 가리키는 주소의 메모리를 사용 | LOAD R1, [R2] |
| Base + Offset | 기준 주소에 변위를 더한 위치 접근 | LOAD R1, [R2 + 16] |
| PC-relative | 현재 명령 위치 기준 상대 주소 사용 | JMP label |
명령어 종류
- 데이터 이동 → register 간 이동, load/store, push/pop
- 산술 / 논리 연산 → add, sub, and, or, xor, shift
- 비교와 분기 → cmp, branch, jump, call, return
- 부동소수점 / 벡터 연산 → floating-point, SIMD, vector instruction
- 시스템 제어 → trap, interrupt, privileged instruction
- 동기화 / 원자 연산 → atomic increment, compare-and-swap 등
이 분류는 고급 언어 코드가 실제로 어떤 종류의 기계 명령으로 분해되는지 이해하는 데 매우 유용합니다. 예를 들어 if 문은 비교와 분기 명령으로, 배열 접근은 주소 계산과 load/store 명령으로 내려갑니다.
자주 헷갈리는 개념과의 차이
| 개념 | 무엇을 뜻하나 | ISA와의 관계 |
|---|---|---|
| CPU 아키텍처 | CPU 전체 설계 개념 | ISA보다 넓게 쓰이는 경우가 많음 |
| ISA | 소프트웨어가 보는 명령어·레지스터 규칙 | 외부에 보이는 공식 인터페이스 |
| 마이크로아키텍처 | ISA를 내부에서 실제 구현하는 방법 | 파이프라인, 캐시, 실행 유닛 수 등 |
| ABI | 함수 호출 규약, 바이너리 인터페이스 | ISA 위에서 OS/툴체인이 정하는 규칙 |
예를 들어 “함수 인자를 어느 레지스터에 담는가”는 ISA만의 문제가 아니라 보통 ABI와 함께 정해집니다. 반면 “그 레지스터가 존재하는가”는 ISA의 문제입니다. 이 차이를 구분하면 시스템 소프트웨어 문서를 훨씬 정확히 읽을 수 있습니다.
소프트웨어와의 관계
고급 언어의 소스 코드는 직접 실행되지 않습니다. 컴파일러는 타깃 ISA에 맞는 명령어를 선택해 기계어를 만들고, 어셈블러와 링커는 이를 실행 가능한 바이너리로 구성합니다. 운영체제는 같은 ISA가 제공하는 특권 명령, 예외, 주소 변환 구조를 이용해 프로세스를 보호하고 스케줄링합니다.
고급 언어 코드
↓
컴파일러
↓
타깃 ISA 기준 기계어 생성
↓
어셈블 / 링크
↓
바이너리 실행
↓
CPU가 ISA 규칙대로 해석
다만 같은 ISA라고 해서 모든 바이너리가 무조건 서로 호환되는 것은 아닙니다. 운영체제, ABI, 실행 파일 형식까지 함께 맞아야 실제 실행 호환성이 성립하는 경우가 많습니다.
예시
아래 예시는 동일한 의미의 연산이 ISA별로 어떻게 다른 표현을 가질 수 있는지 보여 주는 개념 예시입니다.
[고급 언어]
c = a + b;
[x86-64 개념 예시]
add eax, ebx // eax = eax + ebx 형태
[ARM64 개념 예시]
add w0, w1, w2 // w0 = w1 + w2 형태
[RISC-V 개념 예시]
add x10, x11, x12 // x10 = x11 + x12 형태
이처럼 “덧셈”이라는 연산의 의미는 비슷해도, 어떤 레지스터를 쓰는지, 목적지와 원본을 어떻게 배치하는지, 명령어를 몇 비트로 인코딩하는지는 ISA마다 달라집니다.
대표 ISA 계열
- x86-64 → 데스크톱과 서버에서 매우 강한 호환성과 긴 역사를 가진 ISA
- ARM / AArch64 → 모바일, 임베디드, 저전력 환경에서 강하고 최근 범용 컴퓨팅에서도 영향력이 큰 ISA
- RISC-V → 개방형 ISA로 교육, 연구, 임베디드, 맞춤형 설계에서 주목받는 계열
이 계열들은 단순한 문법 차이가 아니라, 역사적 호환성 전략, 확장 방식, 생태계, 툴체인, 타깃 시장까지 다르게 발전해 왔습니다.
RISC와 CISC 관점
| 구분 | RISC 경향 | CISC 경향 |
|---|---|---|
| 기본 철학 | 명령어를 단순하고 규칙적으로 설계 | 더 다양한 고수준 기능을 명령어에 포함 |
| 설계 경향 | 파이프라인과 단순 디코딩에 유리한 방향 | 높은 표현력과 호환성 중시 |
| 대표 예시 | ARM, RISC-V | x86 계열 |
다만 현대 CPU는 내부적으로 명령을 더 작은 마이크로 연산으로 쪼개 실행하기도 하므로, RISC와 CISC를 절대적인 성능 비교 기준으로 단순화하는 것은 적절하지 않습니다.
자주 하는 오해
- ISA = 어셈블리 문법이라고 생각함 → 문법은 표현 방식이고, ISA는 그 아래의 실행 규칙까지 포함합니다.
- ISA = CPU 내부 구조 전체라고 생각함 → 파이프라인, 캐시, 실행 유닛 수는 마이크로아키텍처 문제입니다.
- 같은 ISA면 무조건 같은 성능이라고 생각함 → 구현 방식에 따라 성능 차이는 매우 큽니다.
- 같은 ISA면 모든 바이너리가 어디서나 실행된다고 생각함 → OS와 ABI와 실행 파일 형식도 맞아야 합니다.
- ISA는 명령어 개수만 많고 적은 문제라고 생각함 → 레지스터, 주소 지정, 예외 모델, 확장 구조도 중요합니다.
- RISC는 무조건 빠르고 CISC는 무조건 느리다고 생각함 → 현대 구현은 훨씬 복합적입니다.
공부 루틴
- 기계어와 어셈블리어 관계부터 먼저 이해한다.
- 명령어, 레지스터, addressing mode를 핵심 축으로 잡는다.
- 한 개의 ISA를 정해 짧은 예제를 반복해서 본다.
- ISA와 마이크로아키텍처, ABI를 분리해서 이해한다.
- 같은 고급 언어 코드가 서로 다른 ISA에서 어떻게 달라지는지 비교해 본다.
- 디스어셈블 결과를 보면서 명령어 의미와 상태 변화를 추적하는 연습을 한다.
디버깅과 분석 포인트
요약
- ✅ ISA는 CPU가 외부에 공개하는 명령어·레지스터·메모리 규칙의 집합이다.
- ✅ 컴파일러와 운영체제와 CPU가 모두 의존하는 공통 실행 계약이다.
- ✅ ISA는 명령어뿐 아니라 addressing mode, 특권 명령, 예외 모델 같은 규칙도 포함한다.
- ✅ 마이크로아키텍처는 ISA를 실제로 구현하는 내부 구조이며, ISA와 구분해야 한다.
- ✅ 같은 고급 언어 코드라도 타깃 ISA가 다르면 생성되는 기계어는 달라진다.
- ✅ x86-64, ARM, RISC-V 같은 대표 ISA는 서로 다른 설계 철학과 생태계를 가진다.