1.1 정보는 비트와 컨텍스트로 이루어진다
컴퓨터 시스템은 소프트웨어와 하드웨어로 구성됩니다.
텍스트 파일: 아스키 문자로만 이루어진 파일
바이너리 파일: 제외한 다른 모든 파일
프로그래머가 작성한 소스파일은 컴파일 과정을 거쳐서 0과 1로 표시되는 비트들로 변환됩니다. 바이트(Byte)는 8비트로 구성됩니다.
프로그램 내의 문자 데이터들은 아스키 표준(ASCII)을 사용해서 표시됩니다.
아스키 코드: 각 문자열을 이진수로 표현하고
예시로 'a' = 97, byte 값: 01100001, 저장크기: 1byte(8bit)
아스키 코드는 7 bit 아스키, 8 bit 확장 아스키로 나뉩니다. 7 bit는 알파벳, 기본 기호 다루고 다양한 언어, 특수기호를 다루기 위해 8 bit 아스키/확장 인코딩이 도입되었습니다(예: UTF-8)
- 7 bit 아스키: 128개의 고유한 문자(값의 범위가 0~127)
- 제어문자, 다양한 기호, 숫자, 대소문자를 포함합니다
- 0~127 값을 가지는 7 bit 길이의 정수입니다.
- 8 bit 아스키: 256개의 고유한 문자(값의 범위가 0~255)
- 7 bit 아스키 세트 + 특수문자, 그래픽 기호, 다국어 문자
- 0~255 값을 가지는 8 bit 길이의 정수입니다.
1.2 프로그램은 다른 프로그램에 의해 다른 형태로 번역된다
자세한 컴파일 과정은 👉여기서 확인
프로그래머가 작성한 소스 코드는 컴파일 과정을 거쳐 실행파일로 변환됩니다.
1. 소스코드를 전처리
2. 전처리 된 소스코드를 컴파일
3. 어셈블리어 코드를 어셈블러 통해 어셈블
4. 기계어 코드/목적 파일을 링킹해서 실행파일 생성
1.3 컴파일 시스템이 어떻게 동작하는지 이해하는 것은 중요하다
컴파일 시스템을 이해해야 하는 이유에는 몇 가지가 있습니다.
- 컴파일러가 C언어로 작성된 기계어 코드를 어떻게 번역하는지 알아야 성능 효율적인 코드 작성이 가능합니다.
- 링크 에러, 링커의 동작을 이해하는 것이 중요합니다.
- 버퍼 오버플로우 등 보안약점을 이해하는 것도 중요합니다.
1.4 프로세서는 메모리에 저장된 인스트럭션을 읽고 해석한다
1.5 캐시가 중요하다
시스템이 정보를 한 곳에서 다른 곳으로 이동시키는 데에는 많은 시간이 소요됩니다.
예로 우리가 프로그램을 로딩 시에 보조기억장치(HDD/SSD)에 저장된 데이터를 메인메모리(DRAM)로 복사하는 과정을 거치고, 프로그램을 실행하여 디스플레이에 무언가를 출력할 때도 메인메모리에 저장된 데이터를 프로세서로 복사하는 과정을 거칩니다.
프로그래머의 관점에서 이 '복사' 되는 과정이 실제 작업을 느리게 하는 오버헤드가 됩니다. 이런 복사과정을 빠르게 동작할 수 있도록 하여 오버헤드를 줄이기 위해 바로 캐시(Cache)를 활용하는 것이다.
물리학 법칙으로 인해 큰 용량의 저장장치들은 작은 저장장치보다 느린 속도를 가지고, 빠른 장치들은 느린 장치들보다 만드는데 비용이 더 많이 듭니다. 매년 반도체 기술의 발전으로 인해 프로세서와 메모리간 성능 격차가 지속적으로 증가하는데, 메모리의 동작 속도를 높이는 것보다 프로세서의 동작 속도를 높이는 것이 더 쉽고 비용이 적게 듭니다.
프로세서의 동작 속도를 높이기 위해 고안된 것이 바로 캐시 메모리(캐시)입니다. 캐시는 프로세서 칩 내에 탑재되어 프로세서가 단기간 내에 필요로 할 데이터를 저장하고 필요 시 빠르게 꺼내서 활용할 수 있도록 합니다.
캐시의 종류
L1:
- 대략 수천 바이트의 데이터를 저장합니다.
- 레지스터 파일만큼 빠른 속도로 액세스가 가능합니다.
- L2 캐시보다 용량이 적은 만큼 L2 캐시보다 속도가 5배 정도 빠릅니다.
L2:
- L1 캐시보다 용량이 조금 더 크며 수백 킬로바이트(KB)에서 수 메가 바이트(MB)의 용량을 가집니다.
- 프로세서와 전용 버스를 통해 연결됩니다.
- 메인 메모리 보다 5배~10배 더 빠릅니다.
L1, L2, L3 캐시 모두 SRAM(Static Random Access Memory) 라는 하드웨어 기술을 이용해서 구현합니다.

반도체 기억 장치의 한 종류이며 주기적으로 내용 갱신이 필요한 DRAM(동적 램)과는 다르게 기억 장치에 전원이 공급되는 한 저장된 내용이 계속 보존됩니다. 데이터의 읽고 쓰기가 이루어지는 주소와 관계없이 입출력에 걸리는 시간이 일정하며, DRAM보다 훨씬 빠른 입출력을 가능하게 한다는 장점이 있습니다.
이런 장점으로 인해 속도를 중요시하는 CPU 내부의 기억 장치인 파이프라인, 레지스터, CPU 캐시 등이 사용됩니다.
출처: https://ko.wikipedia.org/wiki/%EC%A0%95%EC%A0%81_%EB%9E%A8
캐시가 L1, L2, L3로 총 3단계로 이루어져 있는 경우도 있습니다.
L3:
- 자주 액세스할 가능성이 높은 데이터만 보관하여 더욱 더 빠른 메모리 작업을 수행할 수 있도록 합니다.
- 지엽적인 영역의 코드와 데이터를 액세스하는 지역성(locality) 특징이 있습니다.
1.6 저장장치들은 계층구조를 이룬다
메모리는 속도, 크기, 가격에 따라 최상위 계층인 L0부터 최하위 계층인 L6까지 나뉩니다. 계층이 낮을 수록 저장장치의 동작 속도는 느리고, 사이즈는 크고, 바이트당 가격이 더 쌉니다.
계층 구조에서 특정 계층의 메모리는 바로 아래 하위 계층 메모리의 캐시 메모리 역할을 합니다. 예를 들어 L1 캐시는 바로 아래 계층인 L2 캐시의 데이터를 저장하고, L2 캐시는 L3 캐시의 데이터를 저장합니다. L3 캐시는 메인 메모리 DRAM의 데이터를 저장합니다.
1.7 운영체제는 하드웨어를 관리한다
쉘이 프로그램을 로드/실행할 때 절대 프로그램이 키보드나, 디스플레이, 디스크, 메인 메모리에 직접 액세스 하지 않습니다. 대신 운영체제(Operation System: OS)가 제공하는 서비스를 활용하여 해당 장치에 액세스하고 장치를 활용합니다.
즉, 응용 프로그램 ➡ 운영체제(OS) ➡ 하드웨어 장치 순으로 접근이 이루어집니다. 운영체제가 응용 프로그램과 하드웨어 장치 사이의 일종의 인터페이스 역할을 하는 것이죠.
운영체제는 기본적으로 두 가지 목적을 가집니다.
1. 오류나 버그로 인해 응용 프로그램이 하드웨어를 잘못 사용하는 것을 방지
2. 응용 프로그램들의 단순하고 균일한 메커니즘을 사용한 복잡한 저수준 하드웨어 장치들의 조작 지원
운영체제는 위 두가지 목적을 추상화를 통해 달성할 수 있도록 합니다.
1.7.1 프로세스
프로세스는 실행 중인 프로그램에 대한 운영체제의 추상화입니다.
대부분의 시스템에서 프로세스를 실행할 CPU의 숫자보다 더 많은 프로세스들이 존재합니다. 과거에는 한 번에 한 개의 프로그램만 실행할 수 있었지만, 요즘의 멀티코어 프로세서들은 여러 개의 프로그램을 동시에 실행할 수 있습니다. 사용자가 실제로 컴퓨터를 사용할 때 CPU의 숫자보다 많은 프로그램을 동시에 실행하기도 합니다.
하지만 이는 프로세서가 프로세스를 동시에 수행하는 것처럼 보이는 것이지 실제로는 아닙니다. 운영체제는 다수의 프로그램을 동시에 실행하는 것이 아닌, 문맥 교환(Context Switching)을 통해서 프로세스의 교차 실행을 수행합니다.
프로세스(프로그램의 추상화)가 실행되는데 필요한 기본적인 정보인 상태정보는 PC, 레지스터 파일, 메인 메모리의 현재 값을 포함하며, 이 정보의 집합을 바로 컨텍스트(Context)라고 합니다. 운영체제는 프로세스에 대한 상태정보인 이 컨텍스트에 대한 변화를 추적합니다. 앞서 언급한 문맥 교환(Context Switching)을 통해 운영체제가 다른 프로세스로 제어를 옮기려고 할 때, 현재 프로세스에 대한 정보를 컨텍스트에 저장하고 옮기려는 새 프로세스의 컨텍스트를 복원시키고 제어권을 새 프로세스로 넘겨줍니다.
위의 프로세스 간의 전환은 운영체제 커널(kernel)에 의해 관리됩니다. 커널은 별도의 프로세스가 아니라 모든 프로세스를 관리하기 위해 시스템이 이용하는 코드와 자료구조의 집합입니다. 커널은 운영체제 코드의 일부분으로 메모리에 상주하는데, 응용 프로그램이 어떤 작업을 OS를 통해 요청하면 컴퓨터는 파일 읽기나 쓰기 같은 특정 시스템 콜(system call)을 실행해서 커널로 넘겨줍니다. 커널은 받은 작업(시스템 콜)을 수행해서 응용프로그램으로 리턴합니다.
1.7.2 스레드(Thread)
프로세스는 하나의 제어흐름을 갖는 것이 아닌 스레드(Thread)라고 하는 다수의 실행 유닛(작업의 단위)으로 구성됩니다. 각각의 스레드는 속한 프로세스의 컨텍스트에서 실행되며 프로세스 내에서 동일한 코드와 전역 데이터를 공유합니다.
다수의 프로세스에서의 데이터 공유보다 프로세스 내에서 스레드 간의 데이터 공유가 더 쉽고 스레드가 프로세스보다 더 효율적이라는 점에서 프로그래밍 모델로서 스레드는 굉장히 중요합니다. 멀티 스레딩을 통해 프로그램을 효율적으로 실행하고 프로그램의 실행 속도를 높일 수도 있습니다.
1.7.3 가상메모리
가상 메모리는 각 프로세스들이 메인 메모리 전체를 독점적으로 사용하고 있는 것 같은 환경을 제공하는 추상화입니다(당연하게 프로세스는 메인 메모리의 일부분을 점유해서 사용하지 전체를 사용하지 않습니다). 각 프로세스들은 가상 주소 공간이라고 하는 균일한 메모리의 모습을 갖습니다.
리눅스에서 주소 공간의 최상위 영역은 모든 프로세스가 공통으로 사용하는 'OS'의 코드와 데이터를 위한 공간이며, 아래 하위 영역들은 '사용자' 프로세스의 코드와 데이터를 저장합니다. 즉 프로세스 가상메모리의 최상위 영역에는 프로그램이 원활하게 동작하기 위한 코드가 저장되고 최상위 영역의 하위 영역에는 사용자가 직접 작성한 코드와 관련 데이터가 저장됩니다. 주소 공간의 위쪽으로 갈수록 주소값은 증가한다는 특징이 있습니다.
주소 공간은 아래의 영역들로 구분됩니다. 높은 주소값을 가지는 영역부터 나열하였습니다.
프로세스 가상주소공간
1. 커널 가상메모리:
주소공간의 맨 윗부분은 커널을 위해 예약되어 있습니다. 응용프로그램이 이 영역의 내용을 읽거나 수정, 커널 코드 내에 정의된 함수를 직접 호출되는 것은 금지되어 있으며 이러한 작업들은 오직 커널을 통해서만 수행이 가능합니다.
즉 해당 주소 공간에 액세스 하기 위해서는 응용프로그램 ➡️ OS ➡️ 커널 ➡️ 프로세스의 커널 가상메모리를 통해 접근하게 됩니다.
2. 스택(Stack):
사용자 가상메모리 공간의 맨 위에 컴파일러가 함수 호출을 구현하기 위해 사용하는 영역입니다. 프로그램이 실행되는 동안 동적으로 크기가 변하는 특징을 가지며, 함수가 호출될 때 크기가 커지고 리턴 시에 줄어듭니다.
3. 공유 라이브러리:
C 표준 라이브러리나 수학 라이브러라 같은 공유 라이브러리의 코드와 데이터를 저장하는 영역입니다.
4. 힙(Heap):
프로세스가 실행되면서 C의 표준함수인 malloc이나 free 호출 시에 런타임에 크기가 동적으로 변합니다.
5. 프로그램 코드와 데이터:
프로그램이 로드되면 기본 주소는 주소 공간 배치 난수화(ASLR, Address Space Layout Randominaztion)를 통해 메모리 주소가 무작위로 배치됩니다. 목적파일이 실행 됨에 따라 전역 변수, 정적 변수, 스택, 힙 등이 적절한 메모리 주소에 배치되고 초기화되는 과정을 거칩니다.
1.7.4 파일
더 설명할 것 없이 연속된 바이트들을 파일이라고 부릅니다. 우리가 사용하는 디스크, 키보드, 디스플레이, 네트워크까지 포함하는 모든 입출력장치는 파일로 모델링(추상화)이 가능합니다. 파일로 모델링 된 입출력 장치는 응용 프로그램이 손쉽게 이용할 수 있습니다. 시스템의 모든 입출력은 유닉스 I/O라는 시스템 콜을 사용하여 파일을 읽고 쓰는 형태로 이루어집니다.
입출력 장치들을 파일로 모델링함으로서 아래와 같은 이점을 얻을 수 있습니다.
1. 일관된 인터페이스를 통해 동일한 시스템 콜(open, read, write, close 등)을 사용하여 파일과 장치 간의 작업 방식을 통일할 수 있습니다.
2. 하드웨어 장치의 복잡한 동작을 추상화하여, 프로그래머가 세부 사항을 신경 쓰지 않고 입출력 작업을 수행할 수 있습니다.
3. 동일한 파일 기반 인터페이스를 사용함으로써 다양한 장치에 대해 동일한 코드를 사용할 수 있습니다.
1.8 시스템은 네트워크를 사용하여 다른 시스템과 통신한다
일반적인 최신 컴퓨터들은 네트워크에 의해 다른 컴퓨터로 연결되어 데이터 전송 및 수신을 할 수 있습니다. 이런 점에서 네트워크 또한 하나의 입출력 장치로 간주 할 수 있습니다.
시스템이 메인 메모리(DRAM)로부터 네트워크 어댑터로 일련의 바이트를 복사하게 되면 데이터는 메인메모리 ➡️ 보조기억장치(SSD, HDD)로 데이터가 복사되지 않고 네트워크를 통해 바로 다른 컴퓨터로 이동합니다. 네트워크를 통해 데이터를 전송받은 컴퓨터는 데이터를 읽어 메인메모리에 복사합니다.
네트워크를 사용한 데이터 전송 및 수신의 예시를 하나 들어봅시다.
telnet과 같은 응용 기술을 사용하여 클라이언트와 서버간의 데이터 통신이 가능합니다. 아래는 그 과정입니다.
1. 로컬 컴퓨터 A에서 telnet 클라이언트에 접속하여 "hello" 스트링을 입력하고 enter를 누른다.
2. telnet 서버가 네트워크에서 스트링을 받아서 원격 쉘 프로그램에 전달한다.
3. 원격 쉘은 hello 프로그램을 실행하고 결과를 telnet 서버로 다시 전달한다.
4. telnet 서버는 네트워크를 통해 출력 스트링을 telnet 클라이언트로 전달하고 클라이언트는 출력 스트링을 로컬 터미널에 표시한다.
1.9 중요한 주제들
결과적으로 시스템이란 것은 응용프로그램의 실행이라는 궁극적 목적을 달성하기 위해 하드웨어와 시스템 소프트웨어가 협력을 위해 연결된 것을 말합니다.
1.9.1 Amdahl의 법칙(암달의 법칙, Amdahl's Law)
Gene Amdahl에 의해 고안된 성능 개선율을 계산하기 위해 사용되는 기법입니다. 암달의 법칙을 통해 어떤 시스템의 한 부분을 개선할 때, 해당 부분 개선으로 인해 전체 시스템의 성능이 얼마나 향상되는지 계산할 수 있습니다.
해당 법칙을 컴퓨터에 적용할 경우, 컴퓨터 시스템의 일부 성능 개선 시에 전체적으로 얼마만큼의 성능 개선이 있는지 계산하는데에 사용할 수 있습니다.
1.9.2 동시성과 병렬성
동시성과 병렬성은 컴퓨터의 작업 처리 속도를 향상시키는데 큰 기여를 해 왔습니다.
동시성(concurrency): 다수에 동시에 벌어지는 일을 갖는 시스템에 대한 일반적인 개념
병렬성(parallelism): 동시성을 사용해서 보다 빠르게 동작하도록 하는 것
대표적인 세 개의 동시성/병렬성의 수준에 대해 알아봅시다.
스레드 수준 동시성
스레드를 이용하면 하나의 프로세스 내에서 다수의 제어흐름을 가질 수 있으며 이러한 스레드를 여러 개를 사용하는 것이 바로 동시성의 예시입니다. 이용자가 음악 스트리밍, 문서 작성, 웹 서핑 등을 동시에 진행할 경우 해당 작업들에 대한 실질적인 계산은 프로세서 하나가 담당하게 된다면 해당 구성은 단일 프로세스 시스템입니다.
반면 시스템이 여러 개의 프로세서를 가지고 하나의 운영체제 커널의 제어 하에 동작하는 경우를 멀티프로세서 시스템이라고 합니다. 최근의 멀티코어 프로세서, 하이퍼스레딩(Hyperthreading)의 등장으로 인해 일반적인 시스템으로 자리잡게 되었습니다.
멀티코어 프로세서:
여러개의 CPU(코어로도 불림)를 하나의 직접화 된 칩에 내장한 것입니다. 예로 인텔의 i7 프로세서는 칩 내에 4개의 코어를 가지고 있으며, 각 코어는 L1, L2 캐시를 가지고 있습니다. L3 캐시는 칩에 하나만 존재합니다.
멀티스레딩:
하이퍼스레딩이라고도 하는 이 기술은 하나의 CPU가 여러개의 제어흐름을 수행 및 관리하는 것을 의미합니다. 이름에서 유추할 수 있듯이, 여러개의 제어흐름을 관리하기 위해서는 다수의 스레드를 필요로 합니다. 기존의 단일 프로세서가 스레드가 전환을 위해 약 2만번의 클럭 사이클이 요구된다면 멀티코어 프로세서의 경우 매 사이클마다 실행할 스레드를 결정하기 때문에 성능을 획기적으로 향상시킬 수 있습니다.
인스트럭션 수준 병렬성
이전의 초기 마이크로프로세스들의 경우 하나의 인스트럭션을 수행하는데 여러 클럭 사이클(대략 3.1개)이 필요했지만 최근의 프로세서들은 클럭 사이클 당 2.4개의 인스트럭션을 수행합니다. 엄청난 발전이죠. 실제로 인스트럭션의 수행 시간은 실제로 긴 시간이 필요하지만 파이프라이닝과 같은 기법을 통해 수행 단계를 여러 단계로 나누어 병렬 처리하여 수행 시간을 크게 단축할 수 있습니다.
슈퍼스케일러:
사이클당 한 개 이상의 인스트럭션을 수행할 수 있는 프로세서입니다. 최근 프로세서들은 대부분 슈퍼스케일러 동작을 지원합니다.
싱글 인스트럭션, 다중 데이터 병렬성(SIMD)
최신 프로세서들의 경우 하나의 인스트럭션을 여러 병렬로 처리할 수 있는 SIMD 병렬성이라는 모드를 지원하는 특수 하드웨어를 가집니다. 인텔과 AMD 사의 최신 세대의 프로세서들은 부동소수의 연산을 병렬로 처리 가능한 인스트럭션을 가집니다.
일반적으로 SIMD 인스트럭션은 대개 소리, 영상, 동영상 데이터 처리를 위한 응용프로그램의 속도 개선을 위해 사용됩니다.
1.9.3 컴퓨터 시스템에서의 추상화의 중요성
앞서 설명했듯이 추상화는 기능 및 하드웨어를 내부 동작을 고려하지 않는(복잡성 배제) 간단하고 바로 사용가능 형태로 제공합니다. 자바의 경우에는 클래스, C의 경우에는 함수 프로토타입을 예시로 들 수 있습니다.
운영체제 측면에서의 세 가지 추상화의 경우
1. 입출력 장치 ➡️ 파일
2. 프로그램 메모리 ➡️ 가상메모리
3. 실행 중인 프로그램 ➡️ 프로세스
운영체제, 프로세서, 프로그램 모두를 포함하여 추상화 한 것을 가상 머신이라고 합니다. 주로 여러 종류의 운영 체제나 동일한 운영체제의 다른 버전에서 프로그램을 실행할 수 있도록 컴퓨터를 관리하기 위해 사용합니다.
'CS' 카테고리의 다른 글
[CS] OSI 7계층(OSI Model) (0) | 2024.08.16 |
---|---|
[CS] BSD 소켓(Socket) (0) | 2024.08.16 |
[C언어] GCC(GNU Compiler Collection) (1) | 2024.07.26 |
[CS] 가상화(Virtualization) (3) | 2024.07.26 |
[C언어] 포인터(Pointer) (1) | 2024.07.25 |