2024년 2월 4주차

Preface

  • 본 교재는 다양한 컴퓨터와 관련된 다양한 분야들을 프로그래머의 관점에서 통합된 시각으로 바라봅니다.

Assumptions about the Reader’s Background

  • 이 책은 x86-64 기계어로 실행되는 시스템에 주목하고, 특히 Unix 기반의 Linux OS 상에서 동작하는 C 프로그램들이 어떻게 동작하는 지를 고려할 것이다.
  • 알잘딱하게 C 언어로 동작하며, GNU 기반으로 컴파일 된 프로그램들이 예시로 쓰일거니 알아서 잘 신경 쓸 것!

How to Read the Book

  • 예시가 되는 것들의 레이팅은 이 정도 느낌이다. 20240229013520

Book Overview

  • 작자 의도를 그대로 담기위해 직역만 진행한다.
  • Chapter 1 : A Tour of Computer Systems.
    • 컴퓨터 시스템에 대한 여행. 이 장은 간단한 “hello, world” 프로그램의 생명 주기를 추적함으로써 컴퓨터 시스템에서의 주요 아이디어와 테마를 소개합니다.
  • Chapter 2 : Representing and Manipulating Information.
    • 정보의 표현과 조작. 컴퓨터 산술을 다루며, 프로그래머에게 영향을 미치는 부호 없는 숫자와 2의 보수 숫자 표현의 속성을 강조합니다. 숫자가 어떻게 표현되는지, 그리고 주어진 단어 크기에 대해 어떤 범위의 값이 인코딩될 수 있는지를 고려합니다. 부호 있는 숫자와 부호 없는 숫자 사이의 캐스팅의 영향을 고려합니다. 산술 연산의 수학적 속성을 다룹니다. 초보 프로그래머들은 두 양수의 (2의 보수) 합이나 곱이 음수가 될 수 있다는 사실을 알게 되면 종종 놀랍니다. 반면, 2의 보수 산술은 정수 산술의 많은 대수적 속성을 만족시키므로, 컴파일러는 상수에 의한 곱셈을 시프트와 덧셈의 시퀀스로 안전하게 변환할 수 있습니다. C의 비트 레벨 연산을 사용하여 부울 대수의 원리와 응용을 보여줍니다. IEEE 부동 소수점 형식을 어떻게 값과 부동 소수점 연산의 수학적 속성을 표현하는지 측면에서 다룹니다.
    • 신뢰할 수 있는 프로그램을 작성하는 데 컴퓨터 산술에 대한 확실한 이해가 중요합니다. 예를 들어, 프로그래머와 컴파일러는 오버플로우의 가능성 때문에 표현식 (x<y)를 (x-y < 0)로 대체할 수 없습니다. 심지어 (-y < -x)로도 대체할 수 없는데, 이는 2의 보수 표현에서 음수와 양수의 범위가 비대칭적이기 때문입니다. 산술 오버플로우는 프로그래밍 오류와 보안 취약점의 일반적인 원인이지만, 다른 책들은 프로그래머의 관점에서 컴퓨터 산술의 속성을 다루는 경우가 드뭅니다.
  • Chapter 3: Machine-Level Representation of Programs.
    • 프로그램의 기계 수준 표현. 우리는 여러분에게 C 컴파일러에 의해 생성된 x86-64 기계 코드를 읽는 방법을 가르칩니다. 우리는 조건문, 반복문, 스위치 문장과 같은 다양한 제어 구조에 대해 생성된 기본 명령 패턴을 다룹니다. 우리는 스택 할당, 레지스터 사용 규칙, 매개변수 전달을 포함한 절차의 구현을 다룹니다. 우리는 구조체, 유니온, 배열과 같은 다양한 데이터 구조가 어떻게 할당되고 접근되는지를 다룹니다. 우리는 정수 및 부동 소수점 산술을 구현하는 명령들을 다룹니다. 또한, 프로그램의 기계 수준 뷰를 사용하여 버퍼 오버플로우와 같은 일반적인 코드 보안 취약점을 이해하고, 프로그래머, 컴파일러, 운영 체제가 이러한 위협을 줄이기 위해 취할 수 있는 단계들을 이해하는 방법으로 사용합니다. 이 장에서의 개념을 배우는 것은 프로그램이 기계에서 어떻게 표현되는지를 이해함으로써 여러분을 더 나은 프로그래머로 만들어줍니다. 한 가지 확실한 이점은 포인터에 대한 철저하고 구체적인 이해를 개발할 것입니다.
  • Chapter 4 : Processor Architecture.
    • 프로세서 아키텍처. 이 장에서는 기본적인 조합 및 순차 논리 요소를 다루고, 이러한 요소들을 어떻게 결합하여 “Y86-64”라고 불리는 x86-64 명령 세트의 간소화된 하위 집합을 실행하는 데이터패스에 적용할 수 있는지 보여줍니다. 우리는 단일 사이클 데이터패스의 설계로 시작합니다. 이 설계는 개념적으로 매우 간단하지만, 매우 빠르지는 않을 것입니다. 그 다음에, 명령을 처리하는 데 필요한 다양한 단계들이 별도의 단계로 구현된 파이프라이닝을 도입합니다. 주어진 시간에 각 단계는 다른 명령어에 대해 작업할 수 있습니다. 우리의 다섯 단계 프로세서 파이프라인은 훨씬 더 현실적입니다. 프로세서 설계에 대한 제어 논리는 HCL이라고 불리는 간단한 하드웨어 설명 언어를 사용하여 설명됩니다. HCL로 작성된 하드웨어 설계는 교재에 제공된 시뮬레이터로 컴파일되고 연결될 수 있으며, 작동하는 하드웨어로 합성하기에 적합한 Verilog 설명을 생성하는 데 사용될 수 있습니다.
  • Chapter 5: Optimizing Program Performance.
    • 프로그램 성능 최적화. 이 장에서는 컴파일러가 효율적인 기계 코드를 생성할 수 있도록 C 코드를 작성하는 방법을 프로그래머들이 배우게 함으로써 코드 성능을 향상시키기 위한 여러 기술을 소개합니다. 우리는 프로그램이 수행해야 할 작업을 줄이는 변환으로 시작하여, 어떠한 기계에서든 어떠한 프로그램을 작성할 때 표준 관행이 되어야 합니다. 그 다음으로, 생성된 기계 코드 내의 명령어 수준 병렬성의 정도를 향상시켜 현대의 “슈퍼스칼라” 프로세서에서 그 성능을 개선하는 변환으로 진행합니다. 이러한 변환을 동기 부여하기 위해, 우리는 현대의 out-of-order(프로그래머의 코드가 아닌, 자체적 최적화 방식의) 프로세서가 어떻게 작동하는지에 대한 간단한 운영 모델을 소개하고, 프로그램의 그래픽 표현을 통한 중요 경로 측면에서 프로그램의 잠재적 성능을 측정하는 방법을 보여줍니다. 단순한 C 코드의 변환으로 프로그램을 얼마나 빠르게 가속할 수 있는 지에 대해 놀랄 것입니다.
  • Chapter 6 : The Memory Hierarchy.
    • 메모리 계층 구조. 메모리 시스템은 애플리케이션 프로그래머에게 컴퓨터 시스템의 가장 눈에 띄는 부분 중 하나입니다. 이 시점까지, 여러분은 메모리 시스템을 균일한 접근 시간을 가진 선형 배열로서 개념화하여 의존해왔습니다. 실제로, 메모리 시스템은 다양한 용량, 비용, 접근 시간을 가진 저장 장치의 계층 구조입니다. 우리는 RAM과 ROM 메모리의 다양한 유형과 Hard Drive 및 Solid State Drive의 기하학적 구성과 조직을 다룹니다. 이러한 저장 장치들이 어떻게 계층 구조로 배열되는지 설명합니다. 우리는 참조의 지역성에 의해 이러한 계층 구조가 가능해짐을 보여줍니다. 우리는 메모리 시스템을 시간적 지역성의 능선과 공간적 지역성의 경사면을 가진 “메모리 산”으로서 독특한 관점을 소개함으로써 이 아이디어를 구체화합니다. 마지막으로, 우리는 애플리케이션 프로그램의 시간적 및 공간적 지역성을 개선함으로써 그 성능을 향상 시키는 방법을 여러분에게 보여줍니다.
  • Chapter 7 : Linking.
    • 이 장은 정적 및 동적 링킹을 모두 다루며, 재배치 가능한 및 실행 가능한 객체 파일, 심볼 해석, 재배치, 정적 라이브러리, 공유 객체 라이브러리, 위치 독립 코드, 그리고 라이브러리 인터포지셔닝의 아이디어를 포함합니다. 링킹은 대부분의 시스템 텍스트에서 다루지 않지만, 우리는 두 가지 이유로 이를 다룹니다. 첫째, 프로그래머가 마주칠 수 있는 가장 혼란스러운 오류 중 일부는 특히 대규모 소프트웨어 패키지에 대해 링킹 도중의 문제와 관련이 있습니다. 둘째, 링커에 의해 생성된 객체 파일은 로딩, 가상 메모리, 그리고 메모리 매핑과 같은 개념과 연결되어 있습니다.
  • Chapter 8 : Exceptional Control Flow.
    • 예외적인 제어 흐름. 이 프레젠테이션의 이 부분에서, 우리는 일반적인 예외적인 제어 흐름(즉, 정상적인 분기와 절차 호출 외부에서의 제어 흐름 변화)의 개념을 소개함으로써 단일 프로그램 모델을 넘어섭니다. 우리는 하드웨어 예외와 인터럽트의 저수준부터, 동시 프로세스 간의 컨텍스트 스위치, 리눅스 신호 수신에 의한 제어 흐름의 급격한 변화, 스택 규율을 깨는 C의 비지역 점프에 이르기까지, 시스템의 모든 수준에서 존재하는 예외적인 제어 흐름의 예시를 다룹니다.
    • 이 책의 이 부분은 실행 중인 프로그램의 추상화인 프로세스의 기본적인 아이디어를 소개하는 곳입니다. 여러분은 프로세스가 어떻게 작동하며, 어떻게 애플리케이션 프로그램에서 생성되고 조작될 수 있는지 배우게 됩니다. 우리는 애플리케이션 프로그래머가 리눅스 시스템 호출을 통해 여러 프로세스를 사용할 수 있는 방법을 보여줍니다. 이 장을 마칠 때, 여러분은 작업 제어를 가진 간단한 리눅스 쉘을 작성할 수 있게 됩니다. 이것은 또한 동시 프로그램 실행으로 발생하는 비결정적인 행동에 대한 여러분의 첫 번째 소개입니다.
  • Chapter 9 : Virtual Memory.
    • 가상 메모리. 우리가 가상 메모리 시스템을 소개하는 것은 그것이 어떻게 작동하는지와 그 특성에 대한 이해를 제공하려는 것입니다. 우리는 여러분이 다양한 동시 프로세스가 어떻게 각각 동일한 주소 범위를 사용할 수 있으며, 일부 페이지를 공유하면서 다른 페이지는 개별 복사본을 가질 수 있는지 알기를 원합니다. 또한, 가상 메모리를 관리하고 조작하는 데 관련된 문제들도 다룹니다. 특히, 표준 라이브러리의 malloc과 free 작업과 같은 저장소 할당자의 작동을 다룹니다. 이 자료를 다루는 것은 여러 목적을 제공합니다. 가상 메모리 공간이 프로그램이 다양한 저장 단위로 나눌 수 있는 바이트 배열에 불과하다는 개념을 강화합니다. 그것은 저장소 누수 및 유효하지 않은 포인터 참조와 같은 메모리 참조 오류를 포함하는 프로그램의 영향을 이해하는 데 도움이 됩니다. 마지막으로, 많은 애플리케이션 프로그래머들은 애플리케이션의 요구와 특성에 최적화된 자체 저장소 할당자를 작성합니다. 이 장은 다른 어떤 것보다도 하드웨어와 소프트웨어의 컴퓨터 시스템 측면을 통합적으로 다루는 것의 이점을 보여줍니다. 전통적인 컴퓨터 아키텍처와 운영 시스템 텍스트는 가상 메모리 이야기의 일부만을 제시합니다.
  • Chapter 10 : System-Level I/O.
    • 시스템 수준 I/O에 대해 다룹니다. 파일과 디스크립터 같은 유닉스 I/O의 기본 개념을 소개합니다. 파일이 어떻게 공유되는지, I/O 리디렉션이 어떻게 작동하는지, 파일 메타데이터에 어떻게 접근하는지 설명합니다. 또한, 라이브러리 함수가 입력 데이터의 일부만을 읽는, ‘단편 카운트’로 알려진 궁금한 동작을 올바르게 처리하는 견고한 버퍼링 I/O 패키지를 개발합니다. C 표준 I/O 라이브러리와 리눅스 I/O와의 관계, 특히 네트워크 프로그래밍에 적합하지 않은 표준 I/O의 한계에 초점을 맞추어 설명합니다. 일반적으로, 이 장에서 다루는 주제들은 다음 두 장의 네트워크 및 동시 프로그래밍을 위한 빌딩 블록입니다.
  • Chapter 11 : Network Programming.
    • 네트워크 프로그래밍. 네트워크는 프로그래밍하기에 흥미로운 I/O 장치로, 텍스트에서 앞서 공부한 많은 개념들을 결합합니다. 예를 들면, 프로세스, 시그널, 바이트 순서, 메모리 매핑, 동적 저장소 할당 등이 있습니다. 네트워크 프로그램은 또한 다음 장의 주제인 동시성에 대한 매력적인 맥락을 제공합니다. 이 장은 네트워크 프로그래밍을 통해 간단한 웹 서버를 작성할 수 있는 지점까지 이르게 하는 얕은 단면입니다. 모든 네트워크 애플리케이션의 기반이 되는 클라이언트-서버 모델을 다룹니다. 인터넷에 대한 프로그래머의 관점을 제시하고 소켓 인터페이스를 사용하여 인터넷 클라이언트와 서버를 작성하는 방법을 보여줍니다. 마지막으로, HTTP를 소개하고 간단한 반복적 웹 서버를 개발합니다.
  • Chapter 12 : Concurrent Programming.
    • 동시 프로그래밍. 이 장에서는 실행 중인 동기 부여 예시로 인터넷 서버 설계를 사용하여 동시 프로그래밍을 소개합니다. 동시 프로그램 작성을 위한 세 가지 기본 메커니즘—프로세스, I/O 멀티플렉싱, 그리고 스레드를 비교 분석하고, 이들을 사용하여 동시 인터넷 서버를 구축하는 방법을 보여줍니다. P 및 V 세마포어 연산을 사용한 동기화의 기본 원리, 스레드 안전성 및 재진입 가능성, 경쟁 상태, 그리고 데드락에 대해 다룹니다. 대부분의 서버 애플리케이션에서 동시 코드 작성은 필수적입니다. 또한, 애플리케이션 프로그램에서 병렬성을 표현하기 위해 스레드 수준 프로그래밍의 사용을 설명하며, 이를 통해 멀티 코어 프로세서에서 더 빠른 실행을 가능하게 합니다. 단일 계산 문제에 모든 코어를 작동시키는 것은 동시 스레드의 정확성과 높은 성능을 달성하기 위한 신중한 조정을 요구합니다.

New to This Edition

  • 1판이 발매된 2003년 이후 엄청난 발전이 이루어졌고 2011년 2판에서는 이에 맞춰 상당한 변화가 존재했다. 그러한 변화 내용을 추가로 기재해둔다.
  • Chapter 1 : A Tour of Computer Systems
    • Amdahl 의 법칙 내용을 챕터 5에서 챕터 1로 옮김
  • Chapter 2 : Representing and Manipulating Information
    • 상당한 난이도가 있는 영역이고 피드백을 통해 수학적인 제시 방식으로 깊이 파고드는 지점을 명확히 만들려고 노력하였다.
  • Chapter 3 : Machine-Level Representation of Programs
    • IA32와 x86-64 기반에서 순수한 x86-64 방식으로 수정됨. 보다 현대적인 GCC의 버전에 맞춰 코드 스타일이 업데이트 됨.
  • Chapter 4 : Processor Architecture
    • 이전의 프로세서 디자인이 32비트 기반이었으나, 64비트로 변경됨.
  • Chapter 5 : Optimizing Program Performance
    • 최신의 x86-64 프로세서에서 성능적 잠재력을 반영하는 업데이트 반영한 내용으로 구성하였음.
  • Chapter 6 : The Memory Hierarchy
    • 최신 기술을 반영하여 내용을 추가 갱신함
  • Chapter 7 : Linking
    • x86-64를 위하여 해당 챕터의 내용은 전체 다시 쓰여졌다.
  • Chapter 8 : Exceptional Control Flow
    • 시그널 핸들러의 엄격한 처리를 추가 하고, 비동기-시그널-안전 함수, 구체적인 가이드 라인등을 추가하였다.
  • Chapter 9 : Virtual Memory
    • 변동사항 거의 없음
  • Chapter 10 : System-Level I/O
    • 파일 계층구조에 관한 내용을 담은 섹션을 파일 섹션에 추가함
  • Chapter 11 : Network Programming
    • 기존의 오래되고 재 진입이 불가능한 gethostbyname, gethostbyaddr 함수를 현대적 메서드인 getaddrinfo와 getnameinfo 함수로 대체하여 프로토콜과 무방하며 스레드-safe가 보장되도록 수정되었습니다.
  • Chapter 12 : Concurrent Programming
    • 멀티코어 기기에 더 빠른 성능으로 프로그램을 구동하기 위해 쓰레드 수준에서의 병렬성의 사용 범위를 증가시켰다.

Origins of the Book

생략

For Instructors: Course Based on the Book

생략

For Instructors: Classroom-Tested Laboratory Exercise

생략

Acknowledgments for Third Edition

생략

Acknowledgments from the Second Edition

생략

Acknowledgments from the First Edition

생략


Chapter 1 A Tour of Computer Systems

1.1 Information Is Bits + Context

  • “hello” 프로그램은 텍스트 파일 hello.c로 저장되는 소스 프로그램으로 시작합니다. 이 파일은 0과 1의 비트로 구성된 바이트의 연속이며, 각 바이트는 ASCII 표준을 사용해 텍스트 문자를 나타냅니다. 모든 정보는 비트로 표현되며, 다양한 데이터는 보는 맥락에 따라 구분됩니다. 기계의 숫자 표현을 이해하는 것이 중요하며, 이는 유한 근사치로서 예상치 못한 방식으로 동작할 수 있습니다. 이 기본적인 아이디어는 추후 자세히 다룹니다.

1.2 Programs Are Translated by Other Programs into Different Forms

  • executable object file
  • 컴파일 절차를 정리한 내용 20240301081234
    • 전처리 단계
    • 컴파일러 단계
    • 어셈블러 단계
    • 링커 단계

1.3 It Pays to Understand How Compilation System Work

  • Compilation System 을 개발자가 이해해야 하는 이유
    • Optimizing program performance : 개발자로써 기본이 되는 컴파일 체계를 이해하는 것은 보다 디테일한 최적화 방법을 이해할 수 있게 만들어준다. 챕터 3, 5, 6 이 이러한 부분을 도와줄 것이다.
    • Undedrstanding link-time errors : 개발자가 C 프로그램을 만들 때 가장 복잡한 부분의 에러라고 할만한 것이 바로 링커의 작업 과정에서 참조 부분에서 발생한다고 볼 수 있다. 왜 링커와 연관된 문제는 런타임 때까지 발견되지 않을까? 이러한 질문에 대한 답으로 챕터 7을 보게 될 것이다.
    • Avoiding security holes : 버퍼 오버플로우 취약점과 같은 것들이 보안체계의 심각성으로 많이 회자 되었고, 이러한 점은 프로그래머들의 빈약한 데이터의 양에 대한 이해도, 형태에 대한 이해도 때문이었다. 따라서 챕터 3를 통해 기계어를 배우면서 오버플로우 취약성에 대해 배우고 프로그래머, 컴파일러, OS 단에서 이러한 공격을 막기 위한 방법들을 배워 볼 것이다.

1.4 Processors REad And Interpret Instructions Stored in Memory

  • Shell 프로그램은 명령어 해석 프로그램으로 명령어를 입력하면, 이를 수행한다. 이때 입력된 명령어가 built-in 되지 않은 단어일 때, shell 은 해당 이름이 executable file이라고 판단하게 되고, hello 프로그램을 수행하게 된 뒤 종료된다.

20240301082950

1.4.1 Hardware Organization of a System

hello 프로그램이 어떻게 동작하는지를 온전히 이해하기 위해서는 전형적인 PC의 하드웨어의 구조를 이해할 필요가 있다.

  • Buses : word 라고 불리는 고정된 사이즈의 바이트를 보내기 위한 길의 역할을 한다. 32, 64비트가 보통(4, 8바이트)의 버스 사이즈.
  • I/O Devices : 입출력 장치, 컨트롤러나 어뎁터를 기준으로 입출력 버스(I/O Bus)와 연결되어 있다. 여기서 컨트롤러란 기본적으로 기기나 메인보드에 부착된 칩셋이며, 어뎁터는 슬롯 형태로 구현되어 있는 것이다. 결국 핵심은 데이터의 입출력에 연관된다는 점이다.
  • Main Memory : 프로그램의 일시적인 데이터 저장소 역할을 하며, 보통 물리적으로 DRAM(Dynamic Random Access Memory)으로 구성되어 있다. 챕터 6에서 이에 대해 어떻게 동작하며, 어떻게 메인 메모리를 구성하고 있는지를 배울 것이다.
  • Processor : 메인 메모리에 적재된 데이터들, 명령을 수행하는 역할을 한다. 해당 연산 장치의 코어에는 word-size의 저장 장치(register)를 가지고 있고, 이를 Program Counter라고 부르는데, 이 저장장치에 명령의 시작 지점을 담고, 해당 지점에서 명령이나 데이터를 로드- 실행한 뒤 다음 포인트로 갱신되는 행위를 무한 반복한다. CPU 에는 몇 가지 매우 심플한 작업을 갖고 있으며, 이는 메인 메모리, 레지스터 파일, ALU와 연관된다.
    • Load : 메인 메모리에서 바이트나, 워드를 레지스터로 불러온다. 레지스터의 이전 데이터는 덮어 씌워진다.
    • Store : 바이트나 워드를 메인 메모리 안의 특정 위치로 레지스터에 복사한다.
    • Operate : 두 레지스터의 컨텐츠를 ALU로 복사하고, 두개의 워드 상에서 연산을 수행하고, 그 결과를 레지스터에 저장한다. 레지스터 상의 기존 정보는 덮어 씌워진다.
    • Jump : 명령어 자체에서 워드를 추출하고, 프로그램 카운터에 그 워드를 복사한다. 기존 PC 의 값은 덮어 씌워지게 된다.
    실제 모던 프로세서는 이보다 훨씬 복잡한 체계를 가지고 있으며, 각 명령어 아키텍쳐와 프로세서의 실제 구현을 설명하는 내용 등을 통해 마이크로아키텍처를 구분하는 것도 가능하다. 챕터 3에서는 기계의 명령어 셋 아키텍쳐가 제공하는 추상화를 학습하며, 실제 구현된 내용이 어떤지를 챕터 4에서, 챕터 5에서는 어떻게 현대적 프로세서들의 동작 방식을 이해함으로써 기계어 프로그램의 성능을 예측, 최적화 하는지를 배울 것이다.

1.4.2 Running the hello Program

  • hello 프로그램이 실행되는 것은 다음의 순서임을 이미지를 통해 알 수 있다.
  1. 키보드 입력의 처리 과정 20240301 090028
  2. 실행파일의 로드 / 실제 명령의 수행 절차 20240301 090039

1.5 Caches Matter

20240301 090046

  • 실제 시스템은 명령 수행을 위해 대부분의 작업이 특정 저장 장치에 위치에서 다른 곳으로 전달하는 것이라는 사실을 알 수 있다.
  • 그렇기에 시스템에 디자이너는 가능한 복사의 작업을 가능한 빠르게 처리할 수 있도록 만드는 것이 핵심이라고 할 수 있다.
  • 이때 물리 법칙으로 저장 공간이 크면 클수록 느리며, 반대로 장치 크기가 작을 수록 빠르다. 하지만 반대로 이를 만들기엔 비싸다는 점이 있다.
  • 이러한 상황에서 프로세서-메모리 사이의 격차를 줄이고자 시스템 디자이너는 캐쉬 메모리(cache memories)라는 장치를 통해 격차를 줄이려고 시도한다. 이는 메인메모리에서 직접 가져오는 것보다 L1, L2에서 접근하는 것이 훨씬 낫기 때문이며, 이러한 캐쉬 메모리는 SRAM(Static Random Access Memory)를 통해 구현되어 있다.
  • 또한 여기서 ‘지역성’이라는 특징을 활용함으로써 가장 효과적인 메모리 작업을 수행할 수 있도록 만드는 것이다.
  • 이 교제에서 가장 중요한 핵심 중 하나로써, 캐쉬 메모리를 이해하고, 이를 통해 프로그램의 성능을 개선시키는데 이용 가능하도록 하는 것이며 이에 대해서는 챕터 6에서 보다 자세히 배우게 된다.

1.6 Storage Devices Form a Hierarchy

20240301 092050

1.7 The Operating System Manages the Hardware

  • 어떤 프로그램을 실행할 때는, 어떤 프로그램이 결국 입출력장치와 직접 연결될 수도 있지만, 실제로는 운영체제라는 중간 레이어를 거쳐서 하드웨어에 대한 조작을 성사 시킨다. 20240301 152606
  • 운영체제는 주요한 두 가지 목적을 가지고 있는데 이는 다음과 같다.
    • 어플리케이션들의 실행에 의해 하드웨어가 잘못 남용되는 것을 막기 위함
    • 복잡하고, 다양하게 저수준에서 다른 디바이스들을 통합된 단일한 메커니즘으로 효과적으로 제어하는 방법을 제공하기 위함
  • 위의 내용을 표현한 것이 Fig. 1.11 의 이미지 이다.

1.7.1 Processes

  • 프로세서는 마치 하나의 프로그램의 명령을 수행하고, 그 다음 다음 명령을 수행하는 것처럼 보이게 되어있다. 또한 프로그램의 코드, 데이터 역시 시스템의 메모리에 올라간 유일한 객체처럼 보인다. 이러한 점은 프로세스의 개념에 의해 제공되는 일종의 환상이며 컴퓨터 과학 분야에서 가장 성공적이고 중요한 개념이다.
  • 전통적인 시스템은 한 번에 하나의 프로그램만을 실행할 수 있었으나 멀티코어 프로세서는 여러 프로그램을 동시에 실행할 수 있게 되었다. CPU는 프로세서가 전환되는 것을 통해 동시에 실행되는 것처럼 보이게 만들었는데, 운영체제는 이를 Context Switching 이라는 메커니즘으로 교차를 수행 했고, 이를통해 실제로는 프로세스 라는 운영체제의 실행 중인 프로그램의 단위가 프로그램 자체로 보기에는 시스템에서 단독으로 실행되는 듯 보이지만, 전체로 보기엔 여러 프로그램이 동시에 실행되는 것처럼 만들어내는 것이다.
  • 운영체제는 프로세스가 수행되기 위해 필요시 되는 모든 정보를 context라는 이름의 정보의 덩어리를 모두 추적하고 관리한다. 이 정보 중에는 PC(program counter) 의 정보도 담겨져 있으며, 이를 통해 다른 프로세스로 넘어가도 기존에 하던 작업의 위치를 기억하고 다른 프로세스로 작업이 변경될 수 있다. 이를 context switch라고 부른다. 20240301 155546
  • kernel은 메모리 상에 항상 상주하는 운영체제 코드의 부분이며, 어플리케이션 프로그램이 실행되고, 요청하는 기능들을 대신 수행해주는 역할을 한다.
  • 프로세스의 추상화를 구현하기 위해선 저수준의 하드웨어와 운영체제 사이의 긴밀한 동시 작업이 필요하며, 이러한 내용을 우리는 챕터 8에서 상세히 다룰 것이다.

1.7.2 Threads

  • threads: 과거 프로세스들은 하나의 단일한 제어 흐름을 가지는 게 당연한 것이었다. 하지만 최신의 경우 프로세스는 실제론 여러 쓰레드라고 불리는 제어의 흐름으로 구성되어 있다. 쓰레드 끼리는 동일한 코드, 전역데이터 등을 공유한다.
  • 쓰레드는 멀티 프로세스 구조 보단 기본적으로 훨신 효율적이기 때문에, 그리고 멀티 프로세서 사이보단 쓰레드 사이에 데이터 전달이 훨씬 용이하다는 점, 네트워크 서버 상에서 동시성이 필수가 되어가는 등의 근거 하에 쓰레드라는 구조는 매우 중요한 프로그래밍 모델이 되었다.

1.7.3 Virtual Memory

  • Virtual Memory : 이 기술은 일종의 추상화로 프로세스에게 메인 메모리를 해당 프로세스가 온전히 독점적으로 쓰고 있다는 환상을 제공하기 위한 기술이다. 20240301 161041
  • 가상 주소 공간에 대해서는 이후 배우겠지만 개념만 정리하면 다음과 같다.
    • Program Code and Data : 모든 프로세스에 대해 코드는 동일한 고정 주소에서 시작하며, 이어서 전역 C 변수에 해당하는 데이터 위치가 있다. 코드와 데이터 영역은 실행 가능한 오브젝트 파일의 내용에서 직접 초기화된다. 7장의 링킹과 로딩을 공부할 때 이 주소 공간의 이 부분에 대해 더 배울 것이다.
    • Heap : 코드와 데이터 영역 바로 다음에는 런타임 힙이 있다. 프로세스가 실행을 시작하면 크기가 고정되는 코드와 데이터 영역과 달리, 힙은 malloc과 free와 같은 C 표준 라이브러리 루틴 호출의 결과로 런타임에 동적으로 확장 및 축소됩니다. 9장의 가상 메모리 관리에 대해 배울 때 힙에 대해 자세히 공부할 것이다.
    • Shared libraries : 주소 공간의 중간 근처에는 C 표준 라이브러리와 수학 라이브러리와 같은 공유 라이브러리의 코드와 데이터가 들어 있는 영역이 있다. 공유 라이브러리의 개념은 강력하지만 다소 어려운 개념이다. 7장의 동적 링킹을 공부할 때 그 작동 방식을 배우게 된다.
    • Stack : 사용자의 가상 주소 공간의 맨 위에는 컴파일러가 함수 호출을 구현하는 데 사용하는 사용자 스택이 있다. 힙과 마찬가지로, 사용자 스택은 프로그램 실행 중에 동적으로 확장 및 축소된다. 특히, 함수를 호출할 때마다 스택이 커진다. 함수에서 반환할 때마다 축소된다. 컴파일러가 스택을 어떻게 사용하는지는 3장에서 배울 것이다.
    • Kernel virtual memory : 주소 공간의 최상위 영역은 커널을 위해 예약되어 있다. 응용 프로그램은 이 영역의 내용을 읽거나 쓰거나 커널 코드에 정의된 함수를 직접 호출할 수 없고 이러한 작업을 수행하려면 커널을 호출해야 한.
    • 가상 메모리에 대해 현대의 시스템에서 왜 이렇게 중요하고, 어떻게 동작하는지를 챕터 9에서 설명해줄 것이다.

1.7.4 Files

  • 파일은 일련의 바이트들의 흐름 그 이상 그 이하가 아니다. 단, 모든 입출력 장치나, 디스크, 디스플레이와 네트워크들도 파일로 모델링 된다.
  • 이 심플 & 우아한 파일의 개념은 시스템 상에 포함된 다양한 입출력 장치에게 단일한 시점으로 어플리케이션에게 제공해주는 핵심 개념이다. Unix I/O 에 대해서는 챕터 10에서 더 자세히 다룰 것이다.

1.8 Systems Communicate with Other Systems Using Networks

20240302 112414

  • 지금까지 기본적으로 우리는 하드웨어와 소프트웨어 독립적인 디바이스 하나를 중심으로 생각해왔다. 하지만 네트워크를 통해 연결되는 것으로, 네트워크 입출력 역시 일종의 파일로, 입출력장치로 바라볼 수 있기 때문에, 1.14 이미지를 보듯 사용이 가능하다.
  • 또한 1.15 를 보듯 네트워크를 통해 telnet client와 telnet server 사이의 연결을 통해 원격으로 hello 를 실행시킬 수 있다.
  • 클라이언트와 서버 사이의 데이터를 교화하는 형식을 전형적인 네트워크 어플리케이션 형식이라고 부르며, 자세한 내용은 챕터 11에서 보게 될 것 이다.

1.9 Important Themes

  • 챕터 1을 걸쳐 핵심은 무엇인가, 그것은 시스템이라는 것은 단순히 하드웨어로만 볼 수 없다는 점이다.
  • 앞으로의 챕터에서는 이렇게 이야기 한 내용들의 디테일을 채워나가면서 우리가 보다 빠르고, 신뢰할만하고, 안전한 프로그램을 만드는데 도움을 줄 것이다.
  • 본 챕터의 마무리하고자, 핵심적인 몇 가지 주요한 개념들에 대해서 한번 더 살펴본다.

1.9.1 Amdahl’s Law

  • Gene Amdahl 시스템의 한 부분의 성능을 향상에 대해 효과성에 관한 인사이트를 제공해주었다. 그것이 해당 법칙이다.
  • 핵심 아이디어는 시스템의 한 부분을 가속화 할 때, 전체 시스템 성능에 대한 영향은 그 부분이 얼마나 중요했는지, 얼마나 많이 속도가 빨라졌는 지에 모두 달려 있단 것이다.
  • 이를 정리한 공식은 다음과 같이 볼 수 있다.
    • a : 시스템의 일부분이 T_old 에서 일정 비율 a 를 차지하며
    • k : 해당 부분의 성능을 향상시킨 배율
    • 결과적으로 T_new 의 값 = T_old[(1-a) + a/k]라고 볼 수 있다.
    • S : 이를 통해 속도 향상 값 = 1/[(1-a) + a/k]가 된다.
  • 이렇게 나온 공식을 활용하여 전체 성능향상 비율을 측정해보면 암달의 법칙이 제공해주는 통찰력을 보게 된다. 각 부분의 향상은 결론적으로 전체 시스템에 미치는 영향이 생각보다 적게 나타나며, 따라서 전체 시스템의 속도 개선을 진행하고자 한다면 전체 시스템에 상당한 속도 개선이 있어야만 하고, 부분적 개선 만으로는 성능 향상이 제한이 있음을 보여준다.

1.9.2 Concurrency and Parallelism

  • 디지털 컴퓨터의 역사 속에서 두 가지 지속적인 동인 요소가 존재 했다. 그것 중 하나가 더 많이 일하는 것, 또 다른 하나는 더 빨리 일하는 것이다.
    • concurrency(동시성) : 다중의 일을 동시에 처리하는 개념
    • parallelism(병령성) : 동시성의 개념을 사용함으로써 시스템을 더 빠르게 만드는 것
  • 여기서 시스템의 계층 안에서 병렬성은 다양한 형태로 구현된다.
  • Thread-Level Concurrency
    • 프로세스 추상화를 통해 다수의 프로그램을 동시에 실행시키는 형태를 만들 수 있었다.
    • 동시에 쓰레드의 개념이 발생함에 따라 하나의 프로세스 내에서 복수의 제어 흐름을 가질 수 있게 발견되었다.
    • 과거 uniprocessor 구조에서 멀티 코어 구조로 바뀌어 가면서 복수의 다양한 일이 가능해져 왔다. 이러한 구조의 받침에는 메모리 계층을 비롯한 다양한 것들이 포함되어 있으며, 향후에는 더 많은 코어가 단일 칩 안에 들어갈 것으로 예상 된다.
    • 하이퍼 쓰레딩 : 동시적 다중 쓰레딩(simultaneous multi-threading)이라고도 불리는 기술은 단일 CPU가 복수의 제어 흐름을 수행하는것을가능케 만든 기술이다. 프로그램 카운터, 레지스터 파일과 같은 CPU 하드웨어의 사본을 가지고, 전통적인 프로세서가 다른 스레드로 전환하는데 약 20000클록 사이클이 필요한 반면, 하이퍼 스레딩 프로세서는 사이클 별로 실행할 스레드를 결정할 수 있고, 이러한 점은 CPU가 처리 자원을 더 효과적인 활용이 가능한 환경을 만들었다.
    • 예시 4코어 8스레드 형태가 되면서, 하나의 코어 내에서도 두개의 스레드를 스위칭해가면서 작업이 가능함 -> 스레드의 작업을 기다리거나 하는 것이 아닌 바꿔가며 효과적으로 처리가 가능함
  • Instruction-Level Parallelism
    • 현대 프로세서들은 다중 명령어를 한번에 수행이 가능하며, 이러한 특성을 instruction-level parallelism 이라 부른다.
    • 챕터 4에서, 파이프라이닝이라는 것을 탐색할 것인데, 이는 다른 스텝이 수행될 필요가 있거나, 프로세서 하드웨어가 이러한 스텝으로 수행되는 일련의 스테이지들로 구성되어진다.
    • 사이클 당 명령어가 1회보다 이상으로 수행하는 비율을 가진 프로세서를 superscalar processor 라고 부른다. 챕터 5에서는 이러한 프로세서들의 고수준 모델에 대해 설명을 할 것이다. 우리는 여기서 프로그래머들이 이 모델을 그들의 프로그램의 성능을 이해하기 위해 사용하는 것을 배울 수 있을 것이다.
  • Single-Instruction, Multiple-Data(SIMD) Parallelism
    • 가장 낮은 단계의 수준에서 많은 현대적 프로세서들은 특수한 하드웨어를 내부에 내장한다. 해당 하드웨어는 단일 명령에 대해 병렬로 수행되는 다중 작업을 가능케 한다. 이를 single-instruction , multiple-data(SIMD) 라고 부른다.
    • 이러한 SIMD 명령어는 대부분 이미지, 소리, 비디오 데이터의 처리를 더욱 빠르게 하는 기능을 제공한다.

1.9.3 The Importance of Abstraction in Computer Systems

20240302 195419

  • 대부분의 언어와 형태에서는 다양한 추상화를 제공한다.
  • 컴퓨터 시스템에서 볼 수 있는 여러 추상화에 대해 이미 소개되었습니다. 그림 1.18에서 언급된 것처럼, 프로세서 측면에서 명령 세트 아키텍처는 실제 프로세서 하드웨어의 추상화를 제공합니다. 이 추상화를 통해, 머신 코드 프로그램은 한 번에 하나의 명령을 수행하는 프로세서에서 실행되는 것처럼 동작합니다. 그러나 기저 하드웨어는 훨씬 더 복잡하게, 여러 명령을 병렬로 실행하지만, 항상 단순하고 순차적인 모델과 일관된 방식으로 실행됩니다. 같은 실행 모델을 유지함으로써, 다른 프로세서 구현은 같은 머신 코드를 실행하면서도 비용과 성능의 범위를 제공할 수 있습니다.
  • 운영 시스템 측면에서는 세 가지 추상화를 소개했습니다: I/O 장치의 추상화로서의 파일, 프로그램 메모리의 추상화로서의 가상 메모리, 실행 중인 프로그램의 추상화로서의 프로세스입니다. 이러한 추상화에 새로운 것을 추가합니다: 전체 컴퓨터의 추상화를 제공하는 가상 머신으로, 운영 시스템, 프로세서, 프로그램을 포함합니다. 이 책의 후속 섹션에서 이러한 추상화에 대해 다시 언급할 것입니다.

1.10 Summary