[컴퓨터 구조론 이론] CPU의 작동 원리

#cs
Written by Sungbin2026년 2월 22일 · 6 min read

시리즈의 글 (8개)

  1. [컴퓨터 구조론 이론] 컴퓨터 구조 시작하기
  2. [컴퓨터 구조론 이론] 데이터
  3. [컴퓨터 구조론 이론] 명령어
  4. [컴퓨터 구조론 이론] CPU의 작동 원리
  5. [컴퓨터 구조론 이론] CPU의 성능 향상 기법
  6. [컴퓨터 구조론 이론] 메모리와 캐시 메모리
  7. [컴퓨터 구조론 이론] 보조기억장치
  8. [컴퓨터 구조론 이론] 입출력 장치

banner

본 포스팅은 인프런의 개발자를 위한 컴퓨터공학 1: 혼자 공부하는 컴퓨터구조 + 운영체제를 참조하여 작성한 글입니다.

ALU와 제어장치

ALU

ALU가 무엇이고 제어장치가 무엇인지 한번 다시 살펴보자. ALU는 쉽게 생각해서 계산하는 장치이고 제어장치는 제어신호를 발생시키고 명령어를 해석하는 장치로 나타낸다. 그러면 ALU가 어떤 정보를 받아들이고 내보내는지부터 한번 알아보자.

ALU는 제어장치로부터 수행할 연산을 알려주는 제어신호를 받아들이며 레지스터들로부터 피연산자들을 받아들인다. ALU는 레지스터와 제어장치로부터 받아들인 피연산자와 제어신호로 산술 연산, 논리 연산등 다양한 연산을 수행한다. 다음으로 ALU가 내보내는 정보에 대해 알아보도록 하겠다. ALU가 내보내는 정보로는 피연산자와 제어신호로부터 계산을 하여 나온 결과 값을 레지스터에 저장한다. 또한 플래그 정보를 플래그 레지스터에 저장을 하는데 여기서 플래그란, 연산 결과에 대한 부가 정보라고 생각하면 좋다. 물론 정보나 명칭은 CPU마다 다르다.

연산결과가 결과를 담을 레지스터에 비해 너무 크면 이것도 플래그 레지스터에 담는다.

ALU가 결과 값을 메모리가 아닌 레지스터에 저장하는 이유는 빠르기 때문이다.

그러면 ALU가 내보내는 플래그 정보에 대해 상세히 알아보자.

  • 부호 플래그
    • 연산한 결과의 부호를 나타낸다.
    • 부호 플래그가 1일 경우 계산 결과는 음수, 0일 경우 계산 결과는 양수를 의미한다.
  • 제로 플래그
    • 연산한 결과가 0인지 여부를 나타낸다.
    • 제로 플래그가 1일 경우 연산 결과는 0, 0일 경우 연산 결과는 0이 아님을 의미한다.
  • 캐리 플래그
    • 연산 결과 올림수나 빌림수가 발생했는지를 나타낸다.
    • 캐리 플래그가 1일 경우 올림수나 빌림수가 발생했음을 의미하고 0일 경우 발생하지 않았음을 의미한다.
  • 오버플로우 플래그
    • 오버플로우가 발생했는지를 나타낸다.
    • 오버플로우 플래그가 1일 경우 오버플로우가 발생했음을 의미하고 0일 경우 발생하지 않았음을 의미한다.
  • 인터럽트 플래그
    • 인터럽트가 가능한지를 나타낸다.
    • 인터럽트 플래그가 1일 경우 인터럽트가 가능함을 의미하고 0일 경우 인터럽트가 불가능함을 의미한다.
  • 슈퍼바이저 플래그
    • 커널 모드로 실행 중인지 사용자 모드로 실행 중인지를 나타낸다.

제어장치

이번에는 제어장치에 대해 알아보자. 제어장치는 제어 신호를 내보내고 명령어를 해석하는 부품이다. 그러면 제어장치가 받아들이는 정보부터 살펴보자.

첫째, 제어장치는 클럭 신호를 받아들인다. 클럭이란 컴퓨터의 모든 부품을 박자에 맞춰 일사분란하게 움직일수 있게 하는 시간 단위이다. 둘째, 제어장치는 해석해야 할 명령어를 받아들인다. CPU가 해석해야 할 명령어는 명령어 레지스터라는 특별한 레지스터에 저장된다. 제어장치는 이 명령어 레지스터로부터 해석할 명령어를 받아들이고 해석한 뒤, 제어신호를 발생시켜 컴퓨터 부품들에 수행해야 할 내용을 알려준다. 셋째, 제어장치는 플래그 레지스터 속 플래그 값을 받아들인다. 플래그는 ALU 연산에 대한 추가적인 상태 정보라고 말했다. 제어장치가 제어신호를 통해 컴퓨터 부품들을 제어할 때 이 중요한 참고사항을 무시하면 안 될 것이다. 그렇기에 제어장치는 플래그 값을 받아들이고 이를 참고하여 제어신호를 발생시킨다. 넷째, 제어장치는 시스템 버스, 그 중에서 제어 버스로 전달된 제어신호를 받아들인다. 제어신호는 CPU뿐만 아니라 입출력장치를 비롯한 CPU 외부 장치도 발생시킬 수 있다. 제어장치는 제어버스를 통해 외부로부터 전달된 제어신호를 받아들이기도 한다.

다음으로 제어장치가 내보내는 정보에 대해 알아보자. 여기에는 크게 CPU 외부에 전달하는 제어신호와 CPU 내부에 전달하는 제어신호가 존재한다. CPU 외부에 전달하는 제어신호로는 메모리에게 전달하는 제어신호가 존재하고 입출력장치로 전달하는 제어신호가 존재한다. 메모리로 보내는 제어신호는 메모리 읽기/쓰기를 위해 보내는 제어신호이며 입출력 장치 또한 입출력 장치 읽기 / 쓰기 / 테스트를 위해 내보낸다. CPU 내부에 전달되는 제어신호로는 레지스터에게 전달하는 신호와 ALU로 전달하는 제어신호가 존재한다. 레지스터로 보내는 제어신호는 레지스터를 움직이게 하는 제어신호이며, ALU에게 보내는 제어신호로는 수행할 연산을 하라고 명령하는 제어신호로 보면 좋을 것 같다.

레지스터

레지스터는 CPU 내부의 작은 임시 저장 장치이다. 프로그램 속 명령어와 데이터는 실행 전후로 레지스터에 저장을 한다. 또한, CPU 내부에는 다양한 레지스터들이 있고 각기 다른 역할들을 한다. 그것에 대해 알아보자.

프로그램 카운터

프로그램 카운터는 메모리에 가져올 다음 명령어의 주소를 저장한다. 즉, 메모리에서 읽어들일 명령어의 주소를 저장하는 것이다. 다른 종류의 CPU에는 이 프로그램 카운터를 Instruction Pointer라고 부른다. 이 프로그램 카운터 덕분에 순차적인 실행 흐름이 이어진다.

순차적인 실행 흐름이 끊기는 경우가 존재한다. 특정 메모리 주소로 실행 흐름을 이동하는 명령어 실행할 때 흐름이 끊긴다. 예를 들어 JUMP같은 명령이다. 다음으로 중간에 인터럽트가 발생할때도 마찬가지다.

명령어 레지스터

명령어 레지스터는 해석 할 명령어를 저장한다. 즉, 방금 메모리에서 읽어들인 명령어를 저장하는 것이다. 이 명령어는 제어장치가 해석한다.

메모리 주소 레지스터

메모리의 주소 레지스터는 메모리 주소를 저장한다. CPU가 읽어들이고자 하는 주소버스로 보낼 때 거치는 레지스터이다.

메모리 버퍼 레지스터

메모리와 주고 받을 값이 저장되는 레지스터이다. CPU가 정보르 데이터 버스로 주고 받을 때 거치는 레지스터이다.

플래그 레지스터

연산결과 또는 CPU 상태에 대한 부가적인 정보를 저장하는 레지스터이다.

범용 레지스터

다양하고 일반적인 상황에서 자유롭게 사용가능한 레지스터이다. 여러개가 존재하며 주소, 명령어, 데이터등을 다 담을 수 있다.

스택 포인터

스택 포인터는 스택 주소 지정 방식이라는 주소 지정 방식에 사용되고 프로그램 카운터와 베이스 레지스터는 변위 주소 지정 방식이라는 주소 지정 방식에 사용된다. 면저 스택 주소 지정 방식에 대해 알아보자. 스택 주소 지정 방식은 스택과 스택 포인터를 이용한 주소 지정 방식이다. 스택은 한쪽 끝이 막혀있는 통과 같은 저장 공간이다. 그래서 스택은 가장 최근에 저장하는 값부터 꺼낼 수 있는 것이다. 여기서 스택 포인터란 스택의 꼭대기를 가리키는 레지스터이다. 즉, 스택 포인터는 스택에 마지막으로 저장한 값의 위치를 저장하는 레지스터인것이다.

그러면 스택은 컴퓨터의 어디에 존재할까? 스택은 메모리 안에 존재한다. 정확히는 메모리 안에 스택처럼 사용할 영역이 정해져 있다. 이를 스택 영역이라고 한다.

변위 레지스터

다음으로 변위 주소 지정 방식에 대해 알아보자. 변위 주소 지정 방식이란 오퍼랜드 필드의 값(변위)과 특정 레지스터의 값을 더하여 유효 주소를 얻어내는 주소 지정 방식이다. 변위 주소 지정 방식은 오퍼랜드 필드의 주소와 어떤 레지스터를 더하는지에 따라 상대 주소 지정 방식, 베이스 레지스터 주소 지정 방식등으로 나뉜다.

상대 주소 지정 방식은 오퍼랜드와 프로그램 카운터의 값을 더하여 유효 주소를 얻는 방식이다. 베이스 레지스터 주소 지정 방식은 오퍼랜드와 베이스 레지스터의 값을 더하여 유효주소를 얻어내는 방식이다.

명령어 사이클과 인터럽트

CPU가 하나의 명령어를 처리하는 과정에는 어떤 정해진 흐름이 있고 CPU는 그 흐름을 반복하여 명령어들을 처리해 나간다. 이렇게 하나의 명령어를 처리하는 정형화된 흐름을 명령어 사이클이라고 한다.

CPU는 정해진 흐름에 따라 명령어를 처리해 나가지만 간혹 이 흐름이 끊어지는 상황이 발생한다. 이를 인터럽트라고 한다. 이번에는 이런 명령어 사이클과 인터럽트에 대해 살펴보도록 하겠다.

명령어 사이클

프로그램 속 명령어들은 일정한 주기가 반복되며 실행한다. 이 주기를 명령어 사이클이라고 한다. 명령어 사이클에는 인출 사이클과 실행 사이클이 존재한다.

  • 인출 사이클: 가장 먼저 CPU로부터 정보를 가져오는 사이클로 프로그램 카운터에 세팅 및 명령어 레지스터와 메모리 주소 레지스터에 업데이트하는 2단계로 나눠진다.
  • 실행 사이클: 가져온 정보를 실행하는 사이클로 명령어에 따라 단계가 달라지며 최대 3단계로 이루어진다.

명령어 사이클은 이렇게 인출 사이클과 실행 사이클을 반복한다. 하지만 모든 명령어가 이렇게 간단하게 실행되는 것은 아니다. 명령어를 인출하여 CPU로 가져왔다 하더라도 곧바로 실행할 수 없는 경우도 존재하기 때문이다. 바로 간접 주소 지정 방식처럼 오퍼랜드 필드에 유효주소를 명시했는데 그 주소가 또 다른 유효주소를 가지고 있을 수 있다. 이런 경우는 명령어를 인출하여 CPU로 가져왔다 하더라도 바로 실행 사이클로 돌입할 수 없다. 명령어를 실행하기 위해서는 메모리 접근을 한 번 더 해야하기 때문이다. 이런 단계를 간접 사이클이라고 한다. 이렇게 간접 사이클까지 추가되는 경우도 존재한다.

인터럽트

인터럽트란, 방해하다, 중단시키다라는 의미로 CPU가 꼭 주목해야 할 때 혹은 CPU가 얼른 처리해야 할 때 다른 작업이 생겼을때 발생한다. 인터럽트 종류에는 크게 동기 인터럽트(예외), 비동기 인터럽트(하드웨어 인터럽트)로 나눠진다.

동기 인터럽트는 CPU에 의해 발생한다. CPU가 명령어들을 수행하다가 예상치 못한 상황에 마주쳤을 때 가령 CPU가 실행하는 프로그래밍상의 오류와 같은 예외적인 상황에 마주쳤을 때 발생하는 인터럽트가 동기 인터럽트로 우리가 프로그래밍하다가 Exception을 만날때가 대표적이다.

비동기 인터럽트(하드웨어 인터럽트)는 주로 입출력장치에 의해 발생하는 인터럽트이다. 마치 알림과 같은 역할을 한다. 이런 인터럽트를 발생시키는 이유는 정말 많지만 대표적으로 입출력 작업 도중에도 효율적으로 명령어를 처리하기 위해서 비동기 인터럽트를 사용한다.

그러면 CPU는 이런 입출력 장치 알람같은 하드웨어 인터럽트는 왜 필요할까? 입출력장치는 CPU에 비해 느리다. 만약 인터럽트가 없었다면 CPU는 입출력 장치 알람을 주기적으로 폴링방식으로 확인하였을 것이다. 하지만 이런 인터럽트가 있어서 주기적인 폴링 방식 없이 CPU는 다른 작업을 하다가 알림이 받으면 그때 처리하면 되는 것이기에 매유 효율적으로 되었다.

하드웨어 인터럽트 처리 순서는 다음과 같다.

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

여기서 낯선 용어들이 나왔으니 용어 정리들을 한번 해보고 넘어가자.

인터럽트는 CPU의 정상적인 실행 흐름을 끊는 것이기에 다른 누군가가 인터럽트하기 전에는 지금 끼어들어도 되는지 CPU에게 물어봐야 한다. 이를 인터럽트 요청 신호라고 한다. 이때 CPU가 인터럽트 요청을 수용하기 위해서는 플래그 레지스터의 인터럽트 플래그가 활성화되어 있어야 한다. 인터럽트 플래그는 말 그대로 하드웨어 인터럽트를 받아들일지, 무시할지를 결정하는 플래그이다. CPU가 중요한 작업을 처리해야 하거나 어떤 방해도 받지 않아야 할 때 인터럽트 플래그는 불가능으로 설정된다. 만약 인터럽트 플래그가 불가능으로 설정되어 있다면 CPU는 인터럽트 요청이 오더라도 해당 요청을 무시한다. 반대로 인터럽트 플래그가 가능으로 설정되어 있다면 CPU는 인터럽트 요청 신호를 받아들이고 인터럽트를 처리한다.

다만 모든 하드웨어 인터럽트를 인터럽트 플래그로 막을 수 있는 것은 아니다. 인터럽트 플래그가 불가능으로 설정되어 있을지라도 무시할 수 없는 인터럽트 요청도 존재한다. 무시할 수 없는 하드웨어 인터럽트는 가장 우선순위가 높은 인터럽트로 대표적을 정전이나 하드웨어 고장으로 인한 인터럽트이다.

CPU가 인터럽트 요청을 받기로 하였다면 CPU는 인터럽트 서비스 루틴이라는 프로그램을 실행한다.

인터럽트 서비스 루틴도 프로그램이기에 메모리에 저장된다.

즉, 정상적으로 작업이 진행되다가 인터럽트가 발생하면 인터럽트 서비스 루틴으로 점프를 하고 인터럽트 서비스 루틴을 실행한 뒹 기존 작업으로 점프 후 작업을 재개하는 식으로 흘러간다. 그렇다면 CPU는 각기 다른 인터럽트 서비스 루틴을 구분할 줄 알아야 할 것이다. CPU는 수 많은 인터럽트 서비스 루틴을 구분하기 위해 인터럽트 벡터를 이용한다. 인터럽트 벡터는 인터럽트 서비스 루틴을 식별하기 위한 정보이다. 인터럽트 벡터를 알면 인터럽트 서비스 루틴의 시작 주소를 알수 있기 때문에 CPU는 인터럽트 벡터를 통해 특정 인터럽트 서비스 루틴을 처음부터 실행할 수 있다.

메모리에 인터럽트 벡터들을 모아 둔 인터럽트 벡터 테이블이 존재한다.

또한, CPU는 인터럽트 서비스 루틴을 실행하기 전에 프로그램 카운터, 레지스터에 저장된 값들을 백업해야 하는데 이 백업 내용들은 스택에 저장이 된다.