728x90
반응형
컴퓨터 시스템(CS:APP)
인트로
아래와 같이 hello.c 프로그램의 수명주기를 따라가면서 주요 개념과 용어, 관련된 구성요소를 소개
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
1.1 정보는 비트와 컨텍스트로 이루어진다.
- hello 프로그램은 소스 프로그램(소스파일)으로 생성되어 hello.c라는 텍스트 파일에 저장됨
- 소스 프로그램 : 0과 1로 표시되는 비트(bit)들의 연속 / 바이트라는 8비트 단위로 구성
- 각 바이트는 프로그램의 텍스트 문자를 나타냄
- 대부분 컴퓨터 시스템은 텍스트 문자를 아스키(ASCII) 표준을 사용해 표시
- 아스키 표준은 각 문자를 바이트 길이의 정수 값으로 나타냄
- hello.c처럼 오로지 아스키 문자들로만 이루어진 파일들은 텍스트 파일, 다른 모든 파일은 바이너리 파일이라 부름
- 모든 시스템의 내부 정보(디스크 파일, 메모리상의 프로그램, 데이터, 네트워크를 통해 전송되는 데이터)는 비트들로 구성
- 서로 다른 객체를 구분하는 유일한 방법은 "컨텍스트(Context)"뿐임(컨텍스트)
- 다른 컨텍스트에서는 동일한 일련의 바이트가 정수, 부동소수, 문자열 또는 기계어 명령을 의미할 수 있음
1.2 프로그램은 다른 프로그램에 의해 다른 형태로 번역된다.
- 위의 hello 프로그램은 인간이 이해하고 읽을 수 있는 고급 C 프로그램으로 생성
- 이것을 시스템에서 실행시키기 위해서는 각 문장들은 여러 프로그램에 의해 저급 기계어 명령어들로 번역되어야 함
- 이 명령어들은 실행가능 목적 프로그램이라고 하는 형태로 합쳐져서 바이너리 디스크 파일로 저장
- 컴파일러 드라이버는 유닉스 시스템에서 소스파일에서 오브젝트 파일로 번역
linux> gcc -o hello hello.c
- GCC 컴파일러 드라이버(GNU C Compiler)는 소스파일 hello.c를 실행파일인 hello로 번역
- 번역은 전처리기(Pre-processor) / 컴파일러(Compiler) / 어셈블러(Assembler) / 링커(Linker)
단계별 정리
- 전처리 단계
- 전처리기(cpp)에 의해 처리됨
- 소스파일에 다른 파일의 텍스트를 포함시키거나 일부 문장을 다른 문장으로 바꾸는 작업을 수행
- 전처리 명령어는 '#'기호로 시작하며, 본래의 C 프로그램을 해당 명령어에 따라 수정
- #include <stdio.h>는 전처리기에게 시스템 헤더파일인 stdio.h를 프로그램 문장에 직접 삽입하라는 의미
- 결과로 .i로 끝나는 새로운 C 프로그램이 생성됨(Text 파일)
- 컴파일 단계
- 컴파일러(cc1)에 의해 처리됨
- 텍스트 파일 hello.i를 어셈블리어 프로그램이 저장된 텍스트 파일인 hello.s로 번역
- 어셈블리어 : 저급 언어의 한 종류로, 기계어를 읽기 편한 형태로 번역한 언어
- 어셈블리어는 여러 상위 수준 언어의 컴파일러들을 위한 공통의 출력 언어를 제공하기 때문에 유용(C, Fortran 컴파일러)
- 어셈블리 단계
- 어셈블러(as)에 의해 처리됨
- hello.s 기계어 명령어를 번역하고, 이들을 재배치 가능한 목적프로그램의 형태로 묶어서 hello.o라는 목적파일에 결과를 저장
- 목적 코드 : 사람이 알아볼 수 없는 기계어로 변환된 코드
- 목적 파일 : 목적 코드로 이루어진 파일
- 이 파일은 main 함수의 명령어들을 인코딩하기 위한 17바이트를 포함하는 바이너리 파일(파일을 열면 알아볼 수 없음)
- 링크 단계
- 링커(ld)에 의해 처리됨
- 링킹이란 작성한 프로그램이 사용하는 다른 프로그램이나 라이브러리를 가져와 연결하는 과정을 뜻함
- hello 프로그램이 호출하는 표준 C 라이브러리의 printf() 함수는 별도의 목적파일인 printf.o에 들어있음
- hello.o파일과 통합하는 작업을 링커 프로그램이 수행
- 링킹이 완료되면 실행가능 목적파일(실행 파일)로 메모리에 적재되어 시스템에 의해 실행
1.3 컴파일 시스템이 어떻게 동작하는지 이해하는 것은 중요하다.
- 컴파일 시스템이 어떻게 동작하는지 이해해야하는 이유
- 프로그램 성능 최적화
- C 프로그램 작성시 올바른 판단을 하기 위해서는 기계어 수준 코드에 대한 기본적인 이해를 할 필요가 있고,
컴파일러가 어떻게 C 문장들을 기계어 코드로 번역하는지 알 필요가 있음 - 예를 들어, switch문은 if-else문을 연속해서 사용하는 것보다 효율적인지, 함수 호출 시 발생하는 오버헤드는 얼마나 되는지, while 루프는 for 루프보다 더 효율적인지, 포인터 참조가 배열 인덱스보다 더 효율적인지 등을 알기 위해서는 해당 지식이 필요
- C 프로그램 작성시 올바른 판단을 하기 위해서는 기계어 수준 코드에 대한 기본적인 이해를 할 필요가 있고,
- 링크 에러 이해하기
- 가장 당혹스러운 프로그래밍 에러는 링커의 동작과 관련되어 있음
- 예를 들어 링커가 어떤 참조를 풀어낼 수 없다고 할 때는 무엇을 의미하는가? 정적변수와 전역변수의 차이는 무엇인가? 만일 각기 다른 파일에 동일한 이름의 두 개의 전역변수를 정의한다면 무슨 일이 일어나는가? 등의 질문에 대한 대답을 배울 수 있음
- 보안 약점 피하기
- 오랫동안 버퍼 오버플로우 취약성이 인터넷과 네트워크상의 보안 약점의 주요 원인으로 설명됨
- 안전한 프로그래밍을 배우는 첫 단계는 무슨 일이 일어나는가? 등의 질문에 대한 대답을 배울 수 있음
- 프로그램 성능 최적화
1.4 프로세서는 메모리에 저장된 인스트럭션(명령 집합)을 읽고 해석한다.
- 위의 과정을 통해 디스크에 저장된 hello 실행파일을 유닉스 시스템에서 실행하기 위해서 Shell(쉘)이라는 응용프로그램에 그 이름을 입력
linux> ./hello
hello, world
linux>
- 쉘은 커맨드라인 인터프리터로 프롬프트로 출력하고 명령어 라인을 입력받아 그 명령을 실행
- 인터프리터(Interpreter) : 코드를 한 줄씩 읽어들여서 실행하는 프로그램
- 프롬프트(prompt) : 컴퓨터가 입력을 받아들일 준비가 되어서 기다리고 있다고 알려주는 메시지. 위 예시에서는 linux>
- 만일 명령어 라인이 내장 쉘 명령어가 아니면 쉘은 실행파일의 이름으로 판단하고 그 파일을 로딩해서 실행
- 이 경우, 쉘은 hello 프로그램을 로딩하고 실행한 뒤 종료를 기다리며, 종료 이후에는 프롬프트를 출력해주고 다음 입력 명령어 라인을 기다림
1.4.1 시스템의 하드웨어 조직
hello 프로그램을 실행할 때 무슨 일이 일어나는지를 전형적인 시스템 하드웨어 조직을 통해 설명
- 버스(Buses)
- 시스템 내를 관통하는 전기적 배선군을 버스라고 함
- 컴포넌트들 간에 바이트 정보들을 전송함
- 일반적으로 워드(word)라고 하는 고정 크기의 바이트 단위로 데이터를 전송하도록 설계됨
- 한 개의 워드를 구성하는 바이트 수는 시스템마다 보유하는 기본 시스템 변수로, 오늘날 대부분의 컴퓨터들은 4바이트(32비트) 또는 8바이트(64비트) 워드 크기를 가짐
- 입출력 장치(I/O Device)
- 시스템과 외부세계와의 연결을 담당
- 입력용 키보드, 마우스, 출력용 디스플레이, 데이터 및 프로그램의 장기 저장을 위한 디스크 드라이브 등이 입출력 장치
- 각 입출력 장치는 입출력 버스에서 컨트롤러나 어댑터를 통해 연결
- 컨트롤러는 디바이스 자체에 있는 칩셋이거나 시스템의 마더보드에 장착
- 어댑터는 마더보드의 슬롯에 장착되는 카드
- 컨트롤러나 어댑터는 입출력 버스와 입출력 장치들 간에 정보를 주고받도록 함
- 메인 메모리(Main Memory)
- 프로세서가 프로그램을 실행하는 동안 데이터와 프로그램을 모두 저장하는 임시 저장장치
- 물리적으로 메인 메모리는 DRAM(Dynamic Random Access Memory) 칩들로 구성되어 있음
- 논리적으로 메모리는 연속적인 바이트들의 배열로, 각각 0부터 시작해서 고유의 주소(배열의 인덱스)를 가지고 있음
- 일반적으로 한 개의 프로그램을 구성하는 각 기계어 인스트럭션은 다양한 바이트 크기를 가짐
- 프로세서(Processor)(=CPU)
- 주처리장치(CPU)라고도 함
- 메인 메모리에 저장된 명령어들을 해독(실행)하는 엔진
- 프로세서의 중심에는 워드 크기의 저장장치(혹은 레지스터)인 프로그램 카운터(PC)가 있음
- PC는 다음에 실행될 명령어의 주소를 가리킴
- 프로세서는 프로그램 카운터가 가리키는 곳의 명령어를 반복적으로 실행하고, 프로그램 카운터 값이 다음 명령어의 위치를 가리키도록 업데이트
- 다음은 명령어 요청에 의해 CPU가 실행하는 단순한 작업의 예
- 적재(Load) : 메인 메모리에서 레지스터에 한 바이트 또는 워드를 이전 값에 덮어쓰는 방식으로 복사한다.
- 저장(Store) : 레지스터에서 메인 메모리로 한 바이트 또는 워드를 이전 값을 덮어쓰는 방식으로 복사
- 작업(Operate) : 두 레지스터의 값을 ALU로 복사하고 두 개의 워드로 수식연산을 수행한 뒤, 결과를 덮어쓰기 방식으로 레지스터에 저장
- 점프(Jump) : 명령어 자신으로부터 한 개의 워드를 추출하고, 이것을 PC에 덮어쓰기 방식으로 복사
1.4.2 hello 프로그램의 실행
hello 프로그램이 실행되는 과정을 하드웨어 조직을 바탕으로 설명
- 처음에 쉘 프로그램은 사용자가 명령을 입력하기를 기다린다. 이용자가 쉘에 “.\hello”를 입력하면 쉘 프로그램은 각각의 문자를 레지스터에 읽어들인 후 메모리에 저장함
- 키보드에서 엔터를 누르면 쉘은 명령 입력을 끝마쳤음을 이해하고 파일 내의 코드와 데이터(”hello, world\n” 스트링 데이터 포함)를 복사하는 일련의 명령어를 실행하여 실행파일 hello를 디스크에서 메인 메모리로 로딩
- 데이터는 직접 메모리 접근 기법을 이용해 프로세서를 거치지 않고 디스크에서 메인 메모리로 직접 이동
- hello 목적파일의 코드와 데이터가 메모리에 적재된 후, 프로세서는 hello 프로그램의 main 루틴의 기계어 명령어를 실행
- 이 명령어들은 “hello, world\n” 스트링을 메모리로부터 레지스터 파일로 복사하고, 거기로부터 디스플레이 장치로 전송하여 화면에 글자들이 표시
728x90
반응형
'크래프톤 정글 - TIL' 카테고리의 다른 글
크래프톤 정글 5기 TIL - Day 8 (0) | 2024.03.25 |
---|---|
크래프톤 정글 5기 TIL - Day 7 (0) | 2024.03.24 |
크래프톤 정글 5기 TIL - Day 5-3 (0) | 2024.03.22 |
크래프톤 정글 5기 TIL - Day 5-2(자료구조) (0) | 2024.03.22 |
크래프톤 정글 5기 TIL - Day 5-1 (2) | 2024.03.22 |