운영체제는 실행할 프로그램에 자원을 할당하고 프로그램이 올바르게 실행되도록 돕는 특별한 프로그램. 부팅 과정에서 운영체제는 메모리의 커널 영역에 적재되며, 이 영역을 제외한 나머지 공간을 사용자 영역이라 함. 운영체제는 응용 프로그램에 자원을 효율적으로 배분하고 시스템 전체가 안정적으로 동작하도록 관리하는 역할을 수행함.
운영체제는 개발자가 하드웨어 상태를 파악하고, 코드 실행 흐름을 이해하며, 오류를 분석할 수 있게 하는 기반 환경을 제공함. 현존하는 소프트웨어 중 가장 규모가 크며 복잡도가 높은 시스템이며, 그 핵심 기능을 수행하는 부분을 커널이라 함. 커널과 달리 사용자 인터페이스(UI)는 운영체제 구성 요소이지만 핵심 서비스에 포함되지는 않음. UI는 CLI와 GUI 형태로 제공됨.
운영체제는 응용 프로그램이 하드웨어 자원에 직접 접근하는 것을 방지하고 시스템 안전성을 확보함. 이를 위해 CPU는 사용자 모드와 커널 모드라는 이중 모드를 가짐.
사용자 모드로 실행 중인 프로그램이 자원 접근을 요청하면 시스템 호출 형태로 운영체제에 전달되고, CPU는 커널 모드로 전환됨. 시스템 호출은 구조적으로 인터럽트와 유사한 메커니즘을 사용하며, 입출력 장치에 의해 발생하는 하드웨어 인터럽트와 구분되는 소프트웨어 인터럽트 형태로 구현됨. 응용 프로그램은 실행 과정에서 매우 빈번하게 시스템 호출을 활용함.
운영체제의 핵심 서비스는 다음과 같이 정리됨.
실행 중인 프로그램을 프로세스라 함. CPU는 한 순간에 하나의 명령어 흐름만 처리할 수 있으므로 운영체제는 프로세스들을 매우 빠르게 전환해가며 실행함. 이 과정은 CPU 스케줄링에 의해 결정되며, 실행 시간, 우선순위, I/O 대기 등을 기준으로 다양한 스케줄링 전략이 존재함. 메모리 관리, I/O 장치 제어 등도 운영체제가 수행하는 주요 기능임.
가상 머신은 소프트웨어적으로 구현된 가상의 컴퓨터. 실제 운영체제에서 별도의 독립된 실행 환경을 구성한다는 점에서 물리적 컴퓨터와 같은 구조를 가짐. 가상 머신은 일반적으로 사용자 모드에서 실행되며, 실제 하드웨어 자원 사용을 위해 하이퍼바이저의 개입을 받음.
하이퍼바이저는 가상 머신과 실제 하드웨어 사이에서 자원 중재 역할을 맡는 소프트웨어 계층. 하이퍼바이저는 운영체제보다 더 높은 권한을 가지는 ‘하이퍼바이저 모드’에서 동작하며, 가상 머신의 CPU, 메모리, I/O 요청을 관리함.
가상 머신 생태계는 크게 두 방식으로 구분됨.
타입 1 하이퍼바이저
타입 2 하이퍼바이저
WSL(Windows Subsystem for Linux)은 일반적인 가상 머신과 완전히 동일한 구조는 아님.
WSL 버전에 따라 동작 방식이 다름.
따라서 질문에 대한 결론은 다음과 같음.
WSL은 버전에 따라 다르지만, WSL 2는 하이퍼바이저 기반의 경량 가상 머신에 해당함.
프로그램은 실행되기 전까지 보조기억장치에 저장된 정적인 데이터 집합. 이 프로그램이 메모리에 적재되어 실행되는 순간 프로세스가 됨. 프로세스는 운영체제에 의해 관리되는 실행 단위이며, ps 명령어 등을 통해 실행 중인 프로세스를 확인할 수 있음. 프로세스는 포그라운드 프로세스와 백그라운드 프로세스로 구분됨. 백그라운드 프로세스는 데몬 또는 서비스라고도 함.
모든 프로세스는 실행을 위해 CPU와 메모리를 필요로 하지만 자원은 한정되어 있음. 운영체제는 빠르게 번갈아 실행되는 프로세스의 수행 순서를 관리하고, 각 프로세스에 필요한 자원을 할당함. 이를 위해 운영체제는 프로세스 제어 블록(PCB)이라는 자료구조를 사용함. PCB는 커널 영역에 생성되며 프로세스 생성 시 함께 만들어지고 종료 시 폐기됨.

PCB는 프로세스의 실행 상태를 유지하기 위한 핵심 자료구조.
| 구성 요소 | 설명 |
|---|---|
| 프로세스 ID(PID) | 프로세스를 구분하기 위한 고유 식별자 |
| 레지스터 값 | CPU 수행 컨텍스트. 프로그램 카운터(PC), 스택 포인터(SP) 등 |
| 프로세스 상태 | 생성, 준비, 실행, 대기, 종료 중 하나 |
| CPU 스케줄링 정보 | 우선순위, CPU 사용 시간, 시간 할당량 등 |
| 메모리 관리 정보 | 페이지 테이블, 세그먼트 정보 등 |
| 사용 파일 및 입출력 장치 목록 | 파일 디스크립터, 열린 입출력 장치 |
운영체제는 하나의 프로세스에서 다른 프로세스로 실행 흐름을 전환하기 위해 문맥 교환을 수행함. 문맥 교환은 다음 과정으로 이루어짐.
문맥 교환은 필수적이나 오버헤드가 발생함. 실행 흐름을 바꾸는 동안 실제 유용한 계산은 이루어지지 않기 때문.
프로세스는 사용자 영역에서 다음과 같은 구조를 가짐.
| 영역 | 특징 |
|---|---|
| 코드 영역(Text) | 실행할 기계어 코드. 읽기 전용 |
| 데이터 영역(Data) | 전역 변수, 정적 변수 등. 정적 할당 영역 |
| 힙 영역(Heap) | 사용자가 동적 메모리를 요청할 때 할당되는 공간. 런타임에 크기 변화 |
| 스택 영역(Stack) | 함수 호출 시 생성되는 지역 변수, 매개변수, 리턴 주소. LIFO 구조 |
| 구분 | 힙 | 스택 |
|---|---|---|
| 관리 방식 | 개발자 또는 런타임이 직접 관리 | 컴파일러/운영체제 자동 관리 |
| 할당 속도 | 느림 | 빠름 |
| 용도 | 객체, 동적 자료구조 | 함수 호출 프레임, 지역 변수 |
| 메모리 구조 | 낮은 주소에서 높은 주소 방향으로 증가 | 높은 주소 → 낮은 주소로 감소 |
| 대표 문제 | 메모리 누수, 단편화 | 스택 오버플로우 |
운영체제는 동시에 실행되는 다수의 프로세스를 다음 상태로 관리함.
| 상태 | 설명 |
|---|---|
| 생성(New) | 프로세스 생성 중 |
| 준비(Ready) | 실행 대기. CPU 할당 대기 |
| 실행(Running) | CPU 사용 중 |
| 대기(Waiting) | I/O 요청 등 외부 이벤트 대기 |
| 종료(Terminated) | 실행 종료 |
프로세스는 실행 도중 시스템 호출을 통해 다른 프로세스를 생성함. 이를 부모-자식 관계라 하며, 각 프로세스는 고유한 PID와 부모 PID(PPID)를 가짐.
예시
로그인 프로세스 → bash 프로세스 → vim 프로세스
최초의 프로세스는 운영체제마다 다름.
새로운 프로세스는 fork 시스템 호출을 통해 생성됨.
fork: 부모 프로세스의 복사본을 생성해 자식 프로세스 생성exec: 자식 프로세스 메모리 공간을 새로운 프로그램으로 교체fork 이후 exec를 호출하지 않으면 부모와 같은 코드를 병행 실행하게 됨.
전통적인 fork 방식은 부모 메모리를 그대로 복사하는 것처럼 보이지만 실제로는 Copy-on-Write(CoW) 최적화를 사용함.
CoW의 특징
따라서 부모 프로세스가 매우 크더라도 실제 전체 복사가 즉시 일어나는 방식은 아님.
스레드는 프로세스 내부에서 실행되는 최소 실행 단위. 하나의 프로세스 안에서 여러 실행 흐름을 동시에 관리할 수 있게 하는 구조.
| 구분 | 프로세스 | 스레드 |
|---|---|---|
| 자원 소유 | 별도의 메모리 공간 가짐 | 같은 프로세스의 메모리 공유 |
| 생성 비용 | 높음 | 낮음 |
| 문맥 교환 비용 | 큼 | 작음 |
| 안정성 | 한 프로세스가 죽어도 다른 프로세스에 영향 없음 | 한 스레드 오류 시 전체 프로세스에 영향 |
스레드는 코드, 데이터, 힙을 공유하며, 스택과 레지스터만 독립적으로 가짐.
| 방식 | 장점 | 단점 | 대표 사례 |
|---|---|---|---|
| 멀티프로세스 | 독립성 높음, 안정성 높음 | 메모리 사용량 많음, 교환 비용 큼 | 웹 서버 워커 모델, Chrome 브라우저 탭 |
| 멀티스레드 | 메모리 효율성 높음, 문맥교환 빠름 | 오류 전파 위험 | 게임 엔진, 데이터 파이프라인 병렬 처리 |
| 혼합 모델 | 높은 처리량, 자원 효율 | 복잡한 설계 | Nginx, JVM, 데이터 엔진 |
Python은 멀티스레드를 지원하는가?
Python은 멀티스레드를 지원하지만, GIL(Global Interpreter Lock) 때문에 CPython에서는 CPU 바운드 작업에서 진정한 병렬 실행이 불가능함.
정리
Python은 멀티스레드를 지원함. threading 모듈을 통해 하나의 프로세스 안에서 여러 스레드를 생성하고 관리할 수 있음. 하지만 Python 인터프리터의 대표 구현체인 CPython에는 전역 인터프리터 락(Global Interpreter Lock, GIL)이라는 구조적 제약이 존재함.
GIL은 한 시점에 하나의 스레드만 Python 바이트코드를 실행하도록 강제하는 잠금 구조. 즉, 멀티스레드를 생성해도 CPU 바운드 작업에서는 동시에 여러 스레드가 병렬로 실행되지 않음.
이로 인해 Python에서 멀티스레딩을 사용해도 다음과 같은 특징이 나타남.
Python에서 스레드는 실제 OS 스레드로 동작하며, 운영체제 입장에서는 완전한 멀티스레드 구조.
그러나 CPython 내부에서는 GIL 때문에 다음과 같은 제약이 발생함.
| 구분 | 설명 |
|---|---|
| 실행 단위 | OS 스레드 |
| 동시 실행 가능성 | Python 바이트코드는 1개 스레드씩만 실행 |
| 스레드 전환 시점 | I/O 발생, 일정 바이트코드 실행 후 인터럽트 기반 |
GIL은 다음과 같은 이유로 존재함.
GIL 때문에 모든 멀티스레드 프로그램이 비효율적인 것은 아님.
특히 I/O 바운드 작업에서는 Python 스레드가 매우 효과적임.
이런 작업은 I/O 요청이 발생하는 순간 GIL이 해제되고 다른 스레드가 실행됨.
따라서 실질적으로 병렬 실행 효과를 얻을 수 있음.
GIL의 영향 때문에 CPU 바운드 작업에서는 멀티스레딩 이점이 거의 없음.
이 경우 멀티스레드를 사용해도 GIL 때문에 CPU를 동시에 여러 스레드가 활용할 수 없으며, 오히려 문맥 교환 오버헤드가 증가할 수 있음.
CPU 바운드 작업에서는 multiprocessing 모듈을 사용하는 것이 적합함.
멀티프로세스는 각 프로세스가 독립된 Python 인터프리터와 GIL을 가지므로 완전한 병렬 처리 가능.
| 방식 | 병렬 처리 가능 여부 | 적합한 작업 |
|---|---|---|
| 멀티스레드 | I/O 바운드에서 가능 | 네트워크, DB, 파일 I/O |
| 멀티프로세스 | CPU 바운드에서 완전 병렬 | 영상 처리, 대규모 계산 |
가능한 경우가 있음.
NumPy, SciPy, TensorFlow 등은 내부 계산 영역에서 GIL을 해제함.
따라서 다음과 같은 상황에서는 Python 코드도 스레드 기반 병렬성이 실현됨.
예시
CPython만 GIL을 갖고 있음.
PyPy STM, Jython, IronPython은 GIL 제약을 받지 않음.
| 질문 | 답변 |
|---|---|
| Python은 멀티스레드를 지원하는가? | 지원함(threading) |
| 병렬 실행 가능한가? | I/O 바운드만 사실상 병렬. CPU 바운드는 GIL로 인해 병렬 불가 |
| CPU 바운드 해결책 | multiprocessing 또는 C 확장 모듈 활용 |
| I/O 바운드에서는? | 매우 효율적 |
| 대표 활용 예 | 웹 크롤러, 비동기 API 호출, 소켓 서버 |
Python은 멀티스레드를 지원하지만 GIL 제약 때문에 CPU 바운드 병렬 처리는 불가능. 다만 I/O 바운드 작업에서는 스레드가 매우 효과적이며, 실제 운영환경에서도 멀티스레드를 광범위하게 활용함. 반대로 연산량이 많은 작업은 멀티프로세스를 사용해 병렬 처리를 수행하는 것이 일반적 구조.