[운영체제] 운영체제란 무엇인가?
#CS#Operating Systems

[운영체제] 운영체제란 무엇인가?

이 포스트의 핵심 메시지
운영체제는 하드웨어의 복잡성을 감추는 추상화 제공자이자, 여러 프로그램 사이에서 자원을 나누는 자원 관리자이다.

이 두 역할은 가상화, 동시성, 영속성이라는 세 테마로 조직됩니다. 각 섹션에서 이 원리가 어떻게 구체적인 메커니즘으로 드러나는지 살펴보세요.

1. 운영체제가 없다면?

추상화 제공자와 자원 관리자

운영체제가 없는 세상을 상상해봅시다. 프로그래머가 파일 하나를 읽으려면 디스크 컨트롤러의 레지스터 주소를 알아야 하고, 정확한 프로토콜에 따라 명령을 보내야 하며, 디스크 종류가 바뀌면 코드를 처음부터 다시 작성해야 합니다.

여기에 여러 프로그램이 동시에 실행된다면 문제는 더 심각해집니다. 프로그램 A가 쓴 메모리를 B가 덮어쓸 수 있고, 하나가 CPU를 독점하면 나머지는 영원히 실행되지 못합니다.

이로부터 운영체제의 두 가지 핵심 역할이 도출됩니다:

1. 확장된 기계 (Extended Machine)

하드웨어의 복잡한 세부사항을 감추고, 깔끔하고 일관된 인터페이스를 제공합니다.

2. 자원 관리자 (Resource Manager)

CPU, 메모리, 디스크 등을 여러 프로그램 사이에서 공정하고 효율적으로 분배합니다.

Q1
운영체제가 없이 여러 프로그램이 동시에 돌아간다면 어떤 문제가 생길까?
프로그램 A가 쓴 메모리를 B가 덮어쓸 수 있고, 하나가 CPU를 독점하면 나머지는 실행되지 못합니다. 이 충돌과 경쟁을 중재할 자원 관리자가 필요합니다.
핵심 정리
운영체제의 두 가지 모자: (1) 하드웨어의 복잡한 디테일을 감추는 추상화 제공자, (2) 여러 프로그램 사이에서 자원을 나누는 자원 관리자.

2. OS를 이해하는 세 가지 테마

OSTEP의 프레임워크: 가상화, 동시성, 영속성

운영체제는 방대한 소프트웨어이지만, OSTEP은 이를 가상화(Virtualization), 동시성(Concurrency), 영속성(Persistence)이라는 세 가지 테마로 조직합니다.

카드를 클릭하여 각 테마의 상세 내용을 확인하세요
Q2
가상화는 단순히 복잡성을 감추는 것과 어떻게 다른가?
단순한 추상화는 인터페이스를 단순하게 만드는 것이고, 가상화는 거기에 더해 "전용 자원이 있는 것 같은 환상"을 만들어냅니다. 물리적으로 하나인 자원을 여러 개인 것처럼 보이게 하는 것이 핵심입니다.
주의 — 영속성은 OS 상태 복원이 아니다
영속성은 "운영체제가 꺼졌다 켜져도 자기 상태를 복원한다"는 뜻이 아닙니다. 사용자의 데이터가 전원 차단 후에도 살아남도록 파일 시스템을 통해 보장하는 것입니다.

3. 인터럽트

CPU가 기다리지 않는 비결

CPU와 디스크의 속도 차이는 수만~수십만 배에 달합니다. 디스크가 데이터를 읽는 동안 CPU가 멈춰서 기다린다면 엄청난 낭비입니다.

폴링(polling)은 CPU가 "끝났어?"를 반복적으로 확인하는 방식입니다. 단순하지만 CPU를 낭비합니다. 인터럽트는 디스크가 작업을 마치면 CPU에게 전기 신호를 보내는 방식입니다. CPU는 그때까지 다른 일을 할 수 있습니다.

인터럽트 처리 흐름 — 단계별 시각화
사용자 모드
단계 1 / 6
프로그램 B 실행 중

CPU가 프로그램 B의 코드를 실행하고 있습니다.

1
2
3
4
5
6
Q3
CPU가 프로그램 B를 실행하고 있는데, 디스크 작업이 완료되었다는 것을 어떻게 알 수 있을까?
두 가지 방법이 있습니다. 폴링(CPU가 주기적으로 확인)과 인터럽트(디스크가 완료 시 CPU에 신호). 폴링은 CPU 낭비가 심하고, 인터럽트는 추가 하드웨어가 필요하지만 CPU를 자유롭게 만듭니다.

4. I/O 구조

CPU의 부담을 점진적으로 줄여나가는 세 가지 방식

동기 I/O는 프로그래머에게 예측 가능성을 제공합니다 — read() 다음 줄에서 데이터가 반드시 준비되어 있습니다. 비동기 I/O는 CPU 효율적이지만 코드가 복잡해집니다. 실제로는 절충안을 사용합니다 — 프로그램에겐 동기로 보이지만 OS 내부에서는 비동기로 처리합니다.

I/O 방식은 폴링 → 인터럽트 → DMA로 발전해왔으며, 반복되는 패턴은 하나입니다: CPU가 무언가를 기다리지 않고 다른 일을 계속 실행하도록 하는 구조.

I/O 방식 비교 시뮬레이터

CPU가 디스크 상태를 반복 확인합니다. 디스크 작업이 끝날 때까지 CPU는 다른 일을 할 수 없습니다.

CPU
폴링 대기
데이터 전송
유용한 작업
디스크 / DMA
디스크 작업
CPU 유용한 작업
CPU 대기(낭비)
디스크 작업
DMA 전송
CPU 활용률: 15%
Q4
동기 I/O가 비효율적인데 왜 존재하는가?
프로그래머 입장에서의 예측 가능성. read() 다음 줄에서 데이터가 반드시 준비되어 있다고 확신할 수 있습니다. 비동기는 콜백, 상태 머신 등 코드 복잡도가 급증합니다.

5. 이중 모드와 시스템 콜

왜 아무 프로그램이나 하드웨어를 만지면 안 되는가

인터럽트 핸들러가 하는 일 — 상태 저장, 데이터 이동, 실행 상태 변경 — 은 모두 특권적 작업입니다. 일반 프로그램이 이런 작업을 할 수 있다면 시스템 전체가 위험해집니다.

이중 모드: CPU 내부의 모드 비트가 해결책입니다. 커널 모드(0)에서는 모든 명령어와 하드웨어 접근이 가능하고, 사용자 모드(1)에서는 제한된 명령어만 실행할 수 있습니다.

프로그램이 커널 서비스를 요청하려면 트랩(trap)이라는 특별한 CPU 명령어(syscall)를 실행합니다. 이것이 시스템 콜 — 사용자 프로그램이 커널 서비스를 요청하는 유일한 공식 통로입니다.

공통점

트랩과 하드웨어 인터럽트 모두 커널 모드 전환을 유발하고, 상태 저장 → 핸들러 → 복귀라는 동일한 처리 구조를 따릅니다.

차이점

트랩은 동기적(프로그래머가 의도적으로 발생), 하드웨어 인터럽트는 비동기적(예측 불가능한 시점에 발생).

프로그램 실행 흐름 — 셸에서 실행 → 종료
사용자
셸에서 명령 입력

사용자가 셸에 프로그램 실행 명령을 입력합니다.

커널
fork() 시스템 콜TRAP

트랩을 통해 커널 모드 진입 → 새 프로세스를 생성합니다.

사용자
사용자 모드 복귀

fork()가 완료되면 사용자 모드로 돌아옵니다.

커널
exec() 시스템 콜TRAP

트랩을 통해 커널 모드 진입 → 프로그램을 메모리에 로드합니다.

사용자
프로그램 실행

로드된 프로그램이 사용자 모드에서 실행됩니다.

커널
read() 시스템 콜 (트랩)TRAP

파일 읽기를 위해 트랩 → 커널 모드 진입 → 디스크 요청 → 프로세스 대기.

커널
디스크 완료 인터럽트HW INTERRUPT

디스크 작업 완료 후 하드웨어 인터럽트 발생 → 커널이 데이터를 전달합니다.

사용자
프로그램 계속 실행

데이터가 준비되어 프로그램이 사용자 모드에서 계속 실행됩니다.

커널
exit() 시스템 콜TRAP

트랩을 통해 커널 모드 진입 → 프로세스를 종료합니다.

사용자
부모 프로세스(셸) wait()로 회수

셸이 자식 프로세스의 종료를 확인하고 자원을 회수합니다.

트랩 (시스템 콜, 동기)
하드웨어 인터럽트 (비동기)
Q5
프로그램은 사용자 모드에 있고 디스크에 직접 접근할 권한이 없는데, 어떻게 운영체제의 도움을 받을 수 있을까?
하드웨어가 인터럽트 신호를 전기적으로 보내는 것처럼, 소프트웨어도 트랩이라는 신호를 발생시킬 수 있습니다. 특별한 CPU 명령어(syscall)를 실행하면 자동으로 커널 모드로 전환되어 운영체제 코드가 실행됩니다. 이것이 시스템 콜입니다.

6. 저장 장치 계층구조

속도, 용량, 비용의 트레이드오프

빠르면서 큰 저장 장치를 만들 수 없는 이유는 속도↑ 용량↓ 비용↑의 트레이드오프 때문입니다. 그래서 컴퓨터는 여러 종류의 저장 장치를 계층적으로 조합합니다.

이 계층이 효과적인 이유는 프로그램의 데이터 접근 패턴에 지역성(locality)이 있기 때문입니다. 시간적 지역성: 최근 접근한 데이터는 곧 다시 접근할 가능성이 높고 (예: for 루프), 공간적 지역성: 어떤 주소에 접근했으면 근처 주소도 곧 접근합니다 (예: 배열 순회).

저장 장치 계층 피라미드 — 각 층을 클릭하세요
← 빠르고 비싸고 작음느리고 저렴하고 큼 →
Q6
레지스터만큼 빠르면서 디스크만큼 큰 저장 장치를 만들면 안 되나?
기술적으로 불가능합니다. 레지스터가 빠른 이유는 CPU 칩 안에 트랜지스터 몇 개로 만들어졌기 때문이고, 이 방식으로 용량을 늘리면 칩 면적과 비용이 급격히 상승합니다. 속도, 용량, 비용을 동시에 만족시키는 장치는 현재 없습니다.

7. 멀티프로그래밍과 멀티태스킹

CPU를 놀리지 않고, 누구도 독점하지 못하게

멀티프로그래밍: 여러 프로그램을 메모리에 동시에 올려놓고, 하나가 I/O를 기다릴 때 다른 프로그램으로 전환합니다. 동기는 CPU 활용률입니다.

그러나 한계가 있습니다. 프로그램이 I/O 없이 계산만 하면 CPU를 독점할 수 있습니다.

멀티태스킹(시분할): 타이머 인터럽트로 강제 전환합니다. 프로그램의 의지와 무관하게 일정 시간이 지나면 끊어줍니다. 동기는 CPU 활용률에 더해 응답성 공정성입니다.

타이머 인터럽트는 앞서 배운 하드웨어 인터럽트의 구체적인 예시입니다. 충분히 빠른 전환(초당 수십~수백 번)으로 동시 실행의 환상을 만들어내는 것이 CPU 가상화의 구체적 메커니즘입니다.

Q7
멀티프로그래밍만으로는 어떤 상황에서 문제가 생길까?
프로그램이 I/O 없이 순수 계산만 계속하면, 자발적으로 CPU를 양보할 계기가 없어서 CPU를 독점합니다. 다른 프로그램들은 영원히 실행되지 못할 수 있습니다.
멀티프로그래밍 vs 멀티태스킹
멀티프로그래밍: I/O 대기 시 전환 → CPU 활용률 향상. 멀티태스킹: 타이머 인터럽트로 강제 전환 → 활용률 + 응답성 + 공정성 보장. 멀티태스킹은 멀티프로그래밍을 포함하는 더 발전된 형태.

8. strace로 보는 시스템 콜

echo hello 하나에 시스템 콜이 38개?

strace echo hello를 실행하면 놀라운 결과를 볼 수 있습니다. 단순히 "hello"를 출력하는 프로그램인데, 38개의 시스템 콜이 발생합니다.

크게 세 덩어리로 나뉩니다: execve(프로그램 실행 요청), 라이브러리 로딩(openat, read, mmap, close 등으로 libc.so.6 로딩), 그리고 실제 목적(write(1, "hello\n", 6) — 단 1개!).

운영체제가 "프로그래머가 printf 한 줄 쓰면 뒤에서 수십 번의 시스템 콜을 처리한다"는 추상화의 실체입니다.

strace 출력 탐색기 — 카테고리별 필터링
execve("/usr/bin/echo", ["echo", "hello"], 0x7ffc...)
brk(NULL) = 0x55a...
arch_prctl(0x3001, 0x7fff...) = -1 EINVAL
mmap(NULL, 8192, PROT_READ|PROT_WRITE, ...) = 0x7f...
access("/etc/ld.so.preload", R_OK) = -1 ENOENT
openat(AT_FDCWD, "/etc/ld.so.cache", ...) = 3
fstat(3, {st_mode=S_IFREG|0644, ...}) = 0
mmap(NULL, 92599, PROT_READ, ...) = 0x7f...
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64.../libc.so.6", ...) = 3
read(3, "\177ELF\2\1\1\3...", 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\24\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\20\0\0\0"..., 68, 896) = 68
fstat(3, {st_mode=S_IFREG|0755, ...}) = 0
mmap(NULL, 2228224, PROT_READ, ...) = 0x7f...
mmap(0x7f..., 1540096, PROT_READ|PROT_EXEC, ...) = 0x7f...
mmap(0x7f..., 319488, PROT_READ, ...) = 0x7f...
mmap(0x7f..., 24576, PROT_READ|PROT_WRITE, ...) = 0x7f...
mmap(0x7f..., 13316, PROT_READ|PROT_WRITE, ...) = 0x7f...
close(3) = 0
mprotect(0x7f..., 16384, PROT_READ) = 0
mprotect(0x55a..., 4096, PROT_READ) = 0
mprotect(0x7f..., 4096, PROT_READ) = 0
munmap(0x7f..., 92599) = 0
getrandom("\x1a\xb4...", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0x55a...
brk(0x55a...) = 0x55a...
set_tid_address(0x7f...) = 12345
set_robust_list(0x7f..., 24) = 0
rseq(0x7f..., 0x20, 0, 0x53053053) = 0
prlimit64(0, RLIMIT_STACK, NULL, ...) = 0
fstat(1, {st_mode=S_IFCHR|0620, ...}) = 0
write(1, "hello\n", 6) = 6
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
총 시스템 콜: 38실제 목적: 1개 (2.6%)
마무리 — 전체 지도
운영체제는 추상화 제공자이자 자원 관리자이고, 이를 가상화, 동시성, 영속성이라는 세 테마로 이해할 수 있습니다. 컴퓨터는 인터럽트 기반 구조로 동작하며, 트랩(동기)과 하드웨어 인터럽트(비동기)라는 두 경로로 커널에 진입합니다. I/O는 폴링 → 인터럽트 → DMA로 발전하면서 CPU 활용률을 높여왔고, 저장 장치는 속도, 용량, 비용의 트레이드오프 때문에 계층구조를 이루며 지역성 덕분에 잘 작동합니다. 이중 모드로 보호를 확보하고 시스템 콜이 유일한 커널 진입 통로이며, 멀티프로그래밍이 CPU 활용률을, 멀티태스킹이 응답성과 공정성을 보장합니다.