개발 서적 정독/뇌를 자극하는 윈도우즈 시스템 프로그래밍

[뇌를 자극하는 윈도우즈 시스템 프로그래밍] Chapter1, Chapter4

JiHxxn 2026. 5. 28. 12:00

📚 목차

  • 🔸 Chapter 1: 시스템 프로그래밍의 이해와 접근
    🔸 Chapter 4: 컴퓨터 구조의 접근 방법

시스템 프로그래밍이란?

  • 시스템(컴퓨터 시스템)의 범위
    • 하드웨어 + 운영체제
  • 시스템 프로그래밍
    • 컴퓨터 시스템을 활용하는 소프트웨어 개발
    • 모든 응용 프로그래밍에 시스템 프로그래밍이 있다
      • window 운영체제 자체의 기능을 십분 활용하는 프로그래밍
  • 응용 소프트웨어 개발과의 차이점
    • 시스템 프로그래밍은 모든 응용 프로그램에 포함되는 요소

컴퓨터 시스템의 주요 구성 요소 (컴구조 + 운영체제)

  • CPU, 캐쉬
    • 컴퓨터 하드웨어 구조
  • 운영체제
    • 메인 메모리
      • 메모리 관리 기법
  • 하드디스크

 

컴퓨터 하드웨어 구성(전체)

  • CPU
    • 중앙 처리 장치
    • 연산이 이뤄지는 원리는 무엇인가?
  • 메인 메모리
    • RAM
    • 프로그램 실행 방식을 이해하는 것
  • 입출력 버스
    • 데이터 송 수신이 이뤄지는 원리

 

CPU란

  • ALU
    • 산술 연산이나 AND, OR 같은 논리 연산을 한다.
  • 컨트롤 유닛
    • CPU 전체를 총괄
    • 명령어를 해석하고 해석된 결과에 따라 적잘한 신호를 CPU의 다른 블록에 보내는 일을 한다
  • 레지스터
    • CPU 내부에 임시적으로 데이터를 저장하기 위한 메모리 공간
    • 2진 데이터를 저장
  • 버스 인터페이스
    • I/O 버스 통신 방식을 이해하고 있는 역할을 한다
    • cpu 내부에 저장되어 있는 데이터를 I/O 버스에 실어 보내거나, 전송되어 오는 데이터를 수신한다.

  1. 컴파일된 실행 파일“하드디스크”에 들어간다. (ex. A.exe)
  2. 컴파일된 파일에 예를 들어 덧셈 기능이 있다.
    • 메인 메모리에 ‘덧셈’, ‘2’, ‘5’가 들어감
  3. 출력 버스를 통해 버스 인터페이스에 도착
    • 입출력 버스는 **통신 규약**에 따라 데이터를 주고 받을 수 있음 그것을 도와주는 것이 해주는 것이 버스 인터페이스
    • 외부(네트워크, 모니터, 키보드) 등에 도 데이터 이동을 위한 입출력 버스가 있다.
  4. 궁극적으로 연산을 위해 ALU로 가야한다.
    • 그 전에 컨트롤 유닛으로 흘러 들어간다.
      • 컨트롤 유닛이 명령어를 해석한다. → cpu가 해야 할 일을 정해준다.
  5. ALU는 순차적으로 일들을 처리 중 이기 때문에 cpu 내부에도 작은 메모리 공간을 배치해서 저장시켜 놓자. → 레지스터
    • 필요할 때 가져갈 수 있는 메모리 공간
    • 덧셈이나 2, 5들이 저장된다.
  6. 이제 차례가 되면 컨트롤 유닛명령어를 해석 후 레지스터에서 데이터를 꺼내 ALU에게 전달해준다.
  7. ALU는 덧셈 결과를 레지스터에게 저장한다 → 러프하게 이해

클럭 신호 (Clock Pulse)

  • 컴퓨터의 모든 장치에는 클럭 신호가 들어가 있다.
  • 모든 장치에 일정한 간격으로 신호를 준다
    • 해당 **신호에 맞춰서 연산**을 한다
    • 동작 타이밍을 결정
  • 오버클럭
    • 일은 빨리 하지만 열이 발생해 CPU가 탈 수 있다.

문제점

  • 만약 출력 장치가 데이터를 가져오는 속도가 더 빠르다면, 출력 장치는 이미 한번 가져간 데이터를 다시 가져다가 출력하는 문제가 발생할 것이고,
  • 연산 장치가 더 빠르다면, 버퍼를 덮어쓰게 되어 연산결과의 일부가 출력되지 않는 문제점이 발생한다

해결 방안

  • 속도가 느린 장치의 장단에 맞춰주면 된다.
    • 가장 낮은 신호동기화해서 시스템을 안정화 시킨다.

폰 노이만 아키텍처

“프로그램이라는 것이 존재하고, 이 프로그램은 컴퓨터 내부에 저장되어서 순차적으로 실행 되어야한다.”

프로그램의 실행 과정

  1. 전처리기에 의한 치환 작업
    • #include, #define 과 같이 #으로 시작하는 지시자의 지시에 따라 소스코드를 변경하는 작업을 한다.
  2. 컴파일러에 의한 번역
    • 전처리에 이어 변경된 소스코드는 여전히 C 언어로 구성되어 있으며, 이 소스코드는 컴파일러에 의해서 어셈블리 코드로 변역된다.
  3. 어셈블러에 의한 바이너리 코드 생성
    • 텍스트 코드에 상대되는 뜻
      • 1과 0으로만 구성되는 코드이다.
    • 컴파일러에 의해 번역된 어셈블리 코드는 컴퓨터에 의해 실행되기에 앞서서 바이너리 코드로 번역되어야 한다. 왜나하면 컴퓨터는 오로지 1과 0만 이해하기 때문이다.

4. 링커에 의한 연결과 결합

  • 라이브러리와의 결합
  • 목적 파일과 라이브러리 파일이 합쳐져서 exe파일이 만들어진다.
  • 모든 작업이 끝나면 실행 가능한 실행파일이 생성된다.

 

Stored Program Concept

  • 폰 노이만 아키텍처 : 프로그램이 메모리에 저장되는 시스템
  • 프로그램의 기본Fetct, Decode, Exeution 단계를 거친다.
  1. Fetch
    • 메모리상에 존재하는 명령어를 CPU로 가져오는 작업
  2. Decode
    • 가져다 놓은 명령어를 CPU가 *하는 단계
  3. Execution
    • 해석된 명령어의 명령대로 CPU가 실행하는 단계

 

Stored Program Concept + 컴퓨터 구조

프로그램 실행 플로우

메모리 → I/O버스 → 레지스터 → 컨트롤 유닛 → ALU

데이터 이동의 버스 시스템

  1. 데이터 버스
    • 데이터를 이동하기 위한 버스
  2. 어드레스 버스
    • 주소값을 이동하기 위해 필요한 버스
    • 메모리 영역의 주소값과 해당 주소의 데이터를 전송하는 컨트롤러를 전달해준다
  3. 컨트롤 버스
    • CPU가 원하는 바를 메모리에 전달할 때 사용된다.
    • 컨트롤 신호 이동
    • 양방향으로 주고 받는 작업을 컨트롤한다.

 

1장 : 이것만은 알고가자

  1. ALU와 컨트롤 유닛의 기능적 역할
  2. 레지스터의 필요성
  3. 클럭 펄스의 필요성
  4. Fetch, Decode, Execution
  5. 버스 인터페이스

4장 : 컴퓨터 구조의 접근 방법

컴퓨터(CPU)를 디자인하자

  • ALU와 컨트롤 유닛은 이미 존재한다고 가정하고 레지스터를 디자인 해보자

레지스터를 디자인하자

  • 레지스터를 디자인하는데 있어서 결정해야 할 요소
    1. 레지스터를 몇 비트로 구성할 것인가?
    2. 몇 개 정도로 레지스터를 구성할 것인가?
    3. 레지스터 각각을 무슨 용도로 사용할 것인가?
  • 책에서는 이해하기 편하도록 16비트로 진행 및 레지스터 개수 8개로 제한한다.
  • 레지스터의 각각의 용도를 셋팅
  • r4~r7 은 특수한 목적으로 사용하기 위한 레지스터
  • r0~r3는 범용(연산을 위한) 레지스터

명령어 구조 및 명령어를 디자인하자

  • cpu에게 일을 시키기 위한 명령어 종류를 디자인해야 한다.
  • 명령어를 몇 비트로 구성할 것인지 결정 해야 한다.
    • 앞서 16비트로 레지스터 크기를 결정하였기 때문에 명령어 길이도 16비트로 구성

  • cpu에게 일을 시킬 때 “레지스터 r1에 있는 값과 숫자 7을 더해서 레지스터 r2에 저장하라” 는 형태로 일을 시키게 된다.
  • 이처럼 덧셈 연산을 위한 피연산자 둘, 연산결과 저장을 위한 피연산자 등을 16비트짜리 명령어 하나에 담을 수 있다.

  • ADD 연산자 뒤를 잇는 세 개의 비트엔 저장할 저장소로 구성
    • 레지스터가 총 8개 이므로 세 비트만 할당하면 충분히 표현 가능하다
    • 일반적인 ALU는 연산결과를 레지스터에 저장하기 때문에 이러한 규칙으로 설정
  • 피연산자를 담는 비트에는 정보나 숫자가 올 수 있도록 디자인
  • 처음 두 개의 비트 용도는 나중에 셋팅

  • 연산에 대한 정보는 세 개의 비트만 할당하였기 때문에 연산자의 개수는 총 8개를 넘지 못한다.
    • 우선 덧셈, 뺄셈, 곱센, 나눗셈 4가지를 정의
  • 레지스터에 대한 심볼과 코드를 정의

저장소에 레지스터를 저장

  • 이번에는 피 연산자 두 개를 표현할 때 숫자 or 레지스터가 될 수도 있다.
    • r2 = r1 + r3
    • r2 = r1 + 4
    • r2 = 5 + r1
    • r2 = 3 + 5
  • 그렇기 때문에 숫자 혹은 레지스터 정보를 구분해주기 위해서 하나의 비트를 희생한다.
    • 네 개의 비트 중에 첫 번째 비트가 1이면 레지스터 저장, 0이면 숫자를 저장한다.

1장에 이어 공부한 컴퓨터 구조의 전체 내용 이미지

  • 위 그림에서 명령어가 ir에 저장되는데, 이 때 ir은 다음 번에 실행 될 명령어를 미리 가져다 놓는 용도로 사용된다.

 

RISC vs CISC

  • CPU 설계 철학의 차이를 말하는 개념
    • 명령어를 복잡하게 많이 제공할 것인가?
    • 간단한 명령어만 제공하고 대신 빠르게 처리한 것인가?

CISC

  • 복잡한 명령어 세트를 가진 CPU 구조
    • 한 명령어로 여러 동작을 한 번에 수행할 수 있음
    • 명령어 종류가 많고 길이가 가변적
    • 하드웨어 설계가 복잡해짐
    • 속도는 명령어마다 다르고, 실행 시간이 일정하지 않음

RISC

  • 단순하고 규칙적이며 빠른 명령어 세트를 가진 CPU 구조
    • 명령어 수가 적고 구조가 단순
    • 하나의 명령은 하나의 작업만 수행
    • 명령어 길이가 고정되고 단순
    • 파이프라인 최적화에 매우 좋음
    • 실행 시간이 일정해 고성능 설계 유리
    • F, D, E F, D, E 이러한 방식으로 클럭수는 n + 2로 진행되어 빠르

CISC = 한 메뉴로 모든 게 들어있는 도시락

  • 하나 주문하면 여러 작업(반찬)이 한 번에 처리됨
  • 하지만 만들기 복잡하고 느릴 때도 있음

RISC = 필요한 반찬을 하나씩 꺼내서 먹는 뷔페

  • 동작은 작지만 빠르고 규칙적
  • CPU가 처리하기 훨씬 쉬움

추가 꿀팁: 사실 요즘 CPU는 혼합형

  • x86(CISC) CPU도 내부는 사실 RISC 마이크로-연산으로 변환해서 실행
  • 즉 "겉은 CISC, 속은 RISC" 

LOAD & STORE 명령어의 필요성

int a = 10; // 0x10번지 할당
int b = 20; // 0x20번지 할당
int c= 0; // 0x30번지 할당
c = a + b;
  • 피연산자로 올 수 있는 것을 숫자와 레지스터로 제한했기 때문에 위의 코드는 주소를 참조 해야 하기 때문에 연산을 할 수 없다.
  • "0x10번지(a)에 저장된 값과, 0x20번지(b)에 저장된 값을 더해서 Ox30번지(c)에 저장해라” 가 가능해야함

  • 메인 메모리에 저장된 데이터를 레지스터로 일단 옮겨다 놓은 다음에, 덧셈을 진행해야 한다.
  • 레지스터와 메모리 사이에 데이터를 전송할 수 있는 명령어가 필요한 상황.
    • 메인 메모리 → 레지스터 명령어 LOAD
    • 레지스터 → 메인 메모리 명령어 STORE

LOAD & STORE 명령어의 디자인

LOAD 디자인

  • 목적지 정보와 데이터가 존재하는 위치(source) 정보를 담을 수 있도록 디자인
  • 목적지에는 메모리 주소가 올 수 있도록 정의하였고, 데이터가 존재하는 위치에는 레지스터 정보를 담을 수 있도록 정의하였다.

  • LOAD | r3 | 0x07
    • 0x07번지에 존재하는 데이터를 레지스터 r3에 저장하라 라는 의미로 해석된다.

STORE 디자인

  • LOAD와 마찬가지로 목적지와 데이터가 존재하는 위치 정보를 담을 수 있도록 디자인
    • 다만 명령어 LOAD와 비교해서 데이터 이동 방향이 정 반대 이다
    • 레지스터 정보가 sorce가 되고, 메모리 주소 정보가 destination이 된다.

  • SOTRE | r2 | 0x08
    • 레지스터 2에 존재하는 데이터를 메인 메모리 Ox08번지에 저장하라. 라는 의미로 해석된다.

LOAD 와 STROE을 이용한 덧셈 과정

  • 위 그림에서 a, b, c는 각각에 주소 번지에 할당 되어 있다.
LOAD r1, 0x10     // 0x10번지에 저장된 데이터를 1로 이동
LOAD r2, 0x20     // 0x20번지에 저장된 데이터를 2로 이동
ADD r3, r1, r2    // rl, r2에 저장된 값을 더해서 3에 결과 저장
STORE r3, 0x30    // r3에 저장된 값을 0x30번지에 저장
  • 피연산자가 레지스터가 되게 끔 하기 위해서 메모리에 저장된 값을 레지스터에 옮겨다 놓은 후 덧셈 연산을 한다.

Direct 모드와 Indirect 모드

  • 16비트로 디자인된 명령어에 많은 정보를 담음으로 문제점이 발생한다. 이를 해결하기 위한 방안.

Direct 모드의 문제점

  • 하나의 명령어에 여러 정보를 담다 보니 데이터 크기 제한이 따른다는 문제점 등장
  • LOAD 명령어를 보면, 메인 메모리의 주소 값을 나타내는 source 부분에서 문제가 생길 수 있다.
    • 이 부분에 여덟 개의 비트 수가 할당되어서, 표현할 수 있는 값의 범위가 0x0000에서 0x00ff까지가 전부 이기 때문이다.
  • 지금까지 우리가 언급한 메모리 접근방법을 가리켜 Direct 모드라 한다.
    • 이 방법을 통해서는 메모리의 모든 영역에 대한 접근이 불가능하다.
      • 할당된 비트 수 안에서 표현 가능한 범위의 메모리 영역만 접근이 가능하기 때문.

Indirect 모드의 이해

  • 첫 번째 상황은 Direct 모드, 두 번째 상황은 Indirect 모드에 해당된다.
  • 앞에서의 LOAD 명령어에서는 레지스터에 저장할 데이터가 존재하는 주소를 직접적으로 표현하였지만, Indirect 모드에선 명령어에서 지정하는 번지에 저장된 값을 주소 값으로 참조하게 된다.

Direct Addressing Mode vs Indirect Addressing Mode

  • 명령어에서 표현하고 있는 0x10번지에서 값을 읽어오는 것이 아니라, 0x10번지에 저장된 값을 주소값으로 참조해서 값을 읽어오고 있다. 이것이 Indirect 모드의 메모리 참조 방식이다.

Direct 모드 연산 구분 방법

  • 앞서 만들어놓은 첫 두 비트의 용도를 Direct모드와 Indirect 모드를 구성

문제 상황

  • 문제는 0x0100번지에 저장된 데이터를 2로 이동시키는 두 번째 명령어에 있다.
    • source를 표현하기 위한 8비트 안에 0x100을 표현할 수 없기 때문이다.
  • 이 문제는 Indirect 모드를 도입하면 가능하다.
    • r0에 MUL 명령어를 사용해 만들어낸 값 16을 저장하고, r2에도 마찬가지로 16을 만들어 저장한다.
    • 마지막은 이 두 레지스터에 저장된 값을 곱해서 그 결과를 r3에 저장한다.
    • 이제 r3에 저장된 주소 값을 참조하여 메모리로부터 값을 읽어다가 레지스터 r2로 저장하면 된다.
      • LOAD | r2 | r3
    • 문제는 r3에 저장된 주소값을 참조하여 메모리로부터 값을 읽어 레지스터 r2에 저장하는 것이다.
      • STORE | r3 | 0x0030
    • 그리고 메인 메모리를 INdirect 모드로 참조해서 값을 읽어오도록 LOAD 명령어를 구성
      • LOAD | r2 | 0x0030
  • ro, 4, 4 // 1에는 데이터 0x0010이 저장되어 있으므로 r0에 연산결과 저장 MUL MUL 12, 4, 4 MUL r3, r0, r2
LOAD rl, 0x0010

MUL r0, 4, 4
MUL r2, 4, 4
MUL r3, r0, r2

STORE r3, 0x0030
LOAD r2, [0x0030]

ADD r3, r1, r2
  • c = a + b에 해당하는 명령어 조합

 

4장 : 이것만은 알고가자

  1. 명령어
    • cpu의 디자인 과정에서 cpu에게 일을 시키기 위한 명령어를 어떻게 구성하는지.
    • 연산자에 대한 정보뿐만 아니라, 피 연산자에 대한 정보도 들어간다.
  2. CPU의 내부 연산과 레지스터
    • 레지스터의 필요성과 덧셈 연산이 이뤄지는 과정을 레지스터와 관련해서 이해
  3. Direct vs Indirect
    • direct모드는 명령어에서 지정하는 위치의 메모리를 참조하는 방식
    • Indirect 모드는 명령어에서 지정하는 위치에 저장된 값을 주소값으로 하여 메모리를 참조하는 방식
    • Indirect 모드는 direct 모드가 지니는 계를 극복하는 데 도움을 준다.

📒 참고 자료

https://www.youtube.com/watch?v=GVX-m3RF-K0&list=PLVsNizTWUw7E2KrfnsyEjTqo-6uKiQoxc

https://www.youtube.com/watch?v=ucJbpFGRYFI&list=PLVsNizTWUw7E2KrfnsyEjTqo-6uKiQoxc&index=7