본문 바로가기

컴퓨터 시스템 이야기

Chapter 1 컴퓨터 시스템으로의 여행

CS:APP 3판(컴퓨터 시스템) 을 읽어 나가면서 간단히 정리하려고 한다. 

https://book.naver.com/bookdb/book_detail.nhn?bid=11037688

 

컴퓨터 시스템

▶ 이 책은 컴퓨터 시스템에 대해 다룬 이론서입니다. 컴퓨터 시스템의 기초적이고 전반적인 내용을 학습할 수 있도록 구성했습니다.

book.naver.com

1장은 유명한 "hello world" 프로그램의 일생 주기를 추적해서 컴퓨터 시스템에서의 주요 아이디어들을 소개한다.

책 처음 부분에 전체적인 구성을 설명해 주는데 읽어보면 좋을 것 같다.

1.1 정보는 비트와 컨텍스트로 이루어진다

소스 프로그램은 0 또는 1로 표현되는 비트의 연속, 비트 8개로 이루어진 바이트로 텍스트 문자를 나타냄. 대부분 컴퓨터 시스템은 텍스트 문자를 아스키 표준을 사용하여 표시한다. 각 바이트는 특정 문자에 대응되는 정수 값을 갖는다. 여기서 텍스트 라인이 '\n'으로 종료되는 것을 주목할 필요가 있다. 아스키 문자들로만 이루어진 파일들은 텍스트 파일이라 부른다. 이외의 다른 파일들은 바이너리 파일 이라고 한다. 모든 시스템 내부의 정보는 비트들로 표시된다. 서로 다른 객체들을 구분하는 유일한 방법은 이들을 바라보는 컨텍스트에 의해서다. 일례로 다른 컨텍스트에서는 동일한 일련의 바이트가 정수, 부동소수, 문자열 또는 기계어를 의미.

1.2 프로그램은 다른 프로그램에 의해 다른 형태로 번역된다

hello.c를 시스템에서 실행시키려면, 각 C문장들은 다른 프로그램들에 의해 저금 기계어 인스트럭션들로 번역 되어야함. 이런 인스트럭션들은 실행가능 목적 프로그램이라고 하는 형태로 합쳐져서 바이너리 디스크 파일로 저장됨. GCC 컴파일러 드라이버는 소스파일 hello.c를 읽어서 실행파일인 hello로 번역한다. 밑의 그림같이 4개의 단계를 거쳐서 실행됨.

이 네단계를 컴파일 시스템이라고 부른다.

  • 전처리 단계: #include<stdio.h> 문장은 전처리기에게 시스템 헤더파일을 프로그램 문장에 직접 삽입하라고 지시. 그 결과 .i로 끝나는 c프로그램이 생성됨
  • 컴파일 단계: 컴파일러는 텍스트파일 hello.i를 텍스트파일인 hello.s로 번역하며, 이 파일에는 어셈블리어 프로그램이 저장됨.
  • 어셈블리 단계: 어셈블러가 hello.s를 기계어 인스트럭션으로 번역하고, 이들을 재배치가능 목적프로그램의 형태로 묶어서 hello.o라는 목적파일에 그 결과를 저장함. 이 파일은 main함수의 인스트럭션들을 인코딩하기 위한 17바이트를 포함하는 바이너리 파일. 
  • 링크 단계: hello프로그램이 c컴파일러에서 제공하는 표준 C 라이브러리에 들어있는 printf 함수를 호출하는 것에 주목할 필요가 있다. printf함수는 이미 컴파일된 별도의 목적파일인 printf.o에 들어 있으며, 이 파일은 hello.o파일과 어떤 형태로든 결합되어야함. 링커 프로그램이 이 작업을 수행. 그 결과 실행가능 목적파일로 메모리에 적재되어 시스템에 의해 실행됨.

1.3 컴파일 시스템이 어떻게 동작하는지 이해하는 것은 중요하다

  • 프로그램 성능 최적화하기: C프로그램 작성 시 올바른 판단을 하기 위해서는 기계어 수준 코드에 대한 기본적인 이해를 할 필요가 있음, (ex. switch 문은 if-else 문을 연속해서 사용하는 것보다 언제나 더 효율적일까? , 함수 호출 시 발생하는 오버헤드는 얼마나 되는가?, while 루프는 for루프보다 더 효율적일까?) --> 5장,6장에서 해결
  • 링크 에러 이해하기: 큰 규모의 소프트웨어 시스템을 빌드하려는 경우에 링커에러는 종종 당혹스러움을 줌. (링커에 대한 질문은 7장에서 해결)
  • 보안 약점 피하기: 오랫동안 버퍼 오버플로우 취약성이 인터넷과 네트워크상의 보안 약점의 주요 원인으로 설명됨. 이 취약성은 프로그래머들이 신뢰할 수 없는 곳에서 획득한 데이터의 양과 형태를 주의 깊게 제한해야할 필요를 거의 인식하지 못해서 생겨남. 안전한 프로그래밍을 배우는 첫 단계는 프로그램 스택에 데이터와 제어 정보가 저장되는 방식 때문에 생겨나는 영향을 이해 하는 것이다.

1.4 프로세서는 메모리에 저장된 인스트럭션을 읽고 해석한다

실행파일은 유닉스 시스템에서 실행하기 위해서 쉘이라는 응용프로그램에 그 이름을 입력함. 쉘은 커맨드라인 인터프리터로 프롬프트를 출력하고 명령어 라인을 입력 받아 그 명령을 실행

  • 시스템 하드웨어 조직

전형적인 시스템의 하드웨어 구성

  • 버스 : 시스템 내를 관통하는 전기적 배선군을 버스라 한다. 컴포넌트들 간에 바이트 정보들을 전송함. 버스는 일반적으로 워드라고 하는 고정 크기의 바이트 단위로 데이터를 전송하도록 설계한다. 한 개의 워드를 구성하는 바이트 수는 시스템마다 보유하는 기본 시스템 변수다. 오늘날 대부분의 컴퓨터는 4바이트 또는 8바이트 워드 크기를 가짐.
  • 입출력 장치:  입출력 장치는 우리가 아는 키보드, 마우스, 출력용 디스플레이 등을 가리킨다. 처음 실행가능 파일인 hello프로그램은 디스크에 저장됨. 입출력 장치는 입출력 버스와 컨트롤러어댑터를 통해 연결됨. 이 두 장치의 차이는 패키징에 있다. 컨트롤러는 디바이스 자체가 칩셋이거나 시스템의 인쇄기판(마더보드)에 장착됨.
  • 메인 메모리: 메인 메모리는 프로세서가 프로그램을 실행하는 동안 데이터와 프로그램을 모두 저장하는 임시 저장장치. 물리적으로 메인 메모리는 DRAM 칩들로  구성되어 있다. 논리적으로 메모리는 연속적인 바이트들의 배열로, 각각 0부터 시작해서 고유의 주소를 가지고 있다. 한개의 프로그램을 구성하는 각 기계어 인스트럭션은 다양한 바이트 크기를 갖는다.
  • 프로세서: CPU 또는 프로세서는 메인 메모리에 저장된 인스트럭션들을 해독하는 엔진. 프로세서 중심에는 워드 크기의 저장장치(레지스터)인 프로그램 카운터가 있다.  시스템에 전원이 공급되는 순간부터 전원이 끊어질 때까지 프로세서는 프로그램 카운터가 가리키는 곳의 인스트럭션을 반복적으로 실행하고 PC값이 다음 인스트럭션의 위치를 가리키도록 업데이트 한다. 프로세서는 자신의 인스트럭션 집합 구조로 정의되는 매우 단순한 인스트럭션 실행 모델을 따라 작동하는 것처럼 보임. 프로세서는 PC가 가리키는 메모리로 부터 인스트럭션을 읽어오고, 이 인스트럭션에서 비트들을 해석하여 인스트럭션이 지정하는 간단한 동작을 실행하고, PC를 다음 인스트럭션 위치로 업데이트함. 이 새로운 위치는 방금 수행한 명령어와 메모리 상에서 연속적일 수도 있고, 그렇지 않을 수도 있다. 이와 같은 몇개의 단순한 동작들이 있고, 메인 메모리, 레지스터 파일, ALU 주위를 순환한다.
    • CPU가 실행하는 단순한 작업의 예
      • 적재(LOAD): 메인 메모리에서 레지스터에 한 바이트 또는 워드를 이전 값에 덮어쓰는 방식으로 복사함.
      • 저장(SAVE): 레지스터에서 메인 메모리로 한 바이트 또는 워드를 이전 값을 덮어쓰는 방식으로 복사함.
      • 작업(Operate): 두 레지스터의 값을 ALU로 복사하고 두 개의 워드로 수식연산을 수행한 뒤, 결과를 덮어쓰기 방식으로 레지스터에 저장함.
      • 점프(Jump): 인스트럭션 자신으로부터 한 개의 워드를 추출하고, 이것을 PC에 덮어쓰기 방식으로 복사함.