본문 바로가기
카테고리 없음

[혼공컴운] 2주차_ CPU A to Z

by Nam Kyeongmin 2024. 7. 14.

안녕하세요, 어느덧 2주차 혼공컴운으로 찾아왔습니다:)

'겨우 2주차'라고 여겨질 수도 있겠지만 저에게는 꽤나 꾸준하고 지속적인 노력을 투자해 스스로 무언가를 해낸 값진 시간이라 꽤나 소중하고 알차게 느껴지네요!

 

이번 2주차에서는 앞서 학습한 CPU와 명령어에 대한 추가적인 학습을 진행하는 시간이었어요. 그럼 2주차 복습 겸 내용정리하러 가보자고~!


Chapter.04 CPU의 작동 원리

핵심-학습목표(키워드)

4-1. ALU와 제어장치

4-2. 레지스터의 종류와 역할

4-3. 명령어 사이클과 인터럽트


 

4-1.에서는 CPU를 이루고 있는 부품(ALU, 제어장치, 복수 개의 레지스터) 중 ALU와 제어장치의 역할을 더 깊이 있게 학습할 수 있었다.

ALU

 

 먼저 ALU는 산술/연산을 담당하기 때문에 계산에 필요한 피연산자(데이터)와 수행할 연산(제어 신호)이 필요하다. 이때 레지스터-피연산자, 제어장치-수행할 연산으로 값을 받아와 연산을 수행한다.

 ALU 수행(산술/연산) 이후 결괏값과 플래그가 내보내지는데 결괏값은 레지스터(바로 메모리 저장X)로, 플래그는 플래그 레지스터로 저장된다. 이때 메모리(CPU 외부접근)가 아닌 레지스터(CPU 내부접근)에 저장함으로써 프로그램 실행 속도를 줄일 수 있다. 여기서 플래그는 앞서 1주차 2장에서 이진수의 양/음 여부를 알려주는 역할로 수행하기도 했는데, 그 외에도 연산 결과에 대한 추가적인 정보를 제공하는 역할을 수행한다. 추가적인 정보의 범위가 넓은 만큼 다양한 플래그 종류가 존재하는데, 몇 가지만 정리해 보자.

더보기

부호플래그: 1이면 연산결과가 음수, 0이면 연산결과가 양수.

제로플래그: 1이면 연산결과가 0, 0이면 연산결과가 0이 아님.

오버플로우(연산결과가 결괏값을 저장할 레지스터보다 크기가 큰 경우) 플래그: 1이면 오버플로우 발생, 0이면 오버플로우 발생하지 않음.

제어장치

 다음으로 제어장치! 제어장치는 클럭신호 / 명령어 / 플래그값 / 제어신호를 받아들인다. 주기를 뜻하는 클럭의 신호를 받아 명령어 해석, 신호 전달 등의 동작을 실행한다. 다음으로 명령어 레지스터로부터 해석할 명령어를 받아들인다. 제어장치는 받아들인 명령어를 해석한 후 제어신호를 발생시켜 다음 수행 내용을 전달한다. 세 번째로 플래그 레지스터 속 플래그 값을 받아들인다. 받아들인 플래그 값을 참고해 제어신호를 발생한다. 마지막으로 CPU 외부에서 발생한 제어신호를 받아들인다. 이때 제어신호는 시스템 버스 중 제어버스를 통해 전달된다.

  제어장치가 내보내는 정보로는 CPU 외부전달 제어신호 / CPU 내부전달 제어신호가 있다. CPU 외부로는 메모리와 입출력장치(보조기억장치 포함), 내부로는 ALU와 레지스터로 볼 수 있다. 이때 CPU 외부로 전달되는 제어신호는 받아들일 때와 마찬가지로 시스템 버스 중 제어버스를 통해 전달된다.

 

01

 이로써 4-1 마무리 문제 풀기 및 채점도 완료!


4-2에서는 CPU를 구성하는 레지스터에 대해 더 깊이 있는 학습을 진행했다. CPU의 구성을 배울 때부터 CPU에는 '여러' 레지스터가 있다는 것을 강조했을 만큼, 레지스터의 종류는 다양했고 반드시 알아야 할 레지스터도 꽤나 많았다. 이번 챕터에선 총 8개의 레지스터를 추가적으로 살펴봤다. 여기서 레지스터의 이름은 CPU마다 다르게 정의되기 때문에 레지스터의 역할에 중점을 두고 학습하는 것이 중요하다.

더보기

1) 프로그램 카운터 : 메모리에서 가져올 명령어(다음에 가져올)의 주소 저장

-> 지속적으로 증가하는 프로그램 카운터 = 프로그램 순차 실행 가능

-> 그러나 특정 명령어(JUMP, CALL)로 순차 실행되지 않을 수 있음.

2) 명령어 레지스터 : 메모리에서 가져온 명령어 저장 (제어장치 해석 전)

3) 메모리 주소 레지스터 : 메모리 주소(유효주소) 저장

4) 메모리 버퍼 레지스터 : 메모리와 주고받을 값(데이터, 명령어) 저장

5) 플래그 레지스터 : ALU 연산 결과의 플래그(CPU와 연산결과에 대한 추가정보) 저장

6) 범용 레지스터 : 데이터, 주소 모두 저장 가능

7) 스택 포인터 : 스택의 꼭대기 주소(Top)를 가리키는 레지스터

8) 베이스 레지스터 : 메모리 주소(기준주소) 저장

 1~4 레지스터는 메모리에 저장된 프로그램을 실행할 때 사용되는 것을 확인할 수 있다. 7,8 레지스터는 주소지정방식과 연관 지어 살펴볼 수 있는데, 먼저 7) 스택 포인터는 '스택 주소 지정 방식'에 사용된다. 스택 포인터에 저장된 값이 Top, 스택의 꼭대기를 가리키는 주소값이기 때문에 스택에 채워진 데이터의 개수를 파악할 수 있다. 8) 베이스 레지스터는 1) 프로그램 카운터와 함께 '변위 주소 지정 방식'에 사용된다. 변위 주소 지정방식이란 오퍼랜드 필드의 값과 특정 레지스터의 값을 더하여 접근할 메모리 주소(유효주소)를 구하는 방식이다. 따라서 변위 주소 지정방식을 사용하는 명령어의 구조는 연산코드(수행할), 레지스터(어떤 특정 레지스터를 더할지), 오퍼랜드(주소값에 변화를 줄 값)로 구성된다.

 이때 특정 레지스터의 값으로 프로그램 카운터가 사용되면 '상대 주소 지정 방식', 베이스 레지스터의 값이 사용되면 '베이스 레지스터 주소 지정 방식'으로 구분할 수 있다. 이때 오퍼랜드 값은 양수, 음수 모두 가능하며, 특히 상대 주소 지정 방식(프로그램 카운터)은 if문과 연관 지어 이해할 수 있다.(코드 일부 실행)

 

(+) 여태껏 배운 CPU를 실제로 보면 이론과 다른 부분이 있어 혼돈이 올 수 있다고 한다. CPU마다 부품의 이름, 구성, 정의가 다르기 때문이다. 나중에 CPU를 더 세분화해서 공부할 일이 생긴다면 지금의 학습 내용과 다른 점이 발견되어도 그 자체의 개념을 수월하게 받아들이는 게 중요할 것이다.

 

 

01

이로써 4-2 마무리 문제 풀기 및 채점도 완료!

필수 기본 숙제 p.125 확인 문제 2번 여기 있어요


 마지막 4-3에서는 명령어 사이클과 인터럽트에 대한 학습을 진행했다. CPU가 하나의 명령어를 수행하는 흐름 단위(주기)를 명령어 사이클이라고 한다. 명령어 사이클은 메모리 속 명령어를 가져오는 인출 사이클(1~4 레지스터 사용해서 명령어 들고 오기)과 명령어를 실행하는 실행 사이클(제어장치로 명령어 해석 및 제어 신호 발생), 크게 2개로 구성된다. 이때 인출한 명령어의 오퍼랜드가 데이터 값이 아닌 주소 값인 경우 바로 실행 사이클을 실행하지 않고, 간접 사이클(오퍼랜드에 저장된 주소값을 통해 메모리 접근)이 추가적으로 요구되기도 한다.

 

 흐름이 끊기는 경우도 있을 수 있는데, 그때는 인터럽트가 발생했다고 볼 수 있다. 인터럽트의 종류는 크게 동기 인터럽트(CPU에 의해 발생, 예외)와 비동기 인터럽트(입출력 장치에 의해 발생, 하드웨어 인터럽트)로 나눌 수 있다.

더보기

동기 인터럽트(예외): CPU 스스로 판단 후 실행 중단, JAVA Exception 연상해서 이해해도 좋음

비동기 인터럽트(하드웨어 인터럽트): 일종의 "알림", 프린터기가 출력 후 실행완료를 '알리는' 경우

=> CPU 효율 증가함(CPU가 하드웨어를 주기적으로 확인하는 시간을 줄이고 다른 업무 수행할 수 있음)

 즉 하드웨어 인터럽트는 CPU의 효율을 높이기 때문에 CPU가 하드웨어 인터럽트를 어떻게 처리하는지 살펴볼 필요가 있다.

더보기

1. 입출력 장치 -> '인터럽트 요청 신호' -> CPU

2. 명령어 호출 전 인터럽트 요청 여부를 확인하고 인터럽트 플래그를 통해 인터럽트 수용 가능한지 판별

3. 수용 가능하다면 현재까지 작업 백업 후 인터럽트 벡터를 참조하여 인터럽트 서비스 루틴 실행

4. 인터럽트 서비스 루틴 실행 종료 후 백업한 작업 복구 후 재개

키워드 : 인터럽트 요청 신호, 인터럽트 플래그, 인터럽트 벡터, 인터럽트 서비스 루틴

 큰 흐름을 파악했으니 4개의 키워드에 대한 학습으로 처리 과정의 이해를 높여보자. 

 인터럽트 요청 신호는 말 그대로 인터럽트를 요청하는 신호다. 이후 인터럽트 수용이 가능한지 판별하기 위해 사용되는 인터럽트 플래그는 플래그 레지스터에서 활성화 및 확인할 수 있다. 플래그가 불가능으로 설정되어 있다면 CPU는 인터럽트 요청을 무시하며 가능으로 설정되어 있을 때 인터럽트 요청을 수용하고 인터럽트를 수행한다. 이때 인터럽트 플래그로 막을 수 없는 인터럽트도 존재한다. (ex. 정전, 하드웨어 고장) 따라서 하드웨어 인터럽트는 막을 수 있는 인터럽트와 막을 수 없는 인터럽트로 구분할 수 있다.

 이후 인터럽트를 수행하기 위해 수많은 인터럽트 서비스 루틴(인터럽트 처리 방식)을 구분하는 인터럽트 벡터를 이용한다. 즉 인터럽트 벡터는 인터럽트 서비스 루틴의 시작 주소를 저장한다. 이때 CPU는 하드웨어 인터럽트 요청 신호를 보낸 대상으로부터 인터럽트 벡터(by. 데이터 버스)를 전달받는다. 인터럽트 벡터를 통해 인터럽트 서비스 루틴을 실행할 수 있는데, 이때 인터럽트 서비스 루틴 또한 명령어와 데이터로 이루어져 있기 때문에 1~4의 레지스터를 사용하여 실행된다.

 이러한 인터럽트 서비스 루틴을 실행하기 전, 현재까지 작업을 백업하는 과정이 있다고 앞서 소개했었다. 이때 백업 과정은 스택에 이루어지며 인터럽트 서비스 루틴이 종료된 후 다시 저장해 둔 값(백업)을 불러와 수행 작업을 재개한다.

이와 같은 단계, 일련의 사이클을 반복하며 프로그램은 실행되는 것이다.

 

(+) 앞서 살펴본 동기 인터럽트, 예외를 4가지 분류로 가볍게 살펴볼 수 있다.

더보기

1. 폴트 : 예외 처리 후 예외가 발생한 명령어부터 실행 재개

2. 트랩 : 예외 처리 후 예외가 발생한 다음 명령어부터 실행 재개 - ex. 디버깅(특정 코드 실행 시 건너뛰기)

3. 중단 : 강제 중단 - 심각한 오류에서 사용

4. 소프트웨어 인터럽트 : 시스템 호출 시 사용 (chapter09에서 학습 예정)

01

이로써 4-3 마무리 문제 풀기 및 채점도 완료!


05-1에서는 빠른 CPU 설계 기법을 위해 클럭, 멀티코어, 멀티스레드에 대한 추가적인 학습을 진행했다.

 1. 클럭은 이미 앞서 간략히 '주기'라고 배웠었다. 이러한 클럭은 속도가 빠를수록 작업을 수행하는 CPU의 속도도 빨라지도록 하기 때문에 CPU 속도 단위로 이해할 수 있다. Hz 단위를 사용하며 1 Hz는 1초에 1번의 클럭이 반복, 100 Hz는 1초에 100번의 클럭이 반복된다는 것을 의미한다. 이러한 클럭은 기본 클럭 속도와 최대 클럭 속도가 정해져 있는 가변적인 성질(항상 일정X)을 가진다. 유연하게 속도 조절을 하는 클럭은 오버클럭킹을 통해 최대 클럭 속도를 강제로 끌어올리기도 한다. 그러나 클럭 속도만으로 효율적인 CPU 성능을 도모하기엔 발열 문제라는 한계를 겪기 때문에 우리는 멀티코어, 멀티스레드 기법을 살펴보게 된다.

 

 2. 코어와 멀티코어

 지금까지 우리가 배워온 CPU의 정의 (= '명령어를 실행하는 부품')로 코어를 이해할 수 있다. 오늘날 기존의 CPU의 개념이 코어로 재정의되면서 CPU는 '코어를 여러 개 포함하는 부품'으로 확장되었다. CPU는 단일 코어를 보유할 수도 있고, 복수 개의 코어를 포함할 수도 있는데, 여러 개의 코어를 포함할 때 우리는 멀티코어 CPU(멀티코어 프로세서)라고 부른다. 멀티코어 CPU는 상대적으로 연산 속도가 빨라지지만, 무조건적으로 비례하여 증가하는 것은 아니라는 것에 주의해야 한다. 코어마다 처리할 연산이 어떻게 분배되느냐에 따라 연산 속도가 결정되기 때문이다.

 

 3. 스레드와 멀티스레드

 앞서 2에서 살펴본 코어마다 처리할 연산을 분배하는 방법에 대해 살펴보기 위해 스레드에 대해 알아볼 필요가 있다. 스레드는 '실행 흐름의 단위'하드웨어적 스레드와 소프트웨어적 스레드로 나눌 수 있다. 하드웨어적 스레드는 '하나의 코어가 동시에 처리하는 명령어 단위'로 지금까지는 1코어 1스레드 형태로 학습했다. 그러나 앞서 언급했던 멀티코어 CPU에서 2코어 8스레드 CPU를 살펴보면 명령어를 실행하는 두 개의 부품과 한 번에 8 개의 명령어를 처리할 수 있는 CPU로 파악할 수 있으며, 이를 멀티스레드 프로세서(멀티스레드 CPU)라고 볼 수 있다. 즉, 여러 개의 하드웨어적 스레드를 지원하는 CPU가 멀티스레드 프로세서이다. 

 소프트웨어적 스레드'하나의 프로그램에서 독립적으로 실행되는 단위'프로그래밍 언어나 OS 학습 시 접하는 스레드를 뜻한다. 이러한 스레드는 하나의 프로그램에서 독립적으로 그러나 동시에 실행될 수 있는데, 이를 멀티스레드라고 부른다. 이러한 멀티스레드는 1코어 1스레드에서도 이루어질 수 있다. 가령 동시에 실행하려는 소프트웨어적 스레드가 여러 개 존재할 경우 그 모음이 하나의 하드웨어적 스레드 단위가 되기 때문이다.

 

 앞서 살펴본 멀티스레드 프로세서 설계는 복잡하지만 여러 개의 레지스터를 통해 구성될 수 있다. 가령 하나의 명령어를 실행하기 위해 필수적인 레지스터의 모음의 개수만큼 CPU에서 동시에 실행할 수 있는 명령어의 개수가 늘어난다.

 

 그리고 컴퓨터 입장에서 바라본 멀티스레드 프로세서는 우리의 인식과 다르다. 가령 2코어 8스레드에 대해 우리는 두 개의 코어를 이용해 8개의 명령어를 동시에 처리한다고 보지만, 컴퓨터는 8개의 코어가 동시에 명령어를 처리하는 것으로 인식하는 것이다. 이를 논리 프로세서라고 부른다.

01

이로써 5-1 마무리 문제 풀기 및 채점도 완료!

필수 기본 숙제 p. 155의 확인 문제 4번 풀고 인증하기

추가 숙제(선택): Ch.05(05-1) 코어와 스레드, 멀티 코어와 멀티 스레드의 개념을 정리하기

여기 있어요


 05-2장에서는 CPU 효율을 위해 사용하는 명령어 병렬 처리 기법에 대해 학습을 진행했다. 즉, 명령어를 동시에 처리하여 CPU 작동을 효율적으로 설계하는 방법이다. 크게 명령어 파이프라이닝, 슈퍼스칼라, 비순차적 명령어 처리로 나누어서 살펴보자.

 

1. 명령어 파이프라이닝

먼저 명령어 처리 과정을 클럭(주기) 단위로 총 4개로 분류하면 다음과 같다.

더보기

명령어 인출

명령어 해석

명령어 실행

명령어 결과 저장

이렇게 구분한 명령어 처리 과정의 단계가 다르다면 아래 그림과 같이 CPU는 동시에 여러 개의 명령어를 수행할 수 있다.

 

이러한 과정을 '명령어 파이프라이닝'이라고 부른다. 시간별로 점선으로 나누어진 구간에 여러 개의 명령어를 넣은 것을 '명령어 파이프라인'이라고 하는데, 이를 통해 여러 개의 명령어를 동시에 처리하는 것이다.

 그러나 명령어 파이프라이닝 기법이 항상 높은 성능을 가져오는 것은 아니다. 데이터 위험 / 제어 위험 / 구조적 위험 등의 '파이프라인 위험'이 존재하기 때문이다.

더보기

데이터 위험 : 명령어 간 데이터 의존성으로 인해 발생. 가령 명령어2가 명령어1이 실행되어야지만 실행될 수 있을 때, 명령어1과 명령어2를 동시에 실행하면 제대로 작동하지 않음.

 

제어 위험 : 프로그램 카운터의 갑작스러운 변화로 인해 발생 -> 명령어 파이프라인에 미리 가지고 와서 처리 중인 명령어들은 갑작스러운 분기(주소 변화)로 실행 불가

=> 이러한 위험을 방지하기 위해 분기 예측 후 해당 주소를 인출하는 기술 존재.

 

구조적 위험 : 자원 위험이라고도 불리며, 서로 다른 명령어가 동일한 자원을 사용하려 할 때 발생.

 

2. 슈퍼스칼라 : 여러 개의 명령어 파이프라인을 포함한 구조. 오늘날의 CPU가 채택한 방식이다.

 슈퍼스칼라 구조로 명령어를 처리하는 CPU를 슈퍼스칼라 프로세서(슈퍼스칼라 CPU)라고 부른다. 명령어 파이프라인이 다양하기 때문에 클럭마다 동일한 단계를 동시에 실행하는 명령어가 여러 개 존재한다. 이론상으로는 파이프라인 개수에 비례하여 처리 속도가 증가하지만, 앞서 언급한 '파이프라인 위험'으로 반드시 비례관계를 가지진 않는다.

 

3. 비순차적 명령어 처리

 OoOE(Out of order execution)라고도 부르며 오늘날 대부분의 CPU가 차용하는 기법이다. 이 방식은 가령 1~6의 명령어가 있을 때 순차적으로 실행하는 것보다 순서를 바꿔서 실행할 때 수행 시간을 줄일 수 있는 경우에 사용될 수 있다. 즉 명령어 순서를 바꿔도 무방한 명령어를 먼저 실행하여 파이프라인 중단(명령어 실행 동안 대기하는 시간 발생)을 방지하는 기법이다. 이러한 명령어 병렬 기법을 사용하기 위해선 명령어 간의 관계(의존성)를 파악하는 것이 중요하다.

 

이로써 5-2 마무리 문제 풀기 및 채점도 완료!


 드디어 마지막! 5-3 학습을 정리해 보자. 이번 장에서는 ISA에 대해 알아보고 그 종류인 CISC와 RISC에 대해 알아볼 수 있었다.

 CPU마다 실행하는 명령어의 세부적인 구조(연산, 생김새, 주소 지정 방식)에는 차이가 발생하는데, 이렇게 CPU가 실행할 수 있는 명령어의 집합을 명령어 집합(명령어 집합 구조)라고 부른다. 이 명령어 집합이 바로 우리가 살펴볼 ISA다. CPU 마다 ISA가 다르다는 것은 실행가능한 명령어가 다르다는 것과 동일하고, 명령어가 다르기 때문에 명령어를 조금 더 이해하기 쉽게 만든 어셈블리어(저급언어)도 다르다는 것을 의미한다. 이러한 측면에서 우리는 ISA를 CPU 언어로 인식할 수 있게 된다.

 CPU 언어, 즉 ISA가 다르기 때문에 그에 따라 명령어를 해석하는 방식, 사용되는 레지스터의 종류와 개수, 메모리 관리 방법도 달라진다. 또한 앞서 배운 명령어 병렬 처리 기법에 적합한 ISA가 있고, 그렇지 못한 ISA도 존재하게 된다. 이때 우리는 명령어 병렬 처리 기법에 적합한 ISA로 CISC와 RISC를 살펴볼 수 있다.

 

1. CISC : Complex instruction set computer

 복잡하고 다양한 명령어를 활용하는 CPU 설계 방식이다. 명령어의 형태와 크기가 다양한 가변 길이 명령어를 활용하며 메모리 접근하는 주소 지정 방식이 다양하다. 다양한 명령어를 사용할 수 있기 때문에 비교적 적은 수의 명령어로 프로그램 실행이 가능해 메모리를 절약할 수 있다.

 그러나 명령어 크기와 시간이 일정하지 않아(규격화 X) 파이프라이닝이 어려우며 복잡한 명령어 사용으로 클럭 주기 또한 일정하지 않아 파이프라이닝에 더욱 적합하지 않다. 높은 CPU 성능을 위해 필수적으로 파이프라이닝을 사용하는 오늘날에 치명적인 약점이다.

 

2. RISC : Reduced instruction set computer

 이름에서 느낄 수 있듯 자주 쓰이는 기본적 명령어를 활용한 CPU 설계 방식이 RISC이다. 따라서 상대적으로 명령어의 종류가 적어 프로그램 실행을 위해 많은 수의 명령어를 사용하지만, 고정 길이 명령어이자 짧고 규격화된 명령어를 사용하기 때문에 파이프라이닝에 적합하다. 또한 CISC와 달리 메모리 접근하는 명령어를 load, store 두 개로 제한해 주소 지정 방식을 단순화하고 최소화한다. 따라서 RISC는 주소 지정 방식을 단순화하는 대신 레지스터를 적극 활용한다. 

 

 

이로써 5-3 마무리 문제 풀기 및 채점도 완료!


 이렇게 이번 혼공컴운 2주차, Chapter04~05 학습을 마무리했다. 사실 학습은 생각보다 지난 주차와 비슷하게 끝냈는데 미리 블로그를 작성하지 않고 한 번에 복습하려니 시간이 꽤나 걸렸다.. ^^(변명 아닌 변명) 앞으로 남은 주차에는 학습 후 미리미리 블로그 글을 작성하는 습관을 다시 들여야겠다는 생각이 드는 2주차였다.

 이번 주차도 보람찼다:)