혼자 공부하는 컴퓨터 구조+운영체제 책을 바탕으로 공부한 내용입니다.
노션에 정리한 것을 그대로 옮겨온 것으로, 일부 글자가 깨질 수 있습니다.
Ch.4 CPU의 작동 원리
4-1. ALU와 제어장치
ALU
- ALU가 계산을 하기 위해서는 피연산자와 수행할 연산이 필요하다.
- 제어장치로 부터 수행할 연산을 알려주는 제어 신호를 받고
- 레지스터를 통해 피연산자를 받는다.
- ALU의 출력(연산 결과)는 특정 숫자나 문자, 또는 메모리 주소가 될 수도 있다. 이는 일시적으로 레지스터에 우선 저장된다.
- ALU는 연산 결과 말고도, 연산 결과에 대한 추가적인 상태정보를 나타내는 플래그(flag)를 출력한다. 플래그에는 다음과 같은 종류가 있다.
- 부호 플래그 : 연산결과의 부호
- 제로 플래그 : 연산결과가 0인지 여부
- 오버플로우 플래그 : 오버플로우 발생 여부 (연산 결과가 레지스터의 크기보다 큰 경우)
- 플래그는 CPU가 프로그램을 실행하는 도중 반드시 기억해야하기 때문에, 플래그 레지스터라는 별도의 레지스터에 저장한다.
제어장치
- 제어장치는 제어 신호를 내보내고, 명령어를 해석하는 부품
- 제어장치가 받아들이는 정보
- 클럭(clock) : 컴퓨터의 CPU가 동작하는 시간 단위
- 컴퓨터의 모든 부품 및 명령은 클럭 단위에 맞추어 작동한다.
- 해석해야 할 명령어 : 명령어 레지스터에 저장된 해석할 명령어를 확인
- 플래그 : 플래그 레지스터에 저장된 플래그 값 확인
- 제어 신호 : 시스템 버스 중 제어 버스를 통해 전달된 제어 신호
- 입출력장치 등 CPU 외부로부터 전달된 제어 신호를 받아들이기도 한다.
- 제어장치가 내보내는 정보
- CPU 외부에 내보내는 신호 (제어 버스를 통해 전달)
- 메모리에 전달하는 제어 신호 - 데이터 읽기/쓰기
- 입출력 장치에 전달하는 제어 신호 - 입출력장치로 부터 정보 받기/쓰기
- CPU 내부에 전달하는 신호
- ALU에 전달하는 제어 신호 - 연산 종류
- 레지스터에 전달하는 제어 신호 - 레지스터 간 데이터 이동, 명령어 해석 등
4-2. 레지스터
- 프로그램 속 명령어와 데이터는 실행 전후로 반드시 레지스터에 저장된다.
반드시 알아야 할 레지스터
- 레지스터의 이름, 크기, 종류는 CPU 제조사마다 상이하지만 많은 CPU가 공통으로 포함하는 레지스터는 총 8가지 정도가 있다.
- 프로그램 카운터(또는 명령어 포인터)
메모리에서 읽어들일 명령어의 주소 저장
- 명령어 레지스터
메모리에서 읽어들여, 앞으로 해석할 명령어 저장
- 메모리 주소 레지스터
메모리의 주소를 저장하는 레지스터, CPU가 메모리에 파일 읽기 제어신호를 보낼 때 이곳의 주소가 함께 전송된다.
- 메모리 버퍼 레지스터(또는 메모리 데이터 레지스터)
메모리와 주고 받을 ‘값’이 저장된 레지스터
💡 기본적으로 프로그램은 프로그램 카운터가 1씩 증가하며 순차적으로 실행된다.
단, 명령어 중 JUMP, CONDITIONAL JUMP, CALL, RET과 같이 실행 흐름을 이동시키는 명령에 의해 실행할 메모리 주소가 변경(실행 순서가 변경)될 수 있다.
- 범용 레지스터
자유롭게 용도를 바꾸어가며 사용할 수 있는 레지스터
데이터와 주소를 모두 저장할 수 있다.
- 플래그 레지스터
연산 결과 또는 CPU 상태에 대한 부가적인 정보인 ‘플래그’를 저장
- 스택 포인터
- 베이스 레지스터
위 두가지 레지스터는 스택 주소 지정 방식과 변위 주소 지정 방식에서 쓰이는 레지스터이다.
스택 주소 지정 방식
- 스택과 스택 포인터를 이용한 주소 지정 방식이다.
- 스택은 LIFO(후입선출)의 자료 저장 구조이며, 스택 포인터는 스택에 마지막으로 저장한 값의 위치를 저장하는 레지스터이다.
- 메모리에 정해진 일정 영역이 스택으로 사용되며, 이를 스택 영역이라 한다.
변위 주소 지정 방식
- 레지스터에 저장된 값과 오퍼랜드에 저장된 값(변위)를 더하여 유효 주소를 얻어내는 방식이다.
- 명령어는 연산 코드 필드, 사용할 레지스터 필드, 더할 값인 주소 데이터가 담긴 오퍼랜드 필드로 구성된다.
- 어떤 레지스터와 연산을 하는지에 따라 다양한 방식이 있다.
대표적으로 상대 주소 지정 방식과 베이스 레지스터 주소 지정 방식이 있다.
- 상대 주소 지정방식
- 오퍼랜드와 프로그램 카운터의 값을 더하여 유효 주소를 얻는다.
- 프로그램 카운터에는 수행할 명령어의 주소가 저장되어 있으므로, 여기에 오퍼랜드를 더한 곳의 메모리에 접근하게 된다.
- 베이스 레지스터 주소 지정 방식
- 오퍼랜드와 베이스 레지스터의 값을 더하여 유효 주소를 얻는다.
- 베이스 레지스터의 값이 기준 주소가 되며 오퍼랜드의 값이 유효 주소가 된다.
💡 주소 저장 방식을 다양하게 사용하는 이유
- 임의의 변수를 통해 손쉽게 데이터를 다루기 위해
4-3. 명령어 사이클과 인터럽트
명령어 사이클
- 명령어 사이클 : CPU가 명령어를 처리하는 하나의 정형화된 주기
- 인출 사이클 : 메모리에 있는 명령어를 CPU로 가져오는 과정 (프로그램 카운터 → 메모리 주소 레지스터 → 메모리 버퍼 레지스터 → 명령어 레지스터의 과정)
- 실행 사이클 : CPU로 인출된 명령어를 실행하는 단계
- 간접 사이클 : 간접 주소 지정 방식을 사용할 경우, 가져온 명령어가 유효 주소일 수도 있다. 이 경우, 곧바로 실행 사이클에 돌입할 수 없고 메모리에 한 번 더 접근해야 한다. 이러한 과정을 간접 사이클이라 함.
인터럽트
- 인터럽트 : CPU에서 실행되고 있는 명령어 사이클이 특정 신호에 의해 끊어지는 것
- 동기 인터럽트 : CPU에 의해 발생하는 인터럽트
- 명령어를 수행하다 에러 등 예상치 못한 상황에 마주했을 경우 발생
- 예외라고도 한다.
- 비동기 인터럽트 : 주로 입출력장치에 의해 발생하는 인터럽트
- 입출력장치에 의해 어떤 입력을 받아들였을 때 발생 (인쇄 요청, 키보드 및 마우스 입력)
- 하드웨어 인터럽트라고도 한다.
하드웨어 인터럽트
- CPU가 입출력 작업을 효율적으로 처리할 수 있도록 한다.
- 일반적으로 입출력 장치가 명령을 처리하고 완료 신호를 보내는 것은 CPU에 비해 오래걸리기 때문에, CPU가 신호가 기다리는 것보다, 인터럽틀을 통해 처리하는 것이 효율적
- 하드웨어 인터럽트 처리순서
- 입출력장치 : ** CPU에 인터럽트 요청 신호** 전송
- CPU : 각 실행 사이클이 끝날 때마다 인터럽트 여부를 확인 (신호 확인)
- CPU : 인터럽트 플래그를 통해 현재 인터럽트를 받아 들일 수 있는지 확인
- CPU : 인터럽트를 받아들일 수 있음을 확인하면 현재까지의 작업을 백업 및 중지
- 프로그램 카운터값, 사용 데이터 등 기존 작업 내역들을 스택에 백업
- CPU : 인터럽트 벡터를 참조하여 인터럽트 서비스 루틴 실행
- CPU : 실행이 끝나면 백업한 작업을 복구하여 재개
- 스택에 저장했던 값을 꺼내옴
- 인터럽트 요청 신호 : 인터럽트 발생을 알리는 신호
- 인터럽트 플래그 : 플래그 레지스터 중 하나에 저장되며, 현재 인터럽트를 받아들일 수 있는지 없는지 여부를 저장
- 단, 가장 높은 우선순위의 인터럽트는 불가능하더라도 최우선 수행함 (고장 인터럽트 등)
- 인터럽트 서비스 루틴 : 인터럽트를 처리하는 프로그램 (메모리에 저장)
- 인터럽트를 어떻게 처리하고 작동해야할 지에 대한 정보로 이루어져있다.
- 인터럽트 핸들러 라고도 한다.
- 인터럽트 벡터 : CPU가 서로다른 인터럽트를 구분하기 위해, 인터럽트 서비스 루틴을 식별하기 위한 정보
- 벡터를 통해 인터럽트 루틴의 시작 주소를 알 수 있다
예외의 종류
- 폴트 : 예외를 처리한 후, 예외가 발생한 명령어로 돌아와 실행 재개
- 트랩 : 예외를 처리한 후, 예외가 발생한 다음 명령어부터 실행 재개
- 중단 : 예외 발생 시, 프로그램을 강제 중단
- 소프트웨어 인터럽트
Ch.5 CPU 성능 향상 기법
5-1. 빠른 CPU를 위한 설계 기법
클럭
- 컴퓨터 부품들은 ‘클럭 신호’에 맞추어 움직인다.
- 즉 클럭 속도가 높아질 수록 CPU는 명령어 사이클을 더 빠르게 반복할 수 있다.
- 클럭 속도는 헤르츠 단위로 측정한다.
코어와 멀티코어
- 오늘날의 CPU는 하나의 CPU 역할을 할 수 있는 여러개의 코어로 구성된다.
- 이렇게 코어를 여러 개 포함하고 있는 CPU를 멀티코어 프로세서라고 부른다.
- 하지만 코어 수를 늘어날수록 연산을 고루 분배하는 것이 어렵기 때문에 코어 수에 비례해 성능(연산 속도)이 증가하지 않는다.
스레드와 멀티스레드
- 스레드(thread) : ‘실행 흐름의 단위’
하드웨어적 스레드 (하드웨어 스레드)
- CPU에서 사용되는 스레드
- ‘하나의 코어가 동시에 처리하는 명령어 단위’
- 이 구조의 CPU는 1코어 1스레드
- 명령어를 실행하는 부품이 2개라면, 한 번에 2개의 명령어를 수행할 수 있게 되고, 이를 멀티스레드 프로세서라고 한다.
- 2개의 코어가 각각 2개의 스레드를 갖고 있으면 이는 2코어 4스레드이다.
소프트웨어적 스레드 (스레드)
- 프로그램에서 사용되는 스레드
- ‘하나의 프로그램에서 독립적으로 실행되는 단위’
- 서로 다른 기능을 가각의 스레드로 만들어 동시에 실행
- 1코어 1스레드 CPU도 프로그램의 소프트웨어적 스레드 수십개를 실행할 수 있다.
멀티스레드 프로세서
- 멀티스레드 프로세서의 가장 큰 핵심은 레지스터이다.
- 실행해야할 명령어가 2개라면, 2개에 대한 프로그램 카운터, 스택 포인터, 데이터 버퍼 등 필요한 레지스터를 2개씩 갖추고 있으면 된다.
- 한 개의 명령어를 처리하기 위한 레지스터의 모음을 레지스터 세트라고 하며, 멀티스레드 프로세서는 레지스터 세트를 여러개 가지고 있다.
- 프로그램 입장에서는 2코어 4스레드 CPU는 4개의 코어를 갖는것처럼 동작하므로, 하드웨서 스레드를 논리 프로세서라고 부르기도 한다.
5-2. 명령어 병렬 처리 기법
- CPU의 성능을 최대로 끌어내기 위해서 CPU를 쉬지 않고 작동시키는 기법을 명령어 병렬 처리 기법이라고 한다.
- 명령어 병렬 처리 기법에는 명령어 파이프라이닝, 슈퍼스칼라, 비순차적 명령어 처리가 있다.
명령어 파이프라인
- 명령어 실행 과정을 크게 나누어 4단계로 나눌 수 있다.
- 명령어 인출
- 명령어 해석
- 명령어 실행
- 결과 저장
- 단계가 겹치지 않는다면 CPU는 각 단계를 동시에 실행할 수 있다.
- 하지만 특정 상황에서 이러한 동작이 실패하는 경우가 있는데, 이러한 상황을 파이프라인 위험이라고 한다. 파이프라인 위험에는 세가지 종류가 있다.
슈퍼파이프라인
- 파이프라인의 각 단계를 세분화한 것, 예를 들어 인출을 두단계로 세분화할 경우 사실상 인출 과정을 한번에 2개 실행하는 효과가 있기 때문에 병렬성이 강화된다.
데이터 위험
- 명령어 A는 레지스터 R1에 있는 값을 참조한다고 할 때, 명령어 A가 실행되는 도중에 명령어 B로 인해 레지스터 R1에 있는 데이터가 다른 값으로 바뀐다면, 명령어를 제대로 수행할 수 없다.
- 에러가 발생하지 않으려면 명령어 B를 모두 실행한 뒤에 명령어 A를 실행해야하는데, 이러한 경우 명령어 A는 명령어 B 데이터에 의존적이다.
- 이러한 의존적 관계의 명령어를 동시에 실행하려고하면 파이프라인이 제대로 작동하지 않는 것을 데이터 위험이라 한다.
제어 위험
- 1번지 → 2번지 → 3번지 순서로 작동되는 명령어에서, 파이프라인이 정상적으로 작동하고 있다면 1번지 명령어가 실행될 때, 2번지와 3번지도 처리중일 것이다.
- 그런데 1번지 명령어 실행결과로 프로그램 카운터가 5번지로 바뀐다면(분기) 처리중이던 2번지 3번지 명령어는 사용할 수 없게 된다.
- 이러한 경우를 제어 위험이라고 한다.
- 이를 방지하기위해 프로그램이 어디로 분기할지 예측하여 주소를 인출하는 분기 예측 기술을 사용한다.
구조적 위험
- 서로 다른 명령어가 ALU, 레지스터 등 동일한 CPU 부품을 사용하려고 할 때 발생한다.
슈퍼 스칼라
- 오늘날 대부분의 CPU는 여러개의 파이프라인을 포함하며, 이러한 구조를 슈퍼스칼라 라고 한다.
- 슈퍼스칼라 프로세서는 매 주기(클럭)마다 동시에 여러 명령어를 인출, 해석, 실행이 가능하다.
- 코어와 마찬가지로 파이프라인 위험 등의 예상치 못한 문제로 항상 파이프라이닝을 할 수 없기 때문에 파이프라인에 비례하여 성능이 증가하지는 않는다. (고도의 설계 필요)
비순차적 명령어 처리
- 보통 OoOE(Out-of-order execution)라고 줄여 부른다.
- 명령어를 순차적으로 처리하지 않는 것을 통해 파이프라인 중단을 방지한다.
- 명령어 파이프라이닝의 경우 예상치 못한 상황에서 명령어가 곧바로 처리되지 못한다면, 명령어 파이프라인이 멈춰버리는 문제가 있다.
- OoOE에서는 이러한 상황이 발생했을 때 서로 충돌이 일어나지 않는 명령어를 가져와 순서를 바꾸어 실행하는 것을 통해 파이프라인을 멈추지 않고 수행할 수 있다.
- 이러한 처리를 하기 위해서는 명령어간 데이터 의존성 관계와, 어떤 명령어끼리 순서를 바꿀 수 있는지 판단할 수 있어야 한다.
5-3. CISC와 RISC
- 명령어 파이프라인과 슈퍼스칼라 기법을 효과적으로 사용하려면, 명령어구조가 이에 최적화되어있어야 한다.
- 이와 관련해 CPU는 ISA라는 언어를 사용하고, 명령어는 각기 다른 성격의 ISA를 기반으로 설계된 CISC와 RISC 구조를 갖고 있다.
명령어 집합
- 명령어 집합 구조(ISA; Instruction Set Architecture) : CPU가 이해할 수 있는 명령어들의 모음
- CPU마다 서로 다른 ISA를 가질 수 있다. (인텔의 x86과 애플의 ARM은 서로 다른 ISA사용)
- ISA가 다르면 CPU가 이해할 수 있는 명령어가 다르기 때문에 어셈블리어까지 달라진다. 즉 같은 프로그램이라도 CPU가 달라지면 사용하지 못할 수 있다.
- 현대 ISA의 양대산맥인 CISC와 RISC는 명령어 병렬 처리 기법을 도입하기 유리한 ISA이다.
CISC (Complex Instruction Set Computer)
- 복잡하지만, 다양하고 강력한 명령어를 활용한다.
- 명령어의 형태와 크기가 다양한 가변길이 명령어를 활용한다.
- 상대적으로 적은 명령어로 프로그램을 실행할 수 있다.
- 즉 컴파일된 실행파일의 크기가 상대적으로 작기때문에 메모리 측면에서 유리하다.
- 반면 명령어의 크기와 실행되기까지의 시간이 일정하지 않고, 하나를 실행하는 데 여러 클럭 주기를 필요로 한다. 이는 명령어 파이프라이닝을 어렵게 만든다.
- 다양한 명령어를 지원하는데 반해 실제로 자주 쓰이는 것은 일부에 불과하다 (비효율적)
RISC (Reduced Instruction Set Computer)
- 짧고 규격화된 명령어, 되도록 1클럭 내외로 실행되는 명령어를 지향
- 고정 길이 명령어를 사용
- 메모리 접근 명령어가 load, store 단 두가지로 단순화 및 최소화를 추구
- 주소 지정 방식의 종류도 적다
- 메모리 접근을 최소화하기 때문에 CISC에 비해 더 많은 레지스터를 사용한다.
- 동일한 프로그램을 실행할 때 더 많은 명령어를 필요로 한다.
질문 정리
메모리는 메인보드 내부에 존재하며 보조기억장치 대비 속도가 빠른 대신 전원이 없으면 저장된 데이터가 휘발된다는 특징이 있습니다.
보조 기억장치는 메인보드 외부에 존재하며 속도가 상대적으로 느리지만, 전원이 없어도 데이터를 저장할 수 있습니다.
- 현재 전세계적으로 가장 많이 쓰이는 문자 인코딩 방식은? 설명해보세요.
현재 가장 많이 쓰이는 문자 방식은 유니코드입니다. 그중 utf-8에 대해 설명드리자면 하나의 문자를 1~4바이트의 가변적으로 인코딩 할 수 있습니다. 각 바이트마다 이 문자가 몇바이트인지를 나타내는 비트를 따로 두어 몇바이트인지 식별이 가능합니다.
- 스택과 큐의 개념과 차이점에 대하여 설명해주세요.
스택과 큐는 데이터를 저장하는 구조를 말합니다. 스택은 후입선출 자료구조로, 자료를 저장할 때 순차적으로 쌓게 됩니다. 꺼낼때는 가장 마지막으로 저장한 자료부터 꺼낼 수 있습니다.
큐는 선입선출 자료구조로, 순차적으로 자료를 저장하는 것은 스택과 동일하지만, 꺼낼때는 가장 먼저 저장한 자료부터 꺼낼 수 있습니다.
- CPU와 GPU의 차이에 대하여 설명해주세요.
CPU는 입력된 명령어를 순서대로 처리하는 직렬 처리 방식에 특화된 구조를 갖고 있습니다. 때문에 복잡한 연산을 순서대로 처리하는데 최적화 되어있습니다.
GPU는 수천 개의 코어로 이루어져 여러 명령어를 동시에 병렬 처리 할 수 있습니다. 쉽고 단순한 연산을 대량으로 처리하는 그래픽 작업 등에 최적화 되어 있습니다.
- 데이터를 읽어 온다고할 때 시스템 버스를 통해 어떤 것을 주고 받는지 설명해주세요.
시스템 버스는 주소 버스, 데이터 버스, 제어 버스로 구성됩니다.
제어 버스에서 데이터를 읽어오는 명령을 전달하며, 주소 버스에서는 읽어올 주소를 전달합니다. 그리고 데이터 버스를 통해 데이터가 CPU로 전달됩니다.
메모리는 현재 실행되고 있는 프로그램의 명령어와 데이터가 탑재되어있는 저장장치로 CPU가 필요할 때마다 읽기와 쓰기를 수행합니다.
레지스터는 CPU 내부에 존재하는 저장장치로, 현재 실행하고 있는 명령과 관련된 명령어 및 데이터가 저장되어 있습니다. 메모리보다 빠른 속도를 갖지만 저장 할 수 있는 데이터가 적습니다.
컴파일은 크게 전처리 → 컴파일 → 어셈블리 → 링킹의 4단계로 진행됩니다.
전처리에서는 소스코드에서 주석을 제거하고, 헤더를 불러오는 등의 전처리를 수행합니다.
컴파일에서는 소스코드를 어셈블리어로 변환합니다.
어셈블리에서는 어셈블리어를 완전한 기계어로 변환한 목적 파일을 생성합니다.
마지막으로 링킹에서는 목적파일에 필요한 라이브러리 파일들을 링킹하여 실행 파일을 생성합니다.
- 저급언어 고급언어의 차이와 고급언어가 필요한 이유
저급언어는 기계 중심의 언어로 컴퓨터가 직접적으로 이해할 수 있는 언어입니다.
고급언어는 사람 중심의 언어로 사람이 직접 이해하고 작성하기가 용이하나, 컴퓨터가 이해하기 위해서는 저급언어로의 변환이 필요합니다.
고급언어가 필요한 이유로는 저급 언어만으로는 사람이 복잡한 프로그램을 작성하기 어렵고, 유지보수또한 쉽지 않기 때문입니다.
- 주소지정방식의 종류와 주소지정방식이 필요한 이유에 대해서 설명하시오
메모리 기준으로 설명드리면, 즉시 주소 지정방식, 직접 주소 지정방식, 간접 주소 지정방식 총 3가지가 있습니다. 즉시는 메모리를 참조하지 않고 데이터를 직접 명령어의 오퍼랜드 필드에 담아 전송합니다. 직접 주소 지정방식은 메모리의 유효주소를 담아 전송합니다. 간접 주소 지정방식은 유효주소가 저장되어있는 메모리의 주소를 담아 전송합니다.
주소 지정방식이 필요한 이유는 명령어에 데이터를 저장할 수 있는 공간이 한정적이기 때문입니다. 보다 다양하고 큰 데이터를 담기 위해서는 주소지정방식이 필요합니다.
컴파일은 사람이 이해할 수 있는 코드인 소스 코드를 기계어로 변환하는 과정을 말합니다.
빌드는 코드를 컴퓨터에서 실제로 실행이 가능한 상태로 만드는 일을 말합니다.
즉 빌드의 과정에 컴파일이 포함된 부분집합 과정이라 볼 수 있습니다.
프로그램은 명령어의 집합으로 된 파일을 말합니다.
프로세스는 프로그램이 실행되어 메모리에 올라와 실행되는 작업 단위를 말합니다.
또는 실행중인 프로그램을 말하기도 합니다.