ALU는 연산한 결괏값과 플래그를 내보낸다.
→ 레지스터와 제어장치로부터 받아들인 정보들을 통해 산술 연산, 논리 연산 등 다양한 연산을 수행한다
→ 이 결괏값은 바로 메모리에 저장되지 않고 일시적으로 레지스터에 저장된다
CPU가 메모리에 접근하는 속도는 레지스터에 접근하는 속도보다 훨씬 느리다 (프로그램 실행 속도를 늦출 수도 있다) 때문에 ALU의 결괏값을 메모리가 아닌 레지스터에 우선 저장함
플래그 종류 | 의미 |
---|---|
부호 플래그 | 연산한 결과의 부호 |
제로 플래그 | 연산 결과가 0인지 여부 |
캐리 플래그 | 연산 결과 올림수나 빌림수가 발생했는지 |
오버플로우 플래그 | 오버플로우가 발생했는지 |
인터럽트 플래그 | 인터럽트가 가능한지 |
슈퍼바이저 플래그 | 커널 모드로 실행 중인지, 사용자 모드로 실행 중인지 |
→ 플래그들은 플래그 레지스터에 저장된다
이외에도 ALU 내부에는 여러 계산을 위한 회로들이 있다 (덧셈을 위한 가산기, 뺄셈을 위한 보수기, 시프트 연산을 수행해 주는 시프터, 오버플로우를 대비한 오버플로우 검출기 등)
제어장치는 제어 신호를 내보내고, 명령어를 해석하는 부품
CPU의 구성 요소 중 가장 정교하게 설계된 부품
제어 신호는 컴퓨터 부품들을 관리하고 작동시키기 위한 일종의 전기 신호
클럭 신호
클럭 - 컴퓨터의 모든 부품을 일사 분란하게 움직일 수 있게 하는 시간 단위
'해석해야 할 명령어'
CPU가 해석해야 할 명령어는 명령어 레지스터라는 특별 레지스터에 저장
플래그 레지스터 속 플래그 값
시스템 버스, 그 중에서 제어 버스로 전달된 제어 신호
CPU 외부에 전달하는 제어 신호
CPU 내부에 전달하는 제어 신호
프로그램 속 명령어와 데이터는 실행 전후로 반드시 레지스터에 저장된다!
➡️ 레지스터에 저장된 값만 관찰해도 프로그램의 실행 흐름을 파악할 수 있다!
상용화된 CPU 속 레지스터들은 CPU마다 이름, 크기, 종류가 매우 다양하다 >> 이건 각 CPU 제조사 홈페이지 등에서 확인해야 함
때문에 이 책에서 다루는 레지스터는 여러 전공 서적에서 중요하게 다루는 레지스터, 많은 CPU가 공통으로 포함하고 있는 여덟 개의 레지스터를 다룬다! 잘 알아보자!
메모리에서 가져올 명령어의 주소를 저장한다
바꿔 말하면 메모리에서 읽어들일 명령어의 주소를 저장한다
명령어 포인터라고 부르는 CPU도 있다
해석할 명령어를 저장한다
해석할 명령어라는 것은 방금 메모리에서 읽어 들인 명령어를 말한다!
메모리의 주소를 저장한다
CPU가 읽고자 하는 주소 값을 주소 버스로 보낼 때 메모리 주소 레지스터를 거친다
메모리와 주고받을 값(데이터와 명령어)을 저장한다
메모리에 쓰고 싶은 값 혹은 메모리로부터 전달받은 값 → 메모리 버퍼 레지스터를 거치게 된다
데이터 버스로 주고받을 값은 메모리 버퍼 레지스터를 거친다
메모리 데이터 레지스터(MDR; Memory Data Register)라고도 부른다
1000번지에 저장된 것 - 1101⑵
프로그램을 처음부터 실행하기 위해 프로그램 카운터에 1000이 저장된다
➡️ 메모리에서 가져올 명령어가 1000번지에 있다는 것을 의미
1000번지를 읽어들이기 위해서 해야 하는 일 → 주소 버스로 1000번지를 내보내야 한다! 이를 위해서 메모리 주소 레지스터에 1000이 저장된다
(제어장치에서 보내는) '메모리 읽기' 제어신호는 제어 버스, 메모리 주소 레지스터 값이 주소 버스를 통해 메모리로 보내진다
메모리 1000번지에 저장된 값은 데이터 버스를 통해 메모리 버퍼 레지스터로 전달되고, 프로그램 카운터는 증가되어 다음 명령어를 읽어들일 준비를 한다 (증가값: 1001⑵)
메모리 버퍼 레지스터에 저장된 값은 명령어 레지스터로 이동한다.
제어장치는 명령어 레지스터의 명령어를 해석하고 제어 신호를 발생시킨다
➡️ 5단계에서 프로그램 카운터 값이 증가한다 그러므로 CPU는 1000번지 명령어 처리가 끝나면 다음 명령어(1001번지)를 읽어들인다
이렇게 프로그램 카운터가 지속적으로 증가하며 다음 명령어를 읽어들일 준비를 한다 이 과정이 반복되면서 CPU가 프로그램을 차례대로 실행해 나간다!
프로그램이 실행된다는 것 = CPU 속 프로그램 카운터가 꾸준히 증가하고 있다!
다양하고 일반적인 상황에서 자유롭게 사용할 수 있는 레지스터
일반적으로 메모리 버퍼 레지스터는 데이터 버스로 주고받을 값만 저장하고, 메모리 주소 레지스터는 주소 버스로 내보낼 주소 값만 저장한다
하지만 범용 레지스터는 데이터와 주소를 모두 저장할 수 있다
연산 결과 또는 CPU 상태에 대한 부가적인 정보를 저장한다
스택에 마지막을 저장한 값의 위치를 저장한다
= 스택의 꼭대기
기준 주소
여러가지 주소 지정 방식 중 특정 레지스터를 알아야만 이해할 수 있는 주소 지정 방식들이 있다.
프로그램 카운터(위에서 말함), 스택 포인터, 베이스 레지스터는 주소 지정에 사용될 수 있는 특별한 레지스터 들이다!
스택 포인터는 스택 주소 지정 방식이라는 주소 지정 방식에 사용되고,
프로그램 카운터와 베이스 레지스터는 변위 주소 지정 방식이라는 주소 지정 방식에 사용된다
스택과 스택 포인터를 이용한 주소 지정 방식이다
스택 포인터란? 스택의 꼭대기를 가리키는 레지스터이다
➡️ 스택의 어디까지 데이터가 채워져 있는지에 대한 표시라고도 할 수 있다
스택은 메모리 안에 있다
정확하게는 메모리 안에 다른 주소 공간과는 다르게 스택처럼 사용할 영역이 정해져 있다 (스택 영역)
명령어는 연산 코드와 오퍼랜드로 이루어져 있다
오퍼랜드 필드에는 메모리의 주소가 담길 때도 있다
변위 주소 지정 방식이란 오퍼랜드 필드의 값(변위)과 특정 레지스터의 값을 더하여 유효 주소를 얻어내는 주소 지정 방식이다
변위 주소 지정 방식을 사용하는 명령어는 다음과 같이 구성되어 있다
연산 코드 필드 | 레지스터 필드 | 오퍼랜드 |
---|---|---|
(이 명령을 수행해라) | (이 레지스터의 값과) | (이 주소를 더한 곳에 있는 데이터로) |
오퍼랜드 필드의 주소와 어떤 레지스터를 더하는지에 따라 방식이 나뉜다
오퍼랜드와 프로그램 카운터의 값을 더하여 유효 주소를 얻는 방식
프로그램 카운터에는 읽어들일 명령어의 주소가 저장되어 있다
연산 코드 | 프로그램 카운터 | -3 |
---|
➡️ 실행할 명령어가 담긴 주소의 세 칸 이전 번지의 명령어를 실행한다
{실행할 명령어 -3}번지
왜... 이러는 거지...
모든 코드를 실행하는 것이 아닌 분기하여 특정 주소의 코드를 실행할 때 사용된다.
뭐... 그렇다고 하네요... if문처럼 생각하라고 한다. 흠...
오퍼랜드와 베이스 레지스터의 값을 더하여 유효 주소를 얻는 방식
베이스 레지스터는 '기준 주소'를 의미한다
오퍼랜드는 '기준 주소로부터 떨어진 거리'의 역할을 한다
베이스 레지스터 속 기준 주소로부터 얼마나 떨어져있는 주소에 접근할 것인지를 연산하여 유효 주소를 얻어내는 방식이다
연산 코드 | 베이스레지스터 | 50 |
---|
이 때, 베이스 레지스터에 200이라는 값이 있다면?!
➡️ 기준 주소 200번지로부터 50번지 떨어진 코드를 실행하라는 의미!
하나의 명령어를 처리하는 정형화된 흐름을 명령어 사이클이라고 한다
하지만 이를 끊기게 만드는 상황이 발생하기도 하는데 이를 인터럽트라고 한다
프로그램 속 각각의 명령어들이 일정한 주기가 반복되며 실행하는 것
인출 사이클
→ 메모리에 있는 명령어를 CPU로 가져온다
실행 사이클
→ CPU로 가져온 명령어를 실행한다
제어창치가 명령어 레지스터에 담긴 값을 해석하고, 제어 신호를 발생시키는 단계
프로그램의 수많은 명령어는 일반적으로 인출과 실행 사이클을 반복하며 실행된다
하지만! 당연히 이렇게 간단하게 실행되기만 하는 것은 아니다
간접 주소 지정 방식(이 생각이 나시나요...? 앞부분 다시 보고 오기)
→ 오퍼랜드 필드에 유효 주소의 주소를 명시한다
이런 경우 명령어를 인출하여 CPU로 가져오더라도 바로 실행 사이클에 돌입할 수 없다!
➡️ 명령어를 실행하기 위해서는 메모리 접근을 한 번 더 해야 한다.
CPU의 작업을 방해하는 신호
✔️ CPU가 꼭 주목해야 할 때
✔️ CPU가 얼른 처리해야 할 다른 작업이 생겼을 때
발생하게 된다
동기 인터럽트 = 예외
CPU에 의해 발생하는 인터럽트
→ 프로그래밍상의 오류와 같은 예외적인 상황에 마주쳤을 때 발생한다
비동기 인터럽트 = 하드웨어 인터럽트
주로 입출력장치에 의해 발생하는 인터럽트
→ 알림 역할을 한다!
이게 왜 필요할까? 라고 생각했는데 입출력 작업 도중에도 효율적으로 명령어를 처리하기 위해 이런 알림과 같은 하드웨어 인터럽트를 사용한다.
우리가 토스터기 알람이 없다면... 빵이 타진 않았을지 계속 확인해야 하기 때문에 다른 일을 못 함. 하지만 알림이 있으면, 안심하고 알림이 울리기 전까지 다른 일을 할 수 있음. 똑같은 원리이다.
CPU가 프린터에 출력을 명령했다면, 입출력장치는 CPU보다 속도가 훨씬 느리기 때문에 CPU가 입출력 작업의 결과를 바로 받아볼 수가 없다. 하드웨어 인터럽트가 없다면 CPU는 주기적으로 프린터의 완료 여부를 확인해야 한다. = 사이클 낭비!
하드웨어 인터럽트를 이용하면 CPU는 주기적으로 프린트 완료 여부를 확인할 필요가 없다. 프린트 완료 인터럽트를 받을 때까지는 다른 작업을 처리할 수 있다!
인터럽트 요청 신호 > 정상 실행 흐름을 끊는 것이기에 지금 끼어들어도 되는지... 여부를 묻는 것
인터럽트 플래그가 활성화되어 있어야 인터럽트 요청을 수용할 수 있다
→ 하드웨어 인터럽트를 받아들일지, 무시할지 결정하는 플래그
✔️ 무시할 수 없는 인터럽트 요청도 있다 (정전, 하드웨어 고장으로 인한 인터럽트)
인터럽트 서비스 루틴 (인터럽트 핸들러)
→ 인터럽트를 처리하기 위한 프로그램
→ 어떤 인터럽트가 발생했을 때 해당 인터럽트를 어떻게 처리하고 작동해야 할지에 대한 정보로 이루어진 프로그램이다
인터럽트 벡터
→ 인터럽트 서비스 루틴을 식별하기 위한 정보
→ 인터럽트 벡터를 알면 인터럽스 서비스 루틴의 시작 주소를 알 수있기 때문에 CPU는 인터럽트 벡터를 통해 특정 인터럽트 서비스 루틴을 처음부터 실행할 수 있다
정리!
➡️ CPU가 인터럽트를 처리한다 = 인터럽트 서비스 루틴을 실행하고, 본래 수행하던 작업으로 다시 되돌아온다.
➡️ CPU가 인터럽트 서비스 루틴을 실행하려면 인터럽트 서비스 루틴의 시작 주소를 알아야 하는데 이는 인터럽트 벡터를 통해서 알 수 있다
CPU는 인터럽트 서비스 루틴을 실행하기 전에 프로그램 카운터 값 등 현재 프로그램을 재개하기 위해 필요한 모든 내용을 스택에 백업한다
그 후 인터럽트 서비스 루틴의 시작 주소가 위치한 곳으로 프로그램 카운터 값을 갱신하고 인터럽트 서비스 루틴을 실행한다
인터럽트를 처리하고 나면 스택의 저장해 둔 값을 다시 불러온 뒤 이전까지 수행하던 작업을 재개한다
인터럽트에는 동기 인터럽트(예외)가 있고 비동기 인터럽트(하드웨어 인터럽트)가 있따
예외가 발생하면 CPU는 하던 일 중단 후 해당 예외를 처리한다!
폴트(fault) 예외를 처리한 직후 예외가 발생한 명령어부터 실행을 재개하는 예외
트랩(trap) 예외가 발생한 명령어의 다음 명령어부터 실행을 재개 (디버깅할 때 사용)
중단(abort) CPU가 실행 중인 프로그램을 강제로 중단시킬 수밖에 없는 심각한 오류를 발견했을 때 발생하는 예외
소프트웨어 인터럽트(software interrupt) 시스템 호출이 발생했을 때 나타난다