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