ALU
는 레지스터를 통해 피연산자
를 받아들이고, 제어장치로부터 수행할 연산을 알려주는 제어 신호
를 받아들여 산술 연산, 논리 연산 등 다양한 연산을 수행한다.
ALU 연산의 결과값
은 메모리가 아닌 레지스터에 우선 저장한다. CPU가 내부가 아닌 외부의 메모리에 자주 접근할 수록 프로그램의 실행 속도가 느려지기 때문이다.
ALU는 연산 결과에 대해 추가적인 상태 정보인 플래그(flag)
를 함께 내보낸다. 이러한 플래그는 CPU가 프로그램을 실행하는 도중 반드시 기억해야 하는 일종의 참고 정보이다. 그리고 플래그들은 플래그 레지스터라는 레지스터에 저장된다.
ALU가 내보내는 대표적인 플래그는 아래와 같다. 오버플로우(overflow)
는 연산 결과가 연산 결과를 담을 레지스터보다 큰 상황을 말한다.
제어장치
제어 신호
: 컴퓨터 부품들을 관리하고 작동시키기 위한 일종의 전기 신호클럭(clock)
이란 컴퓨터의 모든 부품을 일사분란하게 움직일 수 있게 하는 시간 단위이다. 컴퓨터 부품들은 클럭이라는 박자에 맞춰 작동할 뿐 박자마다 작동하는 건 아니다.
CPU가 해석해야 할 명령어는 명령어 레지스터
라는 특별한 레지스터에 저장된다. 제어장치는 이 명령어 레지스터로부터 해석할 명령어를 받아들이고 해석한 뒤, 제어 신호를 발생시켜 컴퓨터 부품들에 수행해야 할 내용을 알려준다.
제어장치는 플래그
값을 받아들이고 이를 참고하여 제어 신호를 발생시킨다.
제어장치는 제어 버스를 통해 외부로부터 전달된 제어 신호를 받아들이기도 한다.
크게 CPU 외부에 전달하는 제어 신호와 CPU 내부에 전달하는 제어 신호가 있다.
CPU 외부에 제어 신호를 전달한다는 것은 곧 제어 버스로 제어 신호를 내보낸다는 말과 같다. 이러한 제어 신호에는 크게 메모리에 전달하는 제어 신호와 입출력장치에 전달하는 제어 신호가 있다. (여기서 입출력장치는 보조기억장치을 포함한다.)
프로그램 속 명령어와 데이터는 실행 전후로 반드시 레지스터에 저장된다. 따라서 레지스터에 저장된 값만 잘 관찰해도 프로그램의 실행 흐름을 파악할 수 있다.
[컴퓨터 공학 기초 강의] 10강. CPU의 내부 구성 - 레지스터
메모리에 저장된 프로그램을 실행하는 과정에서 프로그램 카운터, 명령어 레지스터, 메모리 주소 레지스터, 메모리 버퍼 레지스터에 어떤 값들이 담기는지 알아보자.
위와 같이 프로그램 카운터는 지속적으로 증가하며 계속해서 다음 명령어를 읽어 들일 준비를 한다. 이 과정이 반복되면서 CPU는 프로그램을 차례대로 실행해 나간다.
스택 주소 지정 방식
스택 포인터
스택 영역
변위 주소 지정 방식(displacement addressing mode)
이란 오퍼랜드 필드의 값(변위)과 특정 레지스터의 값을 더하여 유효 주소를 얻어내는 주소 지정 방식이다.
이 방식을 사용하는 명령어는 연산 코드 필드, 어떤 레지스터의 값과 더할지를 나타내는 레지스터 필드, 그리고 주소를 담고 있는 오퍼랜드 필드가 있다. 여기서 어떤 레지스터에 더하는지에 따라 상대 주소 지정 방식
, 베이스 레지스터 주소 지정 방식
등으로 나뉜다.
명령어 사이클(instruction cycle)
: 프로그램 속 각각의 명령어들이 반복되며 실행되는 일정한 주기
인출 사이클(fetch cycle)
: 메모리에 있는 명령어를 CPU로 가지고 오는 단계
실행 사이클(execution cycle)
: CPU로 가져온 명령어를 실행하는 단계
프로그램을 이루는 수많은 명령어는 일반적으로 인출과 실행 사이클을 반복하며 실행된다. 하지만 일부 명령어는 위 경우에 해당하지 않기도 한다. 간접 주소 지정 방식과 같이 명령어를 실행하기 위해서는 메모리 접근을 한 번 더 해야 하기 때문이다. 이 단계를 간접 사이클(indirect cycle)
이라고 한다.
인터럽트(interrupt)
: CPU의 작업을 방해하는 신호동기 인터럽트(synchronous interrupts)
: CPU에 의해 발생하는 인터럽트예외(exception)
라고도 부름비동기 인터럽트(asynchronous interrupts)
: 주로 입출력장치에 의해 발생하는 인터럽트하드웨어 인터럽트
라고 부름CPU가 프린터에 출력 명령을 보냈다고 했을 때, 인터럽트를 사용하지 않으면 주기적으로 프린터의 완료 여부를 확인해야 한다. 이로 인해 CPU는 다른 작업을 처리할 수 없으므로 CPU 사이클 낭비다.
인터럽트를 사용한다면 인터럽트를 받을 때까지 다른 작업을 처리할 수 있다. 하드웨어 인터럽트는 입출력 장업 중에도 CPU로 하여금 효율적으로 명령어를 처리할 수 있게 한다.
CPU가 인터럽트를 처리하는 방식은 종류를 막론하고 대동소이하다.
인터럽트 요청 신호
를 보낸다.인터럽트 플래그
를 통해 현재 인터럽트를 받아들일 수 있는지 여부를 확인한다.인터럽트 벡터
를 참조하여 인터럽트 서비스 루틴
을 실행한다.인터럽트 요청 신호
: 지금 인터럽트를 할 수 있는지 보내는 신호인터럽트 플래그(interrupt flag)
: 플래그 레지스터에 저장되는, 하드웨어 인터럽트를 받아들일지, 무시할지를 결정하는 플래그인터럽트 서비스 루틴(ISR; Interrupt Service Routine)
인터럽트 핸들러(interrupt handler)
라고도 부름
- 정상적으로 작업 진행
- 인터럽트 발생
- 인터럽트 서비스 루틴으로 점프
- 인터럽트 서비스 루틴 실행
- 기존 작업으로 점프
- 기존 작업 수행 재개
CPU가 인터럽트를 처리한다
→ 인터럽트 서비스 루틴을 실행하고, 본래 수행하던 작업으로 다시 되돌아온다. 인터럽트 서비스 루틴을 실행하기 위한 시작 주소는 인터럽트 벡터를 통해 알 수 있다.
인터럽트 서비스 루틴은 여느 프로그램과 마찬가지로 명령어와 데이터로 이루어져 있다. 때문에 인터럽트 서비스 루틴도 프로그램 카운터를 비롯한 레지스터들을 사용하며 실행된다.
CPU는 인터럽트 서비스 루틴을 실행하기 전에 프로그램 카운터 값 등 현재 프로그램을 재개하기 위해 필요한 모든 내용을 스택에 백업한다. 그러고 나서 인터럽트 서비스 루틴의 시작 주소가 위치한 곳으로 프로그램 카운터 값을 갱신하고 인터럽트 서비스 루틴을 실행한다.
인터럽트 사이클까지 추가한 명령어 사이클은 아래와 같다. CPU는 이와 같은 과정을 반복해 나가며 프로그램을 실행한다.
예외
가 발생하면 CPU는 하던 작업을 중단하고 해당 예외를 처리한다. 예외를 처리하고 나면 다시 본래 하던 작업으로 돌아와 실행을 재개한다. 여기서 CPU가 본래 하던 작업으로 되돌아 왔을 때 예외가 발생한 명령어부터 실행하느냐, 예외가 발생한 명령어의 다음 명령어부터 실행하느냐에 따라 폴트
와 트랩
으로 나누어진다.
폴트(falut)
는 예외를 처리한 직후 예외가 발생한 명령어부터 실행을 재개하는 예외이다.
보조기억장치에 접근해야 하는 명령어를 실행한다고 할 때, CPU는 폴트를 발생시키고 보조기억장치로부터 필요한 데이터를 메모리로 가져와 저장한다. 이후 CPU는 폴트가 발생한 그 명령어부터 실행한다.
트랩(trap)
은 예외를 처리한 직후 예외가 발생한 명령어의 다음 명령어부터 실행을 재개하는 예외이다. 주로 디버깅할 때 사용한다.
이 외로 CPU가 실행 중인 프로그램을 강제로 중단시킬 수 밖에 없는 심각한 오류를 발견했을 때 발생하는 예외인 중단(abort)
나, 시스템 호출이 발생했을 때 나타나는 소프트웨어 인터럽트(software interrupt)
등이 있다.
배열의 첫 번째 요소의 주소를 베이스 레지스터에 저장하고, 오프셋(offset)을 더해 원하는 요소에 접근할 수 있다.
; 배열의 주소를 베이스 레지스터에 저장
MOV R2, #base_address_of_array
; 원하는 인덱스 계산
MOV R3, #index
MUL R3, R3, #element_size ; 요소의 크기를 곱해 오프셋 계산
; 베이스 레지스터와 오프셋을 합쳐 실제 주소 계산
ADD R2, R2, R3
; 해당 주소의 데이터에 접근
MOV R1, [R2]
이렇게 하면 배열에서 index에 해당하는 요소에 접근할 수 있다. base_address_of_array는 배열의 시작 주소이고, index는 원하는 요소의 인덱스다. element_size는 배열의 각 요소의 크기를 나타낸다.
구조체의 필드에 접근하는 경우에도 베이스 레지스터 주소 지정 방식이 유용하다. 구조체의 시작 주소를 베이스 레지스터에 저장하고 필드의 오프셋을 더하여 필드에 접근할 수 있다.
; 구조체의 시작 주소를 베이스 레지스터에 저장
MOV R2, #base_address_of_struct
; 필드의 오프셋 계산
MOV R3, #offset_of_field
; 베이스 레지스터와 오프셋을 합쳐 필드의 주소 계산
ADD R2, R2, R3
; 해당 필드의 데이터에 접근
MOV R1, [R2]
여기서 base_address_of_struct는 구조체의 시작 주소이고, offset_of_field는 접근하려는 필드의 오프셋을 나타낸다. 이 방식을 사용하면 구조체의 특정 필드에 쉽게 접근할 수 있다.
베이스 레지스터 주소 지정 방식은 함수에 인자를 전달할 때도 유용하다. 함수 호출 시 인자들을 레지스터에 저장하고 함수가 호출될 때 베이스 레지스터를 통해 인자들에 접근할 수 있다.
; 함수에 전달할 인자들을 레지스터에 저장
MOV R1, #arg1
MOV R2, #arg2
; 함수 호출 시 베이스 레지스터에 인자들의 주소 저장
MOV R3, #base_address_of_args
STR R1, [R3]
ADD R3, R3, #4 ; 다음 인자의 위치로 이동
STR R2, [R3]
; 함수 호출
BL function_name
이 경우 base_address_of_args는 함수에 전달할 인자들의 주소를 가리킨다. 함수가 호출되면 베이스 레지스터를 통해 인자들의 값을 전달할 수 있다.
이러한 예시에서 보듯이 베이스 레지스터 주소 지정 방식은 주로 메모리에 접근할 때 특정 위치를 기준으로 계산할 때 사용된다. 배열, 구조체, 함수 인자 등의 다양한 상황에서 유용하게 쓰일 수 있다.