1. ALU와 제어장치

CPU: 메모리에 저장된 명령어를 읽어 들이고, 해석하고, 실행하는 장치ARU: 계산하는 장치제어장치: 제어신호를 발생시키고 명령어를 해석하는 장치레지스터: 작은 임시 저장 장치
이 책은 ALU나 제어장치를 구성하는 회로나 구현 방법은 다루지 않습니다
1.1 ALU

받아들이는 정보: 레지스터(피연산자), 제어장치(제어신호)내보내는 정보: 레지스터(결과값), 플래그 레지스터(플래그)
ALU는 계산하는 부품이라고 했었죠? 계산을 하기 위해 무엇이 필요할까요? 1+2를 계산할 때,
- 1과 2라는
피연산자와 더하기라는수행할 연산자가 필요하듯, - ALU가 계산을 하기 위해서는
피연산자와수행할 연산이 필요합니다. - 그래서
레지스터로부터 피연산자를 받고,제어장치로부터 수행할 연산을 받습니다. - 그리고
ARU는 이렇게 받아들인 피연산자와 제어신호를 바탕으로 계산하고,- 그 결과값을 레지스터에 담아줍니다.
- cf. 이 결과값에는 숫자, 문자, 메모리 주소가 될 수 있습니다. (0과 1로 표현된 모든 정보들)
- 레지스터로 내보낸 결과값을 메모리가 아니라
레지스터에 저장하는 이유는CPU가 메모리에 접근하는 속도는레지스터에 접근하는 속도보다 훨씬 느립니다.- ALU가 연산할 때 마다 결과를 메모리에 저장한다면, CPU는 메모리에 자주 접근하게 되고,
- 이는 CPU가 프로그램 실행 속도를 늦출 수 있습니다.
- 그래서 ALU의 결괏값을 메모리가 아닌
레지스터에 우선 저장하는 겁니다.
- ARU가 내보내는 또 다른 정보 중에서는
플래그(flag)라는 것도 있습니다.- 플래그는 이전 장에서 봤듯이,
- “이진수만 봐서는 음수인지 양수인지 판단하기 어려워서 이를 구분하기 위해 플래그를 사용한다”
- 즉, 연산 결과에 대한 부가정보를 의미합니다.
- 이처럼
ALU는 결과값뿐만 아니라 연산 결과에 대한 추가적인 정보를 내보내야 할 때가 있습니다.- e.g. 결과값이 음수일 때, ALU는 **‘방금 계산한 결과는 음수’**라는 추가 정보를 보냄
- e.g. 결과값이 결과를 담을 레지스터보다 클 때, ALU는 **‘결과값이 너무 크다’**라는 추가 정보를 보냄
- 이를
오버플로우라고 함
- 이를
💡
오버플로우(overflow)연산 결과가 연산 결과를 담을 레지스터보다 큰 상황을 “오버플로우(overflow)”라고 합니다.
1.1.1 대표 플래그 종류
ALU가 내보내는 대표적인 플래그 종류는 아래와 같습니다.
부호 플래그- 연산한 결과의 부호
- e.g. 1일 경우 계산 결과는 음수, 0일 경우 계산 결과는 양수를 의미
제로 플래그- 연산 결과가 0인지 여부
- e.g. 1일 경우 연산 결과는 0, 0일 경우 연산 결 과는 0이 아님을 의미
캐리 플래그- 연산 결과 올림수나 빌림수가 발생했는지 여부
- e.g. 1일 경우 올림수나 빌림수가 발생했음을 의미하고, 0일 경우 발생하지 않았음을 의미
오버플로우 플래그- 오버플로우가 발생했는지 여부
- e.g. 1일 경우 오버플로우가 발생했음을 의미하고, 0일 경우 발생하지 않았음을 의미
인터럽트 플래그- 인터럽트가 가능한지 여부
- cf. 인터럽트는 04-3절에서 설명
- e.g. 1일 경우 인터럽트가 가능함을 의미하 고, 0일 경우 인터럽트가 불가능함을 의미
슈퍼바이저 플래그- 커널 모드로 실행 중인지, 사용자 모드로 실행 중인지
- cf. 커널 모드와 사 용자 모드는 09장에서 설명
- e.g. 1일 경우 커널 모드로 실행 중임을 의미하고, 0일 경우 사용자 모드로 실행 중임을 의미
- 커널 모드로 실행 중인지, 사용자 모드로 실행 중인지
1.1.2 플래그 레지스터
위 플래그들은 CPU가 프로그램을 실행하는 도중 반드시 기억해야 하는 일종의 참고 정보입니다.
그리고 플래그들은 플래그 레지스터라는 레지스터에 저장됩니다.
플래그 레지스터는 이름 그대로 플래그 값들을 저장하는 레지스터입니다.
이 레지스터를 읽으면 연산 결과에 대한 추가적인 정보, 참고 정보를 얻을 수 있겠죠?

예를 들어, 플래그 레지스터가 그림과 같은 구조를 가지고 있고,
ALU가 연산을 수행한 직후 부호 플래그가 1이 되었다면, 연산 결과는 음수임을 알 수 있습니다.

또 ALU가 연산을 수행한 직후 플래그 레지스터의 제로 플래그가 1이면, 연산 결과는 0임을 알 수 있습니다.
1.2 제어장치
제어장치는 제어 신호를 내보내고, 명령어를 해석하는 부품제어 신호는 컴퓨터 부품들을 관리하고 작동시키기 위한 일종의 전기 신호
제어장치는 CPU의 구성 요소 중 가장 정교하게 설계된 부품이라고 해도 과언이 아닙니다. 그래서 CPU 제조사마다 제어장치의 구현 방식이나 명령어를 해석하는 방식, 받아들이고 내보내는 정보에는 조금씩 차이가 있습니다.

받아들이는 정보:내보내는 정보:
1.2.1 클럭(clock, 시계)

- 컴퓨터의 모든 부품을 박자에 맞춰 일사불란하게 움직일 수 있게 하는 시간 단위
클럭 신호는 일정한 박자에 맞춰서 “똑딱똑딱”이런 식으로 발생하는 신호라고 보면 됩니다.- 그리고 클럭 신호에 맞춰서 명령어들이 수행됩니다.
- e.g.
메모리에서 명령어 가져오기는 그림과 같은 클럭 주기로 수행된다고 보면 됩니다.
- e.g.
- 메모리에 있는 특정 메모리는 이런 클럭 신호를 바탕으로 움직이는 것도 있습니다.
- e.g. 클럭의 “똑딱-똑딱” 주기에 맞춰 한
레지스터에서다른 레지스터로 데이터가 이동되거나,ALU에서 연산이 수행되거나, CPU가 메모리에 저장된 명령어를 읽어들이는 것
1.2.2 해석할 명령어

다음으로 제어장치가 받아들이는 정보는 해석할 명령어를 받아들입니다.
- CPU가 해석해야 할 명령어는
명령어 레지스터라는 특별한 레지스터에 저장됩니다. 제어장치는명령어 레지스터로부터 해석할 명령어를 받아들이고 해석한 뒤,- 제어 신호를 발생시켜 컴퓨터 부품들에 수행해야 할 내용을 알려줍니다.
1.2.3 플래그 레지스터 속 플래그 값

플래그는 ALU 연산에 대한 추가적인 상태 정보라고 했습니다.- 명령어 레지스터로부터 해석할 명령어를 받아들일 떄, 부가정보를 놓쳐서는 안되겠죠?
- 가령, 연산결과가 0인지 아닌지, 음수인지 아닌지 등의 부가정보들도 명령어 해석과정에서 반드시 필요합니다.
- 그렇기에
제어장치는 플래그 값을 받아들이고, 이를 참고하여 제어 신호를 발생시킵니다.
1.2.4 제어신호로 전달된 제어신호

제어장치가 제어신호를 발생시킨다고는 하지만,- 사실 제어신호를 발생시키는 부품은 CPU만 있는 것이 아니라 입출력장치를 비롯한 주변장치도 가능합니다.
- 외부로부터 제어신호가 발생됐다면, 또 그게 CPU로 전달됐다면,
- 이 신호는 어떤 신호인지 제어장치가 받아들이게 됩니다.
- 즉,
제어장치는 제어 버스를 통해 외부로부터 전달된 제어 신호를 받아들이기도 합니다.
1.2.5 제어신호 (내보내는 정보)
이제 제어장치가 내보내는 정보를 알아봅시다.

여기에는 크게 CPU 외부에 전달하는 제어 신호와 CPU 내부에 전달하는 제어 신호가 있습니다.
CPU 내부에 전달하는 제어신호는 레지스터에 보낼지, ALU에 보낼지로 나뉩니다.- 레지스터에 내보내는 제어신호
- 레지스터 간에 정보를 이동, 저장해라 등의 행동 신호
- ARU에 내보내는 제어신호
- 수행할 연산을 지시하는 제어신호
- 레지스터에 내보내는 제어신호
CPU 외부에 전달하는 제어신호는 메모리에 보낼지, 입출력장치에 보낼지로 나뉩니다.- 메모리에 내보내는 제어신호
- 메모리를 읽고, 메모리를 쓰라 등의 지시하는 신호
- 입출력장치에 내보내는 제어신호
- 입출력 장치를 읽어라, 입출력장치를 테스트하라 등의 지시하는 신호
- 메모리에 내보내는 제어신호
2. 레지스터
레지스터 : CPU 내부에 있는 작은 임시 저장 장치

프로그램 속 명령어와 데이터는 실행 전후로 반드시 레지스터에 저장됩니다.
- 따라서 레지스터에 저장된 값만 잘 관찰해도 프로그램의 실행 흐름을 파악할 수 있습니다.
- 즉, 레지스터 속 값을 유심히 관찰하면 프로그램을 실행할 때, CPU 내에서 무슨 일이 벌어지고 있는지,
- 어떤 명령어가 어떻게 수행되는지 알 수 있습니다.
2.1 반드시 알아야 할 레지스터
상용화된 CPU 속 레지스터들은 CPU마다 이름, 크기, 종류가 매우 다양합니다. 이들은 각 CPU 제조사 홈페이지나 공식 문서 등에서 확인할 수 있습니다. 모든 레지스터를 이 책에서 전부 다룰 수는 없기에 이번 절에서는 여러 전공 서적에서 중요하게 다루는 레지스터, 많은 CPU가 공통으로 포함 하고있는 여덟 개의 레지스터를 학습합니다.
프로그램 카운터: 메모리에서 가져올 명령어의 주소 (메모리에서 읽어들일 명령어의 주소)명령어 레지스터: 해석할 명령어 (방금 메모리에서 읽어들인 명령어)메모리 주소 레지스터: 메모리의 주소를 저장메모리 버퍼 레지스터: 메모리와 주고받을 값 (데이터와 명령어)플래그 레지스터: 연산 결과 또는 CPU 상태에 대한 부가적인 정보범용 레지스터: 다양하고 일반적인 상황에서 자유롭게 사용스택 포인터: 스택의 꼭대기 가리킴베이스 레지스터: 기준 주소 저장
우선 첫 번째 프로그램 카운터부터 네 번째 메모리 버퍼 레지스터까지, 네 개의 레지스터를 살펴보겠 습니다.
2.1.1 프로그램 카운터
프로그램 카운터(PC; Program Counter)- 메모리에서 가져올 명령어의 주소, 즉 메모리에서 읽어들일 명령어의 주소를 저장합니다.
- 프로그램 카운터를
명령어 포인터(IP; Instruction Pointer)라고 부르는 CPU도 있습니다.
2.1.2 명령어 레지스터
명령어 레지스터(IR; Instruction Register)- 해석할 명령어, 즉 방금 메모리에서 읽어들인 명령어를 저장하는 레지스터입니다.
- 제어장치는 명령어 레지스터 속 명령어를 받아들이고, 이를 해석한 뒤 제어 신호를 내보냅니다.
2.1.3 메모리 주소 레지스터
메모리 주소 레지스터(MAR; Memory Address Register)- 메모리의 주소를 저장하는 레지스터입니다.
- CPU가 읽어 들이고자 하는 주소 값을 주소 버스로 보낼 때 메모리 주소 레지스터를 거치게 됩니다.
2.1.4 메모리 버퍼 레지스터
메모리 버퍼 레지스터(MBR; Memory Buffer Register)- 메모리와 주고받을 값(데이터와 명령어)을 저장하는 레지스터입니다.
- 즉, 메모리에 쓰고 싶은 값이나 메모리로부터 전달받은 값은 메모리 버퍼 레지스터를 거칩니다.
- CPU가 주소 버스로 내보낼 값이 메모리 주소 레지스터를 거친다면,
- 데이터 버스로 주고 받을 값은 메모리 버퍼 레지스터를 거칩니다.
- cf. 메모리 버퍼 레지스터는
메모리 데이터 레지스터(MDR; Memory Data Register)라고도 부릅니다.
아직은 앞의 레지스터들을 글로만 읽었기에 감이 잘 잡히지 않습니다. 그렇다면 각각의 레지스터들이 메모리에 저장된 프로그램이 어떻게 값이 담기는지 알아봅시다.

(1) CPU로 실행할 프로그램이 1000번지부터 1500번지까지 저장되어 있다고 가정하겠습니다. ( 1000번지에는 1101(2)이 저장되어 있다고 가정 )

(2) 첫 번쟤 명령어부터 실행한다고 가정하면 프로그램 카운터에는 1000이 저장됩니다. 이는 메모리에서 가져올 명령어가 1000번지에 있다는 걸 의미합니다.

(3) 1000번지를 읽어 들이기 위해서는 주소 버스로 1000번지를 내보내야 합니다. 이를 위해 메모리 주소 레지스터에는 1000이 저장됩니다.

(4) ’메모리 읽기’ 제어 신호와 메모리 주소 레지스터 값이
각각 제어 버스와 주소 버스를 통해, 메모리로 보내집니다.

(5) 메모리 1000번지에 저장된 값은 데이터 버스를 통해 메모리 버퍼 레지스터로 전달되고,
프로그램 카운터는 증가되어 다음 명령어를 읽어들일 준비를 합니다.

(6) 메모리 버퍼 레지스터에 저장된 값은 명령어 레지스터로 이동합니다.

(7) 제어장치는 명령어 레지스터의 명령어를 해석하고 제어 신호를 발생시킵니다.
5단계에서 프로그램 카운터 값이 증가한 것을 확인했습니다. 프로그램 카운터 값이 증가했으니 1000번지 명령어 처리가 끝나면 CPU는 다음 명령어(1001번지)를 읽어 들입니다.
이처럼 프로그램 카운터는 지속적으로 증가하며 계속해서 다음 명령어를 읽어들일 준비를 합니다.
이 과정이 반복되면서 CPU는 프로그램을 차례대로 실행해 나갑니다.
결국 CPU가 메모리 속 프로그램을 순차적으로 읽어들이고 실행해 나갈 수 있는 이유는
CPU 속 프로그램 카운터가 꾸준히 증가하기 때문입니다.
💡 순차적인 실행 흐름이 끊기는 경우
일반적으로 프로그램 카운터는 꾸준히 증가하며 프로그램을 차례대로 실행합니다. 하지만 종종 프로그램 카운터가 실행 중인 명령어의 다음 번지 주소가 아닌 전혀 다른 값으로 업데이트되는 경우가 있습니다. 이런 상황이라면 프로그램이 차례대 로 실행되지 않습니다. 이런 상황은 언제 발생할까요?
- 특정 메모리 주소로 실행 흐름을 이동하는 명령어 실행 시
- e.g. JUMP, CONDITIONAL JUMP, CALL, RET 같이 특정 메모리 주소로 실행 흐름을 이동하는 명령어
- 인터럽트 발생 시
- cf. 인터럽트는 다음 절에서 다룸
- etc..
2.1.5 플래그 레지스터
플래그 레지스터(flag register)- 연산 결과 또는 CPU 상태에 대한 부가적인 정보를 저장하는 레지스터
- 플래그 레지스터는 04-1절에서 본 적이 있습니다.
- ALU 연산 결과에 따른 플래그를 플래그 레지스터에 저장한다고 했습니다.
2.1.6 범용 레지스터
범용 레지스터(general purpose register)- 이름 그대로 다양하고 일반적인 상황에서 자유롭게 사용할 수 있는 레지스터
메모리 버퍼 레지스터는 데이터 버스로 주고받을 값만 저장하고,메모리 주소 레지스터는 주소 버스로 내보낼 주소값만 저장하지만,범용 레지스터는 데이터와 주소를 모두 저장할 수 있습니다.- 일반적으로 CPU 안에는 여러 개의 범용 레지스터들이 있고,
- 현대 대다수 CPU는 모두 범용 레지스터를 가지고 있습니다.
스택 포인터, 베이스 레지스터는 주소 지정에 사용될 수 있는 특별한 레지스터입니다.
스택 포인터(stack pointer)는 스택 주소 지정 방식이라는 주소 지정 방식에 사용되고,,
프로그램 카운터와 베이스 레지스터는 변위 주소 지정 방식이라는 주소 지정 방식에 사용합니다.
2.2 특정 레지스터를 이용한 주소 지정 방식
2.2.1 스택 주소 지정 방식
- 스택과 스택 포인터를 이용한 주소 지정 방식
- cf. 스택은 한쪽 끝이 막혀 있는 통과 같은 저장 공간
- 그래서 스택은 가장 최근에 저장하는 값부터 꺼낼 수 있죠.
스택 포인터: 스택의 꼭대기를 가리키는 레지스터- 즉, 스택 포인터는 스택에 마지막으로 저장한 값의 위치를 저장하는 레지스터
예를 들어, 위에서부터 주소가 매겨져 있고, 차곡차곡 데이터가 저장되어 있는 스택이 있다고 가정해 봅시다.
(1)

- 이때 스택 포인터는 스택의 제일 꼭대기 주소, 즉 4번지 를 저장하고 있습니다.
- 이는
‘스택 포인터가 스택의 꼭대기를 가리키고 있다’고 볼 수 있겠죠. - 쉽게 말해,
스택 포인터는 스택의 어디까지 데이터가 채워져 있는지에 대한 표시라고 보면 됩니다.
(2)

- 그럼 이 스택에서 데이터를 꺼낼 때는 어떤 데이터부터 꺼내게 될까요?
- 1 → 2 → 3 순서 대로 데이터를 꺼낼 수 있겠죠.
- 여기서 하나의 데이터를 꺼내면 스택에는 2와 3이 남고,
- 스택의 꼭대기 주소가 달라졌기 때문에 스택 포인터는 5번지를 가리킵니다.
(3)

- 반대로 스택에 데이터를 추가한다면 어떻게 될까요?
- 현재 스택에 4라는 데이터를 저장하면, 스택의 꼭대기에 4가 저장됩니다.
- 이때 스택의 꼭대기 주소가 달라졌기 때문에 스택 포인터는 4번지를 가리킵니다.
그런데 스택이라는 것은 도대체 어디에 있는 걸까요?

스택은 메모리 안에 있습니다. 정확히는 메모리 안에 스택처럼 사용할 영역이 정해져 있습니다.
이를 스택 영역이라고 하지요.
이 영역은 다른 주소 공간과는 다르게 스택처럼 사용하기로 암묵적으로 약속된 영역입니다.
2.2.2 변위 주소 지정 방식
3장에서 명령어는 연산 코드와 오퍼랜드로 이루어져 있다고 언급한 적이 있었습니다. 그리고 오퍼랜드 필드에는 메모리의 주소가 담길 때도 있다고 했었죠.

변위 주소 지정 방식(displacement addressing mode)
- 오퍼랜드 필드의 값(변위)과 특정 레지스터의 값을 더하여 유효 주소를 얻어내는 주소 지정 방식
그래서 변위 주소 지정 방식을 사용하는 명령어는 다음 그림과 같이 연산 코드 필드, 어떤 레지스터의 값과 더할지를 나타내는 레지스터 필드, 그리고 주소를 담고 있는 오퍼랜드 필드가 있습니다.

이때, 변위 주소 지정 방식은 오퍼랜드 필드의 주소와 어떤 레지스터를 더하는지에 따라 상대 주소 지정 방식, 베이스 레지스터 주소 지정 방식 등으로 나뉩니다.
변위 주소 지정 방식에는 CPU의 종류에 따라 다양한 방식들이 있지만, 이 책에서는 대표적인 상대 주소 지정 방식과 베이스 레지스터 주소 지정 방식을 다루겠습니다
(1) 상대 주소 지정 방식

상대 주소 지정 방식(relative addressing mode)
- 오퍼랜드와 프로그램 카운터의 값을 더하여 유효 주소를 얻는 방식
프로그램 카운터에는 읽어들일 명령어의 주소가 저장되어 있습니다.- 만약 오퍼랜드가 음수, 가령 -3이었다면 CPU는 읽기로 한 명령어로부터 ‘세 번째 이전’ 번지로 접근합니다.
- 한마디로 실행하려는 명령어의 3칸 이전 번지 명령어를 실행하는 것이지요.
- 상대 주소 지정 방식은 프로그래밍 언어의 if문과 유사하게 모든 코드를 실행하는 것이 아닌,
- 분기하여 특정 주소의 코드를 실행할 때 사용됩니다.
(2) 베이스 레지스터 주소 지정 방식

베이스 레지스터 주소 지정 방식(base-register addressing mode)
- 오퍼랜드와 베이스 레지스터의 값을 더하여 유효 주소를 얻는 방식
베이스 레지스터는 ‘기준 주소’,오퍼랜드는 **‘기준 주소로부터 떨어진 거리’**로서의 역할을 합니다.- 즉,
베이스 레지스터 주소 지정 방식은 베이스 레지스터 속 기준 주소로부터- 얼마나 떨어져 있는 주소에 접근할 것인지를 연산하여 유효 주소를 얻어내는 방식입니다.
- 가령 베이스 레지스터에 200 이라는 값이 있고, 오퍼랜드가 40이라면,
- 이는 **“기준 주소 200번지로부터 40만큼 떨어진 240번지로 접근하라”**를 의미합니다.
- 또 베이스 레지스터에 550이라는 값이 담겨 있고, 오퍼랜드가 50이라면,
- 이는 **“기준 주소 550번지로부터 50만큼 떨어진 600번지로 접근하라”**를 의미하는 명령어입니다.
2.3 상용화된 CPU 속 레지스터 및 주소 지정 방식
CPU를 구성하는 ALU, 제어장치, 레지스터를 모두 학습했습니다. 이쯤에서 여러분은 선택을 해야 합니다. 지금까지 설명을 잘 따라왔다면 여러분들은 CPU의 기본적인 원리를 이해한 것이고, 앞으로도 CPU의 기본적인 작동법을 파악하는 데 큰 지장이 없습니다. 따라서 CPU의 큰 그림만 그려보고 싶었던 독자들은 바로 다음 절로 넘어가도 좋습니다.
하지만 만약 여러분들이 실제 CPU의 작동법을 자세히 관찰해야 하는 개발자, 이를테면 임베디드 개발자, 게임 엔진 개발자, 보안 솔루션 개발자, 시스템 해커 등을 지망한다면 아직 하나의 단계가 더 남아 있습니다.
사실, CPU는 여러분이 학습할 컴퓨터 부품 중 전공서 속의 모습과 실제의 모습이 가장 다른 부품이라 해도 과언이 아닐 만큼 다소 괴리가 있습니다. 그래서 전공서의 설명만 읽고 실제 CPU의 자세한 작동법을 분석하려고 하면 책과는 다른 모습에 당황할 수 있습니다.
필자는 이러한 괴리가 레지스터, 정확히 말하자면 레지스터 이름 때문에 발생한다고 봅니다. CPU 제조사마다 레지스터 이름이 다르고 역할도 조금씩 다릅니다. 가령 앞에서 ‘범용 레지스터’라고 소개 했던 레지스터는 어떤 CPU에서는 R0, R1, R2 등으로 불리고, 어떤 CPU에서는 EAX, EBX 등으로 불립니다.
그래서 만약 여러분이 실제 CPU의 작동법을 자세히 관찰하고 분석하길 원한다면, 상용화된 CPU 속 레지스터를 들여다볼 필요가 있습니다. 아래 링크에 현재 가장 대중적인 CPU인 x86(x86-64)과 ARM의 레지스터를 첨부해 두었으니 관심 있는 독자들은 아래 링크에서 registers 항목을 읽어보길 권합니다.
3. 명령어 사이클과 인터럽트
CPU가 하나의 명령어를 처리하는 과정에는 어떤 정해진 흐름이 있고,
CPU는 그 흐름을 반복하며 명령어들을 처리해 나갑니다.
이렇게 하나의 명령어를 처리하는 정형화된 흐름을 명령어 사이클이라 고 합니다.

CPU는 정해진 흐름에 따라 명령어를 처리해 나가지만, 간혹 이 흐름이 끊어지는 상황이 발생합니다.
이를 인터럽트라고 합니다. 이번 절에서는 명령어 사이클과 인터럽트에 대해 알아보겠습니다
3.1 명령어 사이클
- 프로그램 속 명령어들은 일정한 주기가 반복되며 실행됨
- 이 주기를
명령어 사이클(instruction cycle)이라고 함
예를 들어, CPU가 메모리에 있는 어떤 프로그램을 실행한다고 가정해보겠습니다.
3.1.1 인출 사이클

- 그러기 위해서는 CPU는 명령어를 메모리에서 CPU로 가져와야 합니다. (
인출) - 메모리에 저장된 명령어를 CPU로 가지고 오는 단계를
인출 사이클(fetch cycle)이라고 합니다.
3.1.2 실행 사이클

- 메모리에서 갖고온 명령어를 CPU에서 실행해야 겠죠?
- CPU로 가져온 명령어를 실행하는 단계를
실행 사이클(execution cycle)이라고 합니다.
3.1.3 간접 사이클
프로그램을 이루는 수많은 명령어는 일반적으로 인출과 실행 사이클을 반복하며 실행됩니다.
즉, CPU는 프로그램 속 명령어를 가져오고 실행하고, 또 가져오고 실행하고를 반복하는 것이죠.
하지만 모든 명령어가 이렇게 간단히 실행되는 건 아닙니다.
명령어를 인출하여 CPU로 가져왔다 하더라도 곧바로 실행할 수 없는 경우도 있기 때문입니다.
e.g. 03장에서 배운 간접 주소 지정 방식
- 간접 주소 지정 방식은 오퍼랜드 필드에 유효 주소의 주소를 명시
- 이 경우 명령어를 인출하여 CPU로 가져왔다 하더라도 바로 실행 사이클에 돌입할 수 없음
- 명령어를 실행하기 위해서는 메모리 접근을 한 번 더 해야 하기 때문에
- 이 단계를
간접 사이클(indirect cycle)이라고 합니다

하지만 명령어 사이클은 이게 끝이 아닙니다. 아직 고려해야 할 것이 하나 더 남아 있는데, 바로 인터럽트입니다.
3.2 인터럽트
프로그램을 개발하다 보면, 인터럽트라는 단어를 쉽게 접할 수 있습니다.
인터럽트(interrupt): 방해하다, 중단시키다- ‘CPU가 꼭 주목해야할 때’, ‘CPU가 어른 처리해야 할 다른 작업이 생겼을 떄‘ 발생
- 현실 e.g. 일하는 도중에 회사 상사가 “이게 더 급한 거니까, 지금 하던 일 멈추고 이것부터 처리해줘”
인터럽트의 종류에는 크게 동기 인터럽트와 비동기 인터럽트가 있습니다
3.2.1 동기 인터럽트 (예외)

- CPU가 명령어들을 수행 하다가 예상치 못한 상황에 마주쳤을 때 발생
- 동기 인터럽트는
예외(exception)라고 부름
💡 참고
동기 인터럽트는 다른 전공서에는 다음과 같이 구분하기도 합니다.
- 폴트
- 트랩
- 중단(abort)
- 소프트웨어 인터럽트
3.2.2 비동기 인터럽트 (하드웨어 인터럽트)

- 주로 입출력장치에 의해 발생하는 인터럽트
- 알림(세탁기 완료 알림, 전자레인지 조리 알림)과 같은 역할
- 입출력 작업 도중에도 효율적으로 명령어를 처리하기 위해 하트웨어 인터럽트 사용
- e.g. 키보드, 마우스와 같은 입출력장치가 어떠한 입력을 받아들였을 때,
- 이를 처리하기 위해 CPU에 입력 알림(인터럽트)을 보냅니다.
- 입출력장치는 CPU에 비해 느리다.
- 인터럽트가 없다면, CPU는 프린트 완료 여부를 확인하기 위해 주기적으로 확인해야 한다.
- 인터럽트가 있다면, 입출력 작업 동안 CPU는 다른 일을 할 수가 있다.
(1) 하드웨어 인터럽트의 처리 순서
CPU가 인터럽트를 처리하는 방식은 종류를 막론하고 대동소이합니다
- 입출력장치는 CPU에 인터럽트 요청 신호를 보냅니다.
- CPU는 실행 사이클이 끝나고 명령어를 인출하기 전 항상 인터럽트 여부를 확인합니다.
- CPU는 인터럽트 요청을 확인하고, 인터럽트 플래그를 통해 현재 인터럽트를 받아들일 수 있는 지 여부를 확인합니다.
- 인터럽트를 받아들일 수 있다면 CPU는 지금까지의 작업을 백업합니다.
- CPU는 인터럽트 벡터를 참조하여, 인터럽트 서비스 루틴을 실행합니다.
- 인터럽트 서비스 루틴 실행이 끝나면 (4) 에서 백업해 둔 작업을 복구하여 실행을 재개합니다.
낯선 용어들이 나왔죠? 여기서 여러분들이 알아야 할 키워드를 뽑아봅시다.
인터럽트 요청 신호인터럽트 플래그인터럽트 벡터인터럽트 서비스 루틴
(1) 인터럽트 요청 신호

인터럽트는 CPU의 정상적인 실행흐름을 끊는 것이라서,
CPU에게 “지금 끼어들어도 되나요?”라고 요청 신호를 보내는 것을 인터럽트 요청 신호라고 합니다.
(2) 인터럽트 플래그

이런 식으로 CPU가 요청신호를 받아들였으면,
실행 사이클이 끝나고, 항상 플래그 레지스터 속에 인터럽트 플래그(interrupt flag)를 확인을 합니다.
이 인터럽트 플래그에는 현재 인터럽트를 받아들일 수 있다, 없다의 0과 1 여부가 표시됩니다.
- 만약에
인터럽트 플래그가 현재 인터럽트를 받아들이 수 있으면,- CPU가 해당 인터럽트 요청을 받아들이고, 인터럽트를 처리하게 됩니다.
- 만약에
인터럽트 플래그가 인터럽트를 받아들일 수 없는 상태라면,- CPU는 인터럽트 요청 신호가 오더라도, 해당 인터럽트는 처리하지 않습니다.
그러나 모든 하드웨어 인터럽트를 인터럽트 플래그로 막을 수 있는 것은 아닙니다.
- 일부 인터럽트 중에서는 하드웨어 고장, 정전 같은 꼭 처리해야 하는 긴급한 인터럽트도 있습니다.
- 이런 긴급한 인터럽트는
인터럽트 플래그로 막을 수 없습니다. - 이렇게
인터럽트 플래그로 막을 수 없는 인터럽트를넌 마스크브 인터럽트(nmi)라도 합니다. - cf.
막을 수 있는 인터럽트(maskable interrupt) - cf.
막을 수 없는 인터럽트(non maskable interrupt; nmi)
CPU가 인터럽트르 받아들이기로 했다면, 인터럽트 서비스 루틴라는 프로그램을 실행합니다.
인터럽트 서비스 루틴(ISR; Interrupt Service Routine)- 프로그램에 “어떤 문제가 생겼을 때는 어떻게 작동한다”와 같이 어떤 인터럽트가 발생했을 때,
- 해당 인터럽트를 어떻게 처리하고 작동해야 할지에 대한 정보로 이루어진 프로그램
인터럽트 핸들러(interrupt handler)라고도 부름
- e.g. “키보드가 인터럽트 요청을 보내면 이렇게 행동해야 한다.”
- e.g. “마우스가 인터럽트 요청을 보내면 이렇게 행동해야 한다.” …
- 인터럽트 서비스 루틴도 프로그램이기에 메모리에 저장됩니다.

- 인터럽트없이 실행된다면, (정상적으로 실행된다면)
- 실행하는 프로그램을 쭉 실행하면 됨
- 인터러브가 발생한 경우,
- 정상적으로 작업 실행
- 인터럽트 발생
- 인터럽트 서비스 루틴으로 점프
- 인터럽트 서비스 루틴 실행
- 기존 작업으로 점프
- 기존 작업 수행 재개

인터럽트를 보낼 수 있는 주체가 여러개가 있습니다.
- 프린터, 마우스 키보드 등이 CPU한테 인터럽트를 보낼 수 있고,
- 각기 다른 인터럽트 서비스 루틴 주소를 갖고 있습니다.
- 즉, 인터럽트마다 고유한 인터럽트 서비스 루틴의 시작 주소를 가지고 있습니다.
- e.g. 프린터 인터럽트가 발생했다고 가정하면,
- CPU는 프린터 인터럽트 서비스 루틴(A)의 시작주소부터 아래로 읽기 시작
- 즉, 인터럽트를 보내는 장치에 따라 인터럽트 서비스 루틴의 시작주소가 달라집니다.
- CPU가 해당 인터럽트 서비스 루틴의 시작주소는 무엇인지
- 그 인터럽트를 구분할 수 있는 정보가 필요합니다.
- 이렇게 각각의 인터럽트를 구분하기 위한 정보를
인터럽트 벡터(interrupt vector)라고 부릅니다.인터럽테 벡터를 통해서, 인터럽트 서비스 루틴의 시작주소를 구분합니다.
- 참고로
인터럽트 벡터를 표 형태로 모아놓은인터럽트 벡터 테이블도 있습니다.
요컨데, **“CPU가 인터럽트를 처리한다“**고 하는 것을 달리 말하면,
- “인터럽트 서비스 루틴을 실행하고, 본래 수행하던 작업으로 다시 되돌아온다.”
- 로 표현할 수 있습니다.
- 그리고 인터럽트의 시작주소는 인터럽트 벡터를 통해 알 수 있다.

그런데 여기서 문제가 있습니다.
- CPU가 실행 중인 프로그램을 쭉 실행하다가
- 인터럽트가 발생하면, 해당 인터럽트 서비스 루틴으로 점프해서 마저 실행하고 되돌아옵니다.
- 그런데 이 프로그램을 실행하는 과정에서도
- 프로그램 카운터 등의 레지스터 값들이 CPU에 저장되어 있습니다.
- 이 값들을 버릴 수도 없기 때문에 문제가 발생합니다.
- 가령, 10번지에 인터럽트 서비스 루틴을 실행한다면,
- 프로그램 카운터에 1500을 10으로 바꿔야 합니다.
- “다음 실행할 명령어는 1500번지“라고 레지스터 값들을 그냥 버릴 수는 없습니다.
- 왜냐하면 언제든 다시 돌아와서 수행을 되게 해야되기 때문에,
- 현재 레지스터에는 어떤 값들이 담겨있는지,
- 현재 CPU는 어떤 상태인지가 다 기억되어 있어야 합니다.
- 그 전에 지금까지 한 작업을 어디다 백업한 뒤에
- 그 다음에 인터럽트 서비스 등을 실행하면서, 이런저런 레지스턱 값들을 사용해야 합니다.
- cf. 인터럽트 서비스 루틴도 프로그램이기 때문에 이런저런 레지스터들을 씁니다.

백업하기 위해서 지금까지 한 작업을 스택 영역에 백업을 하게 됩니다.
- 그 다음에 인터럽트 서비스 루틴을 위해서 레지스터를 쓸 수 있게 됩니다.
- 가령 프로그램 카운터에는 10, 11, 12같은 번지가 담기게 됩니다.
인터럽트 서비스 루틴이 끝나면,
- 다시 스택 영역에 있는 것을 복구합니다.
- 그리고 다시 프로그램 카운터는 “다음 실행할 명령어는 1500번지“라고 복구하면 됩니다.
💡 하드웨어 인터럽트의 처리 순서 정리
- 입출력장치는 CPU에 인터럽트 요청 신호를 보냅니다.
- CPU는 실행 사이클이 끝나고 명령어를 인출하기 전 항상 인터럽트 여부를 확인합니다.
- CPU는 인터럽트 요청을 확인하고, 인터럽트 플래그를 통해 현재 인터럽트를 받아들일 수 있는 지 여부를 확인합니다.
- 인터럽트를 받아들일 수 있다면 CPU는 지금까지의 작업을 백업합니다.
- CPU는 인터럽트 벡터를 참조하여, 인터럽트 서비스 루틴을 실행합니다.
- 인터럽트 서비스 루틴 실행이 끝나면 (4) 에서 백업해 둔 작업을 복구하여 실행을 재개합니다.
용어
인터럽트 요청 신호: CPU의 작업을 방해하는 인터럽트에 대한 요청인터럽트 플래그: 인터럽트 요청 신호를 받아들일지 무시할지를 결정하는 비트인터럽트 벡터: 인터럽트 서비스 루틴의 시작 주소를 포함하는 인터럽트 서비스 루틴의 식별 정보인터럽트 서비스 루틴: 인터럽트를 처리하는 프로그램
CPU가 항상 명령어를 순차적으로만 실행하는 것은 아니며, 인터럽트를 처리하는 과정이 추가된다는 것까지 배웠습니다. 인터럽트 사이클까지 추가한 명령어 사이클은 그림과 같습니다. 결국 CPU는 이와 같은 과정을 반복해 나가며 프로그램을 실행한다고 볼 수 있습니다.
3.3 예외의 종류
예외를 조금 더 자세히 살펴보겠습니다. 예외의 종류에는 폴트, 트랩, 중단, 소프트웨어 인터럽트가 있습니다. 이 종류들을 외울 필요는 없으나 용어에 눈도장을 찍는다는 기분으로 가볍게 읽어 봅시다.
- 인터럽트
- 동기 인터럽트(예외)
- 폴트
- 트랩
- 중단
- 소프트웨어 인터럽트
- 비동기 인터럽트(하드웨어 인터럽트)
- 동기 인터럽트(예외)
3.3.1 폴트
예외가 발생하면 CPU는 하던 일을 중단하고 해당 예외를 처리합니다. 예외를 처리하고 나면 CPU 는 다시 본래 하던 작업으로 되돌아와 실행을 재개합니다. 여기서 CPU가 본래 하던 작업으로 되돌 아왔을 때 예외가 발생한 명령어부터 실행하느냐, 예외가 발생한 명령어의 다음 명령어부터 실행하 느냐에 따라 폴트와 트랩으로 나뉩니다.
폴트(fault)는 예외를 처리한 직후 예외가 발생한 명령어부터 실행을 재개하는 예외입니다.
말이 좀 어렵 게 느껴질 수 있는데, 알고 보면 단순합니다.
가령 CPU가 한 명령어를 실행하려 하는데, 이 명령어를 실행하기 위해 꼭 필요한 데이터가 메모리가 아닌 보조기억장치에 있다고 가정해 봅시다. 프로그램이 실행되려면 반드시 메모리에 저장되어 있어야 하기에 CPU는 폴트를 발생시키고 보조기억장치로부터 필요한 데이터를 메모리로 가져와 저장합니다.
보조기억장치로부터 필요한 데이터를 메모리로 가지고 왔으면 CPU는 다시 실행을 재개하겠죠? 이때 CPU는 폴트가 발생한 그 명령어부터 실행합니다. 이렇게 예외 발생 직후 예외가 발생한 명령어 부터 실행해 나가는 예외를 폴트라고 합니다.
3.3.2 트랩
트랩trap은 예외를 처리한 직후 예외가 발생한 명령어의 다음 명령어부터 실행을 재개하는 예외입니다. 주로 디버깅할 때 사용하지요. 이 또한 예시를 통해 알아봅시다. 여러분들 중에는 프로그램의 소스 코드를 분석하기 위해 디버깅을 해본 분들도 있을 겁니다.
디버깅을 할 때 특정 코드가 실행되는 순간 프로그램의 실행을 멈추게 할 수 있습니다. 쉽게 말해, CPU에 “이 코드가 실행된 그 순간의 프로그램 상태를 보고 싶어, 그러니까 이 코드가 실행되는 순간 잠깐 실행을 멈춰!”라고 명령하는 것이지요.
트랩을 처리하고 나면, 다시 말해 프로그램을 중단시키고 디버깅이 끝나면 프로그램은 다음 명령어부터 실행을 이어 나가면 되겠지요? 이처럼 트랩은 예외가 발생한 명령어의 다음 명령어부터 실행을 재개하는 예외입니다
3.3.3 중단
중단(abort)은 CPU가 실행 중인 프로그램을- 강제로 중단시킬 수밖에 없는 심각한 오류를 발견했을 때 발생하는 예외입니다.
3.3.4 소프트웨어 인터럽트
소프트웨어 인터럽트(software interrupt)는 시스템 호출이 발생했을 때 나타납니다.- 시스템 호출은 09장에서 자세히 다루니 지금은 이런게 있구나 정도로만 이해하고 넘겨도 무방합니다.
