11 minute read

Chapter 1. A Tour of Computer Systems

이 책은 하드웨어와 시스템 소프트웨어가 함께 작동하여 애플리케이션 프로그램을 실행하는 컴퓨터 시스템의 개념을 이해하고, 이를 통해 프로그래밍 능력을 향상시키려는 개발자를 대상으로 한다.

핵심 내용

  1. 컴퓨터 시스템의 개념
    • 하드웨어와 소프트웨어의 기본 구성 요소는 시간이 지나도 비슷하며, 동일한 기능을 수행한다.
    • 시스템의 작동 원리를 이해하면 프로그램의 정확성과 성능을 향상시킬 수 있다.
  2. 배울 수 있는 실용적 기술
    • 컴퓨터의 숫자 표현 방식으로 인한 오류 방지
    • 현대 프로세서와 메모리 시스템을 활용한 C 코드 최적화
    • 버퍼 오버플로우로 인한 보안 문제 방지
    • 링킹 과정에서 발생하는 오류 인식 및 해결
    • 유닉스 셀, 동적 메모리 할당 패키지, 웹 서버 구현
    • 동시성(Concurrency)의 가능성과 한계 이해
  3. “hello” 프로그램을 통한 학습
    • C언어의 “hello” 프로그램을 중심으로 컴퓨터 시스템의 모든 주요 부분이 어떻게 협력하여 프로그램을 실행하는지 탐구
    • 프로그램 생성, 실행, 종료까지의 전체 라이프사이클을 따라가며 주요 개념과 구성 요소를 소개
  4. 학습 목표
    • 컴퓨터 시스템과 애플리케이션 프로그램 간의 상호작용을 이해하여, 파워 프로그래머로 성장할 수 있도록 돕는 것이 이 책의 목표

Chap1-(1)

1.1 Information is Bits + Context

#include <stdio.h>

int main(){
    printf("hello, world\n");
    return 0;
}

“hello.c” 프로그램의 시작과 저장 방식

  • 소스 파일의 생성과 저장
    • 프로그램은 hello.c라는 텍스트 파일로 저장되며, 프로그래머가 에디터를 사용해 작성한다.
    • 소스 프로그램은 0과 1로 이루어진 비트의 연속이며, 8비트 단위로 묶인 바이트로 표현된다.
    • 각 바이트는 프로그램 내 텍스트 문자의 ASCII 표준 값으로 저장된다.
  • ASCII와 텍스트 파일
    • ASCII는 각 문자를 고유한 바이트 크기의 정수 값으로 표현하는 표준
    • 예를 들어, hello.c의 첫 번쨰 바이트 값 35는 문자 “#”을 나타내며, 두 번째 바이트 값 105는 문자 “i”를 나타냄
    • 개행 문자 “\n”은 정수 값 10으로 표현되며, 텍스트 파일은 ASCII 문자로만 구성된 파일을 의미한다.
  • 이진 파일과 데이터 표현
    • 텍스트 파일 외의 모든 파일은 이진 파일(binary file)로 분류된다.
    • 시스템의 모든 정보(디스크 파일, 메모리에 저장된 프로그램과 데이터, 네트워크를 통한 데이터)는 비트의집합으로 표현된다.

Context(문맥)에 따른 데이터의 의미

  • 동일한 바이트 시퀀스도 문맥에 따라 다른 데이터(정수, 실수, 문자열, 기계 명령어 등)로 해석될 수 있음.

프로그래머의 이해 필요성

  • 컴퓨터의 숫자 표현은 실제 수학적 정수와 실수를 정확히 표현하지 못하는 finite approximations(유한한 근사치)로, 예상치 못한 동작을 일으킬 수 있음. (이러한 개념들은 2장에서…)

요약

  • 컴퓨터 시스템에서 모든 데이터는 비트로 표현되며, 이를 이해하면 프로그래밍 및 데이터 표현의 작동 방식을 더 잘 이해할 수 있다.

1.2 Programs Are Translated by Other Programs into Different Forms

“hello.c” 프로그램의 변환 과정

  • “hello.c” 프로그램은 사람이 읽을 수 있는 고급 언어로 시작되지만, 시스템에서 실행되려면 이를 저급 기계어 명렁어로 번역해야 한다. 이 과정에서 컴파일러 드라이버(ex. gcc)에 의해 수행되며, 4단계로 이루어진다.

4단계 변환 과정

Alt text

linux> gcc -o hello hello.c

전처리 단계(Preprocessing phase)

  • 전처리기(cpp)가 “#” 문자로 시작하는 명령어를 처리하여 원본 C 프로그램을 수정
  • ex. #include 는 stdio.h 헤더 파일의 내용을 프로그램에 삽입
  • 결과는 .i 확장자를 가진 새로운 C 프로그램으로 저장됨

컴파일 단계(Compilation phase)

  • 컴파일러(cc1)가 .i 파일을 어셈블리 언어 프로그램(.s 확장자)로 변환
  • 어셈블리 언어는 기계어 명령어를 텍스트 형태로 표현하며, 플랫폼에 종속적이지 않은 공통 언어로 사용됨
  • ex. main 함수가 기계어 명령어 형태로 정의됨(e.g., movl, call, ret).
main:
    subq    $8, %rsp
    movl    $.LCO, %edi
    call    puts
    movl    $0, %eax
    addq    $8, %rsp
    ret

어셈블리 단계(Assembly phase)

  • 어셈블러(as)가 어셈블리 언어 프로그램(.s)을 기계어 명령어로 변환
  • 결과는 재배치 가능 오브젝트 프로그램(hello.o)이라는 바이너리 파일로 저장
  • 이 파일은 함수 main의 명령어를 17바이트 크기의 바이너리 형식으로 포함함

링킹 단계(Linking phase)

  • 링커(ld)가 표준 C 라이브러리의 미리 컴파일된 오브젝트 파일(printf.o)과 hello.o를 병합
  • 최종 결과는 실행 가능한 오브젝트 파일(hello)로 저장되며, 메모리에 로드되어 실행 가능

요약

  • 컴파일 시스템 : 전처리기, 컴파일러, 어셈블러, 링커로 구성되며, 고급 언어를 실행 가능한 기계어 프로그램으로 변환
  • 기계어 변환 : 소스파일->어셈블리 언어->기계어->실행파일
  • 링커는 여러 오브젝트 파일을 병홥하여 완전한 실행 파일 생성

1.3 It Pays to Understand How Compilation Systems Work

단순한 프로그램(hello.c 등)은 컴파일 시스템에 의존해도 정확하고 효율적인 기계어로 변환되지만, 컴파일 시스템의 작동 원리를 이해하면 아래와 같은 이유로 프로그래밍 역량을 크게 향상시킬 수 있다.

1. 프로그램 성능 최적화

  • 현대 컴파일러는 고성능의 코드를 생성하지만, 효율적인 코드를 작성하려면 컴파일러가 C 문장을 기계어로 번역하는 방식을 이해해야한다.
  • ex. switch 문이 항상 if-else 문보다 효율적인가?
  • ex. 함수 호출의 오버헤드는 얼마나 되는가?
  • ex. while 루프와 for 루프 중 무엇이 더 효율적인가?
  • ex. 배열 인덱스보다 포인터 참조가 더 효율적인가?
  • ex. 지역 변수에 합계를 저장하면 전달된 참조 인자보다 왜 더 빠른가?
  • (3,5,6장에서 해답 제공 예정)

2. 링크 타임 에러 이해

  • 대규모 소프트웨어 시스템 개발 시 링커와 관련된 문제는 매우 난해할 수 있다.
  • ex. 링커가 “참조를 해결할 수 없다”고 보고하는 의미는 무엇인가?
  • ex. static 변수와 전역 변수의 차이는 무엇인가?
  • ex. 서로 다른 C 파일에 같은 이름의 전역 변수를 정의하면 어떤 일이 발생하는가?
  • ex. 정적 라이브러리와 동적 라이브러리의 차이는 무엇인가?
  • ex. 커맨드라인에서 라이브러리 순서가 중요한 이유는 무엇인가?
  • ex. 링커 관련 에러가 실행 시간에만 나타나는 이유는 무엇인가?
  • (7장에서 질문들에 대한 해답 제공 예정)

3. 보안 취약점 방지

  • 버퍼 오버플로우 취약점은 네트워크와 인터넷 서버 보안 문제의 주요 원인이다.
  • 이 취약점은 신뢰할 수 없는 데이터 입력을 제한하지 않은 경우 발생한다.
  • 데이터와 제어 정보가 스택(stack)에 저장되는 방식을 이해하는 것이 보안 프로그래밍의 첫 단계이다.
  • 3장에서 스택의 작동 원리와 버퍼 오버플로우 취약점 탐구
  • 프로그램, 컴파일러, 운영 체제가 공격 위협을 줄이기 위해 사용하는 방법 학습

요약

  • 컴파일 시스템의 작동 원리를 이해하면 프로그램 성능 최적화, 링커 오류 해결, 보안 취약점 방지 등 프로그래밍 전반에 걸쳐 더 나은 결정을 내릴 수 있다.

1.4 Processors Read and Interpret Instructions Stored in Memory

1. “hello” 프로그램의 실행 과정

1. 실행 파일 호출

Alt text

  • hello.c는 컴파일 시스템을 통해 실행 파일 hello로 변환되고 디스크에 저장됨
  • shell은 사용자가 ./hello 를 입력하면 해당 파일을 실행
  • shell은 명령을 읽고, 입력이 빌트인 명령이 아니면 실행 파일로 간주해 로드하고 실행

2. “hello” 실행 과정

  • 1단계: shell이 디스크에서 hello 실행 파일을 로드하여 메인 메모리에 복사

Alt text

  • 2단계: DMA(Direct Memory Access)를 통해 데이터를 디스크에서 메모리로 직접 이동

Alt text

  • 3단계: 프로세서가 hello 의 기계어 명령을 실행하여 문자열 “hello, world\n”을 메모리에서 레지스터로 복사하고, 디스플레이 장치로 전달하여 화면에 출력

2. 컴퓨터 하드웨어 구성

버스(Buses)

  • 컴퓨터의 구성 요소 사이에서 데이터를 전송하는 전기적 통로
  • 워드 크기(word size)는 보통 4바이트(32비트) 또는 8바이트(64비트).

입출력 장치(I/O Devices)

  • 외부 세계와 시스템을 연결
  • 키보드, 마우스, 디스플레이, 디스크 드라이브 등이 포함
  • 장치는 컨트롤러 또는 어댑터를 통해 I/O 버스에 연결

메인 메모리(Main Memory)

  • 프로그램과 데이터를 일시적으로 저장
  • DRAM 칩으로 구성되며, 논리적으로는 고유 주소를 가진 바이트 배열로 표현
  • 변수 크기(ex. short는 2바이트, int와 float는 4바이트, long과 double은 8바이트)는 타입에 따라 다름

프로세서(CPU)

  • 메인 메모리에 저장된 명령어를 해석하고 실행
  • 프로그램 카운터(PC)가 현재 실행할 명령어를 가리킴
  • 명령어 실행:
    • Load : 메모리에서 레지스터로 데이터 복사
    • Store : 레지스터에서 메모리로 데이터 복사
    • Operate : ALU를 사용해 산술/논리 연산 수행
    • Jump : 새로운 명렁어 주소를 PC 업데이트
  • PC는 personal computer라는 의미도 있지만 여기서는 Program Conuter의 약자이다. Program counter는 메인 메모리의 기계어 명령을 가리키고 있다.

명령어 세트 구조(ISA)와 마이크로 아키텍쳐

  • ISA: 기계어 명령어의 작동 방식을 정의
  • 마이크로 아키텍쳐 : 실제로 프로세서를 구현하는 복잡한 메커니즘.

1.5 Cashes Matter

1. 정보 이동

  • 시스템은 많은 시간을, 정보 이동에 소비한다.
  • ex. 프로그램 실행 시 디스크에서 메인 메모리로, 메인 메모리에서 프로세서로 데이터가 복사된다.
  • 이러한 복사 작업은 프로그램의 실제 작업을 느리게 만드는 오버헤드로 작용한다.
  • 따라서, 시스템 설계의 주요 목표 중 하나는 이러한 복사 작업을 최대한 빠르게 수행하는 것이다.

2. 저장 장치의 크기와 속도 관계

  • 물리적 한계로 인해 큰 저장 장치작은 저장 장치보다 느리다.
  • ex. 디스크는 메인 메모리보다 1000배 크지만, 데이터를 읽는 데 10000000배 더 오래 걸린다.
  • 레지스터 파일은 메인 메모리보다 작지만, 데이터를 읽는 속도는 약 100배 빠르다.

3. 프로세서-메모리 속도 차이 문제

  • 반도체 기술 발전으로 프로세서 속도는 더 빨리 증가하지만, 메인 메모리 속도는 그렇지 않다.
  • 이 속도 차이는 시간이 지날수록 커지고 있다.

4. 캐시 메모리의 역할

  • 캐시 메모리(Cache memory)는 프로세서와 메인 메모리 간의 속도 차이를 줄이기 위해 설계된 작고 빠른 임시 저장 장치이다.
  • L1, L2, L3 캐시가 사용되며, 각 레벨의 캐시는 크기와 속도에서 차이가 있다.
  • 캐시는 SRAM(Static Random Access Memory) 기술로 구현된다.

5. 캐싱의 원리 - 지역성(Locality)

  • 캐싱은 프로그램의 자주 사용하는 데이터와 코드에 대한 지역성(locality)를 활용한다.
  • 자주 엑세스할 가능성이 높은 데이터를 캐시에 저장하면 대부분의 메모리 작업을 빠른 캐시를 통해 처리할 수 있다.

6. 프로그래밍 성능 향상

  • 캐시 메모리를 이해하고 활용하는 프로그래머는 프로그램 성능을 최대 10배까지 개선할 수 있다.
  • 자세한 내용은 6장에서 다룬다.

1.6 Storage Devices Form a Hierarchy

Alt text

  • 모든 저장 장치는 메모리 계층 구조로 구성되어 있으며, 위로 갈수록 빠르고 작으며 비싸고, 아래로 갈수록 느리고 크며 비용이 저렴하다.

  • 각 계층은 상위 계층의 캐시 역할을 한다.
    • L0(레지스터 파일) : 최상위 계층, 가장 빠르고 작음
    • L1~L3 캐시 : 각 레벨이 다음 레벨의 캐시 역할을 함
    • L4(메인 메모리) : 캐시보다 크고 느림
    • 디스크 : 메인 메모리의 캐시 역할을 함
    • 네트워크 파일 시스템에서는 로컬 디스크가 다른 시스템의 디스크 데이터를 위한 캐시 역할을 수행
  • 프로그래머 관점
    • 프로그래머는 각 캐시의 특성을 활용하여 프로그램 성능을 최적화할 수 있다.
    • 메모리 계층 전체에 대한 이해를 통해 추가적인 성능 개선이 가능하다.
    • 6장에서 자세히 다룸.

1.7 The Operating System Manages the Hardware

1. 운영 체제의 역할

Alt text

  • 운영 체제(Operating System)는 애플리케이션과 하드웨어 사이의 소프트웨어 계층 역할을 한다.
  • 운영체제의 주요 목적
    • 하드웨어 보호 : 제어 불가 애플리케이션으로부터 하드웨어를 보호
    • 일관된 인터페이스 제공 : 복잡하고 다양한 저수준 하드웨어를 간단하고 일관된 방식으로 제어할 수 있도록 지원

Alt text

  • 운영 체제의 주요 추상화
    • 프로세스 : 프로세서를 위한 추상화
    • 가상 메모리 : 메인 메모리와 디스크를 위한 추상화ㅏ
    • 파일 : I/O 장치를 위한 추상화

2. 프로세스

프로세스 : 실행 중인 프로그램을 나타내는 OS의 추상화

Alt text

  • 각 프로세스는 독립적으로 하드웨어를 사용하는 것처럼 보임(독점적 사용의 환상)
  • OS는 프로세스를 스케줄링하여 단일 CPU에서도 여러 프로세스가 동시에 실행되는 것처럼 보이도록 컨텍스트 스위칭(Context Switching) 수행
  • 컨텍스트(Context) : 프로세스 실행 상태 정보(프로그램 카운터, 레지스터 값, 메모리 내용 등)

3. 스레드

현대 시스템에서 하나의 프로세스는 여러 스레드를 가질 수 있다.

  • 스레드 : 프로세스 내부에서 실행되는 독립적인 실행 단위
  • 스레드 간에는 데이터를 공유하기 쉽고, 멀티프로세서 환경에서 병렬 처리가 가능하다.
  • 스레드는 효율성과 성능 햐상에 중요하다.

4. 가상 메모리(Virtual Memory)

가상 메모리 : 각 프로세스에 독점적인 메인 메모리 사용의 환상을 제공

Alt text

  • 가상 주소 공간 : 프로세스마다 동일한 메모리 레이아웃 제공
  • 구성
    • 코드 및 데이터 영역 : 프로그램 실행 파일에서 초기화
    • 힙(Heap) : 동적 메모리 할당(malloc, free)에 사용
    • 공유 라이브러리 : 중간 영역에 위치
    • 스택(Stack) : 함수 호출 시 확장 및 축소
    • 커널 가상 메모리 : OS 전용, 사용자 접근 불가
  • 하드웨어와 소프트웨어의 협력이 필요하며, 디스크를 메모리의 캐시로 사용

5. 파일

파일 : 단순 바이트의 시퀀스로, 모든 I/O장치를 파일로 모델링

  • 모든 I/O작업(디스크, 키보드, 디스플레이, 네트워크)은 Unix I/O system call로 수행
  • 장점 : 하드웨어 종속되지 않는 일관된 인터페이스 제공

요약

  • 운영 체제는 프로세스, 가상 메모리, 파일을 통해 하드웨어와 소프트웨어를 추상화하고, 성능 및 사용 편의성을 제공한다. 이를 이해하면 애플리케이션의 성능을 향상시키고 시스템 리소스를 효율적으로 활용할 수 있다.

1.8 Systems Communicate with Other Systems Using Networks

Alt text

1. 시스템 간 통신의 개념

  • 현대 시스템은 종종 네트워크를 통해 다른 시스템과 연결된다.
  • 네트워크는 시스템 관점에서 또 하나의 I/O장치로 볼 수 있다.
    • 데이터를 메인 메모리에서 네트워크 어뎁터로 복사하면, 데이터가 네트워크를 통해 다른 시스템으로 전송된다.
    • 반대로, 네트워크를 통해 수신된 데이터를 메인 메모리에 복사할 수 있다.

2. 네트워크의 중요성

  • 인터넷과 같은 글로벌 네트워크의 등장으로 시스템 간 데이터 복사는 컴퓨터의 중요한 용도 중 하나가 되었다.
  • 네트워크를 통한 정보 복사는 이메일, 인스턴트 메시징, 웹, FTP, 텔넷 등 다양한 응용 프로그램의 기반이다.

3. 텔넷을 통한 네트워크 통신 예시

  • Telnet은 원격 시스템에서 프로그램을 실행할 수 있는 네트워크 응용 프로그램이다.
  • ex. 원격으로 hello 프로그램 실행
    1. 사용자가 로컬 텔넷 클라이언트에서 “hello” 입력
    2. 텔넷 클라이언트가 입력된 문자열을 네트워크를 통해 원격 텔넷 서버로 전송
    3. 텔넷 서버가 문자열을 원격 셀 프로그램에 전달
    4. 원격 셀이 hello 프로그램을 실행하고 출력 결과를 텔넷 서버로 전달
    5. 텔넷 서버가 결과 문자열을 로컬 텔넷 클라이언트로 전송, 클라이언트가 이를 로컬 터미널에 출력

4. 클라이언트-서버 통신

  • 위 예시는 클라이언트-서버 모델의 전형적인 네트워크 통신 방식
    • 클라이언트 : 요청을 보냄
    • 서버 : 요청을 처리하고 응답을 보냄
  • 모든 네트워크 응용 프로그램은 이 기본 패턴을 따름

5. 네트워크 응용 프로그램 개발

  • 11장에서 네트워크 응용 프로그램을 구축하는 방법과 이를 활용해 간단한 웹 서버를 개발하는 법을 배우게 된다.

1.9 Important Themes

1. 시스템의 본질

  • 시스템은 단순히 하드웨어만이 아니라, 하드웨어와 소프트웨어의 결합체로, 애플리케이션 실행을 목표로 협력해야한다.
  • 이 장에서는 시스템 설계와 관련된 주요 개념을 소개하며, 이는 책 전체에서 반복적으로 다룬다.

2. 주요 주제

2.1 Amdahl’s Law

  • Amdhl’s 법칙 : 시스템 성능 향상을 위해 특정 부분을 개선하면, 전체 성능 개선 효과는 그 부분의 중요성과 개선된 정도에 따라 달라진다.

  • 성능 향상 비율 ( S = \frac{1}{(1 - \alpha) + \frac{\alpha}{k}} )
  • \alpha : 향상된 부분이 차지하는 시간 비율, k : 성능 향상 배수
  • 결론 : 전체 시스템을 크게 개선하려면 시스템의 대부분을 최적화해야 한다.
  • ex. 특정 부분(60%)을 3배 향상시켜도 전체 성능은 1.67배만 향상된다.

2.2 Cocurrency and Parallelism

  • 동시성(Cocurrency) : 시스템이 여러 작업을 동시에 수행할 수 있는 능력
  • 병렬성(Parallelism) : 동시성을 활용하여 시스템 성능을 향상시키는 방법
  • 병렬성의 레벨
    1. 스레드-레벨 동시성
      • 단일 프로세스에서 여러 스레드 실행
      • 멀티코어 및 하이퍼 스레딩 기술로 병렬 처리 가능
    2. 명령어-레벨 병렬성
      • 현대 프로세서가 여러 명령어를 동시에 실행
      • 파이프라이닝 및 초스칼라 프로세서(한 사이클에 여러 명령 실행)활용
    3. SIMD 병렬성
      • 단일 명령어로 여러 데이터를 병렬 처리
      • 이미지, 비디오, 오디오 처리에 유용

2.3 추상화의 중요성

  • 추상화(Abstraction) : 복잡한 시스템의 세부 사항을 숨기고 간단한 인터페이스 제공
  • 컴퓨터 시스템에서의 주요 추상화
    • 프로세서 : 명령어 집합 아키텍쳐는 하드웨어를 단순화된 모델로 표현
    • 운영체제 : 파일(I/O 장치), 가상메모리(메모리), 프로세스(실행 프로그램), 가상머신(전체 컴퓨터 시스템의 추상화)
  • 결론 : 추상화를 통해 다양한 운영 체제 및 하드웨어에서 동일한 프로그램 실행 가능

1.10 Summary

컴퓨터 시스템은 하드웨어와 시스템 소프트웨어로 구성되어 있으며, 애플리케이션 프로그램을 실행하기 위해 협력한다. 컴퓨터 내부의 정보는 비트(bit)들의 그룹으로 표현되며, 문맥에 따라 다양한 방식으로 해석된다. 프로그램은 다른 프로그램들에 의해 여러 형태로 변환되며, 처음에는 ASCII 텍스트 형태로 작성되었다가 컴파일러와 링커에 의해 이진 실행 파일로 변환된다.

프로세스는 메인 메모리에 저장된 이진 명령어를 읽고 해석한다. 컴퓨터는 대부분의 시간을 메모리, I/O장치, CPU 레지스터 사이에서 데이터를 복사하는 데 소비한다. 시스템의 저장 장치는 계층 구조로 배열되며, 가장 위에는 레지스터, 그 아래로 여러 단계의 하드웨어 캐시 메모리, DRAM 메인 메모리, 디스크 저장 장치가 이어진다. 계층 구조에서 상위에 위치한 저장 장치는 하위 장치보다 빠르고, 비트당 비용이 더 비싸다. 상위 장치는 하위 장치의 캐시 역할을 한다. 프로그래머는 메모리 계층 구조를 이해하고 이를 활용하여 C 프로그램의 성능을 최적화 할 수 있다.

운영 체제 커널은 애플리케이션과 하드웨어 사이의 중개 역할을 하며, 세가지 기본 추상화를 제공한다. 파일 : I/O 장치의 추상화 , 가상 메모리 : 메인 메모리와 디스크의 추상화, 프로세스 : 프로세서, 메인 메모리, I/O 장치의 추상화.

마지막으로, 네트워크는 컴퓨터 시스템 간의 통신 방법을 제공한다. 특정 시스템 관점에서 네트워크는 단순히 또 하나의 I/O 장치로 간주된다.