01. 시스템 프로그래밍의 이해와 접근
시스템 프로그래밍이란? “컴퓨터 시스템을 동작시키는 프로그램”이다.
이때, 컴퓨터 동작이란 파일 복사나 파일 이동 등과 같은 우리가 기본적인 동작이라고 간주하는 것들이다.
Ex) UNIX, Windows
이러한 소프트웨어들은 사용자가 하드웨어를 잘 알지 못하여도 컴퓨터를 쉽게 사용할 수 있도록 돕는다. 따라서, 운영체제는 시스템 프로그램의 범주 내에 있다.
즉, Windows 시스템 프로그래밍이란 Windows 운영체제 기반의 컴퓨터에게 일을 시키기 위한 프로그램을 구현하는 것이다. 결론적으로, 시스템 프로그래밍은 운영체제와 컴퓨터 구조 두 개의 구성 요소로 이루어져 있으며, 이를 이해하면, 효율적인 프로그램을 구현하는 것이 가능해진다.
02. 컴퓨터 하드웨어의 구성
먼저, 컴퓨터 하드웨어의 구성은 다음과 같다.
CPU (Central Processing Unit)
: 흔히 말하는 “중앙처리장치”이다.
이는 컴퓨터의 머리에 해당하는 역할을 수행한다.
메인 메모리 (Main Memory)
: 램(RAM)이라는 저장장치로 구성되는 메인 메모리는 컴파일이 완료된 프로그램 코드가 올라가서 실행되는 영역이라고 정의할 수 있다. 예를 들어, 게임을 다운로드했다고 가정하면 이는 당연히 하드디스크에 저장된다. 하지만 이 게임을 실행시킨다면, 이는 메인 메모리에 올라가서 실행된다. 즉, 메인 메모리는 프로그램 실행을 위해 존재하는 메모리이다.
입/출력 버스 (Input/Output Bus)
: 입/출력 버스는 컴퓨터의 구성요소 사이에서 데이터를 주고받기 위해 사용되는 경로이다. 이는 데이터의 종류와 역할에 따라 어드레스 버스(Address Bus), 데이터 버스(Data Bus), 컨트롤 버스(Control Bus) 세 가지로 구분된다. 위의 그림에서 볼 수 있듯이 이러한 버스를 기반으로 하드디스크-메인 메모리, 메인 메모리-CPU 간의 데이터 전송이 가능하다.
03. CPU에 대한 이해
본격적으로 CPU의 내부에 대해 알아보자. CPU의 주요 구성요소는 다음과 같다.
ALU (Arithmetic Logic Unit)
: CPU에서 실제로 연산을 담당하는 요소이다. 이외의 블록들은 연산하는 데에 도움을 주는 블록들이며, ALU는 기본적으로 덧셈이나 뺄셈과 같은 산술 연산과 AND나 OR과 같은 논리 연산을 처리한다.
컨트롤 유닛 (Control Unit)
: CPU에서 총사령관의 역할을 담당한다. 위에서 언급한 ALU는 기본적으로 산술 및 논리 연산만을 할 수 있기 때문에, 컨트롤 유닛이 bit 단위로 보내지는 명령어를 해석하고, 이에 따른 신호를 CPU의 다른 블록에 보내는 일을 맡는다.
레지스터 (Register Set)
: CPU 내부에 이진 데이터(Binary Data) 저장을 위한 저장장치이다. 이는 CPU에 따라서 16bit, 32bit, 64bit 정도의 크기를 가지며, CPU 내부에 여러 개 존재한다. 또한, 종류에 따라서 그 개수와 형태가 다양하고, 레지스터 각각의 용도가 정해져 있는 것이 일반적이다.
이러한 저장장치는 컨트롤 유닛이나 ALU가 필요로 하는 명령어나 데이터들을 저장해 두고 상황이 허락될 때 직접 가져가는 데에 사용된다.
버스 인터페이스 (Bus Interface)
: 우리가 비행기나 지하철의 이용 방법을 알아야 이용할 수 있듯이, CPU는 버스로부터 데이터를 주고받기 위해 I/O 버스의 통신방식의 이해가 필요하다. 이러한 역할을 맡고 있는 것이 버스 인터페이스이다. 이는 버스가 어떻게 데이터를 전송하는지에 대한 프로토콜이나 통신방식을 알고 있다. 또한, CPU는 버스 인터페이스를 통해 레지스터에 있는 데이터를 보내기도 하고, 버스를 통해 데이터를 수신하기도 한다.
클럭 신호 (Clock Pulse)
: 클럭 신호는 CPU를 구성하는 요소는 아니지만, 타이밍(Timing)을 제공하는 중요한 부분을 담당하고 있다. 만약, CPU의 클럭 속도가 1.6Mhz일 경우 클럭 발생기(오실레이터)는 1초당 1,600,000번 클럭을 발생시키고, CPU는 이에 맞춰서 일을 하게 된다. 따라서, 클럭 속도가 빠를수록 컴퓨터의 성능이 좋아진다.
이처럼 CPU에 클럭 신호가 필요한 이유는, 컴퓨터 시스템은 동기화를 요구하기 때문이다. 이러한 사실은 이미 디지털 논리 회로 과목에서 배웠지만, 다시 한번 살펴보면 다음과 같다.
위와 같이 간단한 입출력 장치가 있다고 가정해 보자, 이는 두 개의 입력을 받아 더한 뒤, 버퍼에 저장하고 출력 장치는 버퍼에 있는 내용을 출력하는 간단한 동작을 수행한다. 하지만, 이렇게 간단한 장치에서도 클럭 신호와 관련된 2가지 문제 상황이 발생할 수 있다.
- + 연산장치의 계산 속도가 출력장치의 출력 속도보다 빠르다면, 버퍼에 저장된 이전 연산 결과가 + 연산장치에 의해 덮어 씌워져 출력이 누락되는 경우가 발생한다.
- 반대로 출력장치의 속도가 더 빠르다면, 같은 연산 결과를 중복해서 출력할 수도 있다.
따라서, 이러한 문제를 해결하기 위해 속도가 느린 장치의 속도에 맞춰 신호를 보내고, 이에 따라 연산을 진행하도록 한다. 이러한 역할을 하는 것이 클럭 신호이다.
04. 프로그램의 실행과정
이제 프로그램이 생성되고, 실행되는 과정을 살펴볼 것이다.
위대한 수학자 폰 노이만 (J.von Neumann)
: 폰 노이만은 오늘날 우리가 사용하고 있는 컴퓨터의 기본 모델을 제시한 사람으로, 다음과 같은 구조의 컴퓨터를 생각해 냈다.
위 그림에서 중요한 부분은 메모리와 프로그램으로, 폰 노이만은 프로그램이 컴퓨터 내부에 저장되는 구조를 생각하였다. 이러한 컴퓨터 구조는 “폰 노이만 아키텍처” 또는 “Stored Program Concept”라고 불린다.
프로그램의 실행 과정
: 먼저, 프로그램의 실제 실행단계를 살펴보기 위해 실행파일의 생성 과정을 간략히 정리해 보면 다음과 같다.
단계 1: 전처리기에 의한 치환 작업
전처리기는 ‘#include’, ‘#define’과 같이 ‘#’으로 시작하는 지시자의 지시에 따라 소스 코드를 변환하는 작업을 수행한다.
단계 2: 컴파일러에 의한 번역
1단계에서 변경된 소스코드는 여전히 C 언어로 구성되어 있기 때문에, 컴파일러에 의해 어셈블리 코드로 번역된다. 어셈블리 코드는 CPU가 디자인될 때 CPU에게 일을 시키기 위한 명령어를 조합해서 만들어진 프로그램 코드를 의미한다. (Low-Level)
단계 3: 어셈블러에 의한 바이너리 코드 생성
컴파일러에 의해 번역된 어셈블리 코드를 컴퓨터가 이해할 수 있는 0과 1로만 이루어진 바이너리 코드로 변환한다. 이때, 아래와 같은 표를 참조하여 어셈블리 코드를 바이너리 코드로 변환한다.

단계 4: 링커에 의한 연결과 결합
프로그램 내에서 참조하는 함수나 라이브러리들을 하나로 묶거나 연결하는 작업을 한다.
이러한 과정이 끝나면 실제로 실행 가능한 실행파일이 생성된다. 또한, 이는 바이너리 코드로 구성된다.
이제, 프로그램의 실행 과정을 살펴보자. 간략하게 다음과 같이 나타낼 수 있다.
먼저, 위 그림에서는 실행파일이 메모리 공간에 올라가고, 이것이 CPU에 의해 실행된다. 또한, 실행파일에 포함된 명령어들은 CPU에 의해 순차적으로 실행된다. 이때, 메모리상의 명령어들은 순차적으로 CPU 내부로 이동한 뒤에 실행된다.
이때 CPU 내부로 이동한 뒤, 순차적으로 실행되는 과정을 다음과 같이 3단계로 나눠서 살펴볼 수 있다.
단계 1. Fetch: 메모리상에 존재하는 명령어를 CPU로 가져오는 작업이다.
단계 2. Decode: 가져다 놓은 명령어를 CPU가 해석하는 단계이다.
단계 3. Execution: 해석된 명령어의 명령대로 CPU가 실행하는 단계이다.
프로그램의 기본 실행은 위의 3단계를 거친다.
05. 하드웨어 구성의 재접근
폰 노이만의 컴퓨터 구조 vs 오늘날의 컴퓨터 구조
: 다음의 폰 노이만의 컴퓨터 구조로부터 오늘날의 컴퓨터 구조로 구체화해 보면 다음과 같다.
폰 노이만의 컴퓨터 구조에서 3단계로 이루어지는 프로그램의 실행은 다음과 같이 구체화할 수 있다.
3단계: Execution
CPU 내부의 ALU가 수행하게 된다. 실제로는 명령어를 통해서 요구하는 것이 다양하기 때문에 정확하지는 않지만, 명령어가 산술 및 논리 연산이라면 정확한 사실이다.
2단계: Decode 단계
CPU 내부의 컨트롤 유닛이 진행하게 된다.
추가적으로, 명령어를 CPU 내부에 저장해야 하는데, 이는 CPU 내부의 레지스터가 담당하며, 레지스터 중 IR(Instruction Register)가 담당한다. 추가적으로, PC(Program Counter)라는 레지스터는 다음에 가져와야 할 명령어가 어느 위치에 존재하는지 메모리 주소를 기억하기 위한 용도로 사용된다.
데이터 이동의 기반이 되는 버스(BUS) 시스템
: 마지막으로 남은 것이, 1단계인 Fetch이다. 이는 데이터의 이동을 담당하는 BUS 시스템을 통해 수행되며, 앞에서 설명했던 것과 같이 버스 시스템은 주고받는 데이터의 종류에 따라 어드레스 버스, 데이터 버스, 컨트롤 버스로 나뉜다. 이때, 컨트롤 버스는 CPU가 원하는 바를 메모리에 전달할 때 사용되며, CPU와 메모리가 특별한 사인(sign)을 주고받는 용도이다.
이러한 데이터 이동은 명령, 주소, 데이터 순으로 이루어지게 되며, 다음과 같은 사례를 통해 쉽게 이해할 수 있다.
최종적으로 지금까지의 내용을 통해 폰 노이만의 컴퓨터 구조를 구체화하면 다음과 같다.
참고 자료:
윤성우. 『뇌를 자극하는 윈도우즈 시스템 프로그래밍』.한빛미디어, 2007.
'독서 > [ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ]' 카테고리의 다른 글
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 06. 커널 오브젝트와 오브젝트 핸들 (2) | 2023.09.24 |
---|---|
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 05. 프로세스의 생성과 소멸 (1) | 2023.09.17 |
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 04. 컴퓨터 구조에 대한 두 번째 이야기 (1) | 2023.09.16 |
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 03. 64비트 기반 프로그래밍 (1) | 2023.09.10 |
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 02. 아스키코드 vs 유니코드 (1) | 2023.09.09 |