10-1 프로세스 개요

프로세스(process): 실행중인 프로그램

프로세스 직접 확인하기

ps 명령어를 통해 확인 가능

포그라운드 프로세스(foreground process): 사용자가 보는 앞에서 실행

백그라운드 프로세스(background process): 사용자가 보지 못하는 뒤에서 실행

  • 데몬(daemon): 유닉스 체계의 운영체제의 백그라운드 프로세스
  • 서비스(service): 우니도우 운영체제에서의 백그라운드 프로세스

프로세스 제어 블록

PCB(Process Control Block, 프로세스 제어 블록)

  • 프로세스와 관련된 정보를 저장하는 자료 구조
  • 해당 프로세스를 식별하기 위해 꼭 필요한 정보들이 저장
  • 메모리에 있는 커널 영역에서 생성
  • 프로세스 생성 시에 만들어지고 실행이 끝나면 폐기

PCB에 담기는 정보

  1. PID(프로세스 ID, Process ID)
    • 특정 프로세스를 식별하기 위해 부여되는 고유한 번호
  2. 레지스터 값
    • 이전까지 사용했던 레지스터의 중간값
    • 프로그램 카운터 등의 레지스터 값
  3. 프로세스 상태
    • 입출력장치를 사용하기 위해 기다리는지, CPU를 기다리는지, CPU를 이용하는지 등
  4. CPU 스케줄링 정보
    • 프로세스가 언제, 어떤 순서로 CPU를 할당받았는지
  5. 메모리 관리 정보
    • 프로세스가 어느 주소에 저장되어 있는지
    • 베이스 레지스터, 한계 레지스터 값 등
    • 페이지 테이블 정보
  6. 사용한 파일과 입출력장치 목록
    • 실행과정에서 특정 입출력장치나 파일을 사용하는지

문맥 교환

  • 프로세스 실행에 대한 중간 정보를 저장해야, 다음 차례가 왔을 때 이전까찌 실행했던 내용에 이어 다시 실행을 재개할 수 있음
  • 문맥(context)
    • 해당 프로세스의 PCB에 표현
  • 문맥 교환(context switching)
    • 기존 프로세스의 문맥을 PCB에 백업하고, 새로운 프로세스를 실행하기 위해 문맥을 PCB로 복구하여 새로운 프로세스를 실행하는 것
    • 프로세스 A 실행 → A 문맥 저장 → B 문맥 로드 → 프로세스 B 실행 → B 문맥 저장 → …
    • 너무 자주 하면 오버헤드가 발생하여 부정적인 효과

프로세스의 메모리 영역

PCB는 커널 영역에 생성

사용자 영역에 프로세스가 배치되는 형식

  1. 정적 할당 영역
    • 코드 영역 + 데이터 영역
    • 프로그램이 실행되는 동안 유지되어 크기가 고정된 영역
  2. 동적 할당 영역
    • 힙 영역 + 스택 영역
    • 프로그램 실행 과정에서 그 크기가 변할 수 있는 영역

코드 영역

  • 코드 영역(code segment) = 텍스트 영역(text segment)
  • 실행할 수 있는 기계어로 이루어진 명령어가 저장
  • 쓰기가 금지된 읽기 전용 영역
    • OS는 쓸 수 있고, 프로세스는 쓸 수 없다는 뜻

데이터 영역

  • data segment
  • 프로그램이 실행되는 동안 유지할 데이터
  • ex) 전역 변수(global variable)

힙 영역

  • heap segment
  • 프로그램을 만드는 사용자(프로그래머)가 직접 할당할 수 있는 공간
  • 할당했다면 언젠가는 반환해야 함
  • 메모리 누수(memory leak): 메모리 공간을 반환하지 않아 남아있는 할당된 공간으로 인해 낭비되는 것
    • 현대 운영체제에서는 프로세스가 종료되면 해당 프로세스의 PCB를 폐기하고 이때 이 정보를 바탕으로 관련된 메모리를 청소한다.
    • 하지만 종료되지 않는다면?
      • 단순 대기
        • 다음 실행을 위해 차지하고 있을 뿐, 누수가 아니다.
        • 하지만 영영 실행될 일이 없는 프로세스라면, OS 관점에선 대기지만 개발자 관점에선 누수!
      • 누수
        • 메모리 주소를 잃어버려서 할당된 메모리를 반환하지 못하는 등…
          • 할당을 반환하지 않고 함수가 종료된다거나

          • 똑같은 변수에 여러번 메모리 주소를 할당한다거나..

            1
            2
            3
            4
            5
            6
            7
            
            int* ptr;
            
            ptr = malloc(4); // 1. 힙 영역에 '메모리 A'를 만들고, ptr이 그 주소를 가짐.
                             // (현재 ptr은 A의 위치를 알고 있음)
            
            ptr = malloc(4); // 2. 힙 영역에 '메모리 B'를 새로 만듦.
                             // 3. ptr에 B의 주소를 넣음. (덮어쓰기!)
            

스택 영역

  • stack segment
  • 데이터를 일시적으로 저장하는 공간
  • ex) 매개 변수, 지역 변수 등
  • 저장할 데이터는 PUSH, 필요하지 않은 데이터는 POP
  • 우리가 아는 그 후입선출 스택 구조. 왜?
    • 한 프로세스에서 여러 함수가 순차적으로 호출된다면 나중에 호출된 함수가 먼저 종료된다.
    • 따라서 나중에 호출된 함수에서 할당된 지역변수 등이 먼저 해제된다.
    • 따라서 가장 최근에 PUSH된 것이 가장 먼저 POP된다.

10-2 프로세스 상태와 계층 구조

프로세스 상태

프로세스 상태 다이어그램(process state diagram)

생성 상태

생성 상태(new): 프로세스를 생성 중인 상태. 이후 준비 상태가 됨

준비 상태

준비 상태(ready): CPU를 할당받아 실행할 수 있지만 차례가 아니어 기다리고 있는 상태

dispatch: 준비 상태인 프로세스가 실행 상태로 전환되는 것

실행 상태

실행 상태(running): CPU를 할당받아 실행중인 상태. 타이머 인터럽트가 발생하면 다시 준비 상태로 변경

대기 상태

대기 상태(blocked): 입출력장치의 작업이 끝나는 등 특정 이벤트를 기다리는 상태. 이후 준비 상태로 변경

종료 상태

종료 상태(terminated): 운영체제가 PCB와 프로세스가 사용한 메모리를 정리

프로세스 계층 구조

프로세스는 실행 도중 시스템 콜을 통해 다른 프로세스를 생설할 수 있다.

부모 프로세스(parent process): 새 프로세스를 생성한 프로세스

자식 프로세스(child process): 부모 프로세스에 의해 생성된 프로세스

PPID(Parent PID): 부모 프로세스의 PID

프로세스 계층 구조

  • 결국 컴퓨터는 최초의 프로세스 하나가 자식 프로세스를 생성하고 그 프로세스가 다시 자식 프로세스를 생성하는 것이 반복된다.
  • 최초의 프로세스: init(unix), systemd(linux), launchd(macOS)

프로세스 생성 기법

fork(복제)

  • 자신의 복사본을 자식 프로세스로 생성
  • 메모리 내의 내용, 열린 파일의 목록이 자식 프로세스에 상속
  • PID 값이나 저장된 메모리 위치는 다름

exec(옷 갈아입기)

  • 자신의 메모리 공간을 다른 프로그램으로 교체
  • fork 로 만들어진 자식 프로세스는 exec 시스템 콜을 통해 새로운 프로그램으로 전환

부모가 자식 프로세스를 생성하여 계층이 추가되는 것은은 fork → exec → fork → … 의 반복

예제

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from multiprocessing import Process
import os

def foo():
    print("child process", os.getpid())
    print("parent process", os.getppid())

if __name__ == "__main__":
    print("parent process", os.getpid())
    child = Process(target=foo).start()

을 실행하면

1
2
3
parent process 10184
child process 10186
parent process 10184

이렇게 뜸. 내부에서 함수가 실행되면서 자식 프로세스가 생성되어 실행된 것

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from multiprocessing import Process
import os

def foo():
    print("child process", os.getpid())
    print("parent process", os.getppid())

if __name__ == "__main__":
    print("parent process", os.getpid())
    child = Process(target=foo).start()
    child2 = Process(target=foo).start()
    child3 = Process(target=foo).start()

이렇게 하면

1
2
3
4
5
6
7
parent process 10545
child process 10547
parent process 10545
child process 10548
parent process 10545
child process 10549
parent process 10545

이렇게 한 부모에서 동일한 동작을 하는 3개의 자식 프로세스가 호출됨

10-3 스레드

스레드(thread)

  • 프로세스를 구성하는 실행의 흐름 단위
  • 하나의 프로세스는 여러 개의 스레드를 가질 수 있음

프로세스와 스레드

단일 스레드 프로세스: 하나의 프로세스가 한 번에 하나의 일만 처리

스레드

  • 프로세스를 구성하는 여러 명령어를 동시에 실행할 수 있음
  • 프로세스 내에서 각기 다른 스레드 ID, PC 값을 비롯한 레지스터 값, 스택으로 구성
    • 따라서 각기 다른 코드를 실행할 수 있음
    • 최소한의 정보만으로 실행
    • 그외 프로세스 자원들은 공유

태스크(task)

  • 리눅스에선 프로세스와 스레드를 구분하지 않음
  • 모두 실행의 문맥일 뿐

멀티프로세스와 멀티스레드

멀티프로세스(multiprocess): 여러 프로세스를 동시에 실행

멀티스레드(multithread): 여러 스레드로 프로세스를 동시에 실행

멀티프로세스멀티스레드
print("hello, OS”) 실행여러 개 출력여러 개 출력
자원코드 영역, 데이터 영역, 힙 영역 등 모든 자원이 복제되어 메모리에 적재되므로 중복 발생(쓰기 복사(copy on write)로 방지 가능)레지스터 값, 스택은 독립. 코드 영역, 데이터 영역, 힙 영역, 열린 파일 등은 공유
문제 발생다른 프로세스에 영향 X프로세스 전체에 문제 가능성 O
메모리독립된 메모리 공간프로세스 메모리 공유
1. 코드복제 (논리적 분리)공유
2. 전역 변수독립적 (변경해도 남에게 영향 없음)공유됨 (변경 시 다른 스레드 즉시 반영)
3. 지역 변수독립적독립적 (Stack 분리)
4. DB 연결공유 불가 (프로세스별 재연결 필수)공유 가능하나 동시 사용 시 충돌 위험 (Lock 필요)
5. DB 풀공유 불가 (각자 풀을 만들어야 함)하나의 풀을 공유하여 사용

프로세스 간 통신

프로세스 간 통신(Inter-Process-Communication)

  • 프로세스 간의 자원을 공유하고 데이터를 주고 받는 것

통신

  • 네트워크를 통해 데이터를 주고 받는 방식
  • 같은 컴퓨터 내의 서로 다른 프로세스나 스레드끼리 데이터를 주고 받는 것도 포함
    • ex) 파일을 통한 프로세스 간 통신, 공유 메모리(shared memory)