2주차 - Chapter 4~5

Jan 14, 2023

Contents


Chapter 4 CPU의 작동 원리

CPU의 내부 구성

Remark : CPU의 내부 구성

  • CPU는 다음과 같은 구성으로 이루어져 있다.
    • ALU (Arithmetic and Logic Unit; 산술 논리 연산 장치)
    • 제어 장치 (Control Unit)
    • 여러 종류의 레지스터
    • 캐시 메모리 (후술)

ALU

CPU의 내부 구성요소 중 하나인 ALU는 명령어를 정해진 수행 과정대로 처리하는 역할을 한다.
notion image
  • ALU는 다른 구성요소를 이용하여 명령어를 처리한다.
    • 제어 장치 : 어떤 명령어를 어떻게 처리할지 결정하는 제어 신호를 ALU에 전달한다.
    • 레지스터 : 처리할 명령어에 따라 레지스터에 접근하여 데이터를 가져오거나 저장한다.
    • 플래그 레지스터 : 처리할 명령어에 따라 0 또는 1의 제어 신호를 저장한다.
 

제어 장치

제어 신호를 내보내고, 명령어를 해석하는 장치 (Decode)
notion image
  • 크게 보면, 제어 장치를 기준으로 입력 신호와 출력 신호로 구분할 수 있다.
    • 입력 신호
      • 💡
        클럭 신호, 플래그 신호, 명령어
      • 클럭 신호 : 주기적으로 CPU에 걸리는 전기 신호로, 컴퓨터 시스템 전반에서 클럭 신호에 맞춰서 동작한다.
        • → 주의할 점으로, 클럭 신호에 따라 작동하는 횟수는 컴퓨터 구성 요소마다 다르다는 것이다. 컴퓨터 구성 요소가 모두 클럭 신호마다 한 번씩 동작하지는 않는다. 한 번일수도 있고 여러 번일수도 있다.
      • 플래그 신호 : 플래그 레지스터로부터 입력받는 제어 신호
      • 명령어 : 명령 레지스터로부터 해독할 명령어
      • CPU 외부로부터 입력받는 제어 신호
        • 제어 버스를 통해 전달
       
    • 출력 신호 → 제어 버스를 통해 전달
      • 💡
        내부 → 레지스터, ALU 외부 → 메모리, 입출력장치
      • CPU 내부에 전달되는 제어 신호
        • 레지스터로 전달되는 제어 신호
        • ALU로 전달되는 제어 신호
      • CPU 외부에 전달되는 제어 신호
        • 메모리로 전달되는 제어 신호
        • 입출력장치로 전달되는 제어 신호
 

레지스터

CPU 내부에 명령어, 데이터, 메모리 주소, 레지스터 순번 등을 저장하는 장치
  • 레지스터에는 여러 종류가 있다.
    • 범용 레지스터 (Multi-purpose Register)
      • 명령어, 데이터, 메모리 주소, 레지스터 순번 등 단순히 저장을 위한 레지스터
    • 플래그 레지스터 (Flag Register)
      • 연산 결과 또는 CPU의 현재 상태를 저장하는 레지스터
    • 프로그램 카운터 (Program Counter; PC)
      • 다음에 실행할 명령어가 위치한 메모리 주소를 저장한다. Instruction Pointer라고도 한다.
    • 명령 레지스터 (Instruction Register; IR)
      • CPU가 프로그램 카운터에 저장된 주소로 접근하여 가져온 명령어를 저장하는 레지스터로, 이번에 실행할 명령어가 저장된다.
      • 즉, Fetch cycle의 실행 결과로서 명령어가 명령 레지스터에 저장되는 것이다. 이 부분에 대해서는 후술하겠다.
    • 메모리 주소 레지스터 (Memory Address Register; MAR)
      • 메모리의 주소를 저장하는 레지스터
    • 메모리 버퍼 레지스터 (Memory Buffer Register; MBR)
      • 메모리와 주고받을 값 (데이터 or 명령어)를 저장하는 레지스터
      • 메모리에 접근하는 명령어는 읽기 명령어 또는 쓰기 명령어인데, 이러한 명령어를 실행할 때 사용되는 레지스터
          1. CPU가 주소 버스를 통해 메모리의 특정 주소로 접근한다.
          1. 메모리의 특정 주소에서 명령어나 데이터를 데이터 버스를 통해 CPU의 메모리 버퍼 레지스터로 가져와 저장한다.
    • 스택 포인터 (Stack Pointer)
      • 메모리의 스택 영역에 접근하기 위해, 스택의 최상단 주소를 저장하는 레지스터
        • 메모리는 여러 영역으로 나뉘어져 있는데 (후술), 그 중 하나가 스택 영역이다.
        • 스택은 스택 자료구조의 특성상 한쪽 끝이 막혀 있다. 그래서 스택 영역은 한쪽 방향으로 확장되는데, 스택이 확장하는 방향의 가장 끝 부분을 최상단이라고 한다. 이러한 최상단 부분의 주소를 CPU는 스택 포인터에서 기억하고 있다.
      • CPU는 스택 영역에 데이터를 저장하거나, 가져올 수 있다.
    • 베이스 레지스터 (Base Register)
      • 어떤 기준이 되는 주소를 저장하는 레지스터
 
  • 레지스터를 왜 사용하는가?
    • 💡
      메모리에 접근하는 빈도 줄이기 → 명령어의 실행 속도 향상!
    • 컴퓨터의 핵심 구성요소에는 CPU와 메모리, 입출력 장치, 보조 기억 장치가 있다.
    • CPU가 CPU 외부의 구성요소에 접근하는 데 있어, CPU 내부의 구성요소에 접근하는 것보다 상대적으로 많은 시간이 소요된다. CPU 외부의 구성요소와의 논리적인 거리와 물리적인 거리가 CPU 내부의 다른 구성요소와의 거리보다 훨씬 멀기 때문이다.
      • 가령, CPU가 어떤 명령어 실행을 위해 어떤 값을 어딘가로부터 100번 가져와야 한다고 하자. CPU가 CPU 내부의 레지스터에 접근하는 시간은 만큼 걸리고, CPU 외부의 메모리에 접근하는 시간은 만큼 걸린다고 하자.
      • 우리가 시도할 수 있는 방법으로는 두 가지가 있다. 하나는 CPU 내부의 레지스터에 접근하여 값을 가져오는 방법이고, 다른 하나는 CPU 외부의 메모리에 접근하여 값을 가져오는 방법이다.
      • 접근 시간을 각각 계산해보면, 전자는 만큼 걸리고, 후자는 만큼 걸린다.
      • 이처럼, 어떤 값을 어떤 저장장치로부터 가져오거나 쓰는 횟수가 많아질수록 전체 실행 시간에서의 차이는 더 벌어지게 된다는 것을 알 수 있다.
    • 따라서, 명령어나 데이터, 또는 메모리 주소값을 임시로 저장하는 역할을 하는 레지스터를 CPU 내부에 배치하여 필요한 값들을 레지스터에 읽고 쓰면, CPU 외부에 접근하는 빈도도 줄게 되어 CPU의 명령어 실행 속도도 크게 개선된다.
      • Remark: 레지스터를 사용하여 메모리에 값을 저장하고자 한다면, 레지스터에 유효 주소를 저장하면 된다. (레지스터 간접 주소 지정 방식)
 
  • 플래그 레지스터
    • 연산 정보에 대한 추가적인 상태 정보
    • 플래그는 여러 종류가 있다.
      • 부호 플래그 : 부호 비트를 나타내는 플래그 → 플래그가 1이면 음수, 0이면 양수
      • 제로 플래그 : 계산 결과가 0인지 여부를 나타내는 플래그 → 플래그가 1이면 계산결과가 0
      • 캐리 플래그 : 자리올림이나 빌림 발생 여부를 나타내는 플래그 → 플래그가 1이면 캐리 발생
      • 오버플로 플래그 : 오버플로가 발생하면 1
      • 인터럽트 플래그 (후술) : 인터럽트가 발생하면 1
      • 슈퍼바이저 플래그 (후술) : 플래그가 1이면 CPU의 현재 모드가 커널 모드이며, 0이면 CPU의 현재 모드가 사용자 모드임을 나타낸다.
    • 위에서 서술한 플래그들은 모두 플래그 레지스터에 저장된다.
 
  • 주소를 저장하는 레지스터를 활용한 주소 지정 방식들
    • 스택 주소 지정 방식 : 스택 영역에 명령어와 데이터를 저장하는 방식
    • 레지스터와 명령어의 immediate 값에 따른 주소 지정 방식 (Remark : immediate은 명령어 내부의 피연산자 중에서 상수값을 의미한다.)
      • 변위 주소 지정 방식 : 명령어 내부를 구성하는 피연산자 중 범용 레지스터에 저장된 값과 immediate 값을 더한 주소값으로 유효 주소를 구하는 방식
      • 상대 주소 지정 방식 : 프로그램 카운터에 저장된 주소값과 명령어 내부를 구성하는 피연산자 중 immediate 값을 더한 주소값으로 유효 주소를 구하는 방식
      • 베이스 레지스터 주소 지정 방식 : 베이스 레지스터에 저장된 주소값과 명령어 내부를 구성하는 피연산자 중 immediate 값을 더한 주소값으로 유효 주소를 구하는 방식
 

명령어 사이클

💡
Fetch → Decode → Execution → Writeback (메모리 접근하는 연산자 → 간접 사이클)
  • CPU가 하나의 명령어를 실행하는 주기
    • 명령어에 따라 추가적인 동작이 필요할 수 있다. 대표적으로는 인터럽트가 있다.

인터럽트

인터럽트의 종류

  • 동기 인터럽트 (예외)
    • CPU와 관련된 인터럽트
    • CPU가 프로그램 실행 중 어떤 예외가 발생하면 실행 중인 프로그램을 중단하고 인터럽트를 발생시킴
  • 비동기 인터럽트 (하드웨어 인터럽트)
    • 입출력 장치와 관련된 인터럽트
    • 입출력 장치에 어떤 작업을 수행시키고, 입출력 장치는 작업이 끝나면 CPU에 인터럽트를 발생시켜 완료했다고 알림

인터럽트 처리 과정

  • 입출력 장치에서 CPU에 인터럽트 요청 신호를 보낸다.
    • CPU에서는 Execution cycle이 끝나면 다음 명령어를 Fetch하기 전에 항상 인터럽트 발생 여부를 확인한다.
  • 인터럽트가 발생하면, CPU는 플래그 레지스터에 있는 인터럽트 플래그 여부에 따라 현재 인터럽트를 처리할 수 있는지 확인한다.
  • 처리할 수 있다면, 현재 실행중인 프로그램에 대한 레지스터와 메모리의 현재 상태를 별도의 영역 (PCB, 후술)에 저장한다. (첫 번째 context switching)
  • 인터럽트 벡터 테이블에서 해당 입출력 장치에 대한 인터럽트 서비스 루틴을 찾는다.
    • 인터럽트 서비스 루틴이 저장된 메모리 영역을 인터럽트 벡터 테이블이라고 한다.
    • 인터럽트 서비스 루틴은 CPU가 인터럽트를 처리하기 위한 함수다.
  • 찾은 인터럽트 서비스 루틴을 실행하여 인터럽트를 처리한다.
  • 이후 별도의 영역에 저장한 레지스터와 메모리의 현재 상태를 불러오고, CPU가 실행하던 작업을 이어서 처리한다. (두 번째 context switching)

예외 처리

  • 동기 인터럽트 (예외) → CPU는 실행 중인 동작을 멈추고 예외 처리
    • 폴트 (Fault) : 예외를 처리한 직후 예외가 발생한 명령어로부터 실행을 재개하는 예외
    • 트랩 (Trap) : 예외를 처리한 직후 예외가 발생한 명령어의 다음 명령어로부터 실행을 재개하는 예외
    • 중단 (Abort) : 실행 중인 프로그램을 강제로 중단시킬 수밖에 없는 심각한 오류가 발생했을 때 발생하는 예외
    • 소프트웨어 인터럽트 : 시스템 콜이 발생했을 때 발생 (자세한 내용은 후술)
      • 사용자 프로그램을 실행하다가 시스템 콜이 발생하면, 이를 처리하기 위해 CPU의 실행 모드가 사용자 모드에서 커널 모드로 바뀌어야 한다. 이때 발생하는 인터럽트가 소프트웨어 인터럽트다.
  • 비동기 인터럽트 (하드웨어 인터럽트)
 

Chapter 5 CPU 성능 향상 기법

빠른 CPU를 위한 설계 기법

  • CPU의 성능을 높이려면 어떤 것들을 고려해야 할까?
  • 이와 관련된 핵심 요소인 클럭, 멀티코어, 멀티스레드에 대해 알아보자.

클럭

💡
모든 컴퓨터 부품은 일련의 박자에 맞추어 움직인다. 컴퓨터 시스템에서의 박자를 클럭이라고 한다.
  • 클럭 속도가 높아지면, 박자는 더욱 빨라지게 되어 컴퓨터 부품들은 같은 시간에 더 많은 일을 하게 된다.
    • 따라서, 클럭은 성능과 직결되는 요소이다. 클럭 속도가 높아지면 성능도 높아진다.
    • 하지만 그만큼 짧은 시간에 더 많은 일을 하게 되므로, 더 많은 전력을 소모하게 되고 발열이 더 크게 발생한다. 그러므로 클럭과 소모 전력 사이를 적절히 조절하는 것이 중요하다.

멀티코어

  • 코어란, 명령어를 처리하는 하드웨어로 CPU의 내부 요소들 (ALU, 제어 장치, 레지스터)이 포함된다.
  • 오늘날 CPU 내부에는 두 개 이상의 코어가 들어간다. 이로써 한 번에 두 개 이상의 명령어를 동시에 처리할 수 있게 되었다.

멀티스레드

  • 스레드란 사전적 의미로 ‘실행 흐름의 단위’, ‘작업 단위’를 의미한다.
  • 스레드에는 CPU 내부에서 불리는 하드웨어적 스레드, 프로그래밍에서 불리는 소프트웨어적 스레드 두 가지 의미가 있어 이들 간 분별이 필요하다.
    • 하드웨어적 스레드
      • CPU에서 하나의 코어가 동시에 처리하는 명령어 단위
      • 1코어 1스레드 CPU라면, 코어 하나가 한 번에 하나의 명령어를 처리함을 뜻한다.
        • 2스레드라면? → 코어 하나가 한 번에 두 개의 명령어를 처리하는데, 정확히는 아주 짧은 시간동안 두 개의 명령어를 번갈아가며 실행함을 뜻한다. (멀티스레드 프로세서)
    • 소프트웨어적 스레드
      • 하나의 프로그램에서 독립적으로 실행되는 단위
      • 기본적으로 최소 하나 이상의 스레드가 프로그램 내부에서 동작하고 있으며, 프로그램의 여러 부분이 각각의 스레드가 되어 동시에 실행될 수 있다.
  • CPU에서의 관점과 소프트웨어에서의 관점은 차이가 있다.
    • 가령, 2코어 4스레드 CPU에서 4개의 명령어를 한 번에 처리하고 있다고 하자. CPU에서는 코어당 2개의 명령어를 동시에 실행하여, 2개의 코어가 4개의 명령어를 처리하고 있지만, 소프트웨어에서는 4개의 명령어를 처리하고 있는 것을 1코어 1스레드 CPU 4개가 처리하고 있는 것으로 받아들인다.
      • 따라서, 하드웨어 스레드 (4스레드)를 논리 프로세서 (Logical processor)라고 부른다.

정리

  • 클럭 : 컴퓨터 시스템이 동작하는 박자 → 클럭이 올라가면 시간당 작업량 증가하여 성능 증가, 소모전력 증가
  • 코어 : 명령어를 처리하는 장치 → CPU의 구성 요소인 ALU, 제어 장치, 레지스터가 포함된다.
  • 스레드 : 하드웨어적 스레드와 소프트웨어적 스레드로 나뉜다.
    • 하드웨어적 스레드 : CPU에 의해 한 번에 처리되는 명령어 수
    • 소프트웨어적 스레드 : 하나의 프로그램을 구성하는 독립된 작업 단위
 

명령어 병렬 처리 기법

  • 명령어를 동시에 처리하여 CPU를 쉬지 않고 동작시키는 방법
  • 명령어 파이프라이닝, 슈퍼스칼라, 비순차적 명령어 처리가 대표적인 명령어 병렬 처리 방법이다.

명령어 파이프라이닝

  • 일반적인 명령어 처리 cycle은 4단계로 구성된다.
      1. Fetch : PC에 있는 명령어의 주소를 바탕으로 CPU가 메모리에서 명령어를 가져오고, 이를 IR에 저장
      1. Decode : IR에 있는 명령어를 제어 장치에서 해독
      1. Execution : 해독한 명령어를 ALU로 처리 → 이때 간접 사이클일 경우 메모리 접근 과정이 수행됨
          • Access Memory : 메모리 읽기/쓰기 명령어 수행 시 필요
      1. Writeback : 명령어 처리 결과를 레지스터 또는 메모리에 반영
 
  • 파이프라이닝 적용하기
    • notion image
    • 여러 개의 명령어를 cycle별로 나누어, 앞 순서의 명령어가 사이클 하나를 실행하면, 다음 순서의 명령어가 이어서 그 사이클을 수행하는 방법이다.
    • 적용하지 않으면, 아래와 같이 나온다.
      • notion image
      • 앞 순서 명령어의 모든 사이클이 완료되어야 다음 명령어가 실행되므로, 같은 시간에 더 많은 명령어의 cycle을 처리할 수 있는 파이프라이닝을 적용하는 것이 성능상 효과적임을 알 수 있다.
    • 극복해야 할 문제들
      • 파이프라이닝이 성능 향상을 가져다주지만, 상황에 따라 오히려 성능 향상에 실패하여 명령어 처리를 위해 더 많은 사이클이 처리될 수 있다. 이러한 상황을 Pipeline hazard라고 한다.
      • Pipeline hazard에는 Data hazard, Control hazard, Structural hazard가 있다.
      •  
      • Data hazard (= Data dependency)
        • 💡
          앞 순서 명령어에 따라 뒤 순서 명령어의 처리 결과값이 달라질 수 있는 위험 (by 명령어 간 데이터 의존성)
        • 명령어에 따라서는, 앞 순서 명령어가 writeback cycle까지 모두 실행되어 실행 결과가 레지스터 또는 메모리에 반영되어야 정확하게 처리되는 명령어가 있다.
          • 나중에 운영체제에서 다룰 문제지만, Race condition도 이와 비슷한 이슈를 담고 있다.
          • add r1, r2, r3; add r5, r1, r2;
          • 위의 예시처럼, 앞 순서 명령어가 레지스터 r1에 업데이트하기 전에 뒤 순서 명령어가 아직 업데이트되지 않은 레지스터 r1에 접근해서 데이터를 가져오면 의도치 않은 결과값이 발생할 수 있다.
           
      • Control hazard (= Control dependency)
        • 💡
          앞 순서 명령어 (jump, branch 명령어 등)에서 분기 흐름이 바뀌어 프로그램 카운터의 주소값이 업데이트되어 이어서 실행중인 명령어의 cycle이 의미없게 되는 위험
        • 앞 순서 명령어와 그 뒤에 이어지는 명령어들의 번지가 각각 10, 11, 12라고 하자. 파이프라이닝 중 앞 순서에서 명령어 실행에 의해 프로그램 카운터의 분기가 60으로 바뀌었다고 할 때, 다음 명령어 처리를 위해 CPU는 다음에 처리할 명령어를 60번지에서 가져와야 한다.
        • 이때 파이프라이닝을 위해 11번지, 12번지에서 가져온 명령어들이 순서대로 앞 단계의 cycle을 실행하고 있는데, 다음에 처리할 명령어는 60번지에서 가져온 명령어이므로 이 명령어들은 실행되어서는 안 된다.
        • 그렇게 되면, 현재 파이프라인에 있는 기존 명령어들을 모두 버리고, 60번지부터의 명령어들을 다시 새로 가져와야 한다.
        • 이러한 문제를 Control hazard라고 하며, 해결하기 위한 방법으로는 Branch prediction이 대표적이다.
         
      • Structural hazard
        • 💡
          서로 다른 명령어들이 동시에 ALU, 특정 레지스터와 같은 CPU 자원을 사용하려 할 때 발생할 수 있는 위험으로, Resource hazard라고도 부른다.
 

슈퍼스칼라

  • 여러 개의 명령어 파이프라인을 사용한 파이프라이닝
  • 파이프라인이 여러 개이기 때문에, 명령어 처리 속도는 한 개일 때보다 빨라진다. 그러나, 파이프라인 개수가 늘어난 만큼 Data hazard, Control hazard, Structural hazard의 위험성도 커져 성능 향상의 폭이 줄어들 수 있다. 따라서 hazard를 줄이기 위한 복잡한 설계가 필요하다.
 

비순차적 명령어 처리

💡
앞 순서 명령어에서 처리한 결과가 반영되는 데이터, 레지스터를 사용하는 뒤 순서 명령어의 실행 순서를 파이프라인 내에서 뒤로 미루고, 앞 순서 명령어와의 Data hazard가 없는 다른 뒤 순서 명령어들의 처리 순서를 뒤로 미뤄진 명령어보다 앞당겨 처리하는 방법
  • 기존 방법의 파이프라이닝은 Data hazard가 발생하면 앞 순서 명령어가 끝날 때까지 CPU가 기다렸어야 했는데, 이 경우 상당한 성능 손실이 발생한다.
  • Data hazard가 발생할 때, 앞 순서 명령어와의 Data hazard가 없는 다른 뒤 순서 명령어들의 처리 순서를 앞당기면 CPU는 쉬지 않고 명령어를 처리할 수 있다.
 

CISC와 RISC

ISA

  • CPU의 명령어 집합 (Instruction Set Architecture; ISA)는 CPU가 실행할 수 있는 명령어의 목록을 정의한 아키텍처를 의미한다.
  • ISA는 CPU마다 상이하며, 그 유형 또한 큰 차이가 있다.
    • 컴파일 과정에서 어셈블러로 소스코드를 어셈블리어로 변환하면, 그 결과가 어떤 CPU 아키텍처에 대응하는 컴파일러를 통해 변환했는지에 따라 다르다.
      • 즉, 컴파일한 프로그램이 어떤 CPU 아키텍처에 대응하는 컴파일러로 컴파일했는지에 따라 실행할 수 있는 CPU 아키텍처가 달라진다는 것이다. 여기에 관여하는 것이 CPU의 ISA다.
    • ISA는 명령어의 구조에 따라 크게 두 가지로 분류할 수 있다.
    • CISC (Complex Instruction Set Computer)와 RISC (Reduced Instruction Set Computer)가 바로 그것이며, 특징을 중심으로 두 ISA 간 차이점을 알아보자.

CISC

  • 명령어의 길이가 명령어마다 다르다.
    • 어떤 명령어는 짧을수도, 또 어떤 명령어는 길 수 있다.
  • 적은 명령어로 많은 작업을 수행할 수 있다.
  • 주소 지정 방식이 다양하다.
  • 장점
    • 사용하는 명령어의 수가 적음 → 메모리를 절약할 수 있다.
    • 복잡하고 다양한 명령어를 사용할 수 있다. → but 사용 빈도는 적음
  • 단점
    • 명령어의 길이가 가변적 → 명령어의 길이에 따라 소요되는 clock cycle이 다르다. → 명령어의 처리 시간이 일정하지 않음 → 파이프라이닝 하기에 적합하지 않다

RISC

  • 모든 명령어의 길이가 일정하다.
    • 가령 32비트 CPU라면, 모든 명령어의 길이는 32비트 (=4바이트)다.
    • 명령어의 길이가 일정하기 때문에, 명령어 처리하는 데 걸리는 clock cycle 또한 거의 일정하다. 따라서, 파이프라이닝 적용에 유리하다.
    • 메모리 접근 명령어가 load, store 두 가지로 단순하다.
  • 명령어의 종류가 단순하다.
  • 주소 지정 방식이 단순하다.
  • 명령어가 짧고 일정하기 때문에 같은 작업을 하는 데 필요로 하는 명령어의 개수가 상대적으로 CISC보다 많다.
 

문제풀이

notion image
notion image
notion image
notion image