오늘날 거의 모든 PC는 폰 노이만 구조를 따르고 있습니다. 폰 노이만 구조는 이름 그대로 존 폰 노이만이 제시한 컴퓨터 아키텍처로, 프로그램 내장 방식이라고도 불립니다.

이 글에서는 존 폰 노이만이 제시한 폰 노이만 구조에 대해 자세히 알아보고자 합니다.

역사

폰 노이만 구조 이전의 컴퓨터는 배전반의 연결을 통해 연산을 수행했습니다. 만약 앞선 연산과 다른 연산을 수행하고 싶다면, 배선을 다 들어내고 직접 전선의 위치를 바꿔가며 프로그래밍을 수행해야 하는 번거로움이 있었고, 시간도 많이 소요되었습니다.

폰 노이만 구조 이전의 컴퓨터 프로그래밍 모습

이런 단점을 해결한 새로운 아키텍쳐가 1945년 존 폰 노이만에 의해 "EDVAC의 보고서 최초 초안"에서 등장하게 됩니다.

폰 노이만 구조의 특징

폰 노이만 구조는 최초의 프로그램이 내장된 컴퓨터 방식으로, 하드웨어적으로 전선을 재배치할 필요 없이 소프트웨어만 교체하여 다른 연산을 수행할 수 있습니다.

폰 노이만 구조에서는 먼저 연산의 수행과 관련된 명령어와, 연산에 필요하거나 결과로 나온 데이터를 저장장치(메모리)에 보관하게 되며, 순차적으로 메모리에 저장된 용을 끄집어내 지시대로 연산 수행하는 방식입니다.

오늘날의 모든 컴퓨터는 이런 폰 노이만 구조를 따르고 있습니다.

폰 노이만 구조

폰 노이만 구조 컴퓨터의 명령 주기

폰 노이만 구조의 컴퓨터에서는 미리 연산에 수행에 관련된 명령어가 기억장치에 저장되어 있고 이를 가져와 지시대로 연산을 수행하게 됩니다. 이를 명령어 실행 사이클이라고 합니다.

이 명령어 실행 사이클은 크게

  1. 명령어 가져오기
  2. 명령어 실행하기
  3. 인터럽트 체크

의 과정으로 나뉩니다. 이 과정을 다시 세분화하면 아래와 같이 5가지로 나눌 수 있습니다.

  1. 명령어 가져오기 (IF, Instruction Fetch)
    기억장치(오늘날의 메모리)부터 명령어를 가져오는 과정.
  2. 명령어 해석 (ID, Instruction Decode)
    앞서 가져온 명령어가 어떤 명령어인지 해석을 진행하는 과정.
  3. 피연산자 인출(OF,Operands Fetch)
    명령의 실행에 필요한 정보를 기억장치로에 접근해 가져오는 과정.
  4. 명령어 실행 (EX, Instruction Execution)
    앞서 가져온 연산자와 데이터를 가지고 연산을 수행하고 저장한다.
  5. 인터럽트 체크

폰 노이만 구조이후의 컴퓨터는 명령어 실행 사이클을 계속해서 반복하게 됩니다. 기존의 컴퓨터에 비해 하는 일은 오히려 단순해졌다고도 볼 수 있습니다. 위의 과정만을 계속해서 반복하기만 하면 되니까요.

그러면 이런 명령어 실행 사이클이 실제 컴퓨터에서 어떻게 일어나는지 구체적으로 한번 살펴보겠습니다.

컴퓨터 구조

구체적인 명령어 실행 사이클에 대해 알아보기 전, 컴퓨터 구조에 대한 이해가 먼저 필요합니다.

오늘날의 컴퓨터 하드웨어는 CPU와 주기억장치(메모리), 보조기억장치(HDD, SSD 등..), 그리고 입력 장치와 출력 장치로 구성되어 있습니다. 또 이 장치들은 자료 교환을 위해 버스라는 공용 통로를 두고 연결되어 있습니다.

각각에 대해 하나하나 살펴보겠습니다.

CPU

CPU는 CU와 ALU, 레지스터들로 구성되어 있습니다.

CU는 Control Unit의 약어로 명령을 해석하고, 명령의 실행을 위한 제어신호를 발생시키는 일을 합니다.

ALU는 Ariethematic Logic Unit의 약어로 산술, 논리 연산 장치라고도 불립니다. 이 ALU는 CU의 신호에 따라 실질적인 산술, 논리 연산이 일어나게 됩니다.

레지스터는 CPU 내부에 소량으로 있는 고속의 임시 기억 장치입니다. 이 레지스터는 여러 종류가 있는데 이에 대해서도 한번 알아보겠습니다.

CPU의 개괄적인 모습

레지스터

CPU 내부에 존재하며, 명령 처리에 필수적인 레지스터로는 MAR, MBR, PC, IR, ACC 그 외 범용 레지스터로 R1, R2 등이 있습니다.

MAR는 Memory Address Register의 약어로 영어 그대로 메모리의 주소(번지)를 저장합니다.

MBR은 Memory Buffer Register의 약어로 메모리로부터 읽어왔거나, 메모리에 기록할 데이터를 저장합니다. 다른 이름으로 MDR 이라고도 부르기도 합니다.

PC는 Program Counter의 약어로 다음번에 실행할 명령어의 위치(번지)를 지정합니다. PC는 독특하게 한번 읽힐 때마다 값이 1씩 증가하여 특별한 일이 없다면 순차적으로 다음 메모리에 접근할 수 있도록 되어 있습니다.

IR은 Instruction Register로 현재 실행중인 명령어를 보관합니다.

ACC는 Accumulator로 누산기라고도 부른다. ALU에서의 연산의 결과값을 일시적으로 보관하는 역할을 합니다.

메모리

컴퓨터의 메모리는RAM(Random Access Memory)와 ROM(Read Only Memory)로 나뉘며 RAM은 다시 DRAM과 SRAM으로 나뉘게 됩니다.

DRAM은 Dynamic RAM로 캐퍼시터(충전지)로 구성되어 있습니다. SRAM에 비해 상대적으로 느리다는 단점이 있지만 저렴하기 때문에 주기억장치로 사용됩니다.

SRAM은 Static RAM으로 플립플롭 회로로 구성되어 있어 매우 빠릅니다. 다만 그 가격이 비싸 현실적으로 주 기억장치로 사용되기는 힘들어, DRAM의 느린 속도를 보완하기 위한 캐시 등에 사용됩니다.

이 RAM들은 Random Access Memory라는 이름처럼 순차적으로 접근하는 것이 목적이 아니기 때문에, 이후 설명하겠지만 각각의 저장된 내용에 접근하기 위한 고유의 주소가 존재합니다.

ROM(ROM, PROM, EPROM, ...)은 영어 그대로 읽기만이 가능한 메모리입니다. 컴퓨터의 부팅을 위한 BIOS 등에 사용되며, 과거에는 정말 읽기만 가능했지만 오늘날에는 전기적으로 지우고 새로 쓸 수 있는 ROM도 존재합니다. 하지만 RAM과 달리 일부만 값을 수정하며 보관하는 방식으로 동작하지 않아 그 용도가 다릅니다.

버스

버스는 컴퓨터를 구성하고 있는 각 컴퓨넌트들의 공용 연결 통로 역할을 합니다.

CPU, 메모리, 입출력 장치들이 모두 이 버스를 통해 자료교환을 진행하게 되며, 애석하게도 한 번에 하나의 장치만 버스에 접근이 가능하기 때문에 누군가 버스를 사용한다면 다른 장치들은 순서를 기다려야 합니다. 덕분에 버스는 필연적으로 복잡해질 수밖에 없고 이는 이후 설명할 하버드 구조로 개선이 이루어지게 됩니다.

프로그램의 동작

앞서 설명한 명령주기를 바탕으로 프로그램의 동작을 예시를 들어가며 알아보겠습니다.

본 내용을 이해하기 위해서는 최소한의 프로그래밍 지식이 필요하며, 설명하는 기계어는 조금씩 다를 수 있으니 내용 하나하나 살펴본다기보다는 전반적인 흐름을 이해하고자 하면 됩니다.

A = 10;
B = 20;
C = A + B;

위의 소스코드를 컴퓨터에서 동작시켜본다고 가정하겠습니다. 위 코드는 고급 언어로 작성되어 있으므로 컴파일을 진행하면 아래과 같은 기계어로 된 명령과, 실행에 필요한 데이터(A=10, B=20, C=? 등..)가 합쳐진 목적 모듈이 생성됩니다.

load A	# A를 메모리에서 불러옴
add B	# B를 메모리에서 불러와 더함
store C  # 그 결과를 C에 저장

이 목적 모듈은 실행에 필요한 다른 목적 모듈이나 라이브러리(목적 모듈의 모음)과 합쳐지는 링크라는 과정을 거치게 되어 결과물인 실행 모듈이 됩니다. 우리는 이 실행 모듈을 프로그램이라고 부르며, 하드디스크와 같은 보조 기억장치에 이를 저장하게 됩니다.

이 프로그램을 이용하여 연산을 수행하기 위해서는 폰 노이만 구조의 특성상 이 프로그램을 메모리에 올리는 과정이 필요합니다. 왜냐하면 CPU는 오직 메모리에만 직접 접근할 수 있기 때문입니다.

로더를 통해 메모리에 올라간 프로그램을 프로세스라고 부릅니다. 쉽게 명령어를 100번지부터 데이터를 200번지부터 저장하게 된다고 가정해 보겠습니다.

명령어 실행 사이클을 이해하기 앞서 반드시 레지스터 파트를 다시한번 읽고 오셔야 합니다.

첫 번째 사이클

아래 데이터들이 메모리에 올라가면서 동시에 명령의 시작 번지가 CPU의 PC 레지스터에 저장되게 됩니다.

번지(주소) 명령어 / 데이터 변수명
100 (PC) load A
101 add B
102 stove C
.
.
.
200 10 A
201 20 B
202 ? C

PC에 저장된 현재 명령어의 번지(예시상 100번지)의 값을 컨트롤 유닛의 제어에 따라 MAR에 저장되며 PC의 자동 증가 특징에 따라 PC는 아래와 같이 101번지를 가리킵니다.

번지(주소) 명령어 / 데이터 변수명
100 (MAR) load A
101 (PC) add B
102 stove C
.
.
.
200 10 A
201 20 B
202 ? C

이는 다시 컨트롤 유닛의 제어에 따라 MAR이 가리키는 번지의 값을 메모리에서 불러오게 되며, 메모리 버퍼인 MBR에 저장하게 됩니다.

MAR: 100번지 -> MBR: load A -> IR: load A -> Decoder

MBR에 저장된 load A는 현재 실행 중인 명령을 보관하는 IR에 저장되며, 컨트롤 유닛의 디코더에서 명령어를 해석하여 명령을 실행하게 됩니다. load A는 A라는 변수에 저장된 값을 불러오라는 의미이므로 200번지(A)에 있는 값 10이 MBR을 거쳐 누산기에 저장됩니다.

명령 실행 사이클이 끝나게 되었으므로 cpu는 인터럽트를 체크하고 다시 사이클을 반복하게 됩니다.

두 번째 사이클

이렇게 첫 번째 사이클이 끝나면 바로 두 번째 사이클이 동일하게 진행됩니다.

번지(주소) 명령어 / 데이터 변수명
100 load A
101 (PC) add B
102 stove C
.
.
.
200 10 A
201 20 B
202 ? C

앞서 자동 증가된 PC의 값을 MAR로 불러오게 되며, 다시 PC는 자동 증가하여 102번지를 가리킵니다.

번지(주소) 명령어 / 데이터 변수명
100 load A
101 (MAR) add B (MBR, IR)
102 (PC) stove C
.
.
.
200 10 A
201 20 B
202 ? C

MAR에 있는 메모리 번지의 값을 꺼내와 MBR에 불러오고, 이를 IR에 저장하게 됩니다.
현재 예시를 기준으로 불러온 값은 add B 일 것입니다.

불러온 명령은 컨트롤 유닛의 디코더로 들어가 명령을 해석하게 되며, 명령에 따라 MBR에 변수 B의 값(20)을 불러온 뒤 누산기를 거쳐 앞서 불러온 값 10과 더하는 과정을 진행할 것입니다.

결과적으로 누산기에는 10과 20의 합인 30이 저장되어 있을 것입니다.

마찬가지로 인터럽트를 체크하고 다시 사이클을 반복하게 됩니다.

세 번째 사이클

첫 번째, 두 번째 사이클과 동일하게 동작합니다.

번지(주소) 명령어 / 데이터 변수명
100 load A
101 add B
102 (PC) stove C
.
.
.
200 10 A
201 20 B
202 ? C

아까 두번째 사이클에서 PC의 번지가 자동 증가가 되었기 때문에 현재 PC가 가리키는 번지는 102번지일 것입니다. PC에서 다음 실행할 번지 값을 읽어 MAR에 저장합니다. 

MAR에 저장된 번지에 있는 메모리 값을 컨트롤 유닛의 제어에 따라 MBR로 불러오게 되며, 이 명령어 stove C는 다시 현재 실행중인 명령을 저장하는 IR에 저장됩니다.

IR에 저장된 명령어는 컨트롤 유닛의 디코더에서 해석이 이루어지며, 현재 명령 stove C는 누산기에 저장된 연산 결과를 C라는 변수에 저장하라는 것이기 때문에 누산기에 저장되어 있던 30이라는 값을 MBR을 거쳐 C(202) 번지에 저장하게 됩니다.

번지(주소) 명령어 / 데이터 변수명
100 load A
101 add B
102 (MAR) stove C (IR)
.
.
.
200 10 A
201 20 B
202 30 (MBR)
C

폰 노이만 구조의 문제점

지금까지 폰 노이만 구조의 컴퓨터가 동작하는 방식을 알아보았지만, 여기에는 치명적인 단점이 하나 있습니다.

메모리 파트에서 설명드린 것과 같이 주기억장치로 DRAM을 사용하고 있는데 이것이 CPU의 동작 속도보다 너무나 느리다는 점입니다. CPU는 메모리에서 명령과 데이터를 불러와야 하지만 RAM과 CPU의 속도 차이로 인해 병목 현상(CPU가 메모리의 응답까지 기다리게 됩니다)이 발생합니다.

이를 폰 노이만 병목 현상이라고 부릅니다.

하버드 구조

이런 폰 노이만 병목 현상을 완화하고자 나타난 방법중 하나가 하버드 구조입니다.

하버드 구조는 명령어와 데이터를 나눠서 보관합니다.

버스 또한 폰 노이만 구조에서는 제어신호, 명령어, 데이터를 모두 하나의 버스로 이용했지만 하버드에서는 이 각각을 위한 전용 버스를 구성하여 버스의 혼잡을 줄이게 됩니다.

이로 인해 명령을 읽으면서 동시에 데이터를 저장하는 등의 행위가 가능해져 폰 노이만 병목현상을 조금이나마 완화할 수 있도록 개선되었습니다.

하버드 구조의 버스

 

글에 추가를 원하는 사항이 있으시거나, 잘못된 부분을 발견하셨다면 댓글을 달아주거나 메일을 보내주시면 확인 후 수정하도록 하겠습니다.

최초 발행일 2021-09-29
마지막 수정일 2021-09-29