OS 기초 + 프로세스·스레드
OS 기초 + 프로세스·스레드
🎯 이 lesson 을 읽고 나면
이 lesson 을 다 읽고 나면 아래 3가지를 자신 있게 할 수 있습니다.
- ▸✅ OS 의 4대 역할 (프로세스·메모리·파일·I/O)
- ▸✅ User Mode vs Kernel Mode + syscall
- ▸✅ Node.js 가 왜 단일 스레드인데 빠른지
학습 목표를 체크리스트로 두고 다 답할 수 있게 되면 lesson 을 닫으세요.
OS가 하는 4가지 일
한 줄: 운영체제(OS) = 하드웨어 위에서 프로세스·메모리·파일·I/O 를 추상화하는 소프트웨어 층.
OS 4 대 책임:
부팅 → 카톡 실행 흐름 (6단계):
1. 전원 ON — BIOS/UEFI → 부트로더 → 커널 로드
2. 커널 초기화 — 메모리 매핑·드라이버 로드·스케줄러 시작
3. init 프로세스 — PID 1, 모든 사용자 프로세스의 부모 (systemd / launchd)
4. 로그인 셸 — bash/zsh 시작, 환경 변수 로드
5. 앱 실행 — fork() + exec("/Applications/KakaoTalk.app/...") → 새 프로세스
6. 이벤트 루프 — 키보드·네트워크 입력 대기·처리
> 💡 카톡 한 번 실행 = OS 가 위 6 단계를 보이지 않게 수행. 매번 새로 시작 시 0.5 초 안에.
프로세스 vs 스레드 — 동시성의 두 모델
언제 무엇을:
- ▸프로세스 분리: 안정성 우선 (Chrome 탭별 프로세스), 보안 격리 (Docker 컨테이너), 다른 언어 통합
- ▸스레드 사용: 가벼운 동시성 (웹 서버 요청별 스레드), 공유 데이터 처리 (GUI 이벤트 루프)
- ▸현대 트렌드: 가상 스레드(Java 21)·고루틴(Go)·async/await(Python·Node) — 스레드보다 더 가벼운 경량 동시성
흔한 실수:
- ▸❌ 스레드 1000개 생성 → 컨텍스트 스위치 비용 폭발
- ▸❌ 공유 데이터를 락 없이 수정 → race condition
- ▸✅ 스레드 풀(ThreadPoolExecutor) + 락 또는 async/await 사용
컨텍스트 스위칭 — 동시성의 *진짜 비용*
컨텍스트 스위칭 6단계 (CPU 가 프로세스 A → B 로 전환):
1. 인터럽트 — 타이머·I/O 완료·시스템 콜
2. A 상태 저장 — 레지스터·PC·스택 포인터를 PCB(Process Control Block)에
3. 스케줄러 호출 — 다음 실행할 프로세스 선택 (CFS·O(1)·실시간)
4. B PCB 로드 — 레지스터·MMU 매핑 복구
5. 캐시 무효화 — L1/L2 캐시·TLB(Translation Lookaside Buffer) 일부 플러시
6. B 실행 — 멈췄던 곳부터 재개
비용:
오버헤드 함정:
- ▸❌ 스레드 너무 많이 (1000+) → CPU 가 실제 작업보다 스위칭에 더 많은 시간
- ▸❌ 비동기 코드인데 CPU bound 작업 → 메인 스레드 블록
- ▸✅ CPU 코어 수와 비슷한 스레드 수 (대략 N + 1) 권장
> 💡 왜 비동기가 빠른가? = 컨텍스트 스위칭 없이 한 스레드에서 다중 작업 (이벤트 루프).
시스템 콜 · 인터럽트 · 컨텍스트 스위칭 — 한 페이지
사용자 공간 vs 커널 공간
OS 메모리는 2개 영역:
- ▸사용자 공간 (User Space) — 우리 앱이 실행되는 곳. 권한 제한적. 메모리·디스크·네트워크에 직접 접근 불가.
- ▸커널 공간 (Kernel Space) — OS 자체가 사는 곳. 모든 하드웨어 권한.
사용자 앱이 파일을 열려면 — 직접 디스크에 접근 X. 반드시 OS 에 요청 해야 함. 이 요청이 시스템 콜.
시스템 콜 (System Call)
open / read / write / close / fork / exec — Linux 기준 약 300개. 모든 I/O · 프로세스 생성 이 syscall.
고수준 언어 (Python, Java) 의 모든 파일/네트워크 호출 이 내부적으로 syscall 을 부릅니다.
syscall 의 비용
사용자 공간 → 커널 공간 전환 자체가 비쌉니다 (마이크로초 단위). 그래서:
- ▸버퍼링 — 매번 syscall 안 하고 모아서 한 번에.
BufferedReader,console.log의 내부 버퍼. - ▸비동기 I/O — Node.js, async/await — syscall 대기 중 다른 작업 처리.
인터럽트 vs 폴링
폴링 (Polling) — 계속 묻기
CPU 시간을 낭비. 바쁜 대기.
인터럽트 (Interrupt) — 알려줘
하드웨어가 "준비됐어!" 신호를 보내면 CPU 가 즉시 처리:
- ▸키보드 입력 — 키 누르는 순간 IRQ 발생 → 커널이 이벤트로 변환 → 앱에 전달
- ▸네트워크 패킷 도착 — NIC 가 인터럽트 → 커널이 버퍼에 저장 → 앱 깨움
- ▸타이머 만료 —
setTimeout의 기반
"폴링은 비효율, 인터럽트가 표준" — 현대 OS 의 거의 모든 I/O 는 인터럽트 기반.
컨텍스트 스위칭 (Context Switching)
CPU 가 프로세스/스레드 사이를 바꿀 때 — 현재 상태 (레지스터·PC·메모리 맵핑) 를 저장하고 다음 작업의 상태를 복원.
비용
- ▸프로세스 스위칭 — 메모리 맵핑까지 바꿔야 함. 비쌈 (~수 ㎲).
- ▸스레드 스위칭 — 같은 메모리 공유. 프로세스보다 5~10배 가벼움.
- ▸함수 호출 — 단순 스택 푸시. 나노초 단위.
그래서 — 멀티스레드가 멀티프로세스보다 빠릅니다.
왜 Node.js 가 단일 스레드인데 빠른가
Node.js 의 "단일 스레드 + 이벤트 루프":
핵심 아이디어:
1. I/O 가 99% 의 시간을 잡아먹는다 (DB·API·디스크)
2. I/O 대기 동안 메인 스레드가 다른 요청 처리
3. 컨텍스트 스위칭 비용 거의 0 — 스레드 자체가 1개
Apache 같은 "요청당 스레드" 모델: 1만 요청 = 1만 스레드 = 컨텍스트 스위칭 폭발 + 메모리 폭발.
Node 모델: 1만 요청 = 1 메인 스레드 + 4~8 워커 스레드 + 이벤트 큐. 비교 불가의 효율.
정리 — 바이브 코딩 연결
- ▸fetch / 파일 읽기 = syscall → 비싸다 → 최소화 (배치·캐싱)
- ▸Node.js·async/await = 이벤트 기반 → I/O 많은 서버에 강함
- ▸CPU 무거운 작업은 Worker Thread → 메인 이벤트 루프 막지 마세요
면접에서 "Node.js 가 왜 빠르냐" 물으면 이 페이지의 내용을 그대로 말하면 합격 입니다.
🤖 AI 에게 이렇게 요청해보세요
이 lesson 의 개념을 알면 AI 에게 구체적으로 지시할 수 있습니다. 막연한 "고쳐줘" 가 아니라 어휘를 가진 요청 — 그게 토큰 절약의 출발점입니다.
- ▸"이 작업의 syscall 추적을 strace (Linux) 로 실행하는 명령어 알려줘"
- ▸"이 코드가 사용자 공간 / 커널 공간 어느 쪽에서 비용 큰지 진단해줘"
왜 이게 토큰을 줄이나
개념을 모를 땐 AI 답변을 받고도 "그게 뭐예요?" 를 다시 물어야 합니다. 그 "다시 물음" 이 토큰을 잡아먹습니다. 개념 한 번 익혀두면 대화가 한 번에 끝납니다.