컴퓨터는 어떻게 작동하는가? 단순히 코드가 입력되면 코드가 작은 난쟁이로 변해 컴퓨터 속을 헤집고 다니며 우리의 명령어를 충실히 수행하는 것일까?
이 책은 이러한 물음에 명쾌한 해답을 줄 수 있는 책으로, 하드웨어가 어떻게 구성되고 그 안에서 코드란 것이 어떤 환경에서 어떤 식으로 작동하게 되는지 미시적인 관점에서 근본적인 이해를 돕는 책이다.
다만 기본 영어 원서인 책을 한글로 번역하는 과정에서 다소 의역과 번역체가 난무하기에 비전공자가 바로 공략하기엔 난해할 수 있다.
컨텍스트는 컴퓨터 내부에서 볼 때, 프로세서 안에 있는 레지스터, 플래그 등의 현재 값/상태들의 집합을 말함
프로그램이 번역되는 기본적인 구조 (아래 순서)
source program(xx.c) ->
[preprocessor] Modified source program(xx.i) ->
[complier] assembly program(xx.s) ->
[assembler] Relocatable object programs(xx.o) ->
[linker] Executable object program(xx)
[preprocessor] : # 문자로 시작하는 directive에 따라 수정. ex. #include<stdio.h> : 시스템 헤더파일인 stdio.h를 프로그램 문장에 직접 삽입하라.
[complier] : 어셈블리어로 변환
[assembler] : 기계어 인스트런션으로 번역
[link] : object programs를 통합하여 실행파일로 변환
전처리 → 전처리 명령어 처리
컴파일 → 소스 코드를 어셈블리어로 변환
어셈블리 → 어셈블리어를 기계어(객체 파일)로 변환
링킹 → 객체 파일을 연결하여 실행 파일 생성
로딩 → 실행 파일을 메모리에 로드하고 실행
-O1: 기본적인 최적화로, 코드 크기를 줄이고 약간의 성능 향상을 제공합니다.
-O2: 더 적극적인 최적화로, 실행 성능을 크게 향상시키지만 컴파일 시간이 길어질 수 있습니다.
-O3: 더 많은 루프 최적화와 같은 고급 최적화를 포함하여 최대 성능을 목표로 합니다.
-Ofast: 매우 공격적인 최적화로, 코드의 안전성을 포기하고 성능을 최대한 끌어올리는 옵션입니다.
이와 같은 컴파일 시스템 최적화 방법을 활용하면, 프로그램 성능을 더욱 효과적으로 개선할 수 있습니다. 컴파일러는 소스 코드를 자동으로 분석하고 다양한 최적화를 적용하여 성능을 높일 수 있는 강력한 도구입니다. 최적화는 성능, 코드 크기, 실행 시간 등의 목표에 따라 다르게 설정될 수 있으며, 그 결과 실행 성능을 크게 향상시킬 수 있습니다.
시스템의 하드웨어 구조
버스
컴포넌트 간 바이트 정보 전송하는 전기적 배선군
(컴퓨터의 여러 하드웨어 구성 요소들 간에 데이터를 주고받는 데 사용되는 통로)
(명령어와 주소를 전송하는 중간 매개체 역할)
버스는 일정 크기의 바이트 단위(1 word)로 데이터 전송하도록 설계됨. 운영체제에 따라 32비트, 64비트 등으로 나뉨.
입출력 장치
메인 메모리
프로세서
역할
구성 요소
프로세서의 종류
캐시 메모리
캐시는 일반적으로 다단계(Multi-level) 구조로 되어 있으며, 각 단계마다 속도와 크기가 다릅니다:
L1 캐시 (Level 1 Cache):
프로세서 코어 내부에 위치하며, 가장 작은 크기(수십 KB)와 가장 빠른 속도를 가집니다.
데이터를 거의 즉각적으로 제공할 수 있는 고속 메모리입니다.
각 코어마다 독립적으로 존재합니다.
L2 캐시 (Level 2 Cache):
L1 캐시보다 크고 느리지만 여전히 매우 빠름(수백 KB에서 수 MB).
L2 캐시는 각 코어마다 독립적으로 존재하거나, 여러 코어가 공유할 수도 있습니다.
L3 캐시 (Level 3 Cache):
L2 캐시보다 더 크고 느리지만 여전히 주 메모리보다 빠름(몇 MB에서 수십 MB).
일반적으로 여러 코어가 공유하는 캐시입니다.
L4 캐시 (일부 고성능 시스템에서 존재):
드물게 사용되며, 메모리와의 접근을 더 빠르게 하기 위한 추가적인 캐시로 매우 큰 크기(수백 MB 이상)일 수 있습니다.
웬만한 프로세서에서는 L3 캐시 메모리를 달고있지 않다. L2 캐시로 충분히 커버할 수 있기 때문이다. intel core2 duo나 quad에는 L3 캐시가 없지만, 코어 i7에는 8MB를 달아뒀다. L1/L2 캐시 메모리 정도만 CPU 성능에 직접적인 영향을 미치기에 L3 캐시는 크게 신경쓰지 않는것이 일반적인 추세다. L3 캐시는 CPU가 아닌 메인보드에 내장되는 경우가 더 많다.
작고 빠른 상위 레벨 저장장치는 보다 크고 느린 하위 레벨 저장장치의 캐시 역할을 한다.
일반적인 컴퓨터 프로그램의 구동에는 레지스터, 캐시, RAM의 세 가지 기억 장치를 사용한다. 이들은 SSD와 HDD보다 처리 속도가 훨씬 빠른 기억 장치로, '주 기억장치'라고 부른다.
예를 들어, 지금 이 문서를 읽는 동안 여러분의 컴퓨터(또는 같은 기능을 하는 모바일 기기)는 웹 브라우저를 구동하고 있다.
이때 웹 브라우저는 RAM과 캐시, CPU를 통해 열심히 정보를 읽고 쓰는 중이다.
그 뒤 웹 브라우저를 끄고 게임을 할 때는 어떨까? 웹 브라우저는 더 이상 구동되지 않지만, 필요할 때 언제든지 다시 실행할 수 있도록 SSD, HDD에 브라우저 프로그램의 데이터는 남아 있다. 그러나 지금 당장 사용하고 있지 않기 때문에 RAM 이상의 처리 성능을 지닌 기억장치에는 웹 브라우저가 이미 들어있지 않다. 주 기억 장치는 게임을 실행하느라 바쁘다.
접근 시간이 짧을 수록 비트당 비용이 높아진다.
용량이 클수록 비트당 비용이 낮아진다.
용량이 클수록 접근 시간이 길어진다.
더 빠른 속도
캐시 메모리는 SRAM(Static RAM)이라는 기술을 기반으로 만들어지는데, 이는 DRAM(Dynamic RAM)을 사용하는 메인 메모리보다 훨씬 빠릅니다. SRAM은 DRAM처럼 주기적인 새로 고침(refresh)이 필요하지 않아서 더 빠른 데이터 접근이 가능합니다. 빠른 성능을 제공하는 만큼 제조 공정도 더 복잡하고, 이에 따라 비용이 증가합니다.
복잡한 구조
SRAM은 DRAM에 비해 복잡한 회로 구조를 가지고 있습니다. DRAM은 하나의 트랜지스터와 하나의 커패시터로 비트를 저장하지만, SRAM은 하나의 비트를 저장하는 데 6개의 트랜지스터를 사용합니다. 이 때문에 같은 용량을 만들기 위해서 훨씬 더 많은 트랜지스터가 필요하며, 그 결과 칩의 크기가 커지고 제조비용이 상승합니다.
전력 소비
SRAM은 DRAM보다 더 적은 전력을 소모합니다. 메인 메모리는 데이터 저장을 위해 주기적으로 새로 고침을 해야 하지만, 캐시는 그렇지 않으므로 전력 소모가 적습니다. 이로 인해 효율적인 전력 관리가 가능하지만, 전력 관리 회로의 복잡성도 추가되어 비용이 올라갑니다.
제조 공정의 복잡성
캐시 메모리는 메인 메모리에 비해 더 작은 크기로 더 빠른 성능을 제공해야 하기 때문에 고급 제조 공정이 필요합니다. 높은 성능을 유지하면서도 작은 면적에 많은 트랜지스터를 집적해야 하는 기술적 난제가 있으며, 이로 인해 제조 공정이 더 복잡하고 고가의 장비가 필요합니다.
소량 생산
메인 메모리(RAM)는 상대적으로 대량 생산되며, 수요도 매우 많아 대규모로 생산됩니다. 그러나 캐시 메모리는 CPU 내부에 내장되며, 상대적으로 소량 생산됩니다. 대량 생산에 의한 단가 절감 효과가 적기 때문에 캐시 메모리의 가격은 더 비싸게 책정됩니다.
높은 신뢰성 요구
캐시 메모리는 CPU와 직접 연결되어 즉각적인 데이터 접근을 제공하는 중요한 역할을 하기 때문에 높은 신뢰성이 요구됩니다. 이로 인해 오류 발생을 최소화하기 위한 추가적인 설계와 테스트가 필요하며, 이는 비용 상승의 원인이 됩니다.
저장 장치 간의 성능 차이에는 메모리 자체의 성능 문제도 있겠지만, CPU 차원에서의 소프트웨어적인 주소 처리 문제나 부품의 설계 자체에 의한 물리적 거리 문제도 있다. 이것이 모든 문제의 근원이다.
빠르고 큰 저장 장치는 비싸다는 것은 컴퓨터라는 개념 자체가 정립된 이래로 항상 발생한 문제점이기에, 컴퓨터 공학자들은 싸고 빠른 컴퓨터를 만들 수 있는 방법을 연구하기 시작했다.
그래서 1990년대에 들어서 레지스터-L1캐시-DRAM-HDD-플로피 디스크 정도의 메모리 계층 구조가 정립되었다. 그러나, 각 계층별 격차가 매우 크고 특히 L1 캐시 - DRAM과 DRAM - HDD 사이의 간극이 어마무시했던 관계로, 이로 인한 성능 최적화 한계를 극복하기 위해 메모리 계층 구조는 점점 더 복잡해져왔다.
https://news.skhynix.co.kr/post/memory-semiconductor-capacity
캐시메모리의 역할을 제대로 수행하기 위해서는 CPU가 어떤 데이터를 원할 것인가를 어느 정도 예측할 수 있어야 한다. 캐시의 성능은 캐시 메모리에 CPU가 이후에 참조할, 쓸모 있는 정보가 어느 정도 들어있느냐에 따라 좌우되기 때문이다.
이 때 적중율(Hit rate)를 극대화 시키기 위해 데이터 지역성(Locality)를 사용한다.
지역성의 전제조건으로 프로그램은 모든 코드나 데이터를 균등하게 Access 하지 않는다는 특성을 기본으로 한다.
즉, Locality란 기억 장치 내의 정보를 균일하게 Access 하는 것이 아닌 어느 한 순간에 특정 부분을 집중적으로 참조하는 특성인 것이다.
- 캐시 적중(Cache Hit) : CPU가 액세스하려는 데이터가 이미 캐시에 적재되어있는 상태.
- 캐시 미스(Cache Miss) : CPU가 액세스하려는 데이터가 캐시에 없어 주기억장치로부터 인출해 와야하는 상태.
- 캐시 적중률(Cache Hit rate) : CPU가 원하는 데이터가 캐시에 있을 확률.
- 캐시에 적중되는 횟수 / 전체 기억장치 액세스 횟수
- 미스율(Miss rate) : CPU가 원하는 데이터가 캐시에 없을 확률, 1-(Cache Hit rate)
https://luv-n-interest.tistory.com/1114
데이터 지역성은 대표적으로 시간적 지역성(Tempora Locality)과 공간지역(Spatial Locality)로 나뉜다.
시간적 지역성은 최근에 참조된 주소의 내용은 곧 다음에 다시 참조되는 특성이다.
메모리 상의 같은 주소에 여러 차례 읽기 쓰기를 수행할 경우, 상대적으로 작은 크기의 캐시를 사용해도 효율성을 꾀할 수 있다.
공간적 지역성은 기억장치 내에 서로 인접하여 저장되어 있는 데이터들이 연속적으로 액세스 될 가능성이 높아지는 특성이다.
CPU 캐시나 디스크 캐시의 경우 한 메모리 주소에 접근할 때 그 주소뿐 아니라 해당 블록을 전부 캐시에 가져오게 된다.
이때 메모리 주소를 오름차순이나 내림차순으로 접근한다면, 캐시에 이미 저장된 같은 블록의 데이터를 접근하게 되므로 캐시의 효율성이 크게 향상된다.
지역성의 특성을 고려하여,
CPU의 캐시에 넣을 때 데이터 지역성을 높일 수 있도록 데이터를 처리하는 순서대로 연속된 메모리에 두면 효율을 높일 수 있다.
응용프로그램이 하드웨어를 제어하려면 언제나 운영체제를 통해야 한다.
운영체제(Operating System, OS)는 아래 2가지 주요 목적을 지닌다.
운영체제는 위와 같은 목적 달성을 위해 '추상화' 개념을 도입해 사용중.
추상화는 하드웨어의 복잡한 동작을 감추고, 프로그램이 단순한 인터페이스를 통해 하드웨어를 사용할 수 있게 해줍니다.
컴퓨터 구조 관점에서
프로세스는 실행 중인 프로그램을 의미하며, CPU(프로세서), 메인 메모리, 입출력 장치 등의 하드웨어 자원들을 추상화한 결과입니다. CPU는 여러 응용 프로그램을 동시에 처리할 수 없기 때문에, 운영체제가 각 프로그램을 프로세스라는 단위로 나누어 순차적으로 처리합니다.
예시
여러 응용 프로그램이 동시에 실행되는 환경에서, 웹 브라우저, 텍스트 에디터, 음악 플레이어 등의 프로그램이 각각 하나의 프로세스입니다. 운영체제는 CPU를 적절히 나누어 이 프로세스들이 끊임없이 동작하도록 합니다.
쓰레드는 프로세스 내에서 실행되는 작은 단위의 작업입니다.
한 프로세스는 여러 개의 쓰레드를 가질 수 있으며, 이 쓰레드들은 동일한 메모리 공간을 공유하면서 동시에 작업을 수행할 수 있습니다. 쓰레드는 CPU를 효율적으로 사용하여 프로그램의 성능을 높이고, 병렬 처리를 가능하게 합니다. 쓰레드는 프로세스보다 더 가벼운 작업 단위로 간주되며, 경량 프로세스(Lightweight Process)라고도 불립니다.
컴퓨터 구조적인 설명
쓰레드의 개념
멀티스레드와 병렬 처리
쓰레드의 상태
쓰레드의 관리
멀티 프로세스와 멀티 쓰레드는 모두 동시 작업을 처리하기 위한 방식이지만, 각 방식의 동작 원리와 자원 관리 방법이 다릅니다. 두 방법 모두 컨텍스트 스위칭을 통해 연속적인 작업을 수행할 수 있지만, 성능과 효율성 측면에서 큰 차이가 있습니다.
멀티 프로세스가 적합한 경우:
독립적인 작업이 필요한 경우. 예를 들어, 각 작업이 서로 독립적으로 실행되어야 하며, 작업 간 충돌이나 자원 공유가 필요하지 않은 경우.
안정성이 중요한 경우. 프로세스 간에 서로 영향을 미치지 않도록 해야 할 때, 멀티 프로세스가 더 안전합니다.
보안이 중요한 시스템에서 서로 다른 프로세스 간에 데이터 보호가 필요할 때.
멀티 쓰레드가 적합한 경우:
자원 공유가 필요한 작업이 있을 때. 같은 메모리 공간을 사용하는 여러 작업이 빠르게 데이터를 주고받을 때 멀티 쓰레드가 적합합니다.
컨텍스트 스위칭 비용이 중요한 경우. 자주 스위칭이 필요한 작업에서는 멀티 쓰레드가 더 효율적입니다.
I/O 작업과 CPU 작업을 동시에 처리해야 할 때. 예를 들어, 네트워크 통신과 데이터 처리 작업을 동시에 진행할 때.
가상 메모리는 실제 물리적 메모리(RAM)와 상관없이, 운영체제가 프로세스마다 독립적인 메모리 공간을 제공하는 기술입니다. 이를 통해 모든 프로그램이 자신만의 고유한 메모리 공간을 가진 것처럼 동작할 수 있으며, 실제 메모리가 부족할 경우 하드디스크의 일부를 가상 메모리로 사용해 물리 메모리 용량을 확장할 수도 있습니다.
가상 메모리 구조의 주요 구성 요소
프로그램 코드 (Text Section)
데이터 (Data Section)
힙 (Heap)
공유 라이브러리 (Shared Libraries)
스택 (Stack)
역할: 함수 호출 시 지역 변수와 함수 호출 기록(복귀 주소, 매개변수, 반환값 등)이 저장되는 영역입니다. 함수가 호출될 때마다 스택 프레임이 추가되고, 함수가 종료되면 스택에서 해당 프레임이 제거됩니다.
특징: 스택은 상향 확장됩니다(메모리 주소가 작은 쪽으로 확장). 스택은 LIFO(Last In First Out) 구조를 따르며, 함수가 호출될 때마다 스택 프레임이 추가되고, 함수가 종료되면 해당 프레임이 제거됩니다. 스택의 크기가 너무 커질 경우 스택 오버플로우가 발생할 수 있습니다.
예시: int localVar;와 같은 함수 내부에서 선언된 지역 변수는 스택에 저장됩니다.
커널 가상 메모리 (Kernel Virtual Memory)
역할: 운영체제 커널이 사용하는 메모리 영역입니다. 사용자 프로세스가 직접 접근할 수 없는 보호된 메모리 공간으로, 시스템 호출이 발생할 때 커널이 해당 영역에 접근합니다.
특징: 이 영역은 운영체제 전용으로, 메모리 관리, 디바이스 드라이버, 시스템 호출 처리 등과 관련된 중요한 데이터가 저장됩니다. 사용자 프로그램은 직접 이 메모리에 접근할 수 없으며, 커널이 이를 보호합니다.
예시: 운영체제가 사용하는 파일 시스템 정보나 하드웨어 정보 등이 이 공간에 저장됩니다.
가상 메모리의 구조
사용자 공간(User Space):
일반적으로 사용자 프로세스는 자신이 사용할 메모리 공간만을 가질 수 있는 것으로 보입니다. 하지만, 가상 메모리 시스템에서 사용자 프로세스는 자신의 주소 공간이 물리 메모리에 직접 매핑되지 않고, 필요한 경우에만 실제 메모리로 불러와집니다. 여기서 프로그램 코드, 데이터, 힙, 스택 등의 영역이 포함됩니다.
커널 공간(Kernel Space):
운영체제는 사용자 프로세스와 독립적인 커널 전용 메모리 공간을 가지고 있으며, 이 공간은 프로세스가 시스템 자원에 접근하거나 요청할 때만 접근할 수 있습니다. 보안과 시스템 안정성을 위해 커널 공간은 보호됩니다.
가상 메모리의 장점
가상 메모리는 프로그램의 실행 환경을 추상화하고, 물리 메모리와 상관없이 많은 프로그램이 동시에 실행될 수 있도록 지원하는 중요한 기술입니다.
컴퓨터 구조 관점에서
파일은 하드 디스크나 SSD와 같은 저장 장치(I/O 장치)를 추상화한 개념으로, 데이터를 논리적 단위로 관리하는 방법입니다. 물리적으로 저장 장치는 블록 단위로 데이터를 저장하지만, 운영체제는 파일 시스템을 통해 데이터를 파일이라는 논리적 단위로 추상화하여 사용자와 응용 프로그램이 쉽게 데이터를 다룰 수 있도록 합니다.
운영체제는 파일 시스템을 통해 파일을 관리하며, 디렉터리 구조를 통해 파일을 체계적으로 저장하고 접근할 수 있게 합니다. 파일 시스템은 파일의 읽기, 쓰기, 생성, 삭제 등의 작업을 제공합니다.
예시
사용자가 작성한 문서 파일이나 음악 파일, 응용 프로그램 설치 파일 등은 모두 파일입니다. 예를 들어, 텍스트 파일을 열면, 운영체제는 물리적인 하드 드라이브에서 파일의 내용을 메모리로 불러오고, 응용 프로그램이 그 데이터를 처리할 수 있게 합니다.
네트워크는 컴퓨터가 외부와 통신할 수 있게 해주는 통로로서, 데이터를 입력받고 출력하는 역할을 합니다. 이를 통해 네트워크는 하드디스크, 키보드, 모니터와 같은 다른 입출력 장치와 비슷하게 동작합니다. 다음은 네트워크가 입출력 장치로서 작동하는 방식에 대한 설명입니다:
1. 네트워크의 입력 역할
네트워크를 통해 다른 시스템에서 데이터가 컴퓨터로 전송됩니다.
예를 들어, 사용자가 웹사이트를 요청하면 클라이언트는 서버에 요청을 보내고, 서버는 해당 웹페이지 데이터를 네트워크를 통해 클라이언트로 전송합니다. 이 데이터는 네트워크에서 입력되는 형태로, 웹 브라우저와 같은 프로그램에서 처리됩니다.
예시:
웹 브라우저에서 사용자가 URL을 입력하면, 서버로부터 웹 페이지가 로드되어 데이터가 입력됩니다.
이메일 클라이언트가 서버로부터 새로운 이메일 데이터를 수신할 때 네트워크로부터 데이터가 컴퓨터로 입력됩니다.
2. 네트워크의 출력 역할
반대로, 컴퓨터는 네트워크를 통해 다른 시스템으로 데이터를 전송할 수 있습니다.
예를 들어, 사용자가 전자 메일을 작성하여 보내면, 메일 클라이언트는 이 데이터를 네트워크를 통해 메일 서버로 출력합니다. 이 과정은 데이터를 외부 장치로 보내는 것과 유사합니다.
예시:
사용자가 이메일을 보낼 때, 작성된 이메일은 네트워크를 통해 서버로 출력됩니다.
파일 업로드 시, 로컬에서 네트워크를 통해 원격 서버로 파일이 출력됩니다.
여기서,
S는 성능 향상 배수,
P는 병렬화 가능한 부분의 비율,
N은 병렬로 처리할 수 있는 프로세서 수입니다.
핵심은 프로그램의 일부만 병렬화할 수 있다면, 그 병렬화가 가능한 부분이 아무리 많아도 병렬화할 수 없는 부분 때문에 성능 향상에 한계가 있다는 것입니다.
예시:
만약 프로그램의 90%가 병렬화 가능하고 10%는 병렬화 불가능하다면, 아무리 많은 프로세서가 있어도 성능 향상은 10배로 제한됩니다.
동시성과 병렬성
동시성(Concurrency):
여러 작업이 동시에 실행되는 것처럼 보이는 개념입니다. 실제로는 하나의 CPU 코어가 작업을 빠르게 전환하는 방식으로 처리합니다.
병렬성(Parallelism):
여러 작업이 물리적으로 동시에 실행되는 것을 의미합니다. 여러 CPU 코어나 프로세서를 사용하여 병렬로 작업을 수행합니다.
쓰레드 수준 동시성(Thread-Level Concurrency):
하나의 프로그램 내에서 여러 쓰레드가 동시에 실행되는 것처럼 보이는 방식입니다. 쓰레드는 같은 프로세스의 자원을 공유하면서 병렬로 실행될 수 있습니다.
멀티스레딩을 사용하여 프로그램의 성능을 향상시키며, CPU 코어가 많을수록 더 많은 쓰레드를 동시에 처리할 수 있습니다.
인스트럭션 수준 병렬성(Instruction-Level Parallelism, ILP):
프로세서가 하나의 명령어 흐름에서 여러 명령어를 동시에 실행할 수 있는 능력입니다. 파이프라이닝이나 슈퍼스칼라 구조와 같은 기술을 통해 인스트럭션을 병렬로 처리합니다.
여러 명령어를 동시에 실행하여 CPU의 성능을 최대한 활용하는 방법입니다.
싱글 인스트럭션, 다중 데이터 병렬성(Single Instruction, Multiple Data, SIMD):
하나의 명령어가 동시에 여러 데이터에 대해 동일한 연산을 수행하는 방식입니다. 벡터 프로세싱 또는 GPU에서 많이 사용됩니다.
예시:
이미지 처리에서 하나의 필터 연산이 여러 픽셀에 동시에 적용되는 경우가 이에 해당합니다.