CPU의 관점에서

이진욱·2026년 6월 7일

심심해서해본것들

목록 보기
2/5

해당 내용은 임베디드 개발자 관점에서 학습한 내용이다.

CPU의 정의

Central Processing Unit의 약자, 중앙처리장치로 컴퓨터 안에서 명령을 해석하고 계산을 수행하는 핵심 부품

대부분의 프로그램은 CPU가 실행할 수 있는 언어 즉, 기계어로 번역되어 CPU가 이를 처리한다.

우리가 접근하는 .exe 파일은 CPU가 실행할 수 있는 기계어 코드와 실행에 필요한 정보들을 포함한 실행 파일이며, 평소에는 SSD/HDD 같은 저장장치에 저장되어 있다. 프로그램을 실행하면 운영체제가 해당 실행 파일의 필요한 부분을 프로세스의 가상 메모리 공간에 매핑하고, 실제 실행에 필요한 코드와 데이터가 RAM에 올라온다. CPU는 메모리 계층에서 명령어를 가져와 Decode하고 Execute한다.

다 필요 없고 결국 주목할 점은 CPU가 RAM에서 기계어를 가져와서(Fetch) 해석(Decode)하고 실행(Execute)한다는 것이다.


Instruction cycle

Instruction과 opcode, operand

명령어는 Instruction이라고 하며 크게 opcode(연산자)와 oprand(피연산자)로 구분된다.

ADD R1, R2, R3

예를 들어, 위와 같은 어셈블리어가 있다고 가정하자.
해당 Instruction은 다음과 같은 Opcode와 Operand로 구분된다.

Opcode: ADD
Operand: R1, R2, R3

Instruction cycle

Instruction cycle은 크게 3가지 단계로 구분한다.

Fetch → Decode → Execute

Fetch

첫 번째로 instruction을 가져오는 단계이다.

ADD R1, R2, R3이라는 Instruction이 Flash memory의 0xFF에 위치한다고 하자. CPU 관점에서는 0xFF에 접근하여 Instruction을 가져와 실행해야한다.

먼저, CPU에는 PC(Program Counter)라는 레지스터가 존재한다. 이는 다음에 실행할 Instruction의 주소를 담고 있는 주소 레지스터이다. 따라서 PC에는 0xFF라는 주소값이 저장되어 있다.

CPU는 PC의 주소를 확인하고 읽기 요청을 보낸다. 이때, 내부 버스 프로토콜을 통해 flash memory controller로 전달하고, flash memeory controller는 flash memeory의 OxFF에 접근하여 ADD R1, R2, R3를 가져온다.(정확하게는 기계어이지만 편의상 어셈블리어로 표현하겠다.) CPU는 가져온 Instruction, ADD R1, R2, R3를 IR(Instruction Register)에 저장한다.

Decode

두 번째로 가져온 instruction을 해석하는 단계이다.

Fetch 단계에서 CPU는 Flash memory로부터 가져온 ADD R1, R2, R3 instruction을 IR(Instruction Register)에 저장했다. Decode 단계에서는 CPU의 decoder가 IR에 저장된 instruction을 해석한다.

이때 CPU는 instruction을 opcode와 operand로 구분한다. ADD R1, R2, R3의 경우 opcode는 ADD이고, operand는 R1, R2, R3이다.

CPU는 opcode를 통해 이 instruction이 덧셈 연산을 수행해야 한다는 것을 알 수 있고, operand를 통해 어떤 레지스터들을 사용할지 알 수 있다.

즉, CPU는 이 instruction을 다음과 같이 해석한다.

R2R3의 값을 더해서 그 결과를 R1에 저장하라.

Decode 단계에서는 실제 덧셈이 수행되는 것이 아니라, instruction의 의미를 해석하고 Execute 단계에서 필요한 제어 신호를 준비한다.

여기서 R1, R2, R3는 범용 레지스터로 이전 instruction(ex MOV R2, #10)을 통하여 레지스터에 값들이 저장되었다고 가정한다.

Execute

세 번째로 Decode 단계에서 해석한 instruction을 실제로 수행하는 단계이다.

ADD R1, R2, R3 instruction을 예로 들면, Decode 단계에서 CPU는 이 instruction의 opcode가 ADD이고, operand가 R1, R2, R3라는 것을 해석했다. 즉 CPU는 이 instruction이 R2R3의 값을 더해서 그 결과를 R1에 저장해야 한다는 것을 알게 된다.

이때 CPU 내부의 Control Unit(제어 유닛)은 Decode 단계에서 생성된 제어 신호를 바탕으로 레지스터 파일, ALU, 메모리 접근 경로 등을 제어한다.

Execute 단계에서는 CPU가 실제로 R2R3에 저장된 값을 읽어 ALU(Arithmetic Logic Unit)에 전달한다. ALU는 두 값을 더하고, 그 결과를 destination register인 R1에 저장한다.

예를 들어 Execute 단계가 시작되기 전에 레지스터 값이 다음과 같다고 하자.

R2 = 10
R3 = 20

그러면 CPU는 ADD R1, R2, R3를 실행하여 다음과 같은 결과를 만든다.

R1 = R2 + R3
R1 = 10 + 20
R1 = 30

따라서 Execute 단계가 끝나면 R1에는 30이 저장된다.

즉 Execute 단계는 Decode 단계에서 해석된 instruction의 의미에 따라 실제 연산, 데이터 이동, 메모리 접근, 분기 등을 수행하는 단계이다.


Clock

CPU는 명령어를 “아무 때나” 처리하는 것이 아니라, clock 신호에 맞춰 단계적으로 동작한다. Clock은 CPU 내부 회로가 언제 값을 읽고, 언제 값을 저장하고, 언제 다음 단계로 넘어갈지를 맞춰주는 기준 신호이다.

Clock은 CPU가 instruction을 처리하는 타이밍 기준이다. CPU는 clock에 맞춰 PC, IR, 레지스터 같은 내부 상태를 갱신하며, instruction은 여러 clock cycle에 걸쳐 Fetch, Decode, Execute 단계를 수행한다. 실제 CPU는 pipeline을 사용해 여러 instruction의 단계를 겹쳐 실행할 수 있다.

이때, ALU 같은 조합 논리는 clock에 직접 의존하지 않고 입력 변화에 따라 출력이 결정된다. 반면 레지스터, PC, IR 같은 순차 논리 회로는 clock edge에 맞춰 값을 저장하거나 갱신한다. CPU는 조합 논리가 만들어낸 결과를 clock에 맞춰 순차 회로에 저장하면서 단계적으로 동작한다.


Interrupt 시 CPU 동작

평소 CPU는 Instruction cycle에 맞춰서 동작한다. 그러다가, interrupt가 발생하면 CPU는 현재 실행 흐름을 멈추고 interrupt handler로 이동한다.

  1. Interrupt controller는 주변장치로부터 IRQ 요청을 받고, 해당 interrupt가 enable되어 있는지와 priority를 확인한 뒤, 처리 대기 상태인 pending 상태로 표시한다.

  2. CPU는 현재 실행 중이던 instruction을 마무리한다. 그 후 interrupt를 받아들인다. CPU가 instruction 실행 중간에 interrupt를 처리하면 현재 instruction의 실행 상태가 깨질 수 있기 때문이다.

  3. CPU는 원래 코드로 돌아오기 위해 현재 context를 stack memory에 저장한다. 이때 SP(Stack Pointer)가 사용되며, SP는 context가 저장될 stack의 위치를 가리킨다.

  4. CPU는 vector table에서 해당 IRQ에 매핑된 ISR 주소를 읽고, 그 주소를 PC에 저장한다.

  5. CPU는 PC가 가리키는 ISR로 이동하여 interrupt handler를 실행한다.

  6. ISR이 끝나면 CPU는 stack memory에 저장해둔 context를 복원한다. 이때 SP를 사용해 저장된 context를 읽어오며, 복원된 PC 값을 통해 interrupt 발생 전 실행 위치로 돌아간다.


부팅 시 CPU 동작

MCU에 전원이 들어오면 CPU는 reset 상태가 된다.
이때 CPU 내부 레지스터나 제어 상태가 초기화되고, CPU는 정해진 부팅 절차를 시작한다.

reset은 다음과 같은 이유로 발생할 수 있다.

전원 인가
외부 reset pin
watchdog reset
software reset
brown-out reset
  1. MCU에서는 Flash memory의 시작 부분에 Vector Table이 있다. Vector Table에는 reset 이후 CPU가 사용할 중요한 주소들이 들어 있다.
Vector Table
[0] Initial Stack Pointer 값
[1] Reset Handler 주소
[2] NMI Handler 주소
[3] HardFault Handler 주소
...

CPU는 부팅 시 해당 Vector Table을 읽는다.

  1. CPU는 Vector Table에 존재하는 Stack Pointer 주소를 읽어 SP에 저장하고, Reset Handler의 주소를 PC에 로드한다. 이때, Reset Handler는 startup code의 시작 주소이다.

  2. CPU는 이 Reset Handler를 실행한다. Reset Handler는 보통 C 코드의 main()을 호출하기 전에 필요한 초기화를 수행한다.

.data 영역 초기화
.bss 영역 초기화
시스템 클럭 설정
FPU 설정
C runtime 초기화
전역 객체 초기화(C++인 경우)
main() 호출

초기값이 있는 전역/static 변수는 Flash에 초기값이 저장되어 있고, 실행 중에는 RAM에 있어야 한다. startup code가 이 값을 RAM의 .data 영역으로 복사한다.

초기값이 없는 전역/static 변수는 C 규칙상 0으로 초기화되어야 한다. Startup code는 RAM의 .bss 영역을 0으로 채운다.

  1. main() 실행

부트로더가 있을 경우에는 Flash 앞쪽에 부트로더가 배치된다.

Flash
┌──────────────────────────┐
│ Bootloader Vector Table  │
│ Bootloader Reset_Handler │
│ Bootloader Code          │
├──────────────────────────┤
│ Application Vector Table │
│ Application Reset_Handler│
│ Application Code         │
└──────────────────────────┘

따라서 reset 직후에는 먼저 Bootloader Reset_Handler가 실행된다. 부트로더는 부팅 조건을 확인한 뒤, 유효한 애플리케이션의 초기 SP 값과 Reset Handler 주소를 로드하여 애플리케이션으로 점프한다.

만약 Linux 같은 OS가 있는 임베디드 시스템이라면, Linux kernel은 애플리케이션보다 먼저 실행되는 독립적인 OS 커널 이미지로 부트로더는 kernel 이미지를 RAM에 로드하고, kernel entry point로 점프하여 OS를 실행한다.
Linux에서는 부트로더가 독립적인 kernel 이미지를 먼저 실행하고, 이후 애플리케이션은 kernel이 제공하는 user space에서 동작한다. 애플리케이션은 하드웨어를 직접 제어하지 않고, system call을 통해 kernel에 요청한다.

반면 일반적인 MCU + RTOS 환경에서는 RTOS 커널 코드가 애플리케이션 코드와 함께 하나의 펌웨어 이미지로 링크된다. 이후 main()에서 task를 생성하고 scheduler를 시작하면 RTOS가 task들을 스케줄링하면서 동작한다.
RTOS 환경에서는 보통 애플리케이션 task와 RTOS 커널이 같은 메모리 주소 공간을 공유한다. 즉 Linux처럼 kernel space와 user space가 강하게 분리되어 있지 않은 경우가 많다.

여기서, 커널은 운영체제의 핵심 코드이며, CPU, 메모리, 인터럽트, 장치, 파일 시스템 같은 자원을 관리하고 프로그램들이 안전하게 실행될 수 있도록 중재하는 역할을 한다.
profile
수용적인 자세로 끈임없이 성장해나가는 개발자 이진욱입니다.

0개의 댓글