컴퓨터 시스템
- 1. 프로그램은 어떻게 표현되는가
- 2. 컴파일 시스템 — hello.c가 실행 파일이 되기까지
- 3. 하드웨어 구성
- 4. 캐시와 메모리 계층구조
- 5. 운영 체제
- 6. 가상 메모리
- 7. 파일
- 8. 네트워크
- 9. Amdahl의 법칙
- 10. 동시성과 병렬성
- 11. 추상화의 중요성
컴퓨터 시스템은 하드웨어와 시스템 소프트웨어로 구성되며, 두 시스템은 응용 프로그램 실행이라는 하나의 목적을 위해 함께 작동한다. 시스템 공부는 보통 printf("Hello, world\n"); 한 줄이 작성되고, 실행되고, 메시지를 출력하고, 종료되기까지의 수명주기를 따라가는 것으로 시작한다.
1. 프로그램은 어떻게 표현되는가
먼저 기본 용어부터 정리한다.
| 구분 | 형태 | 사람이 읽을 수 있나? | 저장 형태 |
|---|---|---|---|
| 소스 프로그램(원시 코드) | 고급 언어 | O | hello.c (텍스트 파일) |
| 목적 프로그램 | 기계어 | X | hello.o (바이너리 파일) |
비트(bit)는 0 또는 1을 나타내는 최소 단위이고, 8비트가 모여 1바이트(byte)가 된다. 각 바이트는 프로그램의 텍스트 문자 하나를 표현할 수 있는데, 1바이트로 표현 가능한 값은 2⁸ = 256가지이므로 아스키 문자 집합을 담기에 충분하다.
- 텍스트 파일: 아스키 문자만으로 이루어진 파일
- 바이너리 파일: 그 외 모든 파일
hello 프로그램은 사람이 읽을 수 있는 고급 C 코드로 시작하지만, 다른 프로그램들에 의해 저급 기계어 인스트럭션으로 번역된다. 이 인스트럭션들은 실행 가능 목적 프로그램 형태로 묶여 바이너리 디스크 파일로 저장된다.
2. 컴파일 시스템 — hello.c가 실행 파일이 되기까지
hello.c가 실행 파일 hello로 만들어지는 과정은 네 단계를 거친다. (각 단계의 세부 과정은 일단 생략)
| 단계 | 입력 → 출력 | 담당 | 하는 일 |
|---|---|---|---|
| 전처리 (Preprocessing) | hello.c → hello.i |
전처리기 (cpp) | #include, 매크로 등을 처리해 순수한 C 코드로 정리 |
| 컴파일 (Compilation) | hello.i → hello.s |
컴파일러 (ccl) | C 코드를 어셈블리어 프로그램으로 번역 |
| 어셈블리 (Assembly) | hello.s → hello.o |
어셈블러 (as) | 어셈블리어를 기계어 인스트럭션으로 번역, 재배치 가능 목적 프로그램으로 묶음 |
| 링크 (Linking) | hello.o → hello |
링커 (ld) | printf 등 표준 라이브러리 목적 파일과 통합해 실행 파일 생성 |
printf 함수는 표준 C 라이브러리에 별도의 목적 파일로 들어 있어서, 링커(ld)가 hello.o와 통합해야 비로소 실행 가능한 파일이 완성된다.

3. 하드웨어 구성
버스 (Bus)
시스템 전체를 관통하는 전기적 배선군으로, 데이터가 오가는 통로라고 생각하면 된다. 바이트 단위로 데이터를 전송하며, 오늘날 대부분의 컴퓨터는 4바이트 또는 8바이트의 워드(word) 크기를 갖는다.
입출력 장치 (I/O Device)
시스템과 외부 세계를 연결한다. 키보드, 모니터 등이 여기에 해당하며 I/O 버스를 통해 연결된다.
메인 메모리 (Main Memory)
프로그램 실행 중의 데이터와 프로그램을 저장하는 임시 저장 장치다. DRAM으로 구성되며, 0부터 시작하는 고유 인덱스를 가진 연속된 바이트 배열을 이룬다.
프로세서 (CPU)
메인 메모리에 저장된 인스트럭션을 해독·실행하는 엔진이다. 동작은 다음 사이클을 반복한다.
- PC(Program Counter) 가 가리키는 메모리에서 인스트럭션을 읽어온다
- 인스트럭션의 비트를 해석해 지정된 동작을 실행한다
- PC를 다음 인스트럭션 위치로 갱신한다 (같은 위치일 수도 있다)
CPU는 메인 메모리, 레지스터 파일, ALU(수식/논리 처리기) 사이를 순환하며 작업한다. 대표적인 인스트럭션 동작은 다음과 같다.
| 동작 | 설명 |
|---|---|
| 적재 (Load) | 메인 메모리 → 레지스터로 한 바이트/워드를 덮어쓰기 복사 |
| 저장 (Store) | 레지스터 → 메인 메모리로 한 바이트/워드를 덮어쓰기 복사 |
| 작업 (Operate) | 두 레지스터 값을 ALU로 복사해 연산한 뒤, 결과를 레지스터에 덮어쓰기 저장 |
| 점프 (Jump) | 인스트럭션에서 워드 하나를 추출해 PC에 덮어쓰기 복사 |
4. 캐시와 메모리 계층구조
컴퓨터에는 많이 저장하지만 느린 장치(디스크, 메인 메모리)와, 적게 저장하지만 빠른 장치(레지스터, 캐시)가 공존한다. 캐시 중 L1·L2 캐시는 SRAM(Static Random Access Memory) 기술로 구현된다.
지역성 (Locality)
캐시는 프로그램이 메모리의 특정 부분을 집중적으로 사용하는 지역성 경향을 활용한다.
| 종류 | 원리 | 예시 |
|---|---|---|
| 시간적 지역성 (Temporal) | 한 번 접근한 데이터는 가까운 미래에 다시 접근될 가능성이 높다 | 루프 내에서 반복 사용되는 변수 |
| 공간적 지역성 (Spatial) | 어떤 주소에 접근하면 그 주변 주소도 곧 접근될 가능성이 높다 | 배열, 연속된 메모리 블록 순회 |
캐시는 이 원리에 따라 자주 쓰이거나 곧 쓰일 데이터를 미리 저장해두므로, CPU가 데이터를 빠르게 찾을 수 있다.
메모리 계층구조
핵심 아이디어는 한 레벨의 저장장치가 바로 아래 레벨 저장장치의 캐시 역할을 한다는 것이다. (레지스터 → L1 → L2 → 메인 메모리 → 디스크 순으로 빠르고 작은 쪽에서 느리고 큰 쪽으로 내려간다.)
5. 운영 체제
운영 체제는 응용 프로그램과 하드웨어 사이에 위치해, 응용 프로그램의 요청대로 하드웨어가 작동하도록 중재한다.
| 개념 | 설명 |
|---|---|
| 컨텍스트 (Context) | 프로세스 실행에 필요한 모든 상태 정보 (PC, 레지스터 파일, 메인 메모리의 현재 값 등) |
| 문맥 전환 (Context Switching) | 현재 프로세스의 컨텍스트를 저장하고 새 프로세스의 컨텍스트를 복원해 제어권을 넘기는 과정 |
| 시스템 콜 (System Call) | 제어권을 운영체제로 넘기는 동작 |
| 커널 (Kernel) | 프로세스 전환을 관리하는 코드와 자료구조의 집합으로, 항상 메인 메모리에 상주한다 |
프로세스와 쓰레드
프로세스는 하나의 제어 흐름을 갖는 것처럼 보이지만, 최근 시스템에서는 쓰레드(Thread) 라는 다수의 실행 유닛으로 구성된다. 쓰레드는 같은 프로세스의 컨텍스트에서 실행되며 코드와 전역 데이터를 공유한다. 데이터 공유가 쉽고 프로세스보다 효율적이어서 중요성이 커지고 있다.
| 구분 | 정의 | 자원 공유 방식 |
|---|---|---|
| 프로세스 | 독립적으로 실행되는 프로그램의 인스턴스. 자체 주소 공간·메모리·데이터 스택 등을 보유 | 독립적인 메모리 공간을 가지므로 IPC(Inter-Process Communication)를 통해 공유 |
| 쓰레드 | 프로세스 내부의 실행 흐름 단위. 프로세스의 자원과 주소 공간을 공유하며 실행 | 같은 프로세스 내 쓰레드끼리 코드·데이터·시스템 자원을 직접 공유 |
6. 가상 메모리
가상 메모리는 각 프로세스가 메인 메모리 전체를 독점 사용하는 것 같은 환상을 제공하는 추상화다. 각 프로세스는 동일한 형태의 가상 주소 공간을 갖는다. 위로 갈수록 주소가 증가하며, 상위 영역은 운영체제가, 하위 영역은 사용자 프로세스가 사용한다.
영역을 높은 주소(위) → 낮은 주소(아래) 순으로 정리하면 다음과 같다.
| 영역 | 위치 | 설명 |
|---|---|---|
| 커널 가상 메모리 | 최상단 | 커널 전용. 응용 프로그램이 읽기·쓰기·함수 호출 모두 금지 |
| 스택 (Stack) | 사용자 공간 최상단 | 함수 호출 구현용. 호출 시 커지고(↓ 방향), 리턴 시 줄어든다 |
| 공유 라이브러리 | 중간 | C 표준·수학 라이브러리 등 여러 프로세스가 공유하는 코드·데이터 |
| 힙 (Heap) | 코드·데이터 위 | malloc/free 호출에 따라 런타임에 동적으로(↑ 방향) 늘고 줄어든다 |
| 프로그램 코드·데이터 | 하단 (고정 주소) | 모든 프로세스가 같은 고정 주소에서 시작. 실행 파일로부터 직접 초기화 |
왜 스택은 위, 힙은 아래에 둘까?
성장 방향이 반대라서 충돌을 피하기 위한 설계다. 스택은 함수 호출이 쌓일수록 높은 주소 → 낮은 주소로 자라고, 힙은 메모리를 할당할수록 낮은 주소 → 높은 주소로 자란다. 서로 마주 보는 방향으로 자라게 해 충돌 가능성을 줄인 것이다. 공유 라이브러리는 여러 프로세스가 함께 쓰므로 중간에 배치해 재사용성과 메모리 보호를 높였다.
가상 메모리가 작동하려면 프로세스가 만드는 모든 가상 주소를 하드웨어가 물리 주소로 번역하는 등, 하드웨어와 OS 소프트웨어 간의 복잡한 상호작용이 필요하다. 기본 아이디어는 가상 메모리 내용을 디스크에 저장하고 메인 메모리를 그 디스크의 캐시로 사용하는 것이다.
7. 파일
파일은 연속된 바이트들이다. 모든 입출력 장치는 파일로 모델링되며, 시스템의 모든 입출력은 시스템 콜을 통해 파일을 읽고 쓰는 형태로 이루어진다. 덕분에 사용 중인 디스크의 구체적인 기술을 몰라도 동일한 방식으로 입출력을 다룰 수 있다.
8. 네트워크
네트워크는 일종의 입출력 장치로, 여러 대의 컴퓨터가 데이터를 주고받도록 연결한 것이다. 원격 컴퓨팅(telnet)의 동작 흐름은 다음과 같다.
| 순서 | 동작 |
|---|---|
| 1 | telnet 클라이언트로 로컬/원격 컴퓨터를 연결 |
| 2 | 원격 컴퓨터에 로그인 후 쉘 실행 |
| 3 | "hello" 문자열을 클라이언트에 입력 후 Enter |
| 4 | 클라이언트가 문자열을 telnet 서버로 전송 |
| 5 | telnet 서버가 문자열을 받아 원격 쉘에 전달 |
| 6 | 원격 쉘이 hello 프로그램을 실행하고 출력 문자열을 클라이언트로 전달 |
| 7 | 클라이언트가 출력 문자열을 로컬 터미널에 표시 |
9. Amdahl의 법칙
시스템 개선은 개발자의 필수 역량이다. 어떤 작업의 일부를 개선했을 때 전체 속도가 얼마나 향상되는지를 다루는 법칙이다.
전체 실행 시간을 $T_{\text{old}}$, 개선 대상이 차지하는 비율을 $\alpha$, 그 부분의 성능 개선 배수를 $k$라 하면,
\[\begin{aligned} T_{\text{new}} &= (1 - \alpha) T_{\text{old}} + \left( \alpha T_{\text{old}} \right)/k \\[1em] &= T_{\text{old}} \left[ (1 - \alpha) + \frac{\alpha}{k} \right] \end{aligned}\]여기서 전체 작업량을 1로 두면, 병렬화(개선) 가능한 부분이 $\alpha$, 직렬로 남는 부분이 $1 - \alpha$다. 직렬 부분은 그대로, 병렬 부분은 $k$로 나뉘어 $\alpha/k$가 된다.
속도 향상 $S$는 다음과 같이 구한다.
\[\begin{aligned} S &= \frac{T_{\text{old}}}{T_{\text{new}}} \\[1em] &= \frac{1}{(1 - \alpha) + \frac{\alpha}{k}} \end{aligned}\]$k$를 무한대로 보내면,
\[S_{\infty} = \frac{1}{1 - \alpha}\]개선 가능한 비율 $\alpha$가 1에 가까울수록 $S$는 무한대로 커지지만, $\alpha$가 60%에 불과하면 아무리 $k$를 키워도 속도는 최대 2.5배까지밖에 오르지 않는다. 즉 $k$만 높일 게 아니라 $\alpha$를 키우는 것도 중요하다.
10. 동시성과 병렬성
- 동시성 (Concurrency): 다수의 일이 동시에 벌어지는 시스템에 관한 일반 개념
- 병렬성 (Parallelism): 동시성을 활용해 시스템을 더 빠르게 동작시키는 개념
시스템 계층 구조에서 병렬성은 높은 수준부터 낮은 수준까지 세 단계로 나타난다.
10-1. 쓰레드 수준 동시성
곡예사가 여러 공을 동시에 던지듯, 한 컴퓨터가 프로세스를 빠르게 전환하면 동시성이 만들어진다. 쓰레드를 쓰면 한 프로세스 안에서도 다수의 제어 흐름을 가질 수 있다.
| 구분 | 설명 |
|---|---|
| 단일 프로세서 시스템 | 하나의 프로세서가 단일 OS 커널의 제어 하에 동작 |
| 멀티 프로세서 시스템 | 다수의 프로세서가 단일 OS 커널 제어 하에 동작 (하위 개념: 멀티코어 / 하이퍼쓰레드) |
멀티 프로세서는 동시성을 시뮬레이션할 필요를 줄이고 프로그램을 더 빠르게 실행한다. 단, 프로그램이 멀티쓰레드 형태로 잘 표현되어 병렬 실행이 가능할 때만 그 이점을 누릴 수 있다. 그래서 프로그래머에게 멀티쓰레드 구조 작성법이 중요하다.
멀티 코어: 예를 들어 인텔 i7 프로세서는 4개의 코어를 갖는다. L1·L2 캐시는 코어별로 분리되지만, 상위 캐시와 메인 메모리·인터페이스는 코어들이 공유한다.
멀티 쓰레딩 (하이퍼쓰레딩): 하나의 CPU가 여러 제어 흐름을 실행하는 기술. PC와 레지스터 파일은 여러 벌 갖지만 부동소수 연산기 등은 한 벌만 갖는다.
- 기존 프로세서: 쓰레드 전환에 약 2만 클럭 사이클 소요
- 하이퍼쓰레드 프로세서: 매 사이클마다 실행할 쓰레드를 결정
10-2. 인스트럭션 수준 병렬성
먼저 추상화는 “복잡한 시스템에서 필요한 부분만 뽑아 단순하게 표현”하는 것이다. 고수준 언어(Python, Java)는 추상화 수준이 높고, 저수준 언어(C, 어셈블리)는 낮다.
최근 프로세서는 낮은 추상화 수준에서 여러 인스트럭션을 한 번에 실행하는 인스트럭션 수준 병렬성을 갖는다. 예전엔 인스트럭션 하나에 여러 클럭 사이클이 필요했지만, 지금은 매 클럭당 2.4개가량을 처리한다.
| 용어 | 설명 |
|---|---|
| 파이프라이닝 (Pipelining) | 인스트럭션 실행에 필요한 일을 여러 단계로 나누고, 하드웨어가 그 단계들을 차례로 수행 |
| 슈퍼스케일러 (Superscalar) | 사이클당 한 개 이상의 인스트럭션을 실행할 수 있는 프로세서 |
10-3. 싱글 인스트럭션 다중 데이터 (SIMD)
최신 프로세서는 최하위 수준에서 SIMD(Single Instruction Multiple Data) 병렬성을 지원한다. 하나의 인스트럭션이 다수의 연산을 병렬로 수행하는 특수 하드웨어를 갖춘 것이다. SIMD 인스트럭션은 주로 영상·소리·동영상 데이터 처리 속도를 높이는 데 쓰인다.
11. 추상화의 중요성

추상화는 복잡한 것을 단순하게, 핵심만 보여주는 것이다. 덕분에 사용자는 내부 동작을 몰라도 Python 같은 언어로 프로그래밍할 수 있다.
흔히 기계어 프로그램이 “한 번에 인스트럭션 하나씩” 순차 실행되는 것처럼 느끼지만, 실제 하드웨어는 여러 인스트럭션을 병렬로 처리하면서도 마치 순차적으로 실행된 것처럼 보이게 만든다. 이것 역시 하드웨어가 제공하는 추상화의 결과다.