
프로세스는 실행 중인 프로그램으로, 운영체제가 관리하는 작업의 기본 단위입니다.
운영체제(Operating System, OS)는 하드웨어와 응용 프로그램 사이에서 중개자 역할을 하는 시스템 소프트웨어입니다.
쉬운 비유:
컴퓨터 = 아파트 건물
하드웨어 = 건물 시설 (전기, 수도, 엘리베이터)
운영체제 = 관리 시스템
응용 프로그램 = 입주민들
관리 시스템(OS):
- 엘리베이터 순서 정하기 (CPU 스케줄링)
- 각 집에 전기 배분하기 (자원 할당)
- 세대 간 분쟁 해결하기 (프로세스 간 통신)
- 보안 관리하기 (권한 제어)
운영체제가 없다면?
프로그램이 직접 해야 할 일:
- CPU 사용 시간 조절
- 메모리 주소 계산
- 하드디스크 제어
- 키보드/마우스 입력 처리
- 화면 출력 제어
→ 각 하드웨어마다 다른 코드 작성 → 프로그램 작성이 극도로 어려움
운영체제가 있으면:
print("Hello") # 이 한 줄이면 됨! → OS가 알아서 화면에 출력
운영체제는 크게 커널(Kernel), 셸(Shell), 시스템 프로그램 3가지로 구성됩니다.
┌─────────────────────────────────────┐
│ 응용 프로그램 │
│ 브라우저, 게임, 에디터, 계산기 │
└─────────────────────────────────────┘
↕
┌─────────────────────────────────────┐
│ 셸 (Shell) - 인터페이스 │
│ 사용자 명령어를 커널로 전달 │
└─────────────────────────────────────┘
↕
┌─────────────────────────────────────┐
│ 시스템 프로그램 │
│ ls, cp, mv, ps, 작업 관리자 등 │
└─────────────────────────────────────┘
↕ 시스템 콜
┌─────────────────────────────────────┐
│ 커널 (Kernel) - 핵심 │
│ 프로세스, 메모리, 파일, 장치 관리 │
└─────────────────────────────────────┘
↕
┌─────────────────────────────────────┐
│ 하드웨어 │
│ CPU, 메모리, 디스크, 네트워크 │
└─────────────────────────────────────┘
**운영체제의 실행 권한**
- 사용자 모드 (User Mode): 사용자가 사용하는 일반 앱(브라우저, 게임 등)이 실행되는 상태
하드웨어에 직접 접근할 권한이 없어서 시스템을 망가뜨릴 위험이 적음
- 커널 모드 (Kernel Mode): 운영체제의 핵심인 커널이 실행되는 상태
CPU, 메모리, 디스크 등 모든 하드웨어 자원에 접근하고 제어할 수 있는 '무한 권한'을 가짐
커널은 운영체제의 핵심으로, 항상 메모리에 상주하며 하드웨어를 직접 제어합니다.
커널의 역할:
1. 프로세스 관리
- 프로세스 생성, 종료
- CPU 스케줄링
- 프로세스 간 통신
2. 메모리 관리
- 메모리 할당, 해제
- 가상 메모리
- 메모리 보호
3. 파일 시스템
- 파일 읽기, 쓰기
- 디렉토리 관리
- 권한 제어
4. 장치 드라이버
- 하드웨어 제어
- 입출력 관리
특징:
- 항상 메모리에 상주
- 커널 모드(특권 모드)에서 실행
- 하드웨어 직접 접근 가능
- 가장 신뢰할 수 있는 코드
셸은 사용자와 커널 사이의 인터페이스입니다. 일종의 통역사 역할을 합니다.
셸의 역할:
사용자 명령어 해석:
$ ls -l
↓
셸이 해석
↓
시스템 콜 호출
↓
커널이 처리
↓
결과 반환
↓
화면에 출력
셸의 종류:
- bash (Bourne Again Shell) - Linux/macOS 기본
- zsh (Z Shell) - macOS 최신 기본
- cmd.exe - Windows 명령 프롬프트
- PowerShell - Windows 고급 셸
GUI 셸:
- Windows 탐색기
- macOS Finder
- GNOME, KDE (Linux)
시스템 프로그램은 운영체제가 제공하는 유틸리티입니다.
시스템 프로그램 예시:
파일 관리:
- ls, dir: 파일 목록
- cp, copy: 파일 복사
- mv, move: 파일 이동
- rm, del: 파일 삭제
프로세스 관리:
- ps: 프로세스 목록
- top, 작업 관리자: 프로세스 모니터
- kill: 프로세스 종료
시스템 정보:
- df: 디스크 사용량
- free: 메모리 사용량
- uname: 시스템 정보
네트워크:
- ping: 네트워크 연결 확인
- ifconfig, ipconfig: 네트워크 설정
특징:
- 커널이 아닌 일반 프로그램
- 필요할 때만 실행
- 사용자 모드에서 실행
- 시스템 콜로 커널에 요청
사용자가 터미널에서 명령어를 입력할 때 무슨 일이 일어나는지 봅시다.
$ ls -l # "현재 디렉토리에 있는 파일들의 목록을 '자세히(Long format)' 보여달라"는 의미
내부 동작 과정:
1단계: 사용자 입력
터미널에서 "ls -l" 입력, Enter 키 누름
↓
2단계: 셸이 명령어 받음
명령어 파싱: "ls"와 "-l" 분리
↓
3단계: 셸이 프로그램 찾기
ls 프로그램이 어디 있는지 검색 → /bin/ls 또는 /usr/bin/ls 발견
PATH 환경변수 참조
↓
4단계: 새 프로세스 생성 (시스템 콜)
셸이 시스템 콜 호출:
fork() → 자식 프로세스 생성
exec("/bin/ls", ["-l"]) → ls 프로그램 실행
↓
5단계: ls 프로그램 실행
a. ls가 시스템 콜 호출 → opendir(".") - 현재 디렉토리 열기
b. 커널이 디렉토리 정보 읽기
c. readdir() 반복 - 파일 목록 가져오기
d. stat() - 각 파일 정보 가져오기 (크기, 권한, 날짜)
↓
6단계: 커널 처리 (커널 모드)
a. 파일 시스템에서 디렉토리 데이터 찾기
b. 디스크에서 inode 정보 읽기
c. 파일 메타데이터 수집
d. 권한 확인
↓
7단계: 결과 출력
ls가 포맷팅해서 화면에 출력
-rw-r--r-- 1 user group 1234 Jan 1 12:00 file.txt
drwxr-xr-x 2 user group 4096 Jan 2 13:00 folder
↓
8단계: 프로그램 종료
ls 종료 (exit 시스템 콜)
셸로 제어 복귀
$ (프롬프트 다시 표시)
계층별 역할:
사용자: "현재 디렉토리 파일 목록 보여줘"
↓
셸 (bash): "알았어, ls 프로그램 실행할게"
↓
ls 프로그램: "커널한테 파일 정보 달라고 할게"
↓
커널: "파일 시스템 확인하고, 디스크에서 읽어올게"
↓
하드웨어: "디스크에서 데이터 읽어서 전달"
이미 실행 중인 프로그램이 파일을 열 때는 셸을 거치지 않습니다.
# Python 코드
with open('data.txt', 'r') as f: # 'r': 읽기 모드
content = f.read()
# 위 코드는 다음과 같은 코드로 with 를 사용하면 열린 파일을 자동으로 닫아 줌
# f = open('data.txt', 'r')
# content = f.read()
# f.close()
내부 동작 과정:
1단계: 응용 프로그램 (사용자 모드)
Python 프로그램 이미 실행 중
open('data.txt', 'r') 호출
↓
2단계: 시스템 콜 발생
셸을 거치지 않고 직접 커널에 요청!
사용자 모드 → 커널 모드 전환
시스템 콜: open("/path/to/data.txt", O_RDONLY)
# O_RDONLY (Open Read-Only): 파일을 "읽기 전용"으로 열겠다는 설정 값(플래그)
↓
3단계: 커널 처리 (커널 모드)
a. 파일 시스템에서 'data.txt' 찾기
b. 권한 확인 (읽기 권한 있는가?)
c. 파일 디스크립터 생성 (정수, 예: 3)
d. 파일 테이블에 등록
↓
4단계: 디스크 I/O
a. 파일이 디스크 어디에 있는지 확인 (inode)
b. 디스크 컨트롤러에 명령
c. 디스크에서 데이터 읽기
d. 메모리 버퍼로 복사
↓
5단계: 결과 반환
커널 모드 → 사용자 모드 전환
파일 디스크립터 번호 반환 (예: 3)
↓
6단계: 응용 프로그램으로 복귀
f.read() 호출하면 2-5단계 반복
read(3, buffer, size) 시스템 콜
데이터를 프로그램 변수에 저장
계층별 역할:
응용 프로그램 (Python): "data.txt 파일 열어줘!"
↓
셸: (역할 없음!)
↓
커널: "알았어, 파일 시스템 확인하고, 권한 체크하고, 디스크에서 읽어올게"
↓
하드웨어: "디스크 헤드 이동, 데이터 읽기, 전송"
┌─────────────────┬──────────────┬──────────────┐
│ 구 분 │ 명령어 실행 │ 파일 열기 │
├─────────────────┼──────────────┼──────────────┤
│ 셸 사용 여부 │ ✓ 사용 │ ✗ 미사용 │
│ 예시 │ $ ls -l │ open('file')│
│ 프로세스 생성 │ ✓ (fork) │ ✗ │
│ 시스템 콜 호출자 │ 셸 + ls │ Python │
└─────────────────┴──────────────┴──────────────┘
핵심:
- 명령어 입력: 셸이 중개 역할
- 프로그램 내부: 직접 시스템 콜
1. Windows
기반: 독자적 (NT 커널)
특징:
- 사용자 친화적 GUI
- 게임, 업무용 소프트웨어 풍부
- .exe 실행 파일
- 상업용 (유료)
버전:
- Windows 11
- Windows Server (서버용)
장점: 호환성, 사용 편의성
단점: 비용, 보안 취약점
2. macOS
기반: Unix (BSD 기반)
특징:
- 세련된 디자인
- Unix 기반이라 개발자 친화적
- 상업용 (유료, 하드웨어 포함)
버전:
- macOS Sonoma
- macOS Ventura
장점: 안정성, 디자인, Unix 도구
단점: 비용, 하드웨어 제한
3. Linux
기반: Unix-like
특징:
- 오픈소스
- 커스터마이징 자유
- 배포판(distro)이 다양
주요 배포판:
- Ubuntu: 초보자 친화적
- Fedora: 최신 기술
- Debian: 안정적
- CentOS/RHEL: 서버용
- Arch: 고급 사용자용
장점: 무료, 보안, 유연성, 서버 최적화
단점: 데스크톱 앱 부족, 학습 곡선
4. Unix
역사적 의미:
- 1970년대 개발 (AT&T Bell Labs)
- 현대 OS의 조상
- macOS, Linux의 기반
현재:
- 순수 Unix는 거의 사용 안됨
- Unix-like 시스템들이 계승
- POSIX 표준으로 호환성 유지
영향:
- C 언어 탄생
- 파이프, 셸, 파일 시스템 개념
- "모든 것은 파일이다" 철학
5. Android
기반: Linux 커널
특징:
- 오픈소스
- 다양한 제조사 지원
장점: 개방성, 선택의 폭
단점: 파편화, 보안
6. iOS
기반: Unix (Darwin 커널)
특징:
- iPhone, iPad 전용
- 폐쇄적 생태계
장점: 안정성, 보안, 최적화
단점: 폐쇄성, 비용
┌──────────┬─────────┬─────────┬──────────┐
│ 구 분 │Windows │ macOS │ Linux │
├──────────┼─────────┼─────────┼──────────┤
│비용 │ 유료 │ 유료 │ 무료 │
│오픈소스 │ ✗ │ 일부 │ ✓ │
│사용 난이도│ 쉬움 │ 쉬움 │ 중~어려움 │
│게임 │ ✓✓✓ │ ✓ │ ✓ │
│개발 │ ✓ │ ✓✓ │ ✓✓✓ │
│서버 │ ✓ │ ✓ │ ✓✓✓ │
│보안 │ ✓ │ ✓✓ │ ✓✓✓ │
└──────────┴─────────┴─────────┴──────────┘
사용 목적의 차이:
- 데스크톱: 사용 편의성 (Windows, macOS)
- 서버: 안정성, 보안 (Linux)
- 모바일: 배터리, 터치 (Android, iOS)
- 임베디드: 최소 자원 (특화 Linux)
철학의 차이:
- 상업적: Windows, macOS (수익 모델)
- 오픈소스: Linux (공유, 자유)
특정 운영체제의 사용법이 아니라, 모든 운영체제에 공통되는 핵심 원리를 배웁니다.
학습 흐름:
프로세스 → 스레드 → 스케줄링 → 동시성/병렬성 → 동기화 → 교착 상태 → 메모리 관리
각 주제는 Windows, Linux, macOS 모두에 적용됩니다.
프로그램(Program)과 프로세스(Process)는 다릅니다.
쉬운 비유:
프로그램 = 레시피 (요리책)
- 종이에 적힌 지침
- 실행되지 않음
- 정적(static)
프로세스 = 요리하는 과정
- 레시피를 따라 실제로 요리 중
- 재료, 도구, 진행 상황 포함
- 동적(dynamic)
예: 같은 레시피(프로그램)로 여러 사람이 동시에 요리(프로세스) 가능
프로그래밍으로 이해:
# 프로그램: 디스크에 저장된 파일
# 파일: calculator.py
def add(a, b):
"""덧셈 함수"""
return a + b
def main():
result = add(3, 5)
print(f"결과: {result}")
if __name__ == "__main__":
main()
# 이 코드 자체는 프로그램 실행 전으로 아직 아무것도 안 함
프로세스: 실행 중
# 프로세스: 프로그램을 실행하면
$ python calculator.py
결과: 8
# 실행하는 순간:
# 1. 메모리에 로드됨
# 2. CPU가 코드 실행
# 3. 변수에 값 저장
# 4. 결과 출력
# → 이것이 프로세스!
프로세스는 실행 중인 프로그램으로, 다음을 포함합니다:
프로세스 = 프로그램 코드
+ 현재 실행 상태
+ 메모리 내용
+ 시스템 자원 (파일, 네트워크 등)
즉:
프로그램: 수동적 (passive)
프로세스: 능동적 (active)
프로세스는 메모리를 4개 영역으로 나눠 사용합니다.
높은 주소
↑
┌─────────────┐
│ 스택 │ ← 함수 호출, 지역 변수
│ (Stack) │ 아래로 성장 ↓
├─────────────┤
│ (여유) │ ← 스택과 힙이 만나면 overflow!
├─────────────┤
│ 힙 │ ← 동적 할당 메모리
│ (Heap) │ 위로 성장 ↑
├─────────────┤
│ 데이터 │ ← 전역 변수, 정적 변수
│ (Data) │
├─────────────┤
│ 코드 │ ← 프로그램 명령어
│ (Code) │
└─────────────┘
낮은 주소
↓
왜 스택과 힙은 서로 마주 보고 성장할까?
스택은 아래로(↓), 힙은 위로(↑) 자라는 것을 볼 수 있습니다. 이것은 운영체제의 효율적인 설계 의도가 담겨 있습니다.
- 메모리 공간의 유연성: 스택과 힙 중 어느 쪽이 더 많이 쓰일지 미리 알 수 없기 때문에
양 끝에서 시작해 가운데 여유 공간을 공유하게 함으로써 메모리를 낭비 없이 최대한 활용
- 오버플로(Overflow):
-- Stack Overflow는 함수를 너무 깊게 재귀 호출하여 스택이 힙 영역을 침범할 때 발생
-- Heap Overflow는 동적 할당을 너무 많이 해서 힙이 스택 영역을 침범할 때 발생
역할: 실행할 명령어(기계어) 저장
def greet(name):
"""
이 함수의 기계어 명령어가 코드 영역에 저장됨
"""
message = f"Hello, {name}!"
return message
# 함수 정의 자체는 코드 영역에 실행되지 않으면 메모리만 차지
특징:
- 읽기 전용 (Read-Only)
- 프로그램 실행 중 변하지 않음
- 여러 프로세스가 공유 가능 (같은 프로그램을 여러 번 실행해도 코드는 하나)
역할: 전역(Global) 변수, 정적(Static) 변수 저장
# 전역 변수 - 데이터 영역에 저장
counter = 0 # 프로그램 시작부터 메모리에 존재
PI = 3.14159
def increment():
"""
전역 변수 사용
counter는 데이터 영역에 있으므로 프로그램 종료까지 유지됨
"""
global counter
counter += 1
return counter
# 호출할 때마다 counter가 유지됨
print(increment()) # 1
print(increment()) # 2
print(increment()) # 3
특징:
- 프로그램 시작부터 종료까지 유지
- 초기화된 데이터: .data
- 초기화 안된 데이터: .bss * BSS(Block Started by Symbol)는 별도의 공간
역할: 동적 할당 메모리로 개발자가 필요에 따라 실시간(Run-time)으로 할당하는 메모리 공간
def heap_example():
"""
힙 영역 사용 예시
동적 할당:
- 실행 중에 크기 결정
- 프로그래머가 관리
"""
numbers = [] # 빈 리스트 생성 (힙), 크기가 실행 중에 변함
for i in range(1000000):
numbers.append(i) # 힙 영역 계속 증가
# 힙이 스택과 만나면? → MemoryError!
# 리스트 객체: 힙 영역
data = [1, 2, 3, 4, 5]
# 딕셔너리: 힙 영역
user = {
'name': 'Alice',
'age': 25
}
# 객체: 힙 영역
class Person:
def __init__(self, name):
self.name = name # 힙에 저장
person = Person('Bob') # 힙 할당
return data # 함수 끝나도 data는 힙에 남음
특징:
- 프로그래머가 직접 관리 (명시적 할당/해제)
- 크기가 실행 중에 변함
- 위로 성장 ↑ * 낮은 주소에서 높은 주소 방향으로 쌓여 올라감
- 느림 (할당/해제 오버헤드)
- Python은 자동 가비지 컬렉션 (다 쓰고 나면 반드시 해제해야 하며, 그렇지 않으면 '메모리 누수' 발생)
역할: 함수가 호출될 때 생성되는 지역 변수, 매개 변수, 반환 주소 저장
def stack_example():
"""
스택 영역 사용 예시
함수 호출마다 스택 프레임 생성:
- 매개 변수
- 지역 변수
- 반환 주소
"""
# 지역 변수: 스택에 저장
x = 10 # stack_example의 스택 프레임에
y = 20
# 함수 호출: 새 스택 프레임
result = helper(x, y)
return result
# 함수 종료 → 스택 프레임 제거 → x, y 자동으로 사라짐!
def helper(a, b):
"""
새로운 스택 프레임 생성
스택 구조:
┌───────────────┐ ← 스택 포인터
│ helper 프레임 │
│ - a = 10 │
│ - b = 20 │
│ - temp = 30 │
├───────────────┤
│ stack_example│
│ - x = 10 │
│ - y = 20 │
└───────────────┘
"""
temp = a + b # helper의 스택 프레임에
return temp
# helper 종료 → helper 프레임 제거 → temp 자동 사라짐
# 재귀 호출: 스택 깊이 주의!
def factorial(n):
"""
재귀: 스택 프레임 n개 생성
factorial(5):
┌───────────────┐
│ factorial(1) │
├───────────────┤
│ factorial(2) │
├───────────────┤
│ factorial(3) │
├───────────────┤
│ factorial(4) │
├───────────────┤
│ factorial(5) │
└───────────────┘
너무 깊으면: Stack Overflow!
"""
if n <= 1:
return 1
return n * factorial(n - 1)
# 사용
result = stack_example()
print(f"결과: {result}")
특징:
- 자동 관리 (함수 호출/종료 시)
- LIFO (Last In First Out) 구조
- 아래로 성장 ↓ * 높은 주소에서 낮은 주소 방향으로 내려감
- 빠름 (단순 포인터 이동)
- 크기 제한 (보통 수 MB)
- 초과 시 Stack Overflow
def memory_regions_demo():
"""
메모리 영역 종합 예시
"""
# ===== 코드 영역 =====
# 이 함수의 명령어들
# ===== 데이터 영역 =====
# (함수 밖의 전역 변수)
# ===== 스택 영역 =====
# 지역 변수
local_var = 100 # 스택
# 매개변수도 스택
def func(param): # param은 스택
inner = 10 # inner도 스택
return inner + param
# ===== 힙 영역 =====
# 동적 할당
my_list = [1, 2, 3] # 리스트 객체는 힙
my_dict = {} # 딕셔너리도 힙
# 스택 vs 힙
stack_data = 42 # 스택: 빠름, 자동 관리
heap_data = [42] # 힙: 느림, 크기 유연
print(f"스택: {stack_data}")
print(f"힙: {heap_data}")
프로세스는 실행 중에 5가지 상태를 거칩니다.
┌─────────┐
│ 생성 │ ← 프로세스 시작
│ (New) │
└────┬────┘
↓
┌─────────┐
┌───→│ 준비 │←───┐
│ │ (Ready)│ │
│ └────┬────┘ │
│ ↓ │
│ ┌─────────┐ │
│ │ 실행 │ │ 인터럽트
│ │(Running)│────┘ 또는 시간 초과
│ └────┬────┘
│ ↓
│ ┌─────────┐
└────│ 대기 │ ← I/O 대기
│(Waiting)│
└────┬────┘
↓
┌─────────┐
│ 종료 │ ← 프로세스 끝
│ (Exit │
└─────────┘
import os
import subprocess
def create_process_example():
"""
프로세스 생성 예시
생성 상태:
- 운영체제가 프로세스 생성 중
- 메모리 할당
- 프로세스 제어 블록 (PCB: Process Control Block) 생성
- 아직 실행 안 됨
"""
print("부모 프로세스 시작")
print(f"부모 PID: {os.getpid()}")
# 프로세스 식별자(PID, Process ID): 운영체제가 각 프로세스를 구분하기 위해 부여하는 0 또는 양의 정수 번호로
# 커널이 프로세스를 생성할 때 자동으로 할당하며, 프로세스 종료 시 재사용될 수 있는 임시 고유 번호
# 새 프로세스 생성 (생성 상태)
# subprocess.Popen: 비동기 실행
# Popen은 자식 프로세스를 실행시킨 뒤, 그 프로세스가 끝날 때까지 기다리지 않고 바로 다음 파이썬 코드를 실행
# (기다리게 하려면 .wait()나 .communicate()를 사용)
process = subprocess.Popen(
['python', '-c', 'print("자식 프로세스!")'],
stdout=subprocess.PIPE # .PIPE: 한 프로세스의 출력을 다른 프로세스의 입력으로 연결하는 가상의 데이터 통로
)
print(f"자식 PID: {process.pid}")
# 자식 프로세스 종료 대기
output, _ = process.communicate() # .communicate(): Popen을 사용할 때 자식 프로세스와의 '대화와 기다림'을 한꺼번에
# 처리해주는 도구(데이터 전달(입력), 데이터 수거(출력 및 에러), 프로세스 종료 대기(Wait))
print(f"자식 출력: {output.decode()}")
# 실행
create_process_example()
준비 상태:
- 실행 준비 완료
- CPU만 기다림
- 준비 큐(Ready Queue)에 대기
비유:
은행 대기 줄
- 서류 준비 완료
- 창구만 기다림
실행 상태:
- CPU를 할당받음
- 실제로 명령어 실행 중
전환:
실행 → 준비: 시간 할당량 소진
실행 → 대기: I/O 요청
실행 → 종료: 프로그램 완료
import time
def waiting_state_example():
"""
대기 상태 예시
대기 상태가 되는 경우:
- 파일 읽기/쓰기
- 네트워크 통신
- 사용자 입력
- sleep() 호출
"""
print("실행 상태: 계산 중...")
result = 100 + 200
print("대기 상태 진입: 파일 읽기")
# I/O 작업 → 대기 상태
# CPU를 다른 프로세스에게 양보
with open('example.txt', 'r') as f:
data = f.read() # 대기 상태!
print("실행 상태 복귀: 파일 읽기 완료")
print("대기 상태 진입: sleep")
time.sleep(2) # 2초 동안 대기 상태
print("실행 상태 복귀: sleep 완료")
return result
import sys
def termination_example():
"""
프로세스 종료
종료 방법:
1. 정상 종료: return, 프로그램 끝
2. 비정상 종료: 에러, 강제 종료
"""
# 정상 종료
def normal_exit():
print("작업 완료")
return 0 # 정상 종료 코드
# 비정상 종료
def abnormal_exit():
print("오류 발생!")
sys.exit(1) # 에러 코드 1로 종료
# 예외로 종료
def exception_exit():
raise Exception("예상치 못한 오류")
# 프로세스 종료
# 강제 종료 (외부에서)
# kill 명령어, Ctrl+C 등
PCB (Process Control Block)는 운영체제가 프로세스를 관리하기 위해 유지하는 정보입니다.
PCB = 프로세스의 신분증
포함 정보:
- PID (프로세스 ID)
- 프로세스 상태
- CPU 레지스터 값
- 메모리 정보
- 스케줄링 정보
- 파일/네트워크 정보
개념적 구조:
class ProcessControlBlock:
"""
PCB 개념적 구조
실제로는 C 구조체로 구현되지만 개념 이해를 위한 Python 클래스
"""
def __init__(self, pid, program_name):
# ===== 프로세스 식별 정보 =====
self.pid = pid # 프로세스 ID (고유)
self.parent_pid = None # 부모 프로세스 ID
self.program_name = program_name # 실행 파일명
# ===== 프로세스 상태 =====
self.state = "NEW" # NEW, READY, RUNNING, WAITING, EXIT
# ===== CPU 정보 =====
# 컨텍스트 스위칭 시 저장/복원
self.program_counter = 0 # 다음 실행할 명령어 주소
self.registers = {} # CPU 레지스터 값들
# ===== 메모리 정보 =====
self.memory_base = None # 메모리 시작 주소
self.memory_limit = None # 메모리 크기
self.page_table = None # 페이지 테이블 (가상 메모리)
# ===== 스케줄링 정보 =====
self.priority = 0 # 우선순위
self.cpu_time = 0 # CPU 사용 시간
self.arrival_time = None # 도착 시간
# ===== 파일/자원 정보 =====
self.open_files = [] # 열린 파일 목록
self.network_sockets = [] # 네트워크 연결
# ===== 기타 =====
self.user_id = None # 소유자
self.working_directory = None # 작업 디렉토리
def __repr__(self):
"""PCB 정보 출력"""
return f"""
PCB [PID: {self.pid}]
├─ 프로그램: {self.program_name}
├─ 상태: {self.state}
├─ 우선순위: {self.priority}
├─ CPU 시간: {self.cpu_time}ms
└─ 열린 파일: {len(self.open_files)}개
""".strip()
# 예시: PCB 생성
pcb = ProcessControlBlock(pid=1234, program_name="calculator.py")
pcb.state = "RUNNING"
pcb.priority = 5
pcb.cpu_time = 150
pcb.open_files = ["input.txt", "output.txt"]
print(pcb)
import os
import psutil # pip install psutil
def show_process_info():
"""
실제 프로세스 정보 확인
psutil: 시스템/프로세스 정보 라이브러리
"""
# 현재 프로세스
current_process = psutil.Process()
print("=== 현재 프로세스 정보 ===")
print(f"PID: {current_process.pid}")
print(f"이름: {current_process.name()}")
print(f"상태: {current_process.status()}")
# CPU 정보
cpu_times = current_process.cpu_times()
print(f"\nCPU 시간:")
print(f" 사용자: {cpu_times.user}초")
print(f" 시스템: {cpu_times.system}초")
# 메모리 정보
memory_info = current_process.memory_info()
print(f"\n메모리:")
print(f" RSS: {memory_info.rss / 1024 / 1024:.2f} MB")
print(f" VMS: {memory_info.vms / 1024 / 1024:.2f} MB")
# 열린 파일
try:
open_files = current_process.open_files()
print(f"\n열린 파일: {len(open_files)}개")
for f in open_files[:3]: # 처음 3개만
print(f" - {f.path}")
except:
print("\n열린 파일: 접근 권한 없음")
# 부모 프로세스
try:
parent = current_process.parent()
if parent:
print(f"\n부모 프로세스:")
print(f" PID: {parent.pid}")
print(f" 이름: {parent.name()}")
except:
print("\n부모 프로세스: 없음")
# 실행
show_process_info()
프로세스:
- 독립적인 실행 단위
- 자신만의 메모리 공간
- 프로세스 간 통신 어려움
- 생성/전환 비용 큼
스레드 (다음 글):
- 프로세스 내부의 실행 흐름
- 메모리 공간 공유
- 스레드 간 통신 쉬움
- 생성/전환 비용 작음
선택:
- 독립성 필요: 프로세스
- 속도 중요: 스레드
def memory_leak_prevention():
"""
메모리 누수 방지
메모리 누수:
- 힙에 할당한 메모리를 해제 안함
- 계속 쌓여서 메모리 부족
Python:
- 자동 가비지 컬렉션
- 하지만 주의 필요
"""
# 나쁜 예: 순환 참조
class Node:
def __init__(self):
self.next = None
def bad_example():
node1 = Node()
node2 = Node()
node1.next = node2
node2.next = node1 # 순환!
# 함수 끝나도 메모리 해제 안될 수 있음
# 좋은 예: 명시적 정리
def good_example():
node1 = Node()
node2 = Node()
node1.next = node2
node2.next = node1
# 사용 후 정리
node1.next = None
node2.next = None
# 이제 가비지 컬렉션 가능
프로세스
정의:
실행 중인 프로그램
프로그램 vs 프로세스:
프로그램 = 레시피 (정적)
프로세스 = 요리 중 (동적)
메모리 구조
코드: 명령어 (읽기 전용)
데이터: 전역 변수
힙: 동적 할당 (위로 성장)
스택: 함수 호출 (아래로 성장)
프로세스 상태
생성 → 준비 → 실행 ⇄ 대기 → 종료
준비: CPU 대기
실행: CPU 사용 중
대기: I/O 대기
PCB
프로세스 제어 블록:
- PID
- 상태
- CPU 레지스터
- 메모리 정보
- 스케줄링 정보
[09-02] 스레드 (Thread)
이전 글: [08-09] 기타 복잡도 클래스
다음 글: [09-02] 스레드
시리즈: P1. Computer Science