도서 정리/혼자 공부하는 컴퓨터 구조 + 운영체제

[혼자 공부하는 컴퓨터 구조 + 운영체제] 4강 CPU의 작동원리

라일라엘 2025. 1. 12. 01:43

CPU는 ALU, 제어장치, 레지스터로 구성되어있다.

ALU

ALU는 계산을 담당하는 부품이다.

제어장치로부터 어떤 연산을 수행할지 알려주는 제어 신호를 받고, 레지스터를통해 피연산자를 받는다.

연산을 처리하고 난 뒤에 레지스터를 통해 결과값을 반환하고, 플래그 레지스터를 통해 플래그 값을 반환한다. 메모리에 저장하기엔 너무 오래 걸리기 때문에 레지스터에 저장한다.

Q : ALU가 제어장치로 부터 받는 제어신호는 기계어인가?
A : 아니다. 제어장치가 기계어를 해석하여 ALU가 이해할 수 있는 명령어(마이크로 오퍼레이션)로 번역해서 신호를 보낸다. 이때 하나의 기계어가 여러개의 마이크로 오퍼레이션으로 쪼개지는 경우도 있다.
Q : 명령어에 직접 데이터가 들어있는 경우(즉시 주소 지정방식), 제어장치는 그 데이터를 어디에 저장해두는가?
A : 임시 버퍼나 레지스터에 저장된다.
Q : 레지스터에 저장하지 않고 제어신호에 마이크로 오퍼레이션과 함께 데이터를 직접 ALU에 전달하는 방식은 안되는가?
A : 제어신호에 데이터도 함께 넣을 수 있도록 설계하게되면 제어신호가 복잡해진다. 이는 제어장치와 ALU 사이의 인터페이스가 복잡해지게 되며, 통신속도가 느려질 위험이 있다. 또 하드웨어의 설계 또한 마찬가지로 복잡해진다.
반대로 제어신호와 데이터를 분리하게 된다면, 병렬처리가 가능해져서 CPU 자원을 최대한으로 끌어올릴 수 있다는 장점이 생긴다. 이런 이유로 현대에서는 제어신호와 데이터의 역할을 분리하도록 설계한다.
Q : ALU가 ‘레지스터 A의 값과 레지스터 B의 값을 더해라’라는 제어신호를 받으면, 직접 레지스터에 접근하여 피연산자를 가져오는가?
A : 아니다. ‘레지스터 A의 값과 레지스터 B의 값을 ALU의 입력에 연결하라’라는 제어신호를 레지스터에 전송한다. 이후 레지스터는 내부에 있는 값을 ALU에 연결하게 되면, ‘ALU를 덧셈 모드로 전환한다’라는 제어신호를 ALU에 전송하고, ALU는 입력선에 연결된 값들을 더하는 과정을 거친다.
즉 ALU는 직접 레지스터에 접근하여 값을 가져오지 않는다.

ALU의 연산 결과로는 플래그도 포함된다. 플래그에는 연산에서 발생한 다양한 정보가 담겨있는데, 대표적인 플래그는 다음과 같다.

  • 부호 플래그 : 연산 결과의 부호를 나타낸다
  • 제로 플래그 : 연산 결과가 0인지 여부를 나타낸다
  • 캐리 플래그 : 연산 결과 올림수나 빌림수가 발생했는지 나타낸다
  • 오버플로우 플래그 : 인터럽트가 가능한지를 나타낸다
  • 인터럽트 플래그 : 인터럽트가 가능한지를 나타낸다
  • 슈퍼바이저 플래그 : 커널 모드로 실행중인지 여부를 나타낸다. 이때 플래그는 ‘플래그 레지스터’에 저장된다.

플래그는 CPU가 반드시 기억해야 하는 일종의 참고 정보이며, 플래그 레지스터라는 레지스터에 저장된다.

ALU에는 여러 계산을 위한 회로들이 있다. 덧셈을 위한 가산기, 뺄셈을 위한 보수기, 시프트 연산을 수행하는 시프터, 오버플로우를 대비한 오버플로우 검출기 등이 있다.

제어장치

제어장치는 제어 신호를 내보내고 명령어를 해석하는 부품이다. CPU 제조사마다 제어장치의 구현 방식이나 명령어를 해석하는 방식, 받아들이고 내보내는 정보에는 조금씩 차이가 있기 때문에 제어장치의 역할을 이해하는데 초점을 맞춰 학습하는 것이 좋다.

  • 제어장치는 클럭 신호를 받는다
    • 제어장치는 클럭 신호에 맞춰서 레지스터의 데이터를 이동시키거나 ALU에 연산이 수행된다. 다만, 하나의 클럭 신호에 하나의 연산이 처리되는것이 아니라 여러번의 신호에 하나의 연산이 처리될수도 있다.
Q : 클럭 신호에 맞춰 연산을 처리하려면 제어장치 뿐 아니라 ALU도 클럭신호에 맞춰 연산을 처리해줘야 할 것 같다. 컴퓨터 내 모든 부품은 클럭 신호에 맞춰 동작하는가?
A : 꼭 그렇지는 않다. CPU나 메인보드는 클럭을 기준으로 동작하지만, 내부적으로 다른 주파수의 클럭 도메인이나 비동기방식으로 동작하는 부품도 존재한다.
Q : 클럭 신호는 누가 발생시키는가? 그리고 메모리의 읽기 쓰기 속도가 다르고, CPU의 클럭 수가 다른데 이걸 어떻게 부품에 맞게 처리하는가?
A : 메인보드에는 보통 크리스털 발진기나 클럭 제너레이터 칩이 있습니다. 이 발진기가 기본 주파수를 만들어내고, 그 주파수를 기준으로 각 부품에 필요한 클럭 신호를 생성한다.
PLL(Phase-Locked Loop)을 통해 메인보드로부터 전달받은 주파수를 곱하거나 나눠서 원하는 주파수로 만든다. 예를들어 메인보드가 100MHz 의 주파수를 생성하고 CPU가 4GHz의 주파수를 원한다면, 메인보드로부터 받는 주파수에 40배를 곱해서 원하는 주파수로 만든다.
Q : CPU의 오버클럭도 이와 동일한 방식인가?
A : 그렇다. 오버클럭은 기준 클럭을 높이거나, PLL의 배수를 높이는 방식으로 동작한다.
  • 제어장치는 해석해야 할 명령어를 받아들인다.
    • CPU가 해석해야할 명령어가 들어있는 명령어 레지스터로부터 명령어를 받고 해석한다. 해석한 결과는 제어신호를 발생시켜 컴퓨터의 부품에게 수행할 내용을 알려준다.
Q : 제어장치가 해석한 명령어가 여러개의 마이크로 오퍼레이션으로 나뉠 수도 있는데, ‘명령어 레지스터의 어디까지 해석했는지’와 같은 정보는 어디에 저장되는가?
A : 제어장치 내부 레지스터나 파이프라인 레지스터 또는 마이크로코드 엔진의 마이크로PC(또는 제어 스토어 주소 레지스터) 안에 저장된다.
명령어 레지스터(IR)는 그저 현재 명령어가 무엇인지를 보관하는 용도이고, 디코딩 진행 상태나 마이크로오퍼레이션 분해 상태는 CPU 내부의 훨씬 복잡한 논리와 레지스터, 버퍼들이 관리한다.
명령어의 형태(CISC, RISC)에 따라 마이크로 오퍼레이션으로 분해되는 형태가 달라진다.
  • 제어장치는 플래스 레지스터 속 플래그 값을 받아들인다.
    • 제어장치는 제어신호를 발생시킬때 플래그 값을 참고하여 발생시킨다.
  • 제어장치는 시스템 버스, 그중에서 제어 버스로 제어신호를 받아들인다.
    • 제어신호는 CPU, 입출력장치, CPU 외부장치에서도 발생시킬 수 있다. 제어장치는 제어 버스를 통해 제어신호를 전송하고, 외부로부터 전달된 제어신호를 받아들일 수도 있다.
Q : 제어신호가 ALU나 레지스터같은 CPU 내에 있는 장치로 전송할때에도 동일하게 시스템 버스를 통해서 동작하는가?
A : 시스템 버스와 같이 제어신호를 수신호하는 신호선(control line)이 존재한다. 다만 엄연히 따지면 ‘시스템 버스’라고 말하기엔 어렵다.
추상적인 관점에서는 제어신호를 전달하는 내부 버스(또는 제어 경로)가 있다고 볼 수 있다. 각 하드웨어가 제어 버스에 연결되어, 자신에게 해당하는 신호만 읽고 반응한다는 식으로 이해할 수 있다.
미시적인 관점에서는 여러개의 제어로직으로 분산되어있어서, ‘단일 제어 버스’라기보다는 수많은 신호선과 파이프라인 레지스터가 존재합니다.

레지스터

명령어와 데이터는 실행 전후로 레지스터에 저장된다. 레지스터는 CPU마다 이름, 크기, 종류가 매우 다양하다. 따라서 모든 레지스터를 알 수는 없고, 중요하게 다뤄지면서 많은 CPU가 공통적으로 사용하는 대중적인 8개의 레지스터를 알아보자

  1. 프로그램 카운터(PC; Program Counter)
    • 메모리에서 가져올 명령어의 주소를 가지고 있다. 명령어 포인터(IP; Instruction Pointer)라고도 불린다.
  2. 명령어 레지스터(IR; Instruction Register)
    • 해석할 명령어, 즉 방금 메모리에서 읽어들인 명령어를 저장하는 레지스터이다. 제어장치가 여기에 있는 명령어를 해석한다.
  3. 메모리 주소 레지스터(MAR; Memory Address Register)
    • 메모리의 주소를 저장하는 레지스터이다. CPU가 읽어들이고자 하는 주소 값을 주소 버스로 보낼 때 여기를 거친다.
  4. 메모리 버퍼 레지스터(MBR; Memory Buffer Register)
    • 메모리와 주고받을 값(데이터와 명령어)를 저장하는 레지스터이다. 메모리에 쓰고 싶은 값이나 메모리로부터 전달받은 값은 여기를 거친다.
    • 메모리 데이터 레지스터(MDR; Memory Data Register)라고도 불린다.
  5. 범용 레지스터
    • 다양한 상황에서 자유롭게 사용할 수 있는 레지스터이다. 일반적으로 CPU안에는 여러 개의 범용 레지스터가 존재한다.
  6. 플래그 레지스터
    • 연산 결과 또는 CPU 상태에 대한 부가적인 정보를 저장한다.
  7. 스택 포인터
    • 스택 주소 지정 방식이라는 주소 지정방식에 사용된다. 스택의 가장 마지막에 저장된 값의 위치를 가지고 있다.
    • 이때 사용되는 스택은 ‘스택영역 내에서 스택 자료구조로 구현된 영역’을 의미한다.
  8. 베이스 레지스터
    • 변위 주소 지정방식이라는 주소 지정방식에 사용된다.

레지스터에 대해 디테일한 정보는 여기를 참조한다.

프로그램이 명령어를 처리하는 과정은 다음과 같다.

프로그램이 1000번지부터 시작된다고 가정해보자.

  1. 프로그램 카운터에 1000이 저장된다. (가져올 명령어가 1000번지에 있다)
  2. 1000번지를 읽기 위해 메모리 주소 레지스터에 1000이 저장된다.
  3. 주소버스를 통해 메모리 주소 레지스터에 저장된 1000이 메모리에 전송된다. 또 ‘메모리를 읽어라’라는 제어신호가 제어 버스를 통해 전송된다.
  4. 메모리의 1000번지에 있는 데이터를 데이터 버스를 통해 메모리 버퍼 레지스터에 저장된다.
  5. 메모리 버퍼 레지스터에 저장된 데이터가 명령어 레지스터로 이동한다. 이후 프로그램 카운터가 하나 증가한다(다음에 읽을 명령어의 주소를 가리킨다)
  6. 명령어 레지스터에 있는 명령어를 해석하고 처리한다.

명령어 사이클

CPU는 수많은 명령어들을 하나씩 실행한다. 이때 각각의 명령어들은 일정한 주기가 반복되며 실행되는데, 이 주기를 명령어 사이클이라고 한다.

명령어 사이클은 세 가지 단계로 나눌 수 있다.

  • 인출 사이클 : 명령어를 메모리로부터 CPU로 가져오는 단계
  • 실행 사이클 : 인출한 명령어를 실행하는 단계
  • 간접 사이클 : 메모리 접근을 위해 메모리를 한번 더 접근하는 단계

프로그램은 일반적으로 인출과 실행 사이클을 반복하며 실행한다. 하지만 종종 명령어를 인출하여 CPU로 가져왔다 하더라도 곧바로 실행할 수 없는 경우가 종종 있다. 가령 간접 주소 지정 방식을 사용한 명령어의 경우, 오퍼랜드에 유효주소가 명시되어 있기 때문에 데이터를 가져오려면 메모리에 접근해야한다. 이렇게 CPU에서 간접 주소를 통해 실제 주소를 알아내거나, 실제 데이터를 읽어오는 단계를 간접 사이클이라고 한다.

흐름의 순서도는 다음과 같다.

인터럽트

인터럽트는 CPU의 작업을 방해하는 신호이다. 이는 CPU의 흐름을 중단시킬 정도로 긴급하게 처리되어야 하는 순간에 발생한다. 구체적으로 어떤 상황에 발생하는지 알아보자.

인터럽트의 종류로는 크게 동기 인터럽트와 비동기 인터럽트가 있다.

  • 동기 인터럽트
    • CPU에 의해 발생하는 인터럽트로, 명령어를 수행하다가 오류와 같은 예외적인 상황을 마주했을때 발생하는 인터럽트이다. 이런 점에서 예외(Exception)라고 부른다.
  • 비동기 인터럽트
    • 주로 입출력장치에 의해 발생하는 인터럽트이다. CPU가 프린터와 같은 입출력 장치에 작업을 부탁하면, 작업을 끝냈을 때 CPU에 완료 알림(인터럽트)를 보낸다. 일반적으로 비동기 인터럽트를 인터럽트라고 칭한다. 하지만 헷갈릴 수 있으니, 앞으로 본문에선 하드웨어 인터럽트라고 표기한다.

하드웨어 인터럽트

하드웨어 인터럽트는 알림과 같은 인터럽트이다. CPU가 입출력 작업 도중에도 명령어를 처리하기위해 사용한다. 입출력 장치는 속도가 현저히 느리기 때문에 결과를 즉시 받아볼 수 없다. 따라서 다른 작업을 하고 있다가 입출력 장치의 연산이 끝났을때 인터럽트를 통해서 결과를 받을 수 있다.

하드웨어 인터럽트 처리순서

CPU가 인터럽트를 처리하는 방식은 대부분 비슷하다.

  1. 입출력장치가 CPU에 인터럽트 요청 신호를 보낸다.
  2. CPU는 실행 사이클이 끝나고 명령어 인출전에 인터럽트 여부를 확인한다.
  3. CPU는 인터럽트 요청을 확인하고 인터럽트 플래그를 통해 현재 인터럽트를 받아들일 수 있는지 확인한다.
  4. 인터럽트를 받아들일 수 있다면 CPU는 지금까지의 작업을 백업한다.
  5. CPU는 인터럽트 벡터를 참조하여 인터럽트 서비스 루틴을 실행한다.
  6. 인터럽트 서비스 루틴 실행이 끝나면 4번에서 백업해둔 작업을 복구하여 실행을 재개한다.

여기에서 붉은 글씨로 표기한 부분에 대해서 자세히 알아보자.

  • 인터럽트 요청 신호
    • CPU에게 인터럽트를 해도 괜찮은지 보내는 요청 신호이다. CPU가 인터럽트 요청을 수용하기 위해선 인터럽트 플래그가 활성화되어야 한다.
  • 인터럽트 플래그
    • 인터럽트를 받아들일지, 무시할지를 결정하는 플래그이다. CPU가 중요한 작업을 처리중이거나 방해를 받지 말아야 하는 상황일때는 불가능으로 설정된다.
    • 하지만 모든 하드웨어를 인터럽트 플래그로 막을 수는 없다. 반드시 가장 먼저 처리해야 하는 인터럽트가 이런 경우인데, 정전이나 하드웨어 고장으로 인한 인터럽트가 여기에 해당된다.
  • 인터럽트 서비스 루틴(ISR; Interrupt Service Routine)
    • 인터럽트를 처리하기 위한 프로그램이다. 인터럽트 핸들러(IH; Interrupt Handler)라고도 불린다. 이는 각 장치마다 인터럽트가 발생했을때 어떻게 동작해야 하는지에 대한 정보가 담겨있다. 서비스 루틴을 처리하고 나면 본래 수행하고 있던 작업으로 다시 돌아온다.
    • 인터럽트를 처리하는 방법은 입출력장치마다 다르므로 각기 다른 인터럽트 서비스 루틴을 가지고 있다. 즉, 메모리에는 여러개의 인터럽트 서비스 루틴이 저장되어 있다. CPU는 수많은 서비스 루틴을 구분하기 위해선 인터럽트 벡터를 사용한다.
  • 인터럽트 벡터
    • 인터럽트 서비스 루틴을 식별하기 위한 정보이다. 인터럽트 벡터를 알면 인터럽트 서비스 루틴의 시작 주소를 알 수 있다.
    • CPU는 하드웨어 인터럽트 요청을 보낸 대상으로부터 데이터 버스를 통해 인터럽트 벡터를 전달받는다.
Q : 입출력 기기의 인터럽트 서비스 루틴은 어떻게 얻는가? 입출력 기기를 연결했을때 다운받는가? 혹은 입출력 기기를 판매하는 업체에서 직접 다운받아야 하는가?
A : 인터럽트 서비스 루틴은 기기를 제어하는 소프트웨어(드라이버)안에 포함되어있다. 인터럽트 서비스 루틴은 운영체제나 해당 기기의 드라이버로부터 획득할 수 있다.
운영체제에 이미 포함되어 있다면 기기를 연결했을 때 즉시 사용할 수 있다. 새로운 기기를 구매했을 때 업체에서 제공하는 드라이버를 설치하게되면, 드라이버 내에 포함되어 있기 때문에 함께 설치된다.

인터럽트를 수행하기 위해선 그동안 처리하고 있던 정보들을 백업해줘야 하는데, 이런 정보들은 스택 영역에 저장된다.

지금 까지의 명령어 사이클은 다음과 같다.

예외의 종류

예외의 종류는 다음과 같다.

예외가 발생하면 CPU는 하던일은 중단하고 예외를 처리한다. 예외를 처리하고 나면 본래 하던 작업으로 되돌아와 실행을 재개한다. CPU가 본래 하던 작업으로 되돌아왔을때 예외가 발생한 명령어부터 실행하느냐, 예외가발생한 명령어의 다음 명령어부터 실행하느냐에 따라 폴트와 트랩으로 나뉜다.

  • 폴트(fault)
    • 예외를 처리한 직후 예외가 발생한 명령어부터 실행을 재개한다. 예를들어 보조기억장치에 필요한 데이터가 있을때 사용할 수 있다. 폴트를 발생시키면 보조기억장치의 데이터를 메모리로 옮긴뒤 다시 명령어를 실행시킬 수 있다.
  • 트랩(trap)
    • 예외를 처리한 직후 예외가 발생한 명령어의 다음 명령어부터 실행을 재개하는 예외이다. 주로 디버깅에서 사용된다. 특정 순간에서 멈추고 싶을때 트랩을 사용한 뒤, 상태를 살펴보고 재개하고 싶을때 다음 명령어부터 처리한다.
  • 중단(abort)
    • CPU가 실행중인 프로그램을 강제로 중단시킨다. 이는 심각한 오류를 발견했을 때 발생한다.
  • 소프트웨어 인터럽트(software interrupt)
    • 시스템 호출이 발생했을 때 나타난다. 이는 9장에서 다룬다.