CS:APP 1장 정리 (~ Section 1.6)

Computer Systems: A Programmer's Perspective 3rd edition 스터디 내용을 정리합니다.

1. 컴퓨터 시스템 소개

헬로 월드에 대한 소개로 시작한다.

// hello.c
#include <stdio.h>

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

1.1 정보는 비트들과 컨텍스트의 조합이다

  • 위와 같은 우리가 만든 소스 프로그램(혹은 소스 파일)은 0과 1로 이루어진 비트 8개가 모인 바이트의 모음으로 이루어진다.
  • 데이터 객체를 구별하는 유일한 건 그걸 바라보는 컨텍스트 뿐이다. 동일한 바이트 시퀀스는 정수, 문자열, 기계 명령어를 나타낼 수도 있다.

1.2 프로그램은 다른 프로그램들에 의해 다른 형태로 해석된다

  • 고수준의 C 프로그램이 실행되기 위해선 다른 프로그램들에 의해 저수준의 기계어 명령으로 번역되어야 한다.
  • 이 명령들은 실행 가능한 객체 프로그램으로 불리는 바이너리 디스크 파일로 저장된다.
  • 리눅스의 gcc 컴파일러 드라이버를 예시로: 프리프로세서, 컴파일러, 어셈블러, 링커 라고 알려진 4단계로 프로그램이 해석된다. 이 4단계를 컴파일레이션 시스템이라고 부른다.

    1. 프리프로세싱: # 문자로 시작하는 C 프로그램을 수정한다. stdio.h 시스템 헤더 파일을 프로그램 텍스트로 직접 삽입한다. 이 결과물은 또다른 C 프로그램이 되고, i suffix가 추가된다(hello.i).
    2. 컴파일레이션: 어셈블리 언어 프로그램이 포함된 텍스트 파일로 변환한다(hello.s). 어셈블리 언어는 유용한데, 각기 다른 고수준의 언어를 위한 각기 다른 컴파일러에서 같은 공통 언어 출력을 제공하기 때문이다.
    3. 어셈블리: hello.s 를 기계 언어 명령들로 변환하고, 그것들을 재배치 가능한 오브젝트 프로그램으로 알려진 형태로 포장한다. 아마 이 파일(hello.o)을 텍스트 에디터로 열어보면 알아볼 수 없는 형태로 보일 것이다.
    4. 링킹: 우리 프로그램은 printf 함수를 호출하고 있다. 이 함수는 스탠다드 C 라이브러리의 일부인데, 이 함수는 prinft.o 라는 미리 컴파일된 객체 파일로 존재한다. 링커는 이 파일과 3번 과정에서 해석된 파일을 연결한다. 이 결과는 hello 파일이고, 위에서 말한 실행 가능한 객체 파일이다(simply executable). 메모리에 로드되고, 시스템에 의해 실행될 준비를 마친 상태이다.'

1.3 컴파일레이션 시스템을 이해해야 하는 이유

  • 프로그램 성능 최적화: 모던 컴파일러는 세련된 툴들을 제공하기에 프로그래머들은 효율적인 코드를 작성하기 위해 내부 동작을 잘 몰라도 된다. 하지만 우리 C 프로그램에서 좋은 선택들을 하기 위해서, 머신 레벨의 코드들을 이해하고 어떻게 컴파일러가 C 문들을 어떻게 기계어로 번역하는지 알아야 한다.
  • 링크 타임 에러에 대한 이해: 대부분의 에러는 링커의 동작에서 발생한다.
  • 보안 취약점 막기

1.4 프로세서는 메모리에 저장된 명령어들을 읽고 해석한다

linux> ./hello
hello, world
linux>
  • 유닉스 시스템에서 실행 가능한 파일을 실행하기 위해 이라는 이름을 가진 어플리케이션 프로그램에게 우리 프로그램의 이름을 알려줘야 한다.
  • 쉘은 커맨드-라인(명령줄) 인터프리터이다. 프롬프트를 출력하고, 프롬프트 입력을 대기하고 커맨드를 실행한다. 커맨드 라인의 첫번째 단어가 쉘 빌트인 커맨드와 충돌하지 않는다면 실행가능한 파일로 간주하고 불러온 뒤 실행한다.

1.4.1. 시스템의 하드웨어 조직

hello 프로그램을 실행했을 때 무슨 일이 일어나는지 알기 위해선, 하드웨어 조직에 대해 먼저 이해해야할 필요가 있다.

버스
  • 시스템 부품간의 바이트를 이리저리 전송하는 전기 관 모음이다.
  • 단어들(words)로 알려진 정해진 사이즈의 바이트 청크들을 옮기도록 설계되어있다.
  • 오늘날 대부분의 머신들은 4바이트(32비트), 8바이트(64비트)의 단어 사이즈(word size)를 가진다.
I/O 디바이스들
  • 외부 세계와 시스템의 연결고리. 키보드, 마우스, 디스크 드라이버...
  • 각각의 I/O 디바이스는 I/O 버스와 연결되어있다.
메인 메모리
  • 프로세서가 프로그램을 실행하는동안 임시로 프로그램과 데이터를 담아두는 저장소.
  • 물리적으로, DRAM(dynamic random access memory) 칩들로 구성된다.
  • 논리적으로, 0부터 시작하는 각각 유니크한 주소를 가진 바이트들의 선형 배열로 구성된다.
프로세서
  • CPU(central processing unit), 혹은 프로세서.
  • 메인 메모리에 있는 명령들을 해석하거나 실행한다.
  • 코어에 단어 사이즈(버스가 다루는)의 스토리지 디바이스(레지스터)가 있다. 이는 PC(program counter)라고 불린다.
  • PC는 메인 메모리에 있는 기계어 명령어의 주소를 가르킨다.
  • PC가 가르키는 명령어를 실행하고, PC가 다음 명령어를 가르키도록 계속해서 업데이트한다.
  • 명령들을 실행하는 산술 논리 장치(ALU)로 구성되어있다.

1.4.2 hello 프로그램 실행

hello

1.5 캐시 문제

  • 시스템은 정보를 이리저리 이동하는 데 많은 시간을 쓴다.
  • 일반적으로 레지스터 파일은 굉장히 작은 양의 데이터만 저장할 수 있지만, 프로세서는 메인 메모리보다 레지스터에서 훨씬 빠르게 데이터를 읽을 수 있다.
  • 기술이 발전하면서 프로세서를 빠르게 만드는 게 메인 메모리를 빠르게 만드는 것보다 쉬워졌다.
  • 프로세서-메모리 간의 차이를 메우기 위해, 캐시라 불리는 작고 빠른 스토리지 디바이스를 추가했다. 보통 L1, L2 캐시라고 불리며 SRAM(static random access memory)로 구현되어 있다.

1.6 스토리지 디바이스는 계층 구조를 가진다

  • 한 레벨의 스토리지는 다음 레벨의 스토리지에 대한 캐시 역할을 한다. 예를 들어, 레지스터는 L1 캐시의 캐시이고, L1과 L2 캐시는 L2, L3 캐시의 캐시이다.