////
Search
Duplicate
📐

Chapter 01. CPU를 알면 프로그래밍이 보여요

01. CPU의 내부를 자세히 살펴보자

CPU는 최종적으로 기계어로 된 프로그램의 내용을 해석해서 실행하는 부품이다. 그 과정은 다음과 같다.
1.
프로그래머가 C언어 등의 고급 언어로 프로그램을 작성한다.
2.
프로그램을 컴파일해서 기계어인 실행 파일로 변환한다.
3.
프로그램이 실행될 때 실행 파일의 복사본이 메모리로 이동한다.
4.
CPU가 메모리에 있는 실행 파일을 해석하고 실행한다.
CPU는 레지스터와 제어 장치, 연산 장치 그리고 클록 등의 4대 요소로 구성되어 있으며 이들은 서로 전기적으로 연결되어 있다.
레지스터란 명령어와 데이터를 보관하는 영역으로 일종의 메모리다.
제어 장치는 메모리의 명령어와 데이터를 읽은 후 레지스터에 적재하고 명령어가 실행되는 결과에 따라 컴퓨터 전체를 제어한다.
연산 장치는 레지스터가 메모리부터 읽은 데이터를 연산하는 역할을 한다.
클록은 컴퓨터가 작동하는 시간이 계산되는 클록 신호를 발생시키는 것이며 이것이 CPU의 외부에 있는 컴퓨터도 있다.
보통 메모리란 메인 메모리를 지칭하는데, 이 메모리는 CPU나 제어용 칩과 서로 연결되어 있으며 명령어와 데이터를 보관하는 역할을 한다.
메인 메모리는 읽고 쓸 수 있는 메모리 소자로 구성되어 있는데, 1바이트 씩 구분된다. 이렇게 구분된 메모리의 단위에는 ‘어드레스’라고 부르는 번호가 있다.
CPU는 이 어드레스를 지정해서 메인 메모리에 보관된 명령어나 데이터를 읽거나 데이터를 기록하기도 한다.
그리고 메인 메모리에 기록된 명령어나 데이터는 컴퓨터의 전원을 끄면 모두 삭제된다.

02. 레지스터로 이루어진 CPU의 세계

CPU의 네 가지 요소 중 프로그래머가 꼭 알고 있어야 하는 부분은 레지스터다. 이는 프로그램이 레지스터를 이용해서 데이터를 처리하기 때문이다.
고급 언어로 작성한 프로그램이 컴파일 과정을 거쳐 기계어로 변환된 후 최종적으로 CPU 내부에서 레지스터를 통해 처리되므로 이 과정을 기억해두어야 한다.
레지스터는 명령어 또는 데이터 두 종류의 값을 보관한다.
데이터에는 연산에 사용되는 것과 메모리 어드레스를 나타내는 것 두 종류가 있다. 종류에 따라 사용되는 레지스터의 종류도 달라진다.
연산에 사용되는 값은 어큐뮬레이터에 보관되고 메모리 어드레스를 나타내는 값은 베이스 레지스터와 인덱스 레지스터에 보관된다.

03. 프로그램의 흐름은 프로그램 카운터가 책임진다

대부분의 프로그램은 하나의 흐름만으로 수행되지 않는다. 여러 분기나 반복을 거친다. 이를 가능하게 해주는 것이 프로그램 카운터다.
일반적으로 CPU가 하나의 명령어를 수행하면 프로그램 카운터의 값이 자동으로 1씩 증가한다. 그리고 CPU의 제어 장치는 프로그램 카운터의 값을 참조해 메모리에서 명령어를 읽어 내 실행한다.

04. 알고보면 재미 있는 조건 분기와 반복의 원리

프로그램이 순차, 분기, 반복 세 가지로 이루어진다는 것을 이미 알고 있다. 그럼 우린 프로그램 카운터에 빗대어 이해해볼 수 있다.
순차는 어드레스 값의 순서대로 명령어를 실행하는 것이다.
분기는 조건에 따라 임의의 어드레스에 있는 명령어를 실행하는 것이다.
반복은 같은 어드레스의 명령어를 일정한 횟수만큼 반복 실행하는 것이다.
프로그램 안에 해당 흐름 제어가 포함된 경우에는 기계어 명령어가 프로그램 카운터의 값을 임의의 어드레스로 변경하게 된다. 덕분에 이전의 어드레스로 돌아가거나 임의의 어드레스로 점프할 수 있다.
조건 분기 및 반복에서 사용되는 점프 명령어는 바로 직전에 실행된 연산 결과를 참조해서 점프할지의 여부를 결정한다.
이때 플래그 레지스터가 방금 전 실행된 연산의 결괏값을 기록한다.
이에 따라 조건 분기에서는 점프 명령어 이전에 일종의 비교 연산이 실행된다.
이때 점프 명령어의 실행 여부는 CPU가 플래그 레지스터를 참조해서 판단한다.

05. 함수 호출의 베일을 벗겨 보자

고급 언어로 작성된 프로그램에서 함수를 호출하는 처리 과정도 함수가 보관된 어드레스 값을 프로그램 카운터에 넣어주는 방식으로 구현된다.
함수의 호출은 조건 분기나 반복과 그 원리가 다른데, 단순한 점프 명령어에서는 함수를 호출할 수 없기 때문이다.
프로그램 안에서 함수가 호출되어 함수 내부의 처리가 완료되면 함수를 호출한 명령어의 다음 어드레스로 돌아가야 하기 때문이다.
무작정 함수의 첫 번째 어드레스로 점프해 버린다면 함수의 처리가 끝난 후 어디로 돌아가야 할지 알 수 없게 된다.
그렇다면 우리는 호출 시점의 어드레스를 기억하고 있어야 한다. 어떻게 기억할까? 이 문제는 호출과 리턴으로 구현된다.
일단 함수 호출은 점프 명령어가 아닌 호출 명령어에 의해 실행된다.
호출 명령어는 함수의 시작 어드레스를 프로그램 카운터에 넣기 전에 함수 호출 다음에 실행해야 하는 명령어의 어드레스를 스택이라고 부르는 메인 메모리 영역에 저장한다.
함수 처리가 종료되면 리턴 명령어를 실행한다.
리턴 명령어는 호출 명령어가 스택에 보관한 어드레스를 프로그램 카운터에 집어넣는 기능을 가지고 있다.
고급 언어의 프로그램을 컴파일하면 함수 호출이 호출 명령어로 변환되면서 함수의 끝이 리턴 명령어로 변환된다.

06. 찰떡궁합! 베이스 레지스터와 인덱스 레지스터

이 레지스터들을 함께 사용하여 메인 메모리의 특정 메모리 영역을 구분한 후 배열처럼 사용할 수 있게 된다.
컴퓨터에 탑재된 메모리에 16진수로 00000000~FFFFFFFF까지의 어드레스가 할당되어 있다고 가정하자. 이 범위의 메모리 영역이라면 32비트짜리 레지스터 하나로 모든 어드레스를 참조할 수 있다.
(2^4) * 8이니까 2^32이므로
그러나 배열처럼 특정 메모리 영역을 구분하고 연속적으로 참조하려면 2개의 레지스터를 사용하는 것이 편리하다.
10000000 ~ 1000FFFF번지를 참조한다면 베이스 레지스터에 10000000을 보관해두고 인덱스 레지스터의 값을 00000000 ~ 0000FFFF까지 변화시키면 된다.
이때 CPU는 베이스 레지스터 + 인덱스 레지스터의 값을 실제로 참조하는 메모리의 어드레스로 해석하게 된다.
효율적인 접근을 가능하게 해주는 것이다.

07. CPU는 단순한 작업만 수행할 수 있다?

CPU에서 할 수 있는 작업의 종류는 의외로 적다. CPU가 실행할 수 있는 기계어의 명령어를 기능 기준으로 분리해놓은 다음 표를 확인해보자.
종류
기능
데이터 전송 명령어
레지스터와 메모리, 메모리와 메모리, 레지스터와 주변 기기 간의 데이터 전송을 담당한다.
연산 명령어
어큐뮬레이터를 이용해서 수 계산, 논리 연산, 크기 비교, 시프트 등을 수행한다.
점프 명령어
조건 분기, 반복, 무조건 점프 등을 수행한다.
호출/리턴 명령어
함수를 호출한다. 함수를 호출한 부분으로 되돌아간다.