프로세스는 실행 중인 프로그램으로, 운영체제가 관리하는 작업의 기본 단위입니다.


📖 운영체제란 무엇인가

운영체제의 정의

운영체제(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. 커널 (Kernel) - 핵심

커널은 운영체제의 핵심으로, 항상 메모리에 상주하며 하드웨어를 직접 제어합니다.

커널의 역할:

1. 프로세스 관리
   - 프로세스 생성, 종료
   - CPU 스케줄링
   - 프로세스 간 통신

2. 메모리 관리
   - 메모리 할당, 해제
   - 가상 메모리
   - 메모리 보호

3. 파일 시스템
   - 파일 읽기, 쓰기
   - 디렉토리 관리
   - 권한 제어

4. 장치 드라이버
   - 하드웨어 제어
   - 입출력 관리

특징:
- 항상 메모리에 상주
- 커널 모드(특권 모드)에서 실행
- 하드웨어 직접 접근 가능
- 가장 신뢰할 수 있는 코드

2. 셸 (Shell) - 인터페이스

은 사용자와 커널 사이의 인터페이스입니다. 일종의 통역사 역할을 합니다.

셸의 역할:

사용자 명령어 해석:
$ ls -l
  ↓
셸이 해석
  ↓
시스템 콜 호출
  ↓
커널이 처리
  ↓
결과 반환
  ↓
화면에 출력

셸의 종류:
- bash (Bourne Again Shell) - Linux/macOS 기본
- zsh (Z Shell) - macOS 최신 기본
- cmd.exe - Windows 명령 프롬프트
- PowerShell - Windows 고급 셸

GUI 셸:
- Windows 탐색기
- macOS Finder
- GNOME, KDE (Linux)

3. 시스템 프로그램

시스템 프로그램은 운영체제가 제공하는 유틸리티입니다.

시스템 프로그램 예시:

파일 관리:
- ls, dir: 파일 목록
- cp, copy: 파일 복사
- mv, move: 파일 이동
- rm, del: 파일 삭제

프로세스 관리:
- ps: 프로세스 목록
- top, 작업 관리자: 프로세스 모니터
- kill: 프로세스 종료

시스템 정보:
- df: 디스크 사용량
- free: 메모리 사용량
- uname: 시스템 정보

네트워크:
- ping: 네트워크 연결 확인
- ifconfig, ipconfig: 네트워크 설정

특징:
- 커널이 아닌 일반 프로그램
- 필요할 때만 실행
- 사용자 모드에서 실행
- 시스템 콜로 커널에 요청

🔄 실제 동작 예시

예시 1: 명령어 실행 (셸 사용)

사용자가 터미널에서 명령어를 입력할 때 무슨 일이 일어나는지 봅시다.

$ 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 프로그램: "커널한테 파일 정보 달라고 할게"
  ↓
커널: "파일 시스템 확인하고, 디스크에서 읽어올게"
  ↓
하드웨어: "디스크에서 데이터 읽어서 전달"

예시 2: 프로그램 내부에서 파일 열기 (셸 미사용)

이미 실행 중인 프로그램이 파일을 열 때는 셸을 거치지 않습니다.

# 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  │
├──────────┼─────────┼─────────┼──────────┤
│비용      │ 유료    │ 유료    │  무료    │
│오픈소스   │ ✗      │ 일부    │  ✓      │
│사용 난이도│ 쉬움    │ 쉬움    │ 중~어려움 │
│게임      │ ✓✓✓    │ ✓      │  ✓      │
│개발      │ ✓      │ ✓✓     │  ✓✓✓    │
│서버      │ ✓      │ ✓      │  ✓✓✓    │
│보안      │ ✓      │ ✓✓     │  ✓✓✓    │
└──────────┴─────────┴─────────┴──────────┘

왜 다양한 OS가 존재하는가?

사용 목적의 차이:
- 데스크톱: 사용 편의성 (Windows, macOS)
- 서버: 안정성, 보안 (Linux)
- 모바일: 배터리, 터치 (Android, iOS)
- 임베디드: 최소 자원 (특화 Linux)

철학의 차이:
- 상업적: Windows, macOS (수익 모델)
- 오픈소스: Linux (공유, 자유)

🎯 이 섹션에서 배울 내용

특정 운영체제의 사용법이 아니라, 모든 운영체제에 공통되는 핵심 원리를 배웁니다.

학습 흐름:

프로세스 → 스레드 → 스케줄링 → 동시성/병렬성 → 동기화 → 교착 상태 → 메모리 관리

각 주제는 Windows, Linux, macOS 모두에 적용됩니다.

🎯 프로세스란 무엇인가

프로그램 vs 프로세스

프로그램(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는 동적 할당을 너무 많이 해서 힙이 스택 영역을 침범할 때 발생

1. 코드 영역 (Code Segment)

역할: 실행할 명령어(기계어) 저장

  • 소스 코드가 컴파일되어 이 영역에 들어감
def greet(name):
    """
    이 함수의 기계어 명령어가 코드 영역에 저장됨
    """
    message = f"Hello, {name}!"
    return message

# 함수 정의 자체는 코드 영역에 실행되지 않으면 메모리만 차지

특징:

- 읽기 전용 (Read-Only)
- 프로그램 실행 중 변하지 않음
- 여러 프로세스가 공유 가능 (같은 프로그램을 여러 번 실행해도 코드는 하나)

2. 데이터 영역 (Data Segment)

역할: 전역(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)는 별도의 공간

3. 힙 영역 (Heap)

역할: 동적 할당 메모리로 개발자가 필요에 따라 실시간(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은 자동 가비지 컬렉션 (다 쓰고 나면 반드시 해제해야 하며, 그렇지 않으면 '메모리 누수' 발생)

4. 스택 영역 (Stack)

역할: 함수가 호출될 때 생성되는 지역 변수, 매개 변수, 반환 주소 저장

  • 함수 실행이 끝나면 자동으로 메모리가 회수되므로 관리가 매우 편리하며, 'LIFO(Last In, First Out)' 구조로 동작
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  │
           └─────────┘

1. 생성 (New)

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()

2. 준비 (Ready)

준비 상태:
- 실행 준비 완료
- CPU만 기다림
- 준비 큐(Ready Queue)에 대기

비유:
은행 대기 줄
- 서류 준비 완료
- 창구만 기다림

3. 실행 (Running)

실행 상태:
- CPU를 할당받음
- 실제로 명령어 실행 중

전환:
실행 → 준비: 시간 할당량 소진
실행 → 대기: I/O 요청
실행 → 종료: 프로그램 완료

4. 대기 (Waiting/Blocked)

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

5. 종료 (Exit/Terminated)

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)

PCB란?

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()

💡 실무 팁

프로세스 vs 스레드 (미리보기)

프로세스:
- 독립적인 실행 단위
- 자신만의 메모리 공간
- 프로세스 간 통신 어려움
- 생성/전환 비용 큼

스레드 (다음 글):
- 프로세스 내부의 실행 흐름
- 메모리 공간 공유
- 스레드 간 통신 쉬움
- 생성/전환 비용 작음

선택:
- 독립성 필요: 프로세스
- 속도 중요: 스레드

메모리 누수 방지

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)

  • 스레드의 정의: 프로세스 내부의 실행 흐름
  • 프로세스 vs 스레드: 차이점과 언제 무엇을 사용할까
  • 멀티스레딩: 하나의 프로세스에 여러 스레드
  • 스레드 안전성: 공유 메모리 문제와 해결

이전 글: [08-09] 기타 복잡도 클래스
다음 글: [09-02] 스레드
시리즈: P1. Computer Science

profile
AI 전문가를 꿈꾸는 도전자

0개의 댓글