3 분 소요

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

비트
0과 1 을 가지는 최소한의 정보 표시 단위
바이트
8개의 비트가 모인 단위
아스키(ASCII) 표준
‘알파벳’을 사용하는 대표적인 문자 인코딩 7비트 인코딩으로 총 128(2^7) 개의 문자로 이루어진다 (오로지 아스키 문자들로만 이루어진 파일을 ‘텍스트 파일’이라 한다) (다른 모든 파일은 ‘바이너리 파일’ 이라 한다)

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

인스트럭션(instruction : 명령)
저급 기계어 프로그램에서 사용하는 동작 지시 (컴퓨터의 동작을 지시하는 일종의 단위)
목적프로그램
사람이 사용하는 언어를 사용해 만든 것을 기계어로 번역한 파일 (이 상태로 실행 가능한 것은 아니고, ‘실행 파일’을 만들기 위하여 필요)
컴파일 시스템
전처리 -> 컴파일러 -> 어셈블러 -> 링커
예시
hello.c(sourceProgram) -> 전처리기 -> hello.i(modified source) -> 컴파일러 -> hello.s(assembly program) -> 어셈블러 -> hello.o(relocatable obj program) -> 링커 (이 때, prinf.o와 링크) -> 실행파일(exe) => “hello, World!”
전처리(preprocess)
본래의 C 프로그램을 수정한다. 디렉티브
(# 으로 끝나는 전처리 지시어 ex : 매크로 or include)를 각각의 용법에 맞도록 처리
(ex : #include 이라는 디렉티브 존재 시,
해당하는 addFile.h 로 가서 코드를 위쪽에 붙여준다)
(주석 제거 - 주석은 사람이 보는 용도이므로)
결과물 => 확장, 수정된 소스코드 파일(.i)
컴파일(compilation)
전처리에서 처리된 .i 파일을 어셈블러 파일인 .s로 ‘번역’한다.
해당 번역 과정 중, 정의를 알 수없는 ‘심볼’이 존재하여도 일단 넘어간다
(심볼 : 함수나 변수의 이름)
(실제 구현 내용을 찾는 일은 ‘링커’에게 맡긴다)
결과물 => 어셈블리어 파일(.s)
어셈블(assembling)
컴파일러가 넘겨준 어셈블리어 파일(.s)를 ‘기계어’ 인스트럭션으로 ‘번역’한다.
기계어는 기계가 곧바로 이해가 가능한 코드!
결과물 => ‘오브젝트 코드’(목적 파일)(.o)
링크(linking)
어셈블러가 넘겨준 모든 오브젝트 코드(.o)를 받아 실제 실행가능 목적파일로 만들어준다
‘심볼’의 선언만 되어 있는 부분을 실제로 채우는 역할도 하며, 채우지 못한 경우 ‘링킹 오류’를 내뱉음
(컴파일러가 가져다 썼지만 실제로는 없으면 실행 불가능 : ‘오류’)
해당 심볼의 주소를 가져다 채워준다
결과물 => ‘실행가능 목적파일’(실행 파일)(.exe, .out)
링크 단계가 분리되어 인식되는 경우?? (tmi)
일단 분리하지 않는 경우는 ‘어셈블 이후, 바로 링크를 수행’하는 것으로 인식
프로젝트에 많은 c 파일들이 존재하여, 많은 ‘심볼’등이 만들어졌을 때,
하나의 c파일의 심볼들을, ‘일일이 다른 c 파일을 찾아 메우는 과정’을 모든 c 파일마다 해야 하기에 아주 복잡해진다
또 여러 c 파일들이 동일한 외부 함수를 사용(ex printf)하는 경우,
최종 실행파일에서 중복으로 들어가는 걸 막아야 함

c 파일을 모두 하나로 합쳐 컴파일하는 방법이라면 위의 2문제를 해결할 수 있을지도 모르지만 조금만 수정해도 모든걸 컴파일 해야한다
이러한 문제가 있기에 일반적으로는 링크 단계를 분리한다

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

프로그래머가 컴파일 시스템을 이해해야 하는 이유는 다음과 같다

  • 프로그램 성능 최적화
  • 링크 에러 이해하기
  • 보안 약점 피하기

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

커맨드라인 인터프리터 이며 ‘명령어 라인’을 입력받아 명령을 실행
OS의 kernel과 사용자를 연결해준다

1.4.1 시스템의 하드웨어 조직

버스(Buses)
시스템의 전기적 배선군, 컴포넌트 (ex : cpu, I/O, Memory 등) 간의
바이트 정보를 전송한다
일반적으로 워드(word)라는 고정된 바이트 단위를 사용하며,
보통 4 Byte 혹은 8 Byte (각각 32 bit, 64 bit) 의 크기를 갖는다
입출력 장치
시스템과 외부의 연결을 담당
입력용 키보드, 마우스 및 출력용 디스플레이,
데이터의 장기 저장을 위한 디스크 드라이브
이들은 ‘컨트롤러’나 ‘어댑터’를 통해 연결된다.
(이들의 목적은 ‘입출력 버스’와 ‘입출력 장치’ 간에 정보를 주고 받게 해주는 일이다)
메인 메모리
프로세서가 프로그램을 실행하는 동안 데이터와 프로그램을
모두 저장하는 ‘임시 저장장치’
물리적으로 DRAM(Dynamic Random Access Memory)로 구성
논리적으로 연속적인 바이트들의 배열(0부터 시작하는 고유의 주소
_ 배열의 인덱스 를 가진다)
프로세서(CPU ,주처리장치)
메인 메모리에 저장된 인스트럭션 을 해독(실행)하는 엔진
중심에 워드 크기의 저장장치(또는 레지스터)인 프로그램 카운터(PC)가 존재
(레지스터 : cpu가 요청을 처리하는 데 필요한 데이터를 일시적으로 저장하는 기억장치이며
메인 메모리보다 몇 십배 빠르다.)
(프로그램 카운터 : 다음에 실행될 명령어의 주소를 가진 레지스터)
프로세서는 인스트럭션을 실행한 후,
pc를 다음 인스트럭션 위치로 업데이트 하는 과정을 반복한다
이렇게 데이터는 메인 메모리 <-> 레지스터 파일 <-> ALU 를 순환한다
(레지스터 파일 : 각각의 고유 이름을 갖는워드 크기의 레지스터 집합으로 구성)
(ALU : 수식/논리 처리기로 새 데이터와 주소 값을 계산한다)

cpu가 실행하는 단순한 작업의 예시

  • 적재(Load) : 메인 메모리 -> 레지스터 (바이트 or 워드 를 이전값에 덮어씌워 복사)
  • 저장(Store) : 레지스터 -> 메인 메모리 (바이트 or 워드 를 이전값에 덮어씌워 복사)
  • 작업(Operate) : 두 레지스터의 값을 ALU로 복사하고, 수식 연산 수행 후, 덮어쓰는 방식으로 레지스터에 저장
  • 점프(Jump) : 인스트럭션 자신으로부터 한개의 워드를 추출하고, 이것을 PC에 덮어쓰는 방식으로 복사한다

최신 프로세서의 경우, 더 복잡한 방식을 사용

1.4.2 hello 프로그램의 실행

쉘 프로그램이 사용자의 입력을 받아(“.\hello”)
해당하는 데이터를 ‘레지스터’에 읽어들인 후,
메인 메모리에 저장한다.

이후 입력이 끝났으면,파일 내의 코드와 데이터를 복사하는 일련의 인스트럭션을 실행하여, 디스크에서 메인 메모리로 실행파일을 로드한다

데이터의 경우, 리터럴 문자인 “hello, World\n”을 포함하여 메모리에 로드한다.

직접 메모리 접근(DMA)를 통하여 데이터는 프로세서를 거치지 않고 디스크에서 메인 메모리로 직접 이동한다
(DMA : 데이터가 CPU를 거치지 않고 메모리에 접근하는 방식으로 프로세서의 부담을 줄이기 위함)

목적 파일의 코드와 데이터가 메모리에 적재된 후,
프로세서는 main 루틴의 기계어 인스트럭션을 실행한다

이후, 리터럴 문자를 메모리로부터 레지스터로 복사하며,
이를 ‘디스플레이’로 전송하여 화면에 표시한다

태그: ,

카테고리:

업데이트:

댓글남기기