[CS] 개발자가 알아야 할 CPU

눈치없어·2025년 2월 4일

개발자가 알아야 할 배경지식에 초점을 맞춘 CPU


레지스터

: CPU 안에 있는 작은 임시 저장장치

CPU 안에는 다양한 레지스터들이 있고, 각기 다른 이름과 역할이 있음
프로그램을 이루는 데이터와 명령어가 프로그램의 실행 전후로 레지스터에 저장되기 때문에 레지스터에 저장된 값만 잘 관찰해도 비교적 낮은 수준의 프로그램이 어떻게 작동하는지를 파악할 수 있음

> WinDbg(윈도우), gdb(리눅스/맥) 등 디버깅 도구를 이용해 관찰할 수 있음

📌 대부분의 CPU가 공통적으로 포함하고 있는 대표적인 주요 레지스터

1️⃣ 프로그램 카운터(Program Counter, PC): 메모리에서 다음으로 읽어 들일 명령어의 주소를 저장하는 레지스터

  • CPU는 프로그램을 실행하면서 메모리에 저장된 명령어들을 하나씩 불러와서 실행
  • 프로그램 카운터는 "다음 명령어"의 위치를 알고 있어야 하니까 보통 1씩 증가
  • 다만, 프로그램 카운터가 언제나 증가만 하는것은 아님. 프로그래밍 언의의 조건문이나 리턴문을 생각해보면 프로그램이 마냥 순차적으로 실행되지 않음. 이 경우 프로그램 카운터 값이 임의의 위치로 변경됨
    → (덕분에 우리가 짠 코드가 순서대로 실행될 수 있는것)
>  "명령어 포인터"라고 부르는 CPU도 있음
  • 프로그램 카운터는 CPU가 다음에 실행할 명령어의 위치를 기억하는 역할을 함
  • 보통은 1씩 증가하지만, 조건문, 반복문, 리턴문 같은 코드가 실행되면 특정 위치로 점프할 수도 있음

2️⃣ 명령어 레지스터(Instruction Register, IR): 해석할 명령어, 즉 메모리에서 방금 읽어 들인 명령어를 저장하는 레지스터

  • CPU 내의 제어장치는 명령어 레지스터 속 명령어를 해석한 뒤 ALU(산술논리연산장치)로 하여금 연산하도록 시키거나 다른 부품으로 제어 신호를 보내 해당 부품을 작동시킴
  • 명령어 레지스터는 CPU가 방금 가져온 명령어를 저장하는 공간
  • CPU는 이 명령어를 해석해서 "어떤 연산을 할지", "어떤 장치를 움직일지" 결정

3️⃣ 범용 레지스터(General Purpose Register, GPR): 다양하고 일반적인 상황에서 자유롭게 사용할 수 있는 레지스터

  • 데이터와 명령어, 주소 모두를 저장할 수 있음. 일반적으로 씨피유안에는 여러개의 범용레지스터들이 있음
  • 범용 레지스터는 "아무데나" 쓸 수 있는 빠른 저장 공간
  • 메모리보다 훨씬 빠르게 접근 가능해서 CPU 연산 속도를 높이는 데 도움 됨

4️⃣ 플래그 레지스터(Flag Register): 연산의 결과 혹은 CPU 상태에 대한 부가 정보인 플래그 값을 저장하는 레지스터

  • 플래그는 CPU가 명령어를 처리하는 과정에서 반드시 참조해야할 상태 정보를 의미하는 비트
  • 대표적인 플래그의 종류
  • 예를 들어 플래그 레지스터에서 CPU가 연산을 수행한 직후에 부호 플래그가 1이 되었다면 이는 연산의 결과가 음수임을 나타냄. 또한 CPU가 연산을 수행한 직후에 제로 플래그가 1이라면 연산의 결과가 0임을 나타냄
  • 플래그 레지스터는 "CPU 연산 결과가 어떤 상태인지"를 기록하는 공간
  • 연산 결과가 음수인지(부호 플래그), 0인지(제로 플래그), 자리올림이 있는지(캐리 플래그) 등을 저장
  • CPU는 이 플래그 값을 참고해서 다음 동작을 결정

5️⃣ 스택 포인터(Stack Pointer, SP): 메모리 내 스택 영역의 최상단 스택 데이터 위치를 가리키는 특별한 레지스터

  • 실행중인 각 프로그램들은 스택과 같은 형태로 사용 가능한 주소 공간을 하나 이상 가지고 있음
  • 암묵적으로 스택처럼 사용하자고 약속된 이 메모리 영역을 스택 영역이라함
  • CPU에 있는 스택 포인터가 '스택데이터3'이 저장된 공간을 가리킬때 만약 이 스택에서 데이터를 꺼낸다면 가장 꼭대기에 있는 '스택데이터3'을 꺼내고, 스택 포인터는 '스택데이터2'가 저장된 주소를 가리키게됨
  • 스택포인터는 마지막으로 스택에 저장된 데이터의 위치를 가리키는 레지스터이자, 스택이 채워진 정도를 나타내는 레지스터인 셈
  • 스택 포인터는 "스택의 가장 위에 있는 데이터의 위치"를 기억하는 레지스터
  • 스택에 데이터를 넣으면(푸시) SP가 증가, 데이터를 꺼내면(팝) SP가 감소
  • CPU는 이 스택을 이용해서 함수 호출, 변수 저장 등을 처리

📌 CPU 주요 레지스터 정리

  • 프로그램 카운터 → 다음 실행할 명령어 위치 기억
  • 명령어 레지스터 → 현재 실행할 명령어 저장
  • 범용 레지스터     → 다양한 데이터를 저장하는 다용도 공간
  • 플래그 레지스터 → 연산 결과 상태(음수, 0 등) 기록
  • 스택 포인터        → 스택의 가장 위(top) 데이터 위치 관리

이 레지스터들이 협력하면서 CPU가 프로그램을 실행




인터럽트

: CPU의 작업을 방해하는 신호
(Interrupt: "방해하다", "중단시키다")

인터럽트는 다양한 상황에서 발생할 수 있음
임의로 발생시킬 수도 있고, 잘못된 프로그램으로 인해 발생하기도 하며, 효율적인 입출력을 위해 사용되기도 함

> 사용자가 직접 발생 (예: 키보드 입력, 마우스 클릭)
> 잘못된 프로그램 실행 (예: 0으로 나누는 오류)
> 입출력 장치의 요청 (예: 프린터 완료, 하드디스크 데이터 요청)

📌 인터럽트의 종류

  • 동기 인터럽트(= 예외)
    - CPU 자체에서 발생하는 인터럽트
    - 예시: 0으로 나누기, 접근 불가능한 메모리 접근 등

  • 비동기 인터럽트(= 하드웨어 인터럽트)
    - 주로 입출력 장치에서 발생하는 인터럽트, 특정 시간이 아닌, 언제든지 발생할 수 있음
    - 예시: 키보드 입력, 네트워크 데이터 수신 등

    	> 비동기 인터럽트를 일반적으로 "인터럽트"라고도 하지만, 용어 혼동을 방지하기 위해 "하드웨어 인터럽트"라고 부르기도 함

📌 인터럽트가 발생 시 CPU의 반응

  • 1️⃣ 현재 실행 중인 명령어를 잠시 멈춤
  • 2️⃣ 인터럽트가 발생한 원인을 확인하고 해당 작업을 처리
  • 3️⃣ 처리가 끝나면, 멈췄던 원래 작업으로 다시 돌아감
    이런 과정을 인터럽트 사이클(Interrupt Cycle) 이라고 함

  • 인터럽트는 CPU가 실행 중인 작업을 잠시 멈추고 더 중요한 요청을 처리하는 신호
  • 동기 인터럽트    → CPU가 오류(예외)로 인해 발생시키는 인터럽트
  • 비동기 인터럽트 → 키보드, 마우스, 네트워크 등 외부 장치가 보내는 신호
  • CPU는 인터럽트 요청을 처리한 뒤 원래 작업으로 복귀함

작업 완료 여부를 계속해서 확인하는 것은 인터럽트와 대비되는 폴링 이라는 기법
→ 입출력 작업에서 폴링이란 입출력장치의 상태가 어떤지, 처리할 데이터가 있는지 주기적으로 확인하는것을 말함


하드웨어 인터럽트(=비동기 인터럽트)

  • CPU는 효율적으로 명령어를 처리하기 위해 하드웨어 인터럽트를 사용

  • 입출력 완료 여부를 확인하기 위한 CPU 사이클 낭비를 최소화하고, CPU가 다른 일을 수행할 수 있는 시간을 벌어 줌으로써 효율적으로 명령어를 처리할 수 있도록 도움

📌 일반적으로 CPU가 하드웨어 인터럽트를 처리하는 순서

1️⃣ "인터럽트 요청 신호"를 CPU에 전달
2️⃣ CPU는 실행 사이클이 끝나고 명령어를 인출하기 전에 항상 인터럽트 여부 확인
3️⃣ CPU는 인터럽트 요청을 확인하고, "인터럽트 플래그"를 통해 현재 인터럽트를 받아들일 수 있는지 여부 확인
4️⃣ 인터럽트를 받아들일 수 있다면 CPU가 지금까지의 작업을 백업
5️⃣ CPU는 인터럽트 벡터를 참조하여 인터럽트 서비스 루틴을 실행
6️⃣ 인터럽트 서비스 루틴 실행이 끝나면 4️⃣에서 백업해 둔 작업을 복구하여 실행을 재개

📌 인터럽트와 관련해 알아야할 용어

  • 인터럽트 요청 신호: 하드웨어가 보내는 요청 신호
  • 인터럽트 플래그: 인터럽트 요청을 수락할지 결정하는 플래그
    • 우선순위가 낮은 요청은 무시 가능, 하지만 정전 등 일부 요청은 반드시 처리
  • 인터럽트 벡터: 인터럽트를 처리하기 위한 프로그램 주소
  • 인터럽트 서비스 루틴(ISR): 인터럽트를 처리하는 프로그램(=핸들러)

인터럽트는 CPU의 정상적인 실행 흐름을 끊는 것이기 때문에 인터럽트하기 전에 CPU에게 인터럽트의 가능 여부를 확인해야함 이를 위한 신호를 인터럽트 요청 신호라고 함

이때 CPU가 인터럽트 요청을 수용하기 위해서는 플래그 레지스터의 인터럽트 플래그가 활성화되어 있어야 함
만약 인터럽트 플래그가 불가능으로 설정되어 있다면 CPU는 인터럽트 요청이 오더라도 해당 요청을 무시
다만, 모든 하드웨어 인터럽트를 인터럽트 플래그로 막을 수 있는 것은 아님
인터럽트 플래그가 불가능으로 설정되어 있더라도 무시할 수 없는 인터럽트 요청도 있음
무시할 수 없는 하드웨어 인터럽트는 가장 우선순위가 높은, 다시 말해 가장 먼저 처리해야 하는 인터럽트를 말함
정전이나 하드웨어 고장으로 인한 인터럽트가 이에 해당. 즉, 하드웨어 인터럽트에는 인터럽트 플래그로 막을 수 있는 인터럽트와 막을 수 없는 인터럽트가 있음

CPU가 인터럽트 요청을 받아들이기로 했다면 CPU는 인터럽트 서비스 루틴이라는 프로그램을 실행
CPU가 인터럽트를 처리한다는 말은 인터럽트 서비스 루틴을 실행하고, 본래 수행하던 작업으로 다시 되돌아온다는 말과 같음

  • 하드웨어 인터럽트: CPU가 요청을 받아 입출력장치의 작업을 처리하고 다시 원래 작업으로 돌아가는 과정
  • 효율적 처리를 위해 CPU가 사용하는 중요한 메커니즘

📌 인터럽트 서비스 루틴과 인터럽트 벡터

  • 인터럽트 서비스 루틴(ISR):
    각 입출력장치마다 "인터럽트 발생 시 어떻게 행동할지"를 정의한 프로그램
    CPU는 특정 인터럽트를 처리하기 위해 ISR을 실행
  • 인터럽트 벡터:
    ISR의 시작 주소를 포함한 정보
    CPU는 인터럽트 요청을 받은 후, 인터럽트 벡터를 통해 정확한 ISR을 찾고 실행

📌 CPU가 인터럽트를 처리하는 과정
1️⃣ 하드웨어 장치가 인터럽트 요청 신호를 CPU에 전달
2️⃣ CPU는 실행 중인 명령어를 완료한 뒤, 인터럽트 여부를 확인
3️⃣ 인터럽트 플래그를 확인해 요청을 수락할지 결정
4️⃣ 요청 수락 시, 현재 작업(프로그램 카운터와 레지스터 값)을 스택에 백업
5️⃣ 인터럽트 벡터를 통해 ISR(인터럽트 서비스 루틴) 실행
6️⃣ ISR 처리 완료 후 스택에서 작업을 복구하고 원래 실행하던 작업을 재개

한 줄 정리
- CPU가 인터럽트를 처리한다는 것은 인터럽트 서비스 루틴을 실행하고, 백업된 작업 상태를 복구한 뒤 원래 작업으로 복귀하는 것


예외(= 동기 인터럽트)

예외가 발생한 명령어부터 실행하느냐, 예외가 발생한 명령어의 다음 명령어부터 실행하느냐에 따라 폴트와 트
랩으로 나뉨

  • 폴트: 예외를 처리한 직후에 예외가 발생한 명령어부터 실행을 재개하는 예외
  • 트랩: 예외를 처리한 직후에 예외가 발생한 명령어의 다음 명령어부터 실행을 재개하는 예외
  • 중단: CPU가 실행 중인 프로그램을 강제로 중단 시킬 수 밖에 없는 심각한 오류를 발견했을때 발생하는 예외
  • 소프트웨어 인터럽트: 시스템 콜이 발생했을 때 발생하는 예외



CPU 성능 향상을 위한 설계

CPU 클럭 속도

클럭
: 컴퓨터의 부품을 일사불란하게 움직일 수 있게 하는 시간의 단위

클럭 속도는 헤르츠(Hz) 단위로 측정하며, 최근에는 기가헤르츠(GHz) 단위로 측정하는 것이 일반적

클럭 속도가 높아지면 CPU는 명령어 사이클을 더 빠르게 반복하고, 다른 부품들도 그에 맞춰 더 빠르게 작동할 것으로 기대할 수 있음
실제로 클럭 속도가 높은 CPU는 일반적으로 성능이 더 뛰어남
이런 점에서 클럭 속도는 CPU의 속도 단위로 간주되기도 함

하지만 클럭 속도를 필요 이상으로 높이면 발열 문제가 발생할 수 있어, 단순히 클럭 속도를 높이는 것만으로 CPU 성능을 향상시키는 데에는 한계가 있음

멀티코어와 멀티스레드

클럭 속도를 높이는 방법 외에도, 코어 수나 스레드 수를 늘리는 방식으로 CPU의 성능을 높일 수 있음

📌 코어

  • CPU 내부에서 명령어를 읽고, 해석하고, 실행하는 부품을 의미
  • 과거 전통적인 CPU 설계에서는 '명령어를 읽고, 해석하고, 실행하는 부품'이 CPU 하나에만 존재했지만, 현대 CPU는 기술적 발전으로 여러 코어를 포함할 수 있게 되었고 이를 멀티코어 CPU 혹은 멀티코어 프로세서라고 부름

📌 스레드

  • 스레드는 실행 흐름의 단위를 의미
  • 여기서 혼동을 방지하기 위해 하드웨어 스레드소프트웨어 스레드로 구분하여 기억하는 것이 좋음
  1. 하드웨어 스레드
  • CPU 코어가 동시에 처리할 수 있는 명령어 단위
  • 한 코어가 여러 명령어를 동시에 처리할 수 있는 CPU를 멀티스레드 CPU 혹은 멀티스레드 CPU라고 부름
  • 메모리 관점에서 하드웨어 스레드는 1개의 CPU처럼 동작
    예: 2코어 4스레드 CPU는 프로그램 입장에서 4개의 CPU처럼 보임
  1. 소프트웨어 스레드
  • 프로그램 내에서 독립적으로 실행되는 단위
  • 소프트웨어 스레드는 하드웨어 스레드가 1개인 CPU에서도 실행 가능하며, CPU는 스레드 간 빠르게 전환하여 마치 여러 작업을 동시에 처리하는 것처럼 보임

📌 동시성과 병렬성

하드웨어 스레드와 소프트웨어 스레드의 차이는 동시성과 병렬성이라는 개념으로 더 명확히 이해 가능

  • 병렬성:
    - 작업을 물리적으로 동시에 처리하는 성질
    예: 4개의 하드웨어 스레드가 각각 4개의 명령어를 동시에 실행

  • 동시성:
    - 작업이 동시에 실행되는 것처럼 보이는 성질
    - 실제로는 CPU가 빠르게 작업을 번갈아 처리하여 사용자가 동시 실행으로 인식

한 줄 요약

  • 병렬성: 실제로 여러 작업이 동시에 실행됨(하드웨어 스레드)
  • 동시성: 동시에 실행되는 것처럼 보이는 효과(소프트웨어 스레드)
  • 하드웨어 스레드는 병렬성을, 소프트웨어 스레드는 동시성을 구현
  • 1코어 1스레드 CPU에서도 여러 소프트웨어 스레드를 실행할 수 있음
  • 실제로는 하나의 CPU가 스레드 간 빠르게 전환하며 실행
  • 하드웨어 스레드는 물리적 실행 단위, 소프트웨어 스레드는 운영체제가 관리하는 논리적 실행 단위임



파이프라이닝을 통한 명령어 병렬 처리

명령어 병렬 처리 기법
: 여러 명령어를 동시에 처리하여 CPU를 한시도 쉬지 않고 작동시킴으로써 CPU의 성능을 높이는 기법

기억해야할 명령어 처리 기법인 명령어 파이프라이닝을 이해하려면 우선 하나의 명령어가 처리되는 과정을 비슷한 시간 간격으로 나누어 보아야 함. 일반적으로 아래와 같음

📌 명령어 처리 단계

  1. 명령어 인출(Instruction Fetch): 메모리에서 명령어를 가져옴
  2. 명령어 해석(Instruction Decode): 명령어를 해석하고 실행 계획을 세움
  3. 명령어 실행(Execute Instruction): 명령어에 따라 연산을 수행
  4. 결과 저장(Write Back): 연산 결과를 레지스터나 메모리에 저장

📌 파이프라이닝의 핵심

  • 같은 단계가 겹치지만 않는다면 CPU가 각각의 단계를 동시에 실행할 수 있다는 점
  • CPU는 하나의 명령어가 인출되는 동안 다른 명령어를 실행할 수 있고, 하나의 명령어가 실행되는 동안 결과를 저장할 수 있음

이처럼 공장의 생산 라인과 같이 명령어들을 명령어 파이프라인에 넣고 동시에 처리하는 기법을 명령어 파이프라이닝 이라고 함

> 슈퍼스칼라
  : 현대 대부분의 CPU는 여러 개의 파이프라인을 이용하는데 이처럼 CPU 내부에 여러 명령어 파이프라인을 포함하는 구조
  슈퍼스칼라 구조로 명령어 처리가 가능한 CPU는 슈퍼스칼라 프로세서 혹은 슈퍼스칼라 CPU라고 부름

📌 CISC와 RISC의 차이점

  • CISC (Complex Instruction Set Computing)
    • 복잡하고 다양한 명령어로 구성
    • 적은 수의 명령어로 프로그램을 실행 가능
    • 단점:
      • 명령어 크기와 실행 시간이 일정하지 않아 파이프라이닝에 비효율적
      • 하나의 명령어 실행에 여러 클럭 주기가 필요
  • RISC (Reduced Instruction Set Computing)
    • 짧고 단순한 명령어로 구성, 대부분 1클럭 내외에 실행
    • 장점:
      • 명령어가 규격화되어 파이프라이닝에 최적화
      • 일정한 실행 시간 덕분에 병렬 처리가 효율적

  • CISC: 명령어 실행 시간이 일정하지 않아 단계 겹침이 적음, 파이프라이닝 비효율적
  • RISC: 명령어 실행 시간이 일정해 각 단계가 매끄럽게 연결, 파이프라이닝 효율적

    한 줄 요약
    - 파이프라이닝은 명령어 병렬 처리를 통해 CPU를 쉬지 않고 작동시켜 성능을 높이는 기술
    - CISC는 명령어 복잡성으로 인해 파이프라이닝에 비효율적, RISC는 규격화된 명령어로 최적화되어 있음

📌 파이프라인 위험(Pipeline Hazards)

: 파이프라이닝이 제대로 작동하지 않아 CPU 성능이 저하되는 상황

1️⃣ 데이터 위험: 명령어 간의 데이터 의존성으로 인해 발생

  • 명령어 2가 명령어 1의 결과에 의존
  • 명령어 1이 완료되기 전에 명령어 2가 실행되면 데이터 충돌 발생

2️⃣ 제어 위험: 프로그램 카운터의 갑작스러운 변화에 의해 발생

  • 프로그램 흐름이 점프(JUMP)나 조건문(CONDITIONAL JUMP)으로 바뀌면 미리 인출하거나 해석한 명령어들이 무효화됨.

3️⃣ 구조적 위험: 명령어 간에 CPU 자원을 동시에 사용하려고 할 때 발생

  • 여러 명령어가 동시에 ALU, 레지스터 등 동일한 CPU 자원을 사용하려고 시도
  • 자원 부족으로 병목 현상이 발생
  • 자원 위험 이라고도 부름


참고: 북스터디 - 이것이 취업을 위한 컴퓨터 과학이다 (Chapter 2-3)

profile
dock 사이즈 다르잖아

0개의 댓글