영화 '이미테이션 게임'
지난 게시물에서는 컴퓨터를 프로그래밍 가능한 기계로 정의하고, 하드웨어와 소프트웨어를 분리한다는 아이디어를 튜링 머신을 통해 살펴보았습니다. 튜링이 계산하는 기계에 대한 이론적 토대를 마련했지만 여전히 중요한 질문이 남아있었습니다. 어떻게 그 기계를 현실 세계에 구현할 수 있을까요?
이때 등장한 인물이 바로 미국의 컴퓨터 과학자이자 수학자이자 과학자이자 경제학자였던 존 폰 노이만입니다. 그가 1945년에 제시한 폰 노이만 구조는 오늘날 여러분의 스마트폰부터 슈퍼컴퓨터까지 거의 모든 컴퓨터가 따르고 있는 기본 설계 원리입니다.
이번 글에서는 폰 노이만의 핵심 아이디어가 무엇인지 알아보고, 우리가 이전에 만들었던 논리 게이트들이 어떻게 CPU와 메모리라는 거대한 시스템의 일부가 되는지 큰 그림을 그려보겠습니다. 80년 전의 발상이 어떻게 현대 컴퓨터의 기초가 되었는지 확인해보세요.
폰 노이만 구조가 등장하기 이전의 초기 컴퓨터들은 매우 비효율적인 방식으로 작동했습니다. 다음 자료를 살펴보시죠:
ENIAC을 이후의 모든 컴퓨터들과 구분짓는 특이한 점 중 하나는, 명령어를 기계에 설정하는 방식이었다. 그것은 소형 천공 카드 기계의 플러그보드와 유사했지만, ENIAC에서는 수 피트 크기의 플러그보드가 약 40개나 있었고, 문제 하나의 명령어마다 다수의 와이어를 꽂아야 했다. 문제 하나를 실행 준비하기 위해서는 수천 개의 와이어를 연결해야 했으며, 이 작업에는 수일이 걸렸고, 그 설정을 검증하는 데에는 더 많은 날들이 필요했다.
Franz L. Alt, "Archaeology of Computers—Reminiscences, 1945–1947," Communications of the ACM, Vol. 15, No. 7, July 1972, p. 694.
결국 ENIAC에게 프로그램이란 기계의 물리적인 구조 그 자체였습니다. 이는 마치 새로운 요리를 할 때마다 부엌의 구조를 통째로 바꾸는 것과 같았죠.
폰 노이만은 이 문제를 프로그램의 개념 자체를 재정의함으로써 해결했습니다. 프로그램을 하드웨어의 물리적인 배선 상태로 보는 대신 데이터로 취급한 것입니다. 이는 새로운 요리를 할 때 부엌은 가만히 있고 새로운 요리법만 참고하는 것과 같습니다.
이 아이디어가 바로 폰 노이만 구조의 핵심인 내장형 프로그램 방식(Stored-Program Computer) 입니다. 전선을 만질 필요 없이 메모리에 저장된 프로그램만 새로 불러오면 컴퓨터가 완전히 다른 작업을 수행하게 된 것입니다.
그런데 "프로그램이 데이터"라는 게 구체적으로 무슨 뜻일까요? 이를 이해하기 위해서는 컴퓨터가 이해할 수 있는 언어인 기계어가 어떻게 생겼는지 살펴봐야 합니다
첫 글에서 살펴봤듯이 컴퓨터는 정보를 0과 1로만 표현하며, 프로그램도 예외는 아닙니다. 컴퓨터가 이해할 수 있는 언어, 즉 기계어는 모두 0과 1의 조합으로 이루어져 있습니다. 예를 들어 "두 숫자를 더해라"라는 명령을 컴퓨터에게 내리고 싶다면, 우리는 00110000
과 같은 이진 숫자로 번역해서 전달해야 합니다.
하지만 여기서 중요한 것은 00110000
이 덧셈을 의미한다는 것 자체에는 어떤 마법도 없다는 점입니다. 이는 단순히 CPU를 설계하는 사람과 프로그램을 짜는 사람 사이의 약속일 뿐입니다. "0011
으로 시작하는 명령어를 보면 덧셈 회로를 작동시키자"고 미리 정해둔 규칙인 것이지요.
실제 컴퓨터의 기계어는 꽤나 복잡하겠지만 폰 노이만 구조의 핵심 원리를 이해하기 위해 이번 글을 위한 아주아주 단순한 8비트 기계어를 만들어보았습니다. 아래 명령어 세트 뷰어에서 각 명령어를 클릭하여 구조와 설명을 확인해보세요. 레지스터니, Z 플래그니 모르겠는 단어가 많지만 곧이어 살펴볼테니 여기서는 0과 1이 이렇게 해석될 수 있구나 정도만 챙겨가면 됩니다.
MOVE R1, R2 // R1 = R2
00000110
앗 프로그램은 0과 1로 이뤄져있다는데 저 MOVE
, ADD
같은 사람말은 뭐냐고요? 프로그래머들이 0001
같은 이진수를 매번 외우기는 어려우니, 각 명령어마다 Mnemonic(기억을 돕는 약어)이라는 이름을 붙입니다.
결국 기계어란 복잡해 보이지만 단순한 약속의 집합입니다. "이 숫자 조합을 보면 덧셈을 하자", "저 숫자 조합을 보면 데이터를 옮기자" 같은 규칙들 말이죠. 이런 약속들을 모아놓은 것을 컴퓨터 용어로 명령어 집합 구조(Instruction Set Architecture, ISA) 라고 부릅니다.
자 이제 기계어를 살펴봤으니 이 기계어를 실행시키는 폰 노이만 구조 컴퓨터는 무엇으로 구성되어있는지 살펴봅시다. 크게 메모리, CPU, 입출력 장치로 나뉩니다.
메모리는 컴퓨터가 일할 때 필요한 모든 정보를 저장합니다. 여기서 가장 큰 특징은 각각의 저장 공간이 숫자 주소를 갖고 있다는 점입니다. 예를 들어 '100번 주소에 있는 데이터를 가져와라' 또는 '2048번 주소에 50을 저장해라'와 같이 주소를 지정하여 명령을 내리면 메모리는 해당 위치의 데이터를 즉시 찾아내거나 저장합니다.
아래 메모리 시뮬레이터에서 주소를 통해 데이터를 읽고 써보세요. 실제로는 아래 표처럼 메모리의 내용을 한 눈에 볼 수 있지 않고 주소로 접근한 값만 읽고 쓸 수 있음을 잊지 마세요:
CPU는 메모리에 저장된 프로그램을 순서대로 가져와 해석하고, 필요한 계산을 수행하며, 컴퓨터의 다른 부분들이 움직이도록 제어합니다. 중앙 처리 장치(Central Processing Unit)라는 이름에 걸맞죠?
CPU는 크게 레지스터, ALU, 제어 장치로 나뉩니다. 순서대로 살펴볼게요.
메모리가 컴퓨터의 대용량 창고라면, 레지스터는 CPU 내부에 있는 작은 작업대입니다. 메모리는 용량이 크지만 상대적으로 접근 속도가 느린 반면, 레지스터는 용량은 매우 작지만 초고속으로 접근할 수 있습니다.
만약 레지스터가 없다면 어떻게 될까요? CPU가 연산할 때마다 매번 메모리에서 데이터를 가져와야 하고, 중간 결과도 모두 메모리에 저장해야 합니다. 이는 마치 요리할 때 필요한 재료를 매번 창고에서 하나씩 가져오는 것과 같아서 매우 비효율적이죠.
레지스터 중에서도 특별히 중요한 것이 하나 있습니다. 바로 Program Counter(PC) 입니다. PC는 현재 실행 중인 명령어의 메모리 주소를 저장하는 레지스터로, 요리할 때 레시피의 몇 번째 단계를 하고 있는지 가리키는 손가락과 같은 역할을 합니다.
예를 들어 PC가 100번지를 가리키고 있다면, CPU는 메모리의 100번지에서 명령어를 가져와 실행합니다. 명령어 실행이 끝나면 PC는 기본적으로 다음 주소로 증가하여 다음 명령어를 준비하죠. 이 과정이 반복되면서 프로그램이 순차적으로 실행됩니다. 물론 JUMP
같은 명령어를 만나면 PC가 지정된 주소로 점프하여 프로그램의 실행 흐름을 바꿀 수도 있습니다.
제어 장치의 지시대로 레지스터 값을 사용해 실제 연산을 수행합니다. 덧셈, 뺄셈 같은 산술 연산뿐만 아니라, '참/거짓'을 판단하는 AND, OR, NOT 같은 논리 연산도 처리합니다. 시리즈 초반에 논리 게이트(AND, OR, NOT)를 조합하여 가산기를 만들었던 것을 기억하시나요? 바로 그 회로가 더욱 정교하게 발전한 형태가 이 ALU를 구성합니다.
ALU는 연산 결과와 함께 플래그(Flag) 라는 추가 정보도 제공합니다. 예를 들어 Zero Flag(Z) 는 연산 결과가 0인지 아닌지를 나타냅니다. 5 - 5 = 0 같은 연산을 수행하면 Z 플래그가 1(참)로 설정되고, 3 + 2 = 5 같은 연산에서는 Z 플래그가 0(거짓)으로 설정됩니다. 이런 플래그는 이어서 실행되는 조건문에서 "결과가 0이면 다른 코드로 점프하라" 같은 판단을 내릴 때 사용됩니다.
메모리에서 명령어를 가져와 해독하고, 컴퓨터의 다른 부분에 적절한 신호를 보내 작업을 지시합니다. 아래 제어장치 시뮬레이터에서 각 명령어마다 어떤 지시를 내리는지 확인해보세요.
Rs 레지스터의 값을 Rd 레지스터로 복사합니다.
입출력 장치는 CPU가 세상과 소통하는 창구입니다. 키보드로 글자를 입력받고, 마우스로 좌표를 읽고, 모니터에 결과를 표시하는 모든 과정이 여기에 해당합니다.
입출력 장치를 구현하는 한 가지 흥미로운 방법은 메모리를 활용하는 것입니다. 이는 마치 사무실에서 특정 서랍을 "외부와의 소통함"으로 지정해두는 것과 같습니다. 특정 메모리 주소 영역을 입출력 장치와 미리 약속해두고, CPU는 그저 해당 주소에 값을 읽거나 쓰는 방식으로 장치를 제어합니다.
예를 들어, 메모리의 특정 영역을 모니터 화면의 픽셀과 일대일로 매칭해둘 수 있습니다. CPU가 해당 메모리 주소에 1(검은색)이라는 값을 쓰면, 모니터의 해당 픽셀이 검게 칠해지고 0(흰색)을 쓰면 하얗게 칠해지는 식이죠. 키보드 역시 특정 메모리 주소에 마지막으로 눌린 키의 코드를 계속해서 써주는 방식으로 작동할 수 있습니다. CPU는 그저 해당 메모리를 주기적으로 읽기만 하면 키 입력을 받아올 수 있습니다.
아래는 10x10 크기의 모니터와 여기에 연결된 비디오 메모리(VRAM)를 흉내낸 시뮬레이터입니다. 메모리 값을 직접 바꿔보며 화면이 어떻게 변하는지 관찰해보세요.
* 텍스트 영역의 0과 1을 수정해보세요.
자! 이제 폰 노이만 구조의 모든 핵심 부품들을 살펴봤습니다. 메모리, CPU, 입출력 장치가 어떻게 작동하는지 개별적으로는 이해했지만, 이들이 실제로 어떻게 협력해서 프로그램을 실행하는지는 아직 보지 못했죠. 이제 이 부품들을 하나로 조립해서 완전한 컴퓨터가 되는 과정을 확인해보겠습니다.
아래 시뮬레이터에서 프로그램을 작성하고 실행해보세요. 메모리에 저장된 프로그램을 하나씩 가져와 해석하고, 그에 따라 컴퓨터가 움직이는 과정을 관찰해보세요.
이번 시간에는 튜링 머신이 폰 노이만 구조를 통해 어떻게 현실에 구현되었는지 살펴보았습니다. 프로그램을 하드웨어의 배선이 아닌 메모리에 저장되는 데이터로 취급함으로써, 하드웨어 변경 없이 소프트웨어만으로 다양한 작업을 수행할 수 있게 되었습니다.
폰 노이만 구조의 핵심 구성 요소들도 살펴보았습니다. 프로그램과 데이터를 주소 기반으로 저장하는 메모리, 명령어를 해석하는 제어 장치와 연산을 수행하는 ALU 그리고 임시 저장소인 레지스터로 구성된 CPU, 그리고 사용자와 컴퓨터를 연결하는 입출력 장치까지요.
그런데 여기서 중요한 질문이 하나 남았습니다. 메모리에 저장된 10110000같은 기계어 명령어가 실제로는 어떤 과정을 거쳐 CPU에서 처리되는 걸까요? 제어 장치가 명령어를 '해석한다'고 했지만, 구체적으로 어떤 단계를 거치며, ALU와 레지스터는 언제 개입하고, 그 결과는 언제 어디로 가는 걸까요?
다음 글에서는 CPU의 기본 작업 사이클인 Fetch-Decode-Execute-Write Back을 살펴보며 이 궁금증을 해결해보겠습니다. 하나의 명령어가 처리되는 전 과정을 단계별로 따라가보면서, 여러분이 지금 이 글을 읽고 있는 순간에도 컴퓨터 속에서 초당 수십억 번씩 반복되고 있는 이 사이클을 직접 확인해보겠습니다.
로그인 중...