Jan 14, 2023
Contents
ContentsChapter 4 CPU의 작동 원리CPU의 내부 구성Remark : CPU의 내부 구성ALU제어 장치레지스터명령어 사이클인터럽트인터럽트의 종류인터럽트 처리 과정예외 처리Chapter 5 CPU 성능 향상 기법빠른 CPU를 위한 설계 기법클럭멀티코어멀티스레드정리명령어 병렬 처리 기법명령어 파이프라이닝슈퍼스칼라비순차적 명령어 처리CISC와 RISCISACISCRISC문제풀이
Chapter 4 CPU의 작동 원리
CPU의 내부 구성
Remark : CPU의 내부 구성
- CPU는 다음과 같은 구성으로 이루어져 있다.
- ALU (Arithmetic and Logic Unit; 산술 논리 연산 장치)
- 제어 장치 (Control Unit)
- 여러 종류의 레지스터
캐시 메모리 (후술)
ALU
CPU의 내부 구성요소 중 하나인 ALU는 명령어를 정해진 수행 과정대로 처리하는 역할을 한다.
- ALU는 다른 구성요소를 이용하여 명령어를 처리한다.
- 제어 장치 : 어떤 명령어를 어떻게 처리할지 결정하는 제어 신호를 ALU에 전달한다.
- 레지스터 : 처리할 명령어에 따라 레지스터에 접근하여 데이터를 가져오거나 저장한다.
- 플래그 레지스터 : 처리할 명령어에 따라 0 또는 1의 제어 신호를 저장한다.
제어 장치
제어 신호를 내보내고, 명령어를 해석하는 장치 (Decode)
- 크게 보면, 제어 장치를 기준으로 입력 신호와 출력 신호로 구분할 수 있다.
- 입력 신호
- 클럭 신호 : 주기적으로 CPU에 걸리는 전기 신호로, 컴퓨터 시스템 전반에서 클럭 신호에 맞춰서 동작한다.
- 플래그 신호 : 플래그 레지스터로부터 입력받는 제어 신호
- 명령어 : 명령 레지스터로부터 해독할 명령어
- CPU 외부로부터 입력받는 제어 신호
- 제어 버스를 통해 전달
- 출력 신호 → 제어 버스를 통해 전달
- CPU 내부에 전달되는 제어 신호
- 레지스터로 전달되는 제어 신호
- ALU로 전달되는 제어 신호
- CPU 외부에 전달되는 제어 신호
- 메모리로 전달되는 제어 신호
- 입출력장치로 전달되는 제어 신호
클럭 신호, 플래그 신호, 명령어
→ 주의할 점으로, 클럭 신호에 따라 작동하는 횟수는 컴퓨터 구성 요소마다 다르다는 것이다. 컴퓨터 구성 요소가 모두 클럭 신호마다 한 번씩 동작하지는 않는다. 한 번일수도 있고 여러 번일수도 있다.
내부 → 레지스터, ALU
외부 → 메모리, 입출력장치
레지스터
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 명령어)를 저장하는 레지스터
- 메모리에 접근하는 명령어는 읽기 명령어 또는 쓰기 명령어인데, 이러한 명령어를 실행할 때 사용되는 레지스터
- CPU가 주소 버스를 통해 메모리의 특정 주소로 접근한다.
- 메모리의 특정 주소에서 명령어나 데이터를 데이터 버스를 통해 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단계로 구성된다.
- Fetch : PC에 있는 명령어의 주소를 바탕으로 CPU가 메모리에서 명령어를 가져오고, 이를 IR에 저장
- Decode : IR에 있는 명령어를 제어 장치에서 해독
- Execution : 해독한 명령어를 ALU로 처리 → 이때 간접 사이클일 경우 메모리 접근 과정이 수행됨
- Access Memory : 메모리 읽기/쓰기 명령어 수행 시 필요
- Writeback : 명령어 처리 결과를 레지스터 또는 메모리에 반영
- 파이프라이닝 적용하기
- 여러 개의 명령어를 cycle별로 나누어, 앞 순서의 명령어가 사이클 하나를 실행하면, 다음 순서의 명령어가 이어서 그 사이클을 수행하는 방법이다.
- 적용하지 않으면, 아래와 같이 나온다.
- 앞 순서 명령어의 모든 사이클이 완료되어야 다음 명령어가 실행되므로, 같은 시간에 더 많은 명령어의 cycle을 처리할 수 있는 파이프라이닝을 적용하는 것이 성능상 효과적임을 알 수 있다.
- 극복해야 할 문제들
- 파이프라이닝이 성능 향상을 가져다주지만, 상황에 따라 오히려 성능 향상에 실패하여 명령어 처리를 위해 더 많은 사이클이 처리될 수 있다. 이러한 상황을 Pipeline hazard라고 한다.
- Pipeline hazard에는 Data hazard, Control hazard, Structural hazard가 있다.
- Data hazard (= Data dependency)
- 명령어에 따라서는, 앞 순서 명령어가 writeback cycle까지 모두 실행되어 실행 결과가 레지스터 또는 메모리에 반영되어야 정확하게 처리되는 명령어가 있다.
- 나중에 운영체제에서 다룰 문제지만, Race condition도 이와 비슷한 이슈를 담고 있다.
앞 순서 명령어에 따라 뒤 순서 명령어의 처리 결과값이 달라질 수 있는 위험
(by 명령어 간 데이터 의존성)
add r1, r2, r3; add r5, r1, r2;
- 앞 순서 명령어와 그 뒤에 이어지는 명령어들의 번지가 각각 10, 11, 12라고 하자. 파이프라이닝 중 앞 순서에서 명령어 실행에 의해 프로그램 카운터의 분기가 60으로 바뀌었다고 할 때, 다음 명령어 처리를 위해 CPU는 다음에 처리할 명령어를 60번지에서 가져와야 한다.
- 이때 파이프라이닝을 위해 11번지, 12번지에서 가져온 명령어들이 순서대로 앞 단계의 cycle을 실행하고 있는데, 다음에 처리할 명령어는 60번지에서 가져온 명령어이므로 이 명령어들은 실행되어서는 안 된다.
- 그렇게 되면, 현재 파이프라인에 있는 기존 명령어들을 모두 버리고, 60번지부터의 명령어들을 다시 새로 가져와야 한다.
- 이러한 문제를 Control hazard라고 하며, 해결하기 위한 방법으로는 Branch prediction이 대표적이다.
앞 순서 명령어 (jump, branch 명령어 등)에서 분기 흐름이 바뀌어 프로그램 카운터의 주소값이 업데이트되어 이어서 실행중인 명령어의 cycle이 의미없게 되는 위험
서로 다른 명령어들이 동시에 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보다 많다.