'컴퓨터 구조'에 해당되는 글 1건

hello.c File

#include <stdio.h>


int main()

{

printf("hello world\n");

return 0;

}


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


hello 프로그램은 프로그래머가 에디터로 작성한 소스 프로그램이다.

이 프로그램은 소스파일로 생명을 시작하며, hello.c라는 텍스트 파일로 저장된다.

소스 프로그램은 0과 1로 표시되는 비트들의 연속이며, 바이트라는 8비트 단위로 구성된다.

각 바이트는 프로그램의 텍스트 문자를 나타낸다.


대부분 문자들은 ASCII 표준을 사용하여 표시한다.

아스키 표준은 각 문자를 바이트 길이의 정수 값으로 나타낸다.

예를들면 65는 A 66은 B 이런식으로 나타낸다.


이는 C 자료형인 char형과 int형에 대해 형변환을 시켜보면 알 수 있다.


hello.c 처럼 오로지 아스키 문자들로만 이루어진 파일들을 텍스트 파일이라고 부른다.

그 외 다른 0과 1로 이루어진 파일들은 바이너리 파일이라고 한다.


hello.c 의 표시방법은 기본개념을 분명히 보여준다.

모든 시스템 내부의 정보 디스크파일, 메모리 상의 프로세스, 데이터, 네트워크를 통해 전송되는 데이터는 비트들로 표시된다.

서로다른 객체들을 구분하는 유일한 방법은 이들을 바라보는 컨텍스트에 의해서다.

일례로 다른 컨텍스트에서는 동일한 일련의 바이트가 정수, 부동소수, 문자열 또는 기계어 명령을

의미할 수 있다.


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

hello.c 프로그램은 인간이 그 형태로 바로 이해하고 읽을 수 있기 때문에 고급 C 프로그램이다.

그러나 hello.c를 시스템에서 실행시키려면, 각 C 문장들은 다른 프로그램들에 의해

저급 기계어 명령으로 번역되어야 한다. 이 기계어들은 실행 가능한 목적 프로그램이라는 형태로

합쳐져서 바이너리 디스크 파일로 저장된다.

목적 프로그램은 실행가능 목적 프로그램이라고도 불린다.


컴파일러 드라이버는 유닉스 시스템에서 다음과 같이 소스파일에서 오브젝트 파일로 번역한다.

$> gcc -o hello hello.c

여기서 gcc는 컴파일러 드라이버이다.

gcc 컴파일러는 소스파일 hello.c를 읽어서 실행파일인 hello로 번역한다.

이 번역과정은 총 4개로 분류되는데 아래에서 알아보자


1.3 컴파일 시스템

컴파일 시스템은 총 4가지 과정을 거친다.

_1. 전처리 단계 : 전처리기 는 본래의 C 프로그램을 #문자로 시작하는 디렉티브에 따라 수정한다.

  예를 들어 hello.c 파일 첫 줄의 #include<stdio.h.>는 전 처리기에게 시스템 헤더

  파일인 stdio.h를 프로그램 문장에 직접 삽입하라고 지시한다. 그 결과 일반적으로

  .I 의 확장자로 끝나는 새로운 C프로그램이 생성된다.

_2. 컴파일 단계 : 컴파일러는 텍스트파일 hello.i를 텍스트파일인 hello.s로 번역하며,이 파일에는

   어셈블리어 프로그램이 저장된다.

   이 프로그램은 다음과 같은 main 함수의 정의를 포함한다.

  1 main:

  2    subq        $8, %rsp

  3    movl        $.LCO, %edi

  4    call           puts

  5    movl        $0, $eax

  6    addq        $8, %rsp

  7    ret

ret 은 모든 함수가 시작될때 만들어지는 함수의 포인터이며 함수가 리턴하거나 끝났을때

다시 돌아오는 스택상의 주소이다.


_3. 어셈블리 단계 : 다음에는 어셈블러가 hello.s를 기계어 명령어로 번역하고, 이들을 재배치

      가능한 목적프로그램의 형태로 묶어서 hello.o라는 목적파일에 그 결과를

      저장한다.

      이 파일은 메인함수의 명령어들을 인코딩하기 위한 17바이트를 포함하는

      바이너리 파일이다.

      hello.o 파일을 텍스트 편집기로 열어보면 쓰레기같은 데이터로 보일 것이다.

_4. 링크단계 : hello 프로그램이 C 컴파일러에서 제공하는 표준 C 라이브러리에 들어있는 printf

      함수를 호출하는것에 주목할 필요가 있다.

      printf 함수는 이미 컴파일된 별도의 목적파일인 printf.o 에 들어 있으며,

      파일과 어떤 형태로든 결합되어야 한다. 링커 프로그램이 이러한 통합작업을 수행

      하며 그 결과 hello 파일은 실행가능 목적파일 즉 실행되어 메모리에 적재, 시스템에

      의해 실행되게 된다.


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

hello.c  처럼 간단한 프로그램의 경우 컴파일 시스템이 정확하고 효율적인 기계어 코드를 만들어 줄 거라고 기대할 수 있다.

하지만 반드시 그리되리라는 보장은 어디에도 없다.


프로그램 성능 최적화하기:

최신 컴파일러들은 복잡한 도구로 대개 우수한 코드들을 생성한다.

프로그래머로서 효율적인 코드를 작성하기 위해서 컴파일 내부 동작을 알 필요는 없다.

하지만 C 프로그램 작성 시 올바른 판단을 하기 위해서는 기계어 수준 코드에 대한 기본적인 이해를

할 필요가 있으며, 컴파일러가 어떻게 C 문장들을 기계어 코드로 번역하는지 알 필요가 있다.


예를들면  switch 문은 if-else 삼항연산자 문을 연속해서 사용하는 것보다 언제나 더 효율적인가?

while 루프는 for 루프문 보다 더 효율적인가?

포인터 참조가 배열 인덱스보다 더 효율적인가? 등등 여러가지 의문을 가질때가 많다.


링크에러 이해하기: 

경험에 의하면 가장 당황스러운 프로그래밍 에러는 링커의 동작과 관련되어 있으며, 큰 규모의 소프트웨어 시스템을 빌드하려는 경우에 더욱 그렇다.

예를들면 링커가 어떤 참조를 풀어낼 수 없다고 할 때는 무엇을 의미하는가?

정적 변수와 지역변수의 차이는 무엇인가? 만일 각기 다른 파일에 동일한 이름의 두 개의 전역변수를 정의한다면 무슨일이 일어나는가? 정적 라이브러리와 동적 라이브러리의 차이는 무엇인가? 등등

왜 링커와 관련된 에러들은 실행하기 전 까지는 나타나지 않는것인가? 등등 이다.


보안 약점:

오랫동안 버퍼 오버플로우 취약성이 인터넷과 네트워크상의 보안 약점의 주요 원인으로 설명되었다.

이 취약성은 프로그래머들이 신뢰할 수 없는곳에서 획득한 데이터의 양과 형태를 주의깊게 제한해야 할 필요를 거의 인식하지 못하기 때문에 생겨난다.

안전한 프로그래밍을 배우는 첫 단계는 프로그램 스택에 데이터와 제어정보가 저장되는 방식 때문에

생겨나는 영향을 이해하는 것이다.


1.5 프로세서는 메모리에 저장된 명령어를 읽고 해석한다.

지금까지 hello.c 소스 프로그램은 컴파일 시스템에 의해 hello 라는 실행가능한 목적파일로 번역되어 디스크에 저장되었다.

이 실행파일을 유닉스 시스템에서 실행하기 위해서 쉘 이라는 응용프로그램을 그 이름에 입력한다.

$> ./ hello

hello world

$>

쉘은 커맨드라인 인터프리터로 프롬포트를 출력하고 명령어 라인을 입력받아 그 명령어를 실행한다.

만일 명령어 라인이 내장 쉘 명령어가 아니면 쉘은 실행파일의 이름으로 판단하고 그 파일을 로딩하여 실행해 준다. 그래서 이 경우에 쉘은 hello 프로그램을 로딩하고 실행한 뒤에 종료를 기다린다.

hello 프로그램은 메세지를 화면에 출력하고 종료한다.

그 후 쉘은 프롬포트를 출력해 주고 다음 입력 명령어 라인을 기다린다.


프롬포트란 무엇인가? : $ <--- 이 놈이 프롬포트이다

루트 권한일 경우 # 으로 표기된다.


1.5.1 시스템의 하드웨어 조직

hello 프로그램을 실행할 때 무슨 일이 일어나는지 설명하기 위해서는 전형적인 시스템에서의 하드웨어 조직을 이해할 필요가 있다.


버스 :

시스템 내를 관통하는 전기적 배선군을 버스라고 하며, 컴포넌트들 간의 바이트 정보들을 전송한다.

이 버스는 일반적으로 워드 라고 불리는 고정크기의 바이트 단위로 데이터 들을 전송한다.

한 개의 워드를 구성하는 바이트수는 시스템마다 보유하는 기본 시스템 변수이다.

대부분 컴퓨터들은 4바이트(32비트) 8바이트(64비트) 크기의 워드를 갖는다.

버스는 시스템버스 메모리버스 IO버스가 존재한다.


워드 : cpu가 한번에 처리할 수 있는 단위


입 출력 장치 :

입출력 장치는 시스템과 외부세계와의 연결을 담당한다.

예를들면 시스템은 입출력 장치를 가지고 있다.

입력용 키보드 마우스 웹캠 마이크 출력용 모니터, 디스크드라이브, 프린터 등등

처음에 실행가능 파일인 hello프로그램은 디스크에 저장되어 있다.

각 입출력 장치는 입출력 버스와 컨트롤러나 어댑터를 통해 연결된다.

이 두장치의 차이는 패키징에 있는데, 컨트롤러는 디바이스 자체가 칩셋이거나 시스템의 인쇄기판

(메인보드) 에 장착된다.

어댑터는 메인보드의 슬롯에 장착되는 카드이다.


메인 메모리 : 


이후 좀 쉬고와서 다시쓰겠슴


블로그 이미지

ZeroFlag

,