[혼공컴운] Chapter 04. CPU의 작동 원리

NCOOKIE·2023년 10월 22일
0

ALU와 제어장치

ALU

ALU레지스터를 통해 피연산자를 받아들이고, 제어장치로부터 수행할 연산을 알려주는 제어 신호를 받아들여 산술 연산, 논리 연산 등 다양한 연산을 수행한다.

ALU 연산의 결과값은 메모리가 아닌 레지스터에 우선 저장한다. CPU가 내부가 아닌 외부의 메모리에 자주 접근할 수록 프로그램의 실행 속도가 느려지기 때문이다.

ALU는 연산 결과에 대해 추가적인 상태 정보인 플래그(flag)를 함께 내보낸다. 이러한 플래그는 CPU가 프로그램을 실행하는 도중 반드시 기억해야 하는 일종의 참고 정보이다. 그리고 플래그들은 플래그 레지스터라는 레지스터에 저장된다.

ALU가 내보내는 대표적인 플래그는 아래와 같다. 오버플로우(overflow)는 연산 결과가 연산 결과를 담을 레지스터보다 큰 상황을 말한다.

제어장치

  • 제어장치
    • 제어 신호를 내보내고, 명령어를 해석하는 부품
    • CPU의 구성 요소 중 가장 정교하게 설게된 부품이라고 해도 과언이 아니다.
  • 제어 신호 : 컴퓨터 부품들을 관리하고 작동시키기 위한 일종의 전기 신호

제어장치가 받아들이는 정보

  1. 클럭 신호

클럭(clock)이란 컴퓨터의 모든 부품을 일사분란하게 움직일 수 있게 하는 시간 단위이다. 컴퓨터 부품들은 클럭이라는 박자에 맞춰 작동할 뿐 박자마다 작동하는 건 아니다.

  1. 해석해야 할 명령어

CPU가 해석해야 할 명령어는 명령어 레지스터라는 특별한 레지스터에 저장된다. 제어장치는 이 명령어 레지스터로부터 해석할 명령어를 받아들이고 해석한 뒤, 제어 신호를 발생시켜 컴퓨터 부품들에 수행해야 할 내용을 알려준다.

  1. 플래그 레지스터 속 플래그 값

제어장치는 플래그 값을 받아들이고 이를 참고하여 제어 신호를 발생시킨다.

  1. 시스템 버스, 그 중에서 제어 버스로 전달된 제어 신호

제어장치는 제어 버스를 통해 외부로부터 전달된 제어 신호를 받아들이기도 한다.

제어장치가 내보내는 정보

크게 CPU 외부에 전달하는 제어 신호와 CPU 내부에 전달하는 제어 신호가 있다.

CPU 외부에 제어 신호를 전달한다는 것은 곧 제어 버스로 제어 신호를 내보낸다는 말과 같다. 이러한 제어 신호에는 크게 메모리에 전달하는 제어 신호와 입출력장치에 전달하는 제어 신호가 있다. (여기서 입출력장치는 보조기억장치을 포함한다.)

  1. CPU 외부에 전달하는 제어 신호
  • 메모리
    • 메모리에 저장된 값을 읽거나 새로운 값을 쓰고 싶을 때
  • 입출력장치
    • 입출력장치의 값을 읽거나 새로운 값을 쓰고 싶을 때
  1. CPU 내부에 전달하는 제어 신호
  • ALU
    • 수행할 연산을 지시하기 위해
  • 레지스터
    • 레지스터 간에 데이터를 이동시키거나 레지스터에 저장된 명령어를 해석하기 위해

레지스터

프로그램 속 명령어와 데이터는 실행 전후로 반드시 레지스터에 저장된다. 따라서 레지스터에 저장된 값만 잘 관찰해도 프로그램의 실행 흐름을 파악할 수 있다.

반드시 알아야 할 레지스터

프로그램 카운터

  • PC; Program Counter
  • 메모리에서 가져올 명령어의 주소, 즉 메모리에서 읽어 들일 명령어의 주소를 저장한다.
  • 명령어 포인터(IP; Instruction Pointer)라고 부르는 CPU도 있다.

명령어 레지스터

  • IR; Instruction Register
  • 해석할 명령어, 즉 방금 메모리에서 읽어 들인 명령어를 저장하는 레지스터
  • 제어장치는 명령어 레지스터 속 명령어를 받아들이고 이를 해석한 뒤 제어 신호를 내보낸다.

메모리 주소 레지스터

  • MAR; Memory Address Register
  • 메모리의 주소를 저장하는 레지스터
  • CPU가 읽어 들이고자 하는 주소 값을 주소 버스로 보낼 때 메모리 주소 레지스터를 거치게 된다.

메모리 버퍼 레지스터

  • MBR; Memory Buffer Register
  • 메모리와 주고받을 값(데이터와 명령어)을 저장하는 레지스터
  • CPU가 주소 버스로 내보낼 값이 메모리 주소 레지스터를 거친다면, 데이터 버스로 주고받을 값은 메모리 버퍼 레지스터를 거친다.

예시

[컴퓨터 공학 기초 강의] 10강. CPU의 내부 구성 - 레지스터

메모리에 저장된 프로그램을 실행하는 과정에서 프로그램 카운터, 명령어 레지스터, 메모리 주소 레지스터, 메모리 버퍼 레지스터에 어떤 값들이 담기는지 알아보자.

  1. CPU로 실행한 프로그램이 1000번지부터 1500번지부터 저장되어 있다고 가정
  2. 프로그램을 처음부터 실행하기 위해 프로그램 카운터에 1000이 저장 → 메모리에서 가져올 명령어가 1000번지에 있다는 것을 의미
  3. 1000번지를 읽어 들이기 위해서 주소 버스로 1000번지를 내보내야 한다. 이를 위해 메모리 주소 레지스터에 1000이 저장됨
  4. ‘메모리 읽기’ 제어 신호와 메모리 주소 레지스터 값이 각각 제어 버스와 주소 버스를 통해 메모리에 보내진다.
  5. 메모리 1000번지에 저장된 값은 데이터 버스를 통해 메모리 버퍼 레지스터로 전달됨
    1. 프로그램 카운터는 증가되어 다음 명령어를 읽어 들일 준비를 함
    2. 메모리 주소 레지스터는 리셋됨
  6. 메모리 버퍼 레지스터에 저장된 값은 명령어 레지스터로 이동
  7. 제어장치명령어 레지스터의 명령어를 해석하고 제어 신호를 발생

위와 같이 프로그램 카운터는 지속적으로 증가하며 계속해서 다음 명령어를 읽어 들일 준비를 한다. 이 과정이 반복되면서 CPU는 프로그램을 차례대로 실행해 나간다.

범용 레지스터

  • general purpose register
  • 다양하고 일반적인 상황에서 자유롭게 사용할 수 있는 레지스터
  • 데이터와 주소를 모두 저장할 수 있다.
  • 일반적으로 CPU 안에는 여러 개의 범용 레지스터들이 있고, 현대 대다수 CPU는 모두 범용 레지스터를 가지고 있다.’

플래그 레지스터

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

특정 레지스터를 이용한 주소 지정 방식(1) : 스택 주소 지정 방식

  • 스택 주소 지정 방식
    • 스택과 스택 포인터를 이용한 주소 지정 방식
  • 스택 포인터
    • 스택의 꼭대기를 가리키는 레지스터
    • 스택의 어디까지 데이터가 채워져 있는지에 대한 표시
  • 스택 영역
    • 메모리 안에 스택처럼 사용할 영역이 정해져 있다.
    • 다른 주소 공간과는 다르게 스택처럼 사용하기로 암묵적으로 약속된 영역이다.

특정 레지스터를 이용한 주소 지정 방식(2) : 변위 주소 지정 방식

변위 주소 지정 방식(displacement addressing mode)이란 오퍼랜드 필드의 값(변위)과 특정 레지스터의 값을 더하여 유효 주소를 얻어내는 주소 지정 방식이다.

이 방식을 사용하는 명령어는 연산 코드 필드, 어떤 레지스터의 값과 더할지를 나타내는 레지스터 필드, 그리고 주소를 담고 있는 오퍼랜드 필드가 있다. 여기서 어떤 레지스터에 더하는지에 따라 상대 주소 지정 방식, 베이스 레지스터 주소 지정 방식 등으로 나뉜다.

상대 주소 지정 방식

  • relative addressing mode
  • 오퍼랜드와 프로그램 카운터의 값을 더하여 유효 주소를 얻는 방식
  • 프로그래밍 언어의 if문과 유사하게 모든 코드를 실행하는 것이 아닌, 분기하여 특정 주소의 코드를 실행할 때 사용된다.

베이스 레지스터 주소 지정 방식

  • base-register addressing mode
  • 오퍼랜드와 베이스 레지스터의 값을 더하여 유효 주소를 얻는 방식
  • 베이스 레지스터는 ‘기준 주소’, 오퍼랜드는 ‘기준 주소로부터 떨어진 거리’로서의 역할
    • 베이스 레지스터 속 기준 주소로부터 얼마나 떨어져 있는 주소에 접근할 것인지를 연산하여 유효 주소를 얻어내는 방식


명령어 사이클과 인터럽트

명령어 사이클

  • 명령어 사이클(instruction cycle) : 프로그램 속 각각의 명령어들이 반복되며 실행되는 일정한 주기

  • 인출 사이클(fetch cycle) : 메모리에 있는 명령어를 CPU로 가지고 오는 단계

  • 실행 사이클(execution cycle) : CPU로 가져온 명령어를 실행하는 단계

    • 제어장치가 명령어 레지스터에 담긴 값을 해석하고, 제어 신호를 발생시키는 단계

프로그램을 이루는 수많은 명령어는 일반적으로 인출과 실행 사이클을 반복하며 실행된다. 하지만 일부 명령어는 위 경우에 해당하지 않기도 한다. 간접 주소 지정 방식과 같이 명령어를 실행하기 위해서는 메모리 접근을 한 번 더 해야 하기 때문이다. 이 단계를 간접 사이클(indirect cycle)이라고 한다.

인터럽트

  • 인터럽트(interrupt) : CPU의 작업을 방해하는 신호
  • 동기 인터럽트(synchronous interrupts) : CPU에 의해 발생하는 인터럽트
    • CPU가 명령어들을 수행하다가 예상치 못한 상황에 마주쳤을 때 발생
    • Ex) CPU가 실행하는 프로그래밍 상의 오류와 같은 예외적인 상황에 마주쳤을 때 등
    • 예외(exception)라고도 부름
  • 비동기 인터럽트(asynchronous interrupts) : 주로 입출력장치에 의해 발생하는 인터럽트
    • Ex) 프린터와 같은 입출력장치에서 작업을 끝낸 후 CPU에 완료 알림을 보냄 / 키보드, 마우스와 같은 입출력장치에서 CPU에 입력 알림을 보냄
    • 일반적으로 부르는 인터럽트가 여기에 해당함
    • 여기서는 용어의 혼동을 방지하기 위해 하드웨어 인터럽트라고 부름

하드웨어 인터럽트

CPU가 프린터에 출력 명령을 보냈다고 했을 때, 인터럽트를 사용하지 않으면 주기적으로 프린터의 완료 여부를 확인해야 한다. 이로 인해 CPU는 다른 작업을 처리할 수 없으므로 CPU 사이클 낭비다.

인터럽트를 사용한다면 인터럽트를 받을 때까지 다른 작업을 처리할 수 있다. 하드웨어 인터럽트는 입출력 장업 중에도 CPU로 하여금 효율적으로 명령어를 처리할 수 있게 한다.

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

CPU가 인터럽트를 처리하는 방식은 종류를 막론하고 대동소이하다.

  1. 입출력장치는 CPU에 인터럽트 요청 신호를 보낸다.
  2. CPU는 실행 사이클이 끝나고 명령어를 인출하기 전 항상 인터럽트 여부를 확인한다.
  3. CPU는 인터럽트 요청을 확인하고 인터럽트 플래그를 통해 현재 인터럽트를 받아들일 수 있는지 여부를 확인한다.
  4. 인터럽트를 받아들일 수 있다면 CPU는 지금까지의 작업을 백업한다.
  5. CPU는 인터럽트 벡터를 참조하여 인터럽트 서비스 루틴을 실행한다.
  6. 인터럽트 서비스 루틴 실행이 끝나면 (4)에서 백업해 둔 작업을 복구하여 실행을 재개한다.
  • 인터럽트 요청 신호 : 지금 인터럽트를 할 수 있는지 보내는 신호
  • 인터럽트 플래그(interrupt flag) : 플래그 레지스터에 저장되는, 하드웨어 인터럽트를 받아들일지, 무시할지를 결정하는 플래그
    • 인터럽트 플래그가 불가능으로 설정되어 있더라도 무시할 수 없는 요청이 있음
    • Ex) 정전이나 하드웨어 고장과 같이 반드시 가장 먼저 처리해야 하는 인터럽트
  • 인터럽트 서비스 루틴(ISR; Interrupt Service Routine)
    • 어떤 인터럽트가 발생했을 때 해당 인터럽트를 어떻게 처리하고 작동해야 할지에 대한 정보로 이루어진 프로그램
    • 인터럽트 핸들러(interrupt handler)라고도 부름
  • 인터럽트 벡터(interrupt vecotr)
    • 인터럽트 서비스 루틴을 식별하기 위한 정보
    • 메모리에는 여러 개의 인터럽트 서비스 루틴이 저장되어 있음
    • 인터럽트 벡터를 알면 인터럽트 서비스 루틴의 시작 주소를 알 수 있기 때문에 CPU는 인터럽트 벡터를 통해 특정 인터럽트 서비스 루틴을 처음부터 실행할 수 있음
    • CPU는 하드웨어 인터럽트 요청을 보낸 대상으로부터 데이터 버스를 통해 인터럽트 벡터를 전달받는다.

  1. 정상적으로 작업 진행
  2. 인터럽트 발생
  3. 인터럽트 서비스 루틴으로 점프
  4. 인터럽트 서비스 루틴 실행
  5. 기존 작업으로 점프
  6. 기존 작업 수행 재개

CPU가 인터럽트를 처리한다
인터럽트 서비스 루틴을 실행하고, 본래 수행하던 작업으로 다시 되돌아온다. 인터럽트 서비스 루틴을 실행하기 위한 시작 주소는 인터럽트 벡터를 통해 알 수 있다.

인터럽트 서비스 루틴은 여느 프로그램과 마찬가지로 명령어와 데이터로 이루어져 있다. 때문에 인터럽트 서비스 루틴도 프로그램 카운터를 비롯한 레지스터들을 사용하며 실행된다.

CPU는 인터럽트 서비스 루틴을 실행하기 전에 프로그램 카운터 값 등 현재 프로그램을 재개하기 위해 필요한 모든 내용을 스택에 백업한다. 그러고 나서 인터럽트 서비스 루틴의 시작 주소가 위치한 곳으로 프로그램 카운터 값을 갱신하고 인터럽트 서비스 루틴을 실행한다.

인터럽트 사이클까지 추가한 명령어 사이클은 아래와 같다. CPU는 이와 같은 과정을 반복해 나가며 프로그램을 실행한다.

예외의 종류

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

폴트(falut)는 예외를 처리한 직후 예외가 발생한 명령어부터 실행을 재개하는 예외이다.

보조기억장치에 접근해야 하는 명령어를 실행한다고 할 때, CPU는 폴트를 발생시키고 보조기억장치로부터 필요한 데이터를 메모리로 가져와 저장한다. 이후 CPU는 폴트가 발생한 그 명령어부터 실행한다.

트랩(trap)은 예외를 처리한 직후 예외가 발생한 명령어의 다음 명령어부터 실행을 재개하는 예외이다. 주로 디버깅할 때 사용한다.

이 외로 CPU가 실행 중인 프로그램을 강제로 중단시킬 수 밖에 없는 심각한 오류를 발견했을 때 발생하는 예외인 중단(abort)나, 시스템 호출이 발생했을 때 나타나는 소프트웨어 인터럽트(software interrupt) 등이 있다.

Q&A

베이스 레지스터 주소 지정 방식의 용도 (Ch04-2)

배열(Array) 접근

배열의 첫 번째 요소의 주소를 베이스 레지스터에 저장하고, 오프셋(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는 배열의 각 요소의 크기를 나타낸다.

구조체(Struct) 접근

구조체의 필드에 접근하는 경우에도 베이스 레지스터 주소 지정 방식이 유용하다. 구조체의 시작 주소를 베이스 레지스터에 저장하고 필드의 오프셋을 더하여 필드에 접근할 수 있다.

; 구조체의 시작 주소를 베이스 레지스터에 저장
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는 접근하려는 필드의 오프셋을 나타낸다. 이 방식을 사용하면 구조체의 특정 필드에 쉽게 접근할 수 있다.

함수 인자(Arguments) 전달

베이스 레지스터 주소 지정 방식은 함수에 인자를 전달할 때도 유용하다. 함수 호출 시 인자들을 레지스터에 저장하고 함수가 호출될 때 베이스 레지스터를 통해 인자들에 접근할 수 있다.

; 함수에 전달할 인자들을 레지스터에 저장
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는 함수에 전달할 인자들의 주소를 가리킨다. 함수가 호출되면 베이스 레지스터를 통해 인자들의 값을 전달할 수 있다.

이러한 예시에서 보듯이 베이스 레지스터 주소 지정 방식은 주로 메모리에 접근할 때 특정 위치를 기준으로 계산할 때 사용된다. 배열, 구조체, 함수 인자 등의 다양한 상황에서 유용하게 쓰일 수 있다.

동기 인터럽트와 비동기 인터럽트의 차이와 각각의 예시 (Ch04-3)

동기 인터럽트 (Synchronous Interrupts):

  • CPU 내부에서 발생: 동기 인터럽트는 CPU가 예상치 못한 상황에 직면했을 때 발생
  • 소프트웨어적인 인터럽트: 소프트웨어적인 요인에 의해 발생하며, 프로그램 실행 중에 처리된다.
  • 예시: 0으로 나누기 오류, 스택 오버플로우, 부적절한 메모리 접근 등

비동기 인터럽트 (Asynchronous Interrupts):

  • CPU 외부에서 발생: 비동기 인터럽트는 주로 입출력 장치에 의해 발생하며, 알림과 같은 역할을 한다. 입출력 장치의 읽기 및 쓰기 시간은 CPU가 명령어를 수행하는 시간보다 훨씬 오래 걸리기 때문에, 입출력 작업이 완료되면 CPU에게 알림 신호를 보내도록 설계되었다.
  • 하드웨어적인 인터럽트: 하드웨어적인 요인에 의해 발생하며, 주로 입출력 장치와 상호 작용할 때 발생한다.
  • 예시: 디스크 I/O 완료, 네트워크 패킷 수신, 타이머 인터럽트 등

참고

profile
일단 해보자

0개의 댓글