컴퓨터 구조
컴퓨터 구조
컴퓨터 구조를 공부하면서 알게된 내용을 요약해서 작성해보자.
컴퓨터 밑바닥의 비밀을 읽고 작성한 글이다.
CPU
- 인류는 boolean logic(불 논리)를 기반으로 CPU를 만들었다.
- 간단한 연산에서 인간은 CPU를 따라 잡을 수 없다. 굉장히 빠르다.
- CPU가 등장한 이후, 인간은 두 번째 두뇌를 가지게 되었다.
- CPU의 의지에 따라 프로그래머가 직접 0과 1로 구성된 명령어를 작성했다.
어셈블리어
- CPU는 가산 명령어, 점프 명령어 등 몇 가지 명령어만 실행할 수 있다는 사실을 발견했다.
- 기계어와 해당 특정 작업을 간단하게 대응시켜 기계어를 인간이 읽고 이해할 수 있는 단어와 대응시켰다.
- 프로그래머는 1101… 을 기억할 필요 없이 add, sub 처럼 인간이 인식할 수 있는 단어만 기억하게 되었다.
- 어셈블리어가 탄생했고, 처음으로 인간이 직접 인식할 수 있는 프로그래밍 언어가 탄생했다.
- 어셈블리어는 기계어와 마찬가지로 저수준 언어이다.
- CPU는 인간이 이해할 수 있는 추상적인 표현을 이해 못한다.
컴파일러
- 고수준 언어를 저수준 언어로 번역하는 프로그램이다.
- 컴퓨터는 프로그래밍 언어를 처리할 때 구문 정의에 따라 트리 형태로 코드를 구성할 수 있다.
- 이 작업을 담당하는 프로그램을 컴파일러 라고 한다.
- 이 시점부터 컴파일러는 CPU가 인식할 수 있는 기계 명령어로 번역하는 역할을 담당하게 되었다.
- 형식이 다른 CPU는 각각 자신만의 고유한 언어가 있다.
- CPU마다 상응하는 시물레이션 프로그램을 준비하면 직접 서로 다른 플랫폼에서 시작할 수 있다.
- 이것이 바로 ‘한 번의 코드 작성으로 어디서나 그 코드를 실행하는 방법이다.
- 고급 언어는 추상적 표현이 뛰어나서 사용하기 쉽지만 저수준 계층에 대한 제어 능력이 떨어진다.
- 직접 저수준 계층의 세부 사항을 제어할 수 있어야 하는 운영 체제 중 일부분은 어셈블리어로 만들어진다.
- 프로그래머가 인간이 인식할 수 있는 단어로 코드를 작성하면, 코드는 일반적인 텍스트파일 형태로 저장된다.
- 프로그래머가 작성한 텍스트 파일 형태로 저장된 파일을 소스파일(source file)이라고 한다.
- 이 소스파일을 컴파일러가 CPU가 직접 실행할 수 있는 기계 명령어로 실행 파일을 만들어 준다.
컴파일러 동작 순서
- 소스파일을 컴파일러가 각각의 토큰으로 추출한다.
- 토큰은 각 항목에 추가로 정보를 결합한 것을 말한다.
- ex: ①T_Keyword ②int 1이 키워드이고 2가 int 키워드 이다.
- 컴파일러가 토큰을 읽고 처리한다. 이 과정을 해석(parsing)이라고 한다.
- 토큰을 해석한 후 생성된 트리가 구문 트리이며, 이 트리를 생성하는 전체 과정을 ‘구문 분석’이라고 한다.
- 구문 트리에 이상이 없는지 확인해야 하고 이상이 없으면 컴파일 오류가 없다는 것을 증명한다.
- 이 과정을 의미 분석(semantic analysis)라고 한다.
- 의미 분석이 끝나면 컴파일러는 결과를 좀 더 다듬어진 형태인 중간 코드를 생성한다.
- 이 과정이 완료되면 중간 코드를 어셈블리어 코드로 변환한다.
- 마지막으로 컴파일러는 이 어셈블리어 코드를 기계 명령어로 반환한다.
- 이것으로 전체 컴파일 과정이 끝난 것은 아니다.
- 모든 소스파일에는 각각의 대상 파일이 있다. 하지만 우리가 원하는 것은 실행 파일이다.
- 이 대상 파일들을 하나의 실행 파일로 합쳐주는 작업을 링크(link)라고 한다.
- 링크를 담당하는 프로그램을 링커(Linker)라고 한다.
링커
- 링커는 컴파일러가 생성한 대상 파일 여러 개를 하나로 묶어 하나의 최종 실행 파일을 생성한다.
- 링커는 심벌 해석, 실행 파일 생성, 재배치 등이 있다.
- 심벌은 전역 변수(global variable)와 함수(function)의 이름을 포함하는 모든 변수 이름을 의미한다.
- 이 단계에서 링커가 해야 할 일은 대상 파일에서 참조하고 있는 각각의 모든 외부 심벌마다 대상 정의가
반드시 존재하는지, 단 하나만 존재하는지 확인하는 것이다.- 명령어 부분: 소스 파일에 정의된 함수에서 변환된 기계 명령어가 저장되는 부분이다.
- 코드 영역(code section)이라고 한다.
- 데이터 부분: 소스 파일의 전역 변수가 저장되는 부분이다.
- 데이터 영역(data section)이라고 한다.
- 컴파일러가 외부 심벌 정보를 기록하는 표를 심벌 테이블(symbol table)이라고 한다.
- 심벌 테이블은 공급과 수요 두 가지 내용만 표현한다.
- 내가 정의한 심벌, 즉 다른 모듈에서 사용할 수 있는 심벌
- 내가 사용하는 외부 심벌
- 공급이 수요를 초과할 수 있다. 실제로는 사용하지 않는 함수를 많이 정의할 수도 있다.
- 수요가 공급을 초과하는 상황이 발생해서는 안된다. 심벌에 대한 참조가 없다는 오류가 발생할 수 있다.
- 링커는 모든 코드와 데이터, 라이브러리를 한데 묶어 실행 파일을 생성하는 역할을 한다.
라이브러리
- 코드를 별도로 컴파일한 후 패키지로 묶고, 구현된 모든 함수의 선언을 포함하는
헤더 파일을 정적 라이브러리 라고 한다.- 소스 파일 여러 개를 미리 컴파일하고 링크하여 정적 라이브러리로 생성할 수 있다.
- 소스 파일마다 단독으로 컴파일을 한다.
- 실행 파일을 생성할 때는 자신의 코드만 컴파일 하고, 컴파일이 완료된 정적 라이브러리는
다시 컴파일할 필요 없이 링크 과정에서 그대로 실행 파일에 복제된다.- 코드가 의존하는 외부 코드를 매번 컴파일하지 않아도 되기 때문에 컴파일 속도가 빨라진다.
- 이를 정적 링크 라고 한다.
- 정적 링크는 라이브러리를 실행 파일에 직접 복사한다.
- 거의 모든 프로그램에 적용되는 표준 라이브러리를 사용한다면 디스크와 메모리를 엄청나게 낭비할 수 있다.
- 정적 라이브러리의 모든 내용에 종속성이 있다면 코드가 변경될 때 마다 다시 컴파일 해야 한다.
- 이를 해결 하기 위해 동적 라이브러리를 사용한다.
- 동적 라이브러리는 공유 라이브러리 또는 동적 링크 라이브러리 라고 한다.
- 윈도 시스템에서 매우 많이 사용한다.
- 정적 라이브러리를 사용하면 코드 영역과 데이터 영역을 모두 한데 묶어 실행 파일에 복사한다.
- 동적 라이브러리를 사용하면 이름, 심볼 테이블, 재배치 정보 등 필수 정보만 실행 파일에 포함된다.
- 정적 라이브러리에 비해 실행 파일의 크기를 확실히 줄일 수 있다.
- 필수 정보는 동적 링크가 일어날 때 사용된다.
- 동적 링크는 실제 프로그램의 실행 시점까지 미룬다. 동적 링크에 2가지 방법이 있다.
- 첫 번째 방식은 프로그램이 메모리에 적재될 때 동적 링크가 진행 된다.
- 실행 파일을 실행하기 위해 디스크에서 읽어 메모리의 특정 영역으로 이동시키는 과정이다.
- 이 과정에서 적재 도구라는 전용 프로세스가 실행된다.
- 동적 라이브러리가 필요하다면 동적 링커 라는 별도의 프로세스가 실행되어 참조하는 동적 라이브러리
존재 여부와 위치, 심벌의 메모리 위치 등을 확인하여 링크를 마무리 한다.- 적재 중 동적 링크를 사용하려면 실행 파일이 어떤 동적 라이브러리를 참조하는지
컴파일러에 명시적으로 알려 주어야 한다.- 두 번째 방식은 적재 중 고정적으로 일어나는 동적 링크 외에도 프로그램이 먼저 실행된 후,
프로그램의 실행 시간 동안 코드가 직접 동적 링크를 실행할 수 있다.- 실행 시간이란 CPU가 프로그램을 실행하기 시작한 시점부터 실행이 완료되어 프로그램이 종료된 시점까지 시간이다.
- 동적 라이브러리는 의존하는 프로그램 개수가 얼마가 되었든 상관 없이 디스크에는
동적 라이브러리의 복사본 하나만 저장된다.- 동적 라이브러리의 코드가 수정되어도 해당 라이브러리만 다시 컴파일 하면 된다.
- 동적 라이브러리는 여러 언어를 혼합하여 개발할 때도 매우 유용하다.
- 동적 라이브러리는 프로그램이 적재되는 시간 또는 실행 시간에 링크되기 때문에
정적 링크를 사용할 때 보다 성능이 약간 떨어진다.- 동적 라이브러리의 코드는 특정 메모리 주소와 독립적으로 동작하기 때문에 독립 코드로 불린다.
- 동적 라이브러리는 임의의 메모리 절대 주소로 참조할 수 없다.
- 적재할 때 동적 링크를 수행하는 프로그램은 실행 파일만으로는 실행이 불가능하다.
- 심벌의 메모리 주소를 수정하는 과정을 재배치 라고 한다.
가상 메모리와 프로그램 메모리 구조
- 프로그램이 실행되면 해당 프로그램의 프로세스가 메모리에 적재된다.
- 가상 메모리는 물리적으로 존재하지 않는 가짜 메모리다.
- 메모리의 상위 주소에 스택 영역, 그 아래 비어있는 큰 공간이 존재하고,
이어서 힙 영역이 존재한다. malloc 함수가 바로 힙 영역에서 메모리를 할당 받는다.
데이터 영역과 코드 영역은 실행 파일의 내용이 메모리에 적재되는 곳이다.- CPU가 프로그램 A를 실행할 때 메모리 주소 0x400000에서 가져온 명령어는 프로그램 A에 속하고,
프로그램 B를 실행할 때 메모리 주소 0x400000에서 가져온 명령어는 프로그램 B에 속한다.- 운영 체제의 가상 메모리 기술 때문에 이런 일이 가능하다.
- 모든 프로세스의 가상 메모리는 표준화 되어 있고 크기가 동일하다.
- 프로세스마다 각 영역의 크기가 다를 수 있지만 영역이 배치되는 순서는 동일하다.
- 실제 물리 메모리의 크기는 가상 메모리의 크기와 무관하다.
- 물리 메모리에는 힙 영역, 스택 영역 등 영역 구분조차 존재하지 않는다. 운영체제마다 다를 수 있다.
추상화
- 소프트웨어는 복잡하지만, 프로그래머는 추상화를 통해 복잡도를 제어할 수 있다.
- 컴퓨터 시스템은 기본적으로 추상화 기반 위에 구축된다.
- 입출력 장치는 파일로 추상화되어 있다.
- 실행 중인 프로그램은 프로세스로 추상화 된다.
- 물리 메모리와 파일은 가상 메모리로 추상화 된다.
컴파일러 순서
- 소스파일 -> 컴파일러 -> 토큰 추출(어휘 분석) -> 파싱(해석) -> 구문트리 생성(구문 분석)
-> 의미 분석 -> 중간 코드 생성 -> 어셈블리어 변환 -> 기계 명령어 변환
-> 링커 -> 실행파일 -> CPU