Chapter 04 CPU의 작동 원리
04-1 ALU와 제어장치
ALU(Arithmetic and Logical Unit)
- 산술논리연산장치
- CPU에서 계산하는 부품
- 레지스터를 통해 피연산자를 받아들이고, 제어장치로부터 수행할 연산을 알려주는 제어 신호를 받아들인다.
- 레지스터와 제어장치로부터 받아들인 피연산자와 제어 신호로 산술 연산, 논리 연산 등 다양한 연산을 수행.
- 플래그
- 연산 결과에 대한 추가적인 상태 정보
- CPU가 프로그램을 실행하는 도중 반드시 기억해야하는 참고 정보
- 플래그 레지스터에 따로 저장됨.
- 플래그 종류
- 부호 플래그: 연산 결과의 부호
- 제로 플래그 : 연산 결과가 0인지
- 캐리 플래그 : 연산 결과가 올림수나 빌림수가 발생하는지
- 오버플로우 플래그 : 오버플로우가 발생했는지
- 인터럽트 플래그 : 인터럽트가 가능한지
- 슈퍼바이저 플래그 : 커널 모드로 실행 중인지, 사용자 모드로 실행 중인지
📝 CPU의 ALU란 연산장치이며, 레지스터와 제어장치로부터 정보를 받아들여서 연산을 수행한다. 그 중 플래그라는 정보도 함께 내보내는데 연산 결과에 대한 추가적인 정보를 가지고 있으며 플래그 레지스터에 따로 저장되는 정보이다.
제어장치
- 제어 신호를 내보내고, 명령어를 해석하는 부품
- 제어 신호 : 컴퓨터 부품들을 관리하고 작동시키기 위한 일종의 전기 신호.
제어장치가 받아들이는 정보
클럭 : 컴퓨터의 시간 단위
CPU가 해석해야할 명령어는 명령어 레지스터에 저장되며 제어장치는 이곳에서 명령어를 받아들이고 해석한다.
플래그는 ALU 연산에 대한 추가적인 상태 정보이기 때문에 이를 참고하여 제어 신호를 발생시킨다.
제어장치가 내보내는 정보
- CPU 외부에 전달하는 제어 신호
- CPU 내부에 전달하는 제어 신호
- ALU에 전달 : 수행할 연산을 지시하기 위해
- 레지스터에 전달 : 레지스터 간에 데이터를 이동 or 레지스터에 저장된 명령어를 해석하기 위해
📝 CPU의 주요 장치는 ALU, 제어장치, 레지스터 총 3가지이며 이 중 제어장치가 가장 정교하게 설계된 부품이다. 사실상 명령을 내리는 부품이 바로 이친구. 이 제어장치가 받아들이는 정보는 클럭, 명령어 레지스터, 플래그 레지스터, 제어 신호. 그리고 제어장치가 내보내는 정보는 CPU 내부와 외부로 나뉜다. CPU 외부는 메모리, 입출력장치로 내부는 ALU, 레지스터다.
04-2 레지스터
레지스터의 종류와 역할, 명령어가 처리되는 과정 이해해보기
반드시 알아야 할 레지스터
- 프로그램 카운터 : 메모리에서 읽어 들일 명령어의 주소를 저장
- 명령어 레지스터 : 방금 메모리에서 읽어들인 명령어를 저장하는 레지스터
- 메모리 주소 레지스터 : 메모리의 주소를 저장하는 레지스터
- 메모리 버퍼 레지스터 : 데이터와 명령어를 저장하는 레지스터
프로그램 카운터에 읽어들일 명령어의 주소가 입력되면 → 메모리 주소 레지스터에 해당 주소가 입력되고 → 해당 주소에 있는 데이터와 명령어어를 읽어들인 다음 → 명령어 레지스터에 저장해서 제어신호를 발생시킴.
→ CPU가 메모리 속 프로그램을 순차적으로 읽어 들이고 실행해 나갈 수 있는 이유는 CPU 속 프로그램 카운터가 꾸준히 증가하기 때문.
특정 레지스터를 이용한 주소 지정 방식(1) : 스택 주소 지정 방식
- 스택 포인터 : 스택의 꼭대기를 가리키는 레지스터
- 메모리의 스택 영역에 마지막에 저장한 값의 위치를 저장하는 레지스터
특정 레지스터를 이용한 주소 지정 방식(2) : 변위 주소 지정 방식
- 오퍼랜드 필드의 값과 특정 레지스터의 값을 더하여 유효 주소를 얻어내는 주소 지정 방식
상대 주소 지정 방식
- 오퍼랜드와 프로그램 카운터의 값을 더하여 유효 소를 얻는 방식.
- 만약 프로그램 카운터의 값이 +3이면 메모리에서 3번째 주소를 가져오고, -3이면 메모리에서 뒤로 3번째 주소값을 가져오는 방식
베이스 레지스터 주소 지정 방식
- 오퍼랜드와 베이스 레지스터의 값을 더하여 유효 주소를 얻는 방식
- 베이스 레지스터가 200이고, 오퍼랜드가 50이면 250번지에 있는 메모리 주소를 가져옴.
📝 레지스터는 CPU 내에 있는 작은 임시저장장치이다. 꼭 알아야 할 레지스터가 프로그램 카운터, 메모리 주소 저장, 메모리 버퍼, 명령어 레지스터 등이 있다. CPU에 명령어가 순차적으로 실행되는 것은 프로그램 카운터가 증가하기 때문이며 프로그램 카운터가 증가하면 해당 주소의 메모리값을 읽어들이면서 실행하게 된다.
특정 레지스터를 이용해서 주소를 지정하는 방식도 있는데 스택영역에서 들고오는 방식, 변위 주소를 사용하는 방식이 있다. 변위 주소는 상대 주소로 하는 방법, 베이스 레지스터를 사용해서 계산하는 방식도 있다.
04-3 명령어 사이클과 인터럽트
명령어 사이클
- 프로그램 속 각각의 명령어들은 일정한 주기가 반복되며 실행되는데 이 주기
- 인출 사이클 : 메모리에 있는 명령어를 CPU로 가지고 오는 단계
- 실행 사이클 : CPU로 가져온 명령어를 실행하는 단계
- 제어장치가 명령어 레지스터에 담긴 값을 해석하고 제어 신호를 발생시키는 단계
- 간접 사이클 : 명령어를 실행하기 위해서는 메모리 접근을 한 번 더 해야하는 단계
→ CPU가 명령어를 실행하는 단계를 표현하면 인출 사이클(메모리에 있는 명령어를 CPU로 가져옴), 실행 사이클(CPU로 가져온 명령어를 실행하는 단계), 실행하다 메모리에 한번 더 접근해야하면 이를 간접 사이클이라고 한다.
인터럽트
- CPU의 작업을 방해하는 신호
- 인터럽트 종류는 크게 동기 인터럽트와 비동기 인터럽트가 있다.
동기 인터럽트(예외)
- 프로그래밍상의 오류와 같은 예외적인 상황에 마추졌을 때 발생하는 인터럽트
비동기 인터럽트(하드웨어 인터럽트)
- 입출력장치에 의해 발생하는 인터럽트
- 하드웨어 인터럽트
CPU가 만약 프린트에 출력을 명령했다고 가정했을 때 프린트는 CPU다 속도가 느리기 때문에 완료되었는지 알려면 계속 polling하면 물어봐야한다. CPU 사이클 낭비다.
하드웨어 인터럽트를 사용하면 CPU는 프론터로부터 완료 인터럽트를 받을 때까지 다른 작업을 처리할 수 있다.
- 입출력장치는 CPU에 인터럽트 요청 신호를 보낸다.
- CPU는 실행 사이클이 끝나고 명령어를 인출하기 전 항상 인터럽트 여부를 확인
- CPU는 인터럽트 요청을 확인하고 인터럽트 플래그를 통해 현재 인터럽트를 받아들일 수 있는지 여부를 확인
- 인터럽트를 받아들일 수 있으면 CPU는 지금까지의 작업을 백업
- CPU는 인터럽트 벡터를 참조하여 인터럽트 서비스 루틴을 실행
- 인터럽트 서비스 루틴 실행이 끝나면 백업해둔 작업을 복구하여 실행을 재개
인터럽트 서비스 루틴
- 인터럽트가 발생했을 때 해당 인터럽트를 어떻게 처리하고 작동해야할지에 대한 정보로 이루어진 프로그램
CPU가 인터럽트를 처리한다 → 인터럽트 서비스 루틴을 실행하고 본래 수행하던 작업으로 다시 되돌아온다라는 말과 같다. CPU가 인터럽트 서비스 루틴을 실행하려면 인터럽트 서비스 루틴의 시작 주소를 알아야 하는데, 이는 인터럽트 벡터를 통해 알 수 있다.
📝
명령어 사이클은 인출, 실행, 간접, 인터럽트 사이클로 이루어져 있다.
인터럽트는 CPU의 작업을 방해하는 신호로 예외 인터럽트, 하드웨어 인터럽트 두 가지로 나늰다.
예외 인터럽트는 오류로 인한 작업 방해
하드웨어 인터럽트는 입출력장치에 의한 신호이다.
하드웨어 인터럽트는 CPU와 입출력장치간의 속도 차이가 나기 때문에 이를 효율적으로 실행하기 위한 방법
인터럽트 요청 신호 : CPU의 작업을 방해하는 신호
인터럽트 플래그 : CPU가 인터럽트 실행이 가능한지에 대한 정보가 담겨 있고
인터럽트 벡터 : 인터럽트 서비스 루틴의 메모리 주소가 들어있으며
인터럽트 서비스 루틴 : 인터럽트를 처리하는 프로그램
Chapter 05 CPU 성능 향상 기법
05-1 빠른 CPU를 위한 설계 기법
클럭
- 컴퓨터 부품들은 클럭 신호에 맞춰 일사불란하게 움직인다.
- CPU는 명령어 사이클이라는 정해진 흐름에 맞춰 명령어들을 실행한다.
- 클럭 속도가 높아지면 CPU는 명령어 사이클을 더 빠르게 반복할 것이고, 다른 부품들도 그에 발맞춰 더 빠르게 작동할 것이다.
- 클럭 속도를 무작정 높이면 발열 문제가 심각해지기 때문에 클럭 속도만으로 CPU의 성능을 올리는 것에는 한계가 있다.
코어와 멀티코어
- 코어 : 명령어를 실행하는 부품
- CPU : 명령어를 실행하는 부품을 여러 개 포함하는 부품
- 멀티코어 CPU/멀티코어 프로세서 : 코어를 여러 개 포함하고 있는 CPU
- 코어를 무작정 늘린다고 CPU의 성능이 올라가는 것은 아니다.
- 코어마다 처리할 연산이 적절히 분배되지 않는다면 코어 수에 비례하여 연산 속도가 증가하지 않는다.
- 코어마다 처리할 명령어들을 얼마나 적절하게 분배하느냐이고 그에 따라서 연산 속도는 크게 달라진다.
스레드와 멀티 스레드
- 스레드 : 실행 흐름의 단위
- 스레드에는 하드웨어적 스레드와 소프트웨어적 스레드가 있다.
하드웨어적 스레드
- 하나의 코어가 동시에 처리하는 명령어 단위
- 멀티스레드 프로세스/멀티스레드 CPU : 하나의 코어로 여러 명령어를 동시에 처리하는 CPU
소프트웨어적 스레드
멀티스레드 프로세서
- 멀리스레드 프로세서는 실제로 설계하는 일은 매우 복잡하지만, 가장 큰 핵심은 레지스터다.
- 하나의 코어로 여러 명령어를 동시에 처리하도록 만들려면 프로그램 카운터, 스택 포인터, 메모리 버퍼 레지스터, 메모리 주소 레지스터와 같이 하나의 명령어를 처리하기 위해 꼭 필요한 레지스터를 여러 개 가지고 있으면 된다.
- 논리 프로세스 : 프로그램 입장에서는 4코어 8스레드 CPU는 마치 8개의 CPU가 있는 것처럼 느껴지기 때문에 논리 프로세스라고 부르기도 한다.
📝
CPU의 성능을 결정하는 것들을 배웠다.
클럭이란 마치 cpu의 심장박동 소리 같달까. 클럭의 신호에 맞춰 CPU가 동작하기 때문에 클럭이 빠를수록 CPU도 더 빠르게 동작할 것이다. 그런데 클럭이 높으면 발열 이슈가 있기 떄문에 한계가 있다.
코어는 명령어를 처리하는 부품이다. CPU를 명령어를 실행하는 부품이라고 하는데 사실상 코어를 의미하는 것이며 CPU는 코어의 집합이라고 볼 수 있다. 2코어라고 하면 명령어를 실행하는 부품이 2개인것이다. 그런데 코어의 수를 늘린다고 성능이 마냥 좋아지진 않는다.
스레드는 하드웨어적 스레드(CPU), 소프트웨어적 스레드 두 가지로 나뉜다. 하드웨어적 스레드는 코어에서 동시에 처리할 수 있는 명령어 단위를 의미하는데, 하나의 코어로 여러 명령어를 처리할 수 있는 CPU를 멀티스레드 프로세스라고 한다.
코어는 명령어를 실행하는 부품인거고, 그 안에서 처리하는 인력의 수를 스레드라고 한다.
소프트웨어적 스레드는 프로그램 내에서 실행되는 단위로, javascript 같은 경우는 싱글스레드로 명령어를 실행하는 단위가 1개라 한번에 하나의 명령어만 실행 가능하다.
멀티스레드 프로세스의 핵심은 레지스터라 스레드 수만큼 레지스터를 가지고 있다.
05-2 명령어 병렬 처리 기법
명령어 파이프라인
- 명령어 처리 과정을 클럭 단위로 나누면
- 명령어 인출
- 같은 단계가 겹치지 않으면 CPU는 각 단계를 동시에 실행할 수 있다.
- 명령어 파이프라이닝 : 공장 생산 라인과 같이 명령어들을 명령어 파이프라인에 넣고 동시에 처리하는 기법
파이프라인 위험
- 파이프라이닝은 높은 성능을 가져오기는 하지만, 특정 상황에서는 성능 향상에 실패한다.
- 파이프라인 위험에는 데이터 위험, 제어 위험, 구조적 위험 3가지가 있다.
- 데이터 위험 : 데이터 의존적인 두 명령어를 무작정 동시에 실행하려고 하면 파이프라인이 제대로 작동하지 않는 것
- 제어 위험 : 분기 등으로 인한 프로그램 카운터의 갑잡스러운 변화에 의해 발생한다.
- 구조적 위험(자원 위험) : 서로 다른 명령어가 동시에 ALU, 레지스터 등과 같은 CPU 부품을 사용하려고 할 때 발생
슈퍼스칼라
- CPU 내부에 여러 개의 명령어 파이프라인을 포함한 구조
- 명령어 파이프라인을 하나만 두는 것이 마치 공장 생산 라인을 한 개 두는 것과 같다면, 슈퍼스칼라는 공장 생산 라인을 여러 개 두는 것과 같다.
- 이론적으로는 파이프라인 개수에 비례하여 프로그램 처리 속도가 빨라야져야 하는게 맞으나 하나의 파이프라인을 사용할 때 보다 데이터 위험, 제어 위험, 자원 위험을 피하기가 더욱 까다롭다.
- 그래서 더욱 더 고도로 설계되어야함.
비순차적 명령어 처리
- 대부분의 CPU가 차용하는 기법
- 명령어를 순차적으로만 실행하지 않고 순서를 바꿔 실행하도 무방한 명령어를 먼저 실행하여 명령어 파이프라인이 멈추는 것을 방지하는 기법
- 명령어들이 어떤 명령어와 데이터 의존성을 가지고 있는지, 순서를 바꿔 실행할 수 있는 명령어에는 어떤 것들이 있는지를 판단할 수 있어야 한다.
👩🏻💻
코어, 스레드도 중요하지만 명령어를 병렬적으로 잘 처리하는 것도 중요하다. 명령어를 처리할 때 인출, 해석, 실행, 저장 4가지 단계가 있으며 이 단계는 겹치지 않으면 동시에 실행이 가능하다. 그래서 공장 생산 라인처럼 명령어들을 파이프라인에 넣어서 동시에 처리하는 기법을 명령어 파이프라이닝이라고 한다.
파이프라인이 여러개인것을 슈퍼스칼라라고 하며
대부분의 CPU가 차용하는 기법인 비순차적 명령어 처리는 명령어 파이프라인이 멈추지 않도록 의존성이 없는 명령어를 먼저 처리하는 기법이다.
05-3 CISC와 RISC
명령어 집합
- CPU제조사마다 명령어의 세세한 생김세, 명령어로 할 수 있는 연산, 주소 지정 방식 등이 차이가 있다.
- 명령어 집합(ISA) : CPU가 이해할 수 있는 명령어들의 모음, CPU의 언어이자 하드웨어가 소프트웨어를 어떻게 이해할지에 대한 약속.
- CPU마다 ISA가 다를 수 있다.
- 같은 소스코드로 만들어진 같은 프로그램이라 할지라도 ISA가 다르면 CPU가 이해할 수 있는 명령어도 어셈블리어도 달라진다.
CISC(Complex Instruction Set Computer)
- x86, x86-64는 대표적인 CISC 기반의 ISA
- 복잡하고 다양한 수의 가변 길이 명령어 집합을 활용
- 그래서 적은 수의 명령어로도 프로그램을 실행할 수 있다.
- 프로그램을 실행하는 명령어 수가 적다는 말은 '컴파일된 프로그램의 크기가 작다'는 것을 의미
- 같은 소스를 컴파일해도 CPU마다 생성되는 실행 파일의 크기가 다를 수 있다.
- 명령어 수행 시간이 길고 가지각색이기 때문에 파이프라인이 효율적으로 명령어를 처리할 수 없음.
- 대다수의 복잡한 명령어는 사용 빈도가 낮음
RISC(Reduced Instruction Set Computer)
- 고정 길이 명령어를 사용
- 명령어 파이프라이닝에 최적화
👩💻
CPU 아키텍처에 따라 이해할 수 있는 언어가 다르다.
CPU가 이해할 수 있는 명령어들의 모음을 ISA.
명령어는 고정 길이여야 명령어 파이프라인에 유리하며 그 예시로 RISC가 있다.