
소스 프로그램은 0 or 1로 표시되는 bit들의 연속이며 byte 라는 8bit 단위로 구성된다.
대부분의 컴퓨터 시스템은 텍스트 문자를 ASCII 표준을 사용하여 각 문자를 바이트 길이의 정수값으로 나타낸다.
오로지 아스키 문자로만 이루어진 파일은 텍스트 파일이라고 부르며, 다른 모든 파일은 바이너리 파일이라고 한다.
고급 C 프로그램을 시스템에서 실행시키려면, 각 C 문장들은 다른 프로그램에 의해서 저급 기계어 인스트럭션들로 번역되어야 한다. 이 인스트럭션들은 실행가능 목적 프로그램(Executable Object Program)이라는 형태로 합쳐져서 바이너리 디스크 파일로 저장된다. 목적 프로그램은 실행가능 목적 파일이라고도 부른다.
컴파일 드라이버는 소스파일을 읽어서 실행파일로 번역하고, 이 번역은 4단계를 거쳐서 실행된다. 이 4단계를 실행하는 프로그램들 (전처리기, 컴파일러, 어셈블러, 링커)을 합쳐서 컴파일 시스템이라고 부른다.
전처리기(CCP)는 본래의 C 프로그램(hello.c)을 #문자로 시작하는 directive에 따라 수정한다.
예) C 파일 첫 줄의 #include<stdio.h>는 전처리기에 시스템 헤더 파일인 stdio.h를 프로그램 문장에 직접 삽입하라고 지시한다.
그 결과, 일반적으로 .i로 끝나는 새로운 C프로그램이 생성된다.
hello.c => hello.i
컴파일러(CCL)은 텍스트파일 (예시: hello.i)을 텍스트파일인 (hello.s)로 번역하며, 이 파일에는 어셈블리어 프로그램이 저장된다.
어셈블러(as)가 hello.s를 기계어 인스트럭션으로 번역하고, 이를 재배치가능 목적 프로그램(relocatable object programs)으로 묶어서 hello.o 라는 목적파일에 그 결과를 저장한다. 이 파일은 바이너리 파일이다. (2진수)
hello.c 파일에서 printf 함수를 호출할 때, printf 함수는 이미 컴파일러된 별도의 목적 파일인 printf.o에 들어있으며 이 파일은 hello.o 파일과 어떤 형태로든 결합되어야 한다. 링커 프로그램(LD)가 이 통합작업을 수행한다.
그 결과 hello파일은 실행 가능 목적 파일(실행 파일)로 메모리에 적재되어 시스템에 의해 실행된다.
효율적인 코드를 작성하기 위해서 컴파일러 내부 동작을 알 필요는 없지만, 컴파일러가 어떻게 C문장들을 기계어 코드로 번역하는지 알 필요가 있다. 어떤 함수를 사용할지, 어떤 구조가 더 효율적일지, 왜 이 코드가 저 코드보다 더 빠른지
큰 규모의 소프트웨어 시스템을 빌드하려고 하는 경우일수록 중요해진다.
안전한 프로그래밍의 첫 단계는 프로그램 스택에 데이터와 제어 정보가 저장되는 방식 때문에 생겨나는 영향을 이해하는 것이다.
C 소스 프로그램은 컴파일 시스템에 의해 실행가능한 목적 파일로 번역되어 디스크에 저장되었다.
이 실행파일을 유닉스 시스템에서 실행하기 위해서 shell이라는 응용프로그램에 그 이름을 입력한다.
쉘은 커맨드라인 인터프리터로 프롬프트를 출력하고 명령어 라인(command line)을 입력받아 그 명령을 실행한다. 만약 명령어 라인이 내장 쉘 명령어가 아니면 쉘은 실행파일의 이름으로 판단하고 그 파일을 로딩해서 실행해준다. 그 파일은 로딩되고 실행한 뒤 종료되며, 쉘은 프롬프트를 출력해주고 다음 입력 명령어 라인을 기다린다.
시스템 내를 관통하는 전기적 배선군. 버스는 일반적으로 word 라고 하는 고정크기의 바이트 단위로 데이터를 전송하도록 설계된다. 64bit 환경에서는 1word = 8byte의 크기를 가진다. (데이터가 지나가는 통로)
(입력용 키보드, 마우스, 출력용 디스플레이, 디스크 드라이브 등등)
시스템과 외부세계와의 연결을 담당. 각 입출력 장치는 입출력 버스와 컨트롤러 / 어댑터를 통해 연결된다.
두 장치의 차이는 패키징에 있으며 컨트롤러는 디바이스 자체가 칩셋이거나 시스템의 인쇄기판(마더보드)에 장착된다. 어댑터는 마더보드의 슬롯에 장착되는 카드이다.
프로세서가 프로그램을 실행하는 동안 데이터가 프로그램을 모두 저장하는 임시 저장장치. 물리적으로 메인 메모리는 DRAM으로 구성되어 있다. 논리적으로는 메모리는 연속적인 바이트들의 배열로 각각 0부터 시작해서 고유의 주소(배열의 인덱스)를 가지고 있다.
short 타입 데이터는 2byte. int, float는 4byte. long, double은 8byte
프로세서는 메인 메모리에 저장된 인스트럭션들을 해독(실행)하는 엔진이다.
프로그램 카운터(PC)는 프로세서의 중심에 있는 워드 크기의 저장장치(또는 레지스터) 를 뜻하지만 메인 메모리의 기계어 인스트럭션을 가리키기도 한다.
시스템에 전원이 공급되는 순간부터 전원이 끊어질 때 까지 프로세서는 프로그램 카운터가 가리키는 곳의 인스트럭션을 반복적으로 실행하고, PC값이 다음 인스트럭션의 위치를 가리키도록 업데이트 한다.
프로세서는 PC가 가리키는 메모리로부터 인스트럭션을 읽어오고, 이 인스트럭션에서 비트들을 해석하여 인스트럭션이 지정하는 간단한 동작을 실행하고, PC를 다음 인스트럭션 위치로 업데이트 한다.
CPU는 PC값이 가리키는 인스트럭션을 순차적으로 실행한다. 이런 동작은 메인 메모리, 레지스터 파일, 수식/논리 처리기(ALU) 주위를 순환한다.
적재(Load): 메인 메모리에서 레지스터에 한 바이트 or 워드를 이전 값에 덮어쓰는 방식으로 복사 (RAM => PC)
저장(Store): 레지스터에서 메인 메모리로 한 바이트 or 워드를 이전 값으로 덮어쓰는 방식으로 복사한다.(PC => RAM)
작업(Operate): 두 레지스터의 값을 ALU로 복사하고 두 개의 워드로 수식연산을 수행한 뒤, 결과를 덮어쓰기 방식으로 레지스터에 저장한다.
점프(Jump): 인스트럭션 자신으로부터 한 개의 워드를 추출하고, 이것을 PC에 덮어쓰기 방식으로 복사한다.
처음에 쉘 프로그램은 자신의 인스트럭션을 실행하면서 사용자의 명령입력을 기다린다. 여기에 C 프로그램 이름을 입력하면 쉘 프로그램은 각각의 문자를 레지스터에 읽어들인 후, 메모리에 저장한다.
키보드에 엔터키를 누르면 쉘은 명령 입력을 끝냈다는 것을 알게 된다.
그러면 쉘은 파일 내의 코드와 데이터를 복사하는 일련의 인스트럭션을 실행하여 실행파일을 디스크에서 메인 메모리로 로딩한다.
목적 파일의 코드와 데이터가 메모리에 적재된 후, 프로세서는 프로그램의 main루틴의 기계어 인스트럭션을 실행하기 시작한다. 이 인스트럭션들이 실행된 결과가 메모리로부터 레지스터 파일로 복사되고, 디스플레이 장치로 전송된다.
시스템은 정보를 한 곳에서 다른 곳으로 이동시키는 일에 많은 시간을 보낸다. 이러한 복사 과정들이 프로그램의 ‘실제 작업’을 느리게 하는 오버헤드다.
더 큰 저장장치들은 더 작은 저장장치보다 더 느린 속도를 가질 수밖에 없고, 프로세서는 메인 메모리보다 훨씬 빠르다. 게다가 메인 메모리를 더 빠르게 동작하도록 만드는 것 보다 프로세서를 더 빨리 동작하도록 만드는 것이 더 쉽고 비용이 적게 든다.
프로세서 - 메모리 격차에 대응하기 위해 캐시 메모리라는 저장장치 안에 프로세서가 단기간에 필요로 할 가능성이 높은 정보를 임시로 저장한다.
프로세서 안에 들어있는 L1 캐시는 거의 레지스터 파일만큼 빠르게 액세스 가능하다.
이보다 좀 더 큰 L2 캐시는 수백 킬로바이트에서 수 메가바이트의 용량을 가지며, 프로세서와 전용버스를 통해 연결된다.
L2 캐시는 L1 캐시보다 5배정도 느리지만, 메인 메모리보다는 5-10배 빠르다. 이러한 캐시메모리를 잘 사용한다면 프로그램 성능을 10배 이상 개선할 수 있다.
위와 같이 작고 빠른 저장장치를 프로세서와 좀 더 크고 느린 장치 사이에 끼워넣는 아이디어는 곧 레지스트리에서 내려가는 피라미드 구조로 구성되어 있다.
계층의 꼭대기일수록 더 빠르고, 더 작고, 비싸다.
메모리 계층구조의 주요 아이디어는 한 레벨의 저장장치가 다음 하위레벨의 저장장치의 캐시 역할을 한다는 것이다.
쉘에서 프로그램이 해석되고 결과가 출력될 때, 프로그램은 키보드, 디스플레이, 디스크, 메인 메모리를 직접 액세스 하지 않는다. 응용 프로그램은 하드웨어를 제어하려면 반드시 운영체제를 통해서 해야한다.
프로세스는 실행중인 프로그램에 대한 운영체제의 추상화다. 다수의 프로세스들은 동일한 시스템에서 동시에 실행될 수 있으며 각 프로세스는 하드웨어를 배타적으로 사용하는 것 처럼 느낀다.
이전의 시스템에선 한 번에 한 개의 프로그램만 실행할 수 있었지만 지금의 멀티코어 프로세서들은 여러개의 프로그램을 동시에 실행할 수 있다.
하지만, 어느쪽이건 프로세서가 프로세스들을 바꿔주는 방식으로 한 개의 CPU가 다수의 프로세스를 동시에 실행하는 것 처럼 보이게 해준다.
운영체제는 문맥 전환(context switching)이라는 방법을 통해서 이러한 교차실행을 수행한다.
운영체제는 프로세스가 실행하는데 필요한 모든 상태정보의 변화를 추적한다. 단일 프로세서 시스템은 한개의 프로세스의 코드만을 실행할 수 있다. 운영체제는 현재 프로세스에서 다른 새로운 프로세스로 제어를 옮기려고 할 때, 현재 프로세스의 컨텍스트를 저장하고 새 프로세스의 컨텍스트를 복원하는 문맥 전환을 실행하여 제어권을 새 프로세스로 넘겨준다.
하나의 프로세스에서 다른 프로세스로의 전환은 운영체제 커널에 의해 관리된다.
커널은 운영체제 코드의 일부분으로 메모리에 상주하며 별도의 프로세스가 아니고 모든 프로세스를 관리하기 위해 시스템이 이용하는 코드와 자료구조의 집합이다.
프로세스는 실제로 스레드 라고 하는 다수의 실행 유닛으로 구성되어있다.
쓰레드가 중요한 이유는 다수의 프로세스들에서보다 데이터의 공유가 쉽다는 점, 쓰레드가 프로세스보다 더 효율적이라는 점이다.
가상메모리는 각 프로세스들이 메인메모리 전체를 독점적으로 사용하고 있는 것 같은 환상을 제공하는 추상화이다.
기본적인 아이디어는 프로세스의 가상메모리의 내용을 디스크에 저장하고, 메인 메모리를 디스크의 캐시로 사용하는 것이다.
연속된 바이트들
최신 시스템들은 네트워크에 의해 다른 시스템과 종종 연결되며 이러한 개별 시스템의 관점에서 볼 때, 네트워크는 또 다른 입출력 장치라고 볼 수 있다.
예시)
1. 이용자가 키보드로 “hello”입력
2. 클라이언트가 telnet 서버로 “hello” 전송
3. 서버에서 “hello”를 원격 쉘로 전송 후 프로그램 실행, 결과 출력
4. telnet 서버가 “hello, world \n”을 클라이언트로 전송
5. 클라이언트가 display에 “hello, world\n” 출력
시스템은 단지 하드웨어 그 이상의 것이며 응용프로그램의 실행이라는 궁극의 목적을 달성하기 위해 협력해야 하는 하드웨어와 시스템 소프트웨어가 서로 연결된 것을 말한다.
우리가 어떤 시스템의 한 부분의 성능을 개선할 때, 전체 시스템 성능에 대한 효과는 그 부분이 얼마나 중요한가와 이 부분이 얼마나 빨라졌는가에 관계된다는 것이다.
전체 시스템을 상당히 빠르게 하기 위해선 전체 시스템의 매우 큰 부분의 성능을 개선해야 한다.
동시성: 다수의 동시에 벌어지는 일을 갖는 시스템에 관한 일반적인 개념
병렬성: 동시성을 사용해서 시스템을 보다 빠르게 동작하도록 하는 것을 말할 때 사용
다수의 프로그램이 동시에 실행되는 시스템 => 동시성
쓰레드를 사용하면 한 개의 프로세스 내에서 실행되는 다수의 제어 흐름을 가질 수 있다.
이러한 형태의 동시성은 여러명이 한 개의 웹 서버로부터 페이지를 사용하고자 할 때 처럼 다수의 사용자들이 시스템과 동시에 교신할 수 있게 해주며, 또한 한 명의 사용자가 다수의 태스크에 동시에 연관될 수 있게 해준다.
어떤 시스템이 여러개의 프로세서를 가지고 하나의 운영체제 커널의 제어 하에 동작하는 경우를 멀티프로세서 시스템이라고 한다.
멀티쓰레딩이라고도 하는 하이퍼 쓰레딩은 하나의 CPU가 여러 개의 제어흐름을 실행할 수 있게 해주는 기술이다.
멀티프로세싱의 이용은 시스템 성능을 두 가지 방법으로 개선할 수 있다.
1. 다수의 태스크를 실행할 때, 동시성을 시뮬레이션할 필요를 줄여준다.
2. 멀티프로세싱으로 한 개의 응용프로그램을 빠르게 실행할 수 있지만, 프로그램이 병렬로 효율적으로 실행할 수 있는 멀티쓰레드의 형태로 표현되었을 때에만 가능하다.
최근의 프로세서들은 훨씬 낮은 수준에서의 추상화로 여러 개의 인스트럭션을 한 번에 실행할 수 있다. 파이프라이닝에서는 하나의 인스트럭션을 실행하기 위해 요구되는 일들을 여러 단계로 나누고, 프로세서 하드웨어가 일련의 단계로 구성되어 이들 단계를 각각 수행한다. 사이클당 한 개 이상의 인스트럭션을 실행할 수 있는 프로세서를 슈퍼스칼라라고 한다.
최신 프로세서들은 최하위 수준에서 싱글 인스트럭션, 다중 데이터 즉 SIMD 병렬성이라는 모드로 한 개의 인스트럭션이 병렬로 다수의 연산을 수행할 수 있는 특수한 하드웨어를 가지고 있다.
이 SIMD 인스트럭션은 대개 소리, 영상, 동영상 데이터 처리를 위한 응용프로그램의 속도를 개선하기 위해 제공된다.
좋은 프로그래밍 연습의 한 가지 측면은 함수들을 간단한 응용프로그램 인터페이스 API로 정형화 하는 것으로 프로그래머가 그 내부의 동작을 고려하지 않으면서 코드를 사용할 수 있도록 해준다.
컴퓨터 시스템은 응용프로그램을 실행하기 위해 함께 동작하는 하드웨어와 시스템 소프트웨어로 구성된다.
프로세서는 메인 메모리에 저장된 바이너리 인스트럭션을 읽고 해석한다.
시스템의 저장장치들은 계층구조를 형성하여 CPU 레지스터가 최상위에, 하드웨어 캐시메모리, DRAM 메인 메모리, 디스크 저장장치등이 순차적으로 위치한다.
상부에 위치한 저장장치들은 하부의 장치들보다 비트당 단가가 더 비싸고, 더 빠르다.
상부의 저장장치들은 하부의 장치들을 위한 캐시 역할을 수행한다.
운영체제 커널은 응용프로그램과 하드웨어 사이에서 중간자 역할을 수행한다.
운영체제는 3가지 근본적인 추상화를 제공한다.
파일은 입출력장치의 추상화다.
가상메모리는 메인 메모리와 디스크의 추상화다.
프로세스는 프로세서, 메인 메모리, 입출력 장치의 추상화다.
네트워크는 컴퓨터 시스템이 서로 통신할 수 있는 방법을 제공하며 일종의 입출력장치로 볼 수 있다.