[운영체제] Ch3 프로세스와 프로세스 관리

박소미·2024년 10월 14일

운영체제

목록 보기
3/10

1. 프로세스 개요

1.1 프로그램과 프로세스

프로그램은 하드 디스크나 USB 등 저장 장치에 저장된 실행 가능한 파일이며 프로그램이 메모리에 적재되어 실행 중일 때 프로세스라고 부른다.

프로세스 특징

  • 운영체제는 프로그램을 메모리에 적재하고 이를 프로세스로 다룬다.

  • 서로 독립적인 메모리 공간을 가지므로 다른 프로세스의 영역에 접근할 수 없다.

  • 고유한 번호 (프로세스 ID)를 할당한다.

  • 프로세스에 관한 정보는 운영체제 커널에 의해 관리된다.

  • 프로세스를 만들고 실행하고 대기시키고 종료시키는 모든 관리는 커널에 의해 수행된다.

1.2 프로세스 관리

커널은 프로세스를 위해 사용자 공간에 메모리를 할당하고 프로세스 ID를 부여하며 커널 영역에 프로세스 테이블을 만들고 이 테이블을 이용해 생성된 모든 프로세스의 정보를 관리한다.

1.3 프로그램의 다중 인스턴스

하나의 프로그램을 여러 번 실행시키면 실행될 때마다 독립된 프로세스가 생선된다. 이런 프로세스들을 프로그램의 다중 인스턴스라고 한다.

서로 다른 프로세스 번호를 부여받고 서로 다른 메모리 공간에 적재되는 등 커널에 의해 별개의 프로세스들로 다루어진다.

1.4 프로세스 주소 공간

CPU 주소 공간이란?
컴퓨터 내에 CPU가 접근 가능한 전체 메모리 공간이며 크기는 CPU의 주소선 개수에 달려 있다. 32비트 CPU는 주조선이 32개로 최대 2³²개의 번지를 접근할 수 있고 CPU의 주소 공간의 크기는 2³² 바이트 = 4GB이다.

프로세스의 구성
4개의 메모리 영역으로 구성되며 사용자 공간에 형성된다.

  • 코드 영역: 프로세스 코드가 적재되는 영역, 텍스트 영역으로도 불림

  • 데이터 영역: 프로세스의 전역 변수들과 정적 변수들이 적재되는 영역

  • 힙 영역: 프로세스가 실행 중에 동적 할당 받는 영역

  • 스택 영역: 함수가 호출될 때 지역변수, 매개변수, 함수로부터 돌아갈 주소 등이 저장되는 영역

항목영역
전역 변수데이터 영역
지역 변수스택 영역
new 또는 malloc()으로 할당된 메모리힙 영역
사용자가 작성한 함수 코드코드 영역
라이브러리에 작성된 함수 코드코드 영역
라이브러리 함수에 선언된 지역 변수들스택 영역


프로세스 주소 공간 = 사용자 공간 + 커널 공간

프로세스가 실행 중에 접근할 수 있도록 허용된 주소의 최대 범위이다.

  • 논리 공간(가상 공간) ->프로세스 주소 공간의 크기

    • 0번지에서 시작하여 연속적인 주소

    • 32비트 CPU의 경우 4GB(윈도우, 리눅스 모두 동일)

  • 프로세스의 크기

    • 적재된 코드 + 전역 변수 + 힙에서 할당받은 동적 메모리 공간 + 스택에 저장된 데이터 크기
  • 사용자 공간

    • 프로세스의 코드, 데이터, 힙, 스탭이 순서대로 할당되는 공간
  • 커널 공간

    • 시스템 호출을 통해 이용하는 커널 공간
    • 커널 코드, 커널 데이터, 커널 스택

📌정리

  • 프로세스는 운영체제에서 설정한 사용자 공간의 최대 범위까지 코드, 데이터, 힙, 스택을 늘려갈 수 있다
  • 시스템 호출을 통해 커널 공간까지 접근할 수 있다.
  • 사용자 공간과 커널 공간을 합쳐 CPU가 액세스할 수 있는 전체 공간이 프로세스 주소 공간이다.

커널 공간의 의미

  • 시스템 호출을 통해 커널 코드를 실행할 때 커널 공간 사용

    • 커널 코드를 실행하고 있는 것은 사용자 프로세스

      • 사용자 프로세스가 커널 모드에서 실행되고 있다.

프로세스의 주소 공간은 가상 주소 공간
프로세스의 주소 공간은 물리 공간이 아니라 컴파일러와 운영체제가 만들어 준 가상 공간이다.

모든 프로세스는 생성될 때 운영체제에 의해 0번지부터 코드, 데이터 ,힙, 스택 순서대로 할당된다. 이 주소 공간은 가상 주소 공간이다.

운영체제는 각 프로세스마다 프로세스의 가상 주소 공간과 물리 메모리의 물리 주소 공간을 연결하는 매핑 테이블 을 두고 두 주소 공간을 관리한다.

프로세스 주소 공간은 충돌하는가? NO
프로세스는 자신의 매핑 테이블을 통해 물리 메모리에 접근하며 각 프로세스의 영역은 운영체제에 의해 물리 메모리의 서로 다른 공간에 배치되므로 프로세스들 사이의 가상 주소 공간은 충돌하지 않는다.

2. 커널의 프로세스 관리

2.1 프로세스 테이블과 프로세스 제어 블록

  • 프로세스 테이블(Process Table)

    • 시스템에 한 개

    • 프로세스의 정보를 관리

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

    • 프로세스당 하나

    • 프로세스가 생성될 때 만들어지고 종료되면 삭제

    • 커널이 관리

  • 프로세스 테이블과 PCB의 위치 : 커널 영역
    - 커널 코드(커널 모드)만 액세스 가능

2.2 프로세스 제어 블록, PCB

  • 프로세스 번호, PID (Process Identification Number)

    • 커널은 프로세스를 생성할 때 프로세스 번호 PID를 할당한다.
  • 부모 프로세스 번호, PPID (Parent Process Identification Number)

  • 프로세스 상태 정보

  • 프로세스 컨텍스트 정보

    • 실행 중인 프로세스를 중단시키고 다른 프로세스를 실행시킬 때 현재 프로세스의 정보, 즉 프로세스 컨텍스트를 PCB에 저장한다.
  • 스케줄링 정보

    • 프로세스의 우선 순위
    • 프로세스가 사용한 CPU 시간 등
  • 종료 코드

    • 프로세스가 종료할 때 종료 이유를 부모 프로세스에게 전달하기 위한 정수 값으로 종료한 프로세스의 PCB에 저장된다.

    • exit 시스템 호출의 매개변수 값이나 main()의 return 값

    • PCB에 남겨놓은 종료코드가 부모 프로세스에 의해 읽혀질 때까지 운영체제는 PCB를 제거하지 않고 프로세스 테이블도 남겨둔다.

      • 부모가 종료코드를 읽어가지 않은 상태를 좀비 프로세스라고 한다.
  • 프로세스의 오픈 파일 테이블

  • 메모리 관리를 위한 정보들

  • 프로세스 사이의 통신 정보

  • 회계 정보

  • 프로세스의 소유자 정보


2.3 프로세스의 생명 주기와 상태 변이

  • 상태 정보는 PCB에 기록

  1. New (생성 상태)

    • 프로세스가 새로 생성된 직후의 상태이다.

    • 이 상태에서 필요한 자원을 할당받거나 초기 설정이 이루어진다.

    • 프로세스가 준비되면 Ready 상태로 전이된다.

  2. Ready (준비 상태)

    • 프로세스가 CPU를 사용할 준비가 된 상태이다.

    • 실행을 기다리며 스케줄러가 CPU를 할당해 주기를 기다린다.

    • CPU 시간이 할당되면 Running 상태로 전이된다.

  3. Running (실행 상태)

    • 프로세스가 CPU를 할당받아 실행되고 있는 상태이다.

    • 할당된 CPU 시간이 끝나거나, 자발적으로 CPU 사용을 양보하면 Ready 상태로 돌아간다.

    • 입출력 작업이나 다른 자원이 필요하면 Blocked 상태로 전이된다.

    • 프로세스가 종료되면 Terminated/Zombie 상태로 전이된다.

  4. Blocked (대기 상태)

    • 프로세스가 입출력 작업을 수행하거나, 특정 자원이 사용 가능해질 때까지 기다리는 상태이다.

    • 예를 들어, sleep() 시스템 호출로 알람을 기다리거나 입출력 장치가 완료되기를 기다린다.

    • 요청한 자원이 준비되면 다시 Ready 상태로 전이된다.

  5. Terminated/Zombie (종료/좀비 상태)

    • 프로세스가 종료된 상태이나, 부모 프로세스가 종료 상태를 확인하지 않은 경우 Zombie 상태로 남아있다.

    • 이 상태는 부모 프로세스가 프로세스의 종료를 확인하기 전까지 유지된다.

    • 부모 프로세스가 종료 상태를 확인하면 Terminated/Out 상태로 전이된다.

  6. Terminated/Out (완전 종료 상태)

    • 부모 프로세스가 종료를 확인한 이후, 프로세스가 완전히 종료되어 시스템에서 제거된 상태이다.

정리

  • 프로세스는 생성(New)준비(Ready) 상태로 대기하며, CPU가 할당되면 실행(Running) 상태가 된다.
  • 실행 중 입출력이 필요하거나 자원을 기다리면 대기(Blocked) 상태로 전환된다.
  • 프로세스가 작업을 마치면 종료(Terminated) 상태가 되며, 부모 프로세스가 확인하면 최종적으로 완전 종료(Out) 상태로 시스템에서 제거된다.

2.4 프로세스 스케줄링

  • 과거 운영체제 실행 단위는 프로세스

    • Ready 상태의 프로세스 중 실행시킬 프로세스 선택
  • 오늘날 운영체제는 스레드를 대상으로 스케줄링

    • 실행 단위는 스레드
    • Ready 상태의 스레드 중 실행시킬 스레드 선택
  • 프로세스는 스레드들에게 공유 자원 제공하는 컨테이너

2.5 프로세스 정보 보기

프로세스 정보는 커널의 PCB에 저장되어 있지만 사용자나 응용프로그램은 직접 이 정보를 접근할 수 없고 리눅스의 쉘이나 Windows의 작업 관리자와 같은 도구 소프트웨어를 이용하면 된다.

리눅스 쉘 명령으로 프로세스 정보 보기
$ ps -eal : 모든 프로세스 정보 출력
$ ps -U kitae -u : 사용자 kitae가 소유한 프로세스 출력

3. 프로세스 계층 구조

3.1 프로세스의 부모-자식 관계

  • 프로세스는 프로세스에 의해 생성되며 생성한 프로세스를 부모 프로세스, 생성된 프로세스를 자식 프로세스라고 한다.
  • #0 프로세스가 시스템 부팅 시 실행되는 최초의 프로세스
  • 모든 프로세스는 부모 프로세스를 가짐 ( #0 제외)

자식 프로세스의 생성

  • 시스템 호출로 한다.
  • fork(), clone()
  • 예외: PID 0, 1, 2 등의 몇몇 조상 프로세스는 수작업으로 생성

  • #0 프로세스는 시스템 내에 스케줄될 프로세스(Ready 상태)가 하나도 없을 때 실행되는 유휴 프로세스 (idle process)

  • #1 프로세스init 프로세스 혹은 systemd 프로세스라고 부른다. 부팅 과정에서 시스템을 초기화하여 사용자가 시스템을 사용할 수 있는 상태까지 부팅을 완료한다.
    시스템 종료 (shutdown) 까지 살아 있어 시스템 종료 과정을 책임진다.

    • inint 프로세스: 외부 네트워크로부터 연결 요청을 대기하는 sshd 프로세스를 생성

      • 외부 네트워크로부터 접속을 받으면 확인 후 쉘 프로세스(bash)를 자식으로 생성
    • 사용자 모드에서 실행되는 모든 프로세스들의 조상은 #1 init 프로세스

  • #2 프로세스 kthreadd는 부팅 때 생성되어 커널 공간에서 커널 모드로 실행되면서 커널의 기능을 돕는 프로세스이다.

    • 커널 모드에서 커널 코드로만 실행되는 모든 커널 프로세스(thread)의 조상
      커널 모드에서 커널 코드로만 실행되는 모든 커널 프로세스(thread)의 조상

$ pstree 0 : 리눅스에서 실행 중인 프로세스들의 계층 구조 보기


#0과 #1프로세스: idle 프로세스와 init 프로세스

리눅스에서 idle 프로세스는 무한 루프를 돌며 아무 일도 하지 않으며 우선순위도 낮아 실행 가능한 다른 프로세스가 있으면 실행될 일이 없다.

근데 왜 idle 프로세스를 만드는가?
모든 프로세스가 블록 상태여서 시스템에 실행시킬 Ready 상태의 프로세스가 1개도 없는 상황에 빠지지 않기 위해서이다.

즉 리눅스는 실행시킬 프로세스가 없을 때 #0 idle 프로세스를 실행시킨다.

3.3 부모 자식 프로세스의 실행 관계

  • fork(): 자식 프로세스를 생성하는 시스템 호출 함수

  • exit(): 현재 프로세스의 종료를 처리하는 시스템 호출 함수

    • 프로세스의 종료를 커널에 알림 -> 종료를 처리하는 커널 코드 실행
  • wait(): 부모가 자식 프로세스가 종료할 때까지 기다리는 시스템 호출 함수

(a) 부모가 자식을 생성한 후 자식의 종료를 기다리는 경우

  1. 부모 프로세스가 fork() 호출

    • 부모 프로세스가 fork()를 호출하여 자식 프로세스를 생성한다. fork()는 새로운 프로세스를 만들어 부모와 자식이 각각 독립적인 실행 흐름을 가지게 한다.
  2. 부모 프로세스가 wait() 호출하여 자식 종료 대기

    • 부모 프로세스는 자식 프로세스가 종료될 때까지 기다리기 위해 wait() 함수를 호출한다.
    • wait() 함수는 자식 프로세스가 종료될 때까지 부모 프로세스를 일시 중단시킨다.
  3. 자식 프로세스가 exit() 호출하여 종료

    • 자식 프로세스는 작업을 마치고 exit()를 호출하여 정상적으로 종료된다.
  4. 부모 프로세스가 wait()을 통해 자식 종료 확인 후 종료

    • 자식 프로세스가 종료되면, wait() 함수는 자식의 종료 상태를 받아 부모 프로세스가 이를 확인한다.
    • 이후 부모 프로세스도 종료된다.
  • (a) 경우: 부모가 wait()를 통해 자식의 종료를 기다리므로, 자식 프로세스는 좀비 상태로 남지 않고 정상적으로 종료된다.

(b) 자식이 부모보다 먼저 종료한 경우

  1. 부모 프로세스가 fork() 호출

    • 부모 프로세스가 fork()를 호출하여 자식 프로세스를 생성한다.
  2. 자식 프로세스가 먼저 exit() 호출하여 종료

    • 자식 프로세스가 부모 프로세스가 wait()를 호출하기 전에 먼저 작업을 마치고 exit()로 종료된다.
    • 이때 자식 프로세스는 종료되었지만, 부모 프로세스가 자식의 종료 상태를 확인하지 않았으므로 좀비(zombie) 상태로 남는다.
  3. 자식 프로세스가 좀비 상태로 유지

    • 자식 프로세스는 좀비 상태로 남아 있으며, 부모 프로세스가 이를 확인하기 전까지 자식 프로세스의 PID와 종료 정보가 시스템에 남아 있다.
  4. 부모 프로세스가 wait() 호출하여 자식 종료 상태 확인

    • 부모 프로세스가 wait()를 호출하여 자식 프로세스의 종료 상태를 확인하고, 이로 인해 자식 프로세스의 좀비 상태가 해제된다.
    • 이후 부모 프로세스도 종료된다.
  • (b) 경우: 자식이 부모보다 먼저 종료되면 일시적으로 좀비 상태가 되며, 부모가 wait()를 호출하여 이를 확인한 후에 완전히 종료된다.

3.4 좀비 프로세스: 종료 후 방치된 자식 프로세스

종료하였지만 부모가 종료코드를 읽지 않는 상태로 시스템에 남아있을 때 이 프로세스를 좀비 프로세스라고 한다.

프로세스 종료 시 수행되는 작업

  1. 종료 코드 저장 및 상태 표시

    • 프로세스가 종료할 때, PCB(Process Control Block)종료 코드(exit status)가 저장되며, 프로세스 상태를 Terminated로 표시한다.
  2. 메모리 반환

    • 종료된 프로세스에 할당된 모든 메모리 자원이 반환된다. 이는 시스템의 메모리 자원을 효율적으로 관리하기 위함이다.
  3. PCB와 프로세스 테이블 항목 유지

    • 종료된 프로세스의 PCB와 프로세스 테이블 항목은 즉시 제거되지 않음. 부모 프로세스가 자식의 종료 상태를 확인할 때까지 PCB가 유지된다.

부모 프로세스의 의무

  • 부모 프로세스는 wait() 함수를 통해 자식 프로세스의 종료 코드를 읽어야 한다. 이를 통해 자식의 종료 상태를 확인하고, 좀비 상태를 해제할 수 있다.

  • 자식 프로세스가 종료되면, 부모 프로세스에게 SIGCHLD 신호가 전송된다. 이때 부모가 wait()를 호출하지 않으면 자식 프로세스는 좀비 프로세스(zombie process)가 된다.

좀비 프로세스(zombie process)

  • 좀비 프로세스는 프로세스 테이블에 남아 있어, ps 명령어로 프로세스 목록을 출력할 때 표시된다.
  • 좀비 프로세스는 자식 프로세스가 종료되었지만, 부모가 이를 확인하지 않아 시스템 리소스를 사용하지는 않지만, 프로세스 테이블 항목을 차지하게 된다.

좀비 프로세스 제거 방법

  1. 부모 프로세스에게 SIGCHLD 신호 보내기
    • 부모 프로세스에 SIGCHLD 신호를 보내 자식 프로세스의 종료 상태를 확인하게 할 수 있다. 이를 통해 부모가 자식 프로세스를 회수하게 하여 좀비 상태를 해제한다.
$ kill –SIGCHLD PPID
  1. 부모 프로세스 강제 종료
    • 부모 프로세스를 강제로 종료하여 좀비 프로세스를 제거할 수 있다. 이 경우, 좀비 프로세스는 init 프로세스가 입양하게 되어 자동으로 정리된다.
    • 명령어:
$ kill –9 PPID

3.5 고아 프로세스와 입양

고아 프로세스(Orphan Process)란?

  • 고아 프로세스부모 프로세스가 종료되어 부모가 없는 상태로 남아 있는 자식 프로세스를 의미한다.
  • 부모 프로세스가 exit() 함수를 호출하여 종료될 때, 커널은 해당 부모 프로세스에 자식 프로세스가 있는지 확인한다.

고아 프로세스의 처리 방법

  1. init 프로세스에게 입양

    • 일반적으로 커널은 부모 프로세스가 종료될 경우, 해당 부모 프로세스의 자식 프로세스를 init 프로세스에게 입양시킨다.

    • init 프로세스는 시스템에서 가장 첫 번째로 실행되는 프로세스이며, 고아 프로세스를 관리하고 종료 상태를 회수하는 역할을 한다.

  2. 운영체제 또는 쉘에 따른 강제 종료

    • 운영체제의 종류나 쉘에 따라, 부모 프로세스가 종료될 때 모든 자식 프로세스를 강제로 종료시키기도 한다.

    • 예를 들어, 일부 쉘은 부모 프로세스가 종료될 때 자식 프로세스도 함께 종료되도록 설정할 수 있다.

고아 프로세스와 좀비 프로세스의 차이

  • 고아 프로세스는 부모 프로세스가 없을 때 init 프로세스에게 입양되어 정상적으로 관리된다.
  • 반면, 좀비 프로세스는 종료된 후에도 부모가 종료 상태를 회수하지 않아 프로세스 테이블에 남아 있는 상태를 말한다.

3.6 백그라운드 프로세스와 포그라운드 프로세스

구분포그라운드 프로세스백그라운드 프로세스
제어 방식사용자가 직접 제어사용자의 개입 없이 실행
터미널 점유터미널을 점유하고 실행터미널을 점유하지 않고 실행

3.7 CPU 집중 프로세스와 I/O 집중 프로세스

CPU 집중 프로세스 (CPU-bound Process)

  • CPU 집중 프로세스는 주로 CPU 연산을 많이 필요로 하는 작업으로 구성된다.

  • 배열 곱, 인공지능 연산, 이미지 처리와 같은 작업이 예시이다.

  • 이러한 프로세스는 CPU의 속도가 성능을 좌우하므로, CPU가 빠를수록 처리 성능이 높아진다.

I/O 집중 프로세스 (I/O-bound Process)

  • I/O 집중 프로세스는 입출력 작업에 많이 의존하는 작업이다.

  • 네트워크 전송이나 파일 입출력과 같은 작업이 많아, CPU 사용보다는 입출력 장치의 속도에 따라 성능이 좌우된다.

운영체제의 스케줄링 우선순위

  • I/O 집중 프로세스 > CPU 집중 프로세스

    • I/O 집중 프로세스는 CPU 사용이 짧고, 주로 입출력 대기 상태에 있기 때문에, 빠르게 실행 후 대기 상태로 전환할 수 있다. 따라서 CPU가 유휴 상태가 되지 않고 다른 프로세스를 처리할 수 있는 기회를 증가시킨다.

    • 반면 CPU 집중 프로세스는 CPU를 오랜 시간 점유하므로, 후순위로 배정하여 다른 프로세스들이 자원을 효율적으로 사용할 수 있도록 한다.

4. 프로세스 제어 (fork, exec, wait, exit)

4.1 프로세스 생성과 fork()

프로세스가 생성되는 경우

  • 시스템 부팅 과정에서 프로세스 생성

  • 로그인 시 쉘 프로세스 생성

  • 사용자 명령에 따라 응용프로그램 프로세스 생성

  • 배치 프로세스 생성

  • 응용프로그램이 다중처리를 위해 자식 프로세스 생성

프로세스 생성

  • 시스템 호출을 통해서만 가능
    • 리눅스 : fork()
    • Windows : CreateProcess()

프로세스 생성 과정

  1. PID 할당

    • 운영체제는 새로 생성되는 프로세스에 고유한 PID(프로세스 ID)를 할당한다.

    • PID는 시스템 내에서 프로세스를 식별하는 중요한 정보이다.

  2. PCB 생성

    • 프로세스의 상태와 정보를 관리하기 위해 PCB(Process Control Block)가 생성된다.

    • PCB에는 프로세스의 ID, 상태, 우선순위, 레지스터 값 등 다양한 정보가 기록된다.

  3. 프로세스 테이블 항목에 PCB 연결

    • 생성된 PCB는 프로세스 테이블에 항목으로 추가되어 관리된다.

    • 이를 통해 운영체제가 프로세스를 추적하고 제어할 수 있다.

  4. 메모리 공간 할당

    • 프로세스가 실행될 메모리 공간을 할당받는다.

    • 코드, 데이터, 스택, 힙 영역이 포함되며, 각각의 메모리 영역에 필요한 만큼의 공간이 제공된다.

  5. 코드와 데이터 적재

    • 할당받은 메모리 공간에 프로세스의 코드와 데이터가 적재된다.

    • 이로 인해 프로세스가 실행할 코드와 필요한 데이터가 메모리에 준비된다.

  6. PCB에 프로세스 정보 기록

    • PCB에 프로세스와 관련된 정보가 기록된다. 예를 들어, PID, 메모리 주소, 프로세스 상태 등이 포함된다.
  7. 프로세스 상태를 Ready로 설정 및 준비 큐에 추가

    • PCB에 프로세스의 상태를 Ready(준비 상태)로 표시하고, 준비 큐(Ready Queue)에 넣어 스케줄러가 실행할 수 있게 한다.

    • 준비 큐에 들어간 프로세스는 CPU가 할당될 때까지 대기하게 된다.


fork() 시스템 호출

  • fork() 함수는 현재 프로세스를 복사하여 새로운 자식 프로세스를 생성하는 역할을 한다.
pid_t pid; // pid 변수 선언

pid = fork(); // 자식 프로세스 생성

if(pid > 0) {
    // 부모 프로세스에서 실행될 코드
} else if(pid == 0) {
    // 자식 프로세스에서 실행될 코드
} else {
    // fork() 오류 처리 코드
}

(a) fork() 호출 전 - 부모 프로세스 상태

  • fork() 호출 전, 부모 프로세스가 실행 중이다.

  • 부모 프로세스는 pid = fork(); 코드 줄에 도달하며, fork()를 호출하게 된다.

(b) fork() 실행 - 자식 프로세스 생성

  • fork()가 호출되면 커널 모드로 전환되어 커널에서 fork()의 실제 작업이 수행된다.

  • 커널은 부모 프로세스를 복사하여 새로운 자식 프로세스를 생성한다.

  • 생성된 자식 프로세스에는 새로운 PID가 할당된다. 이 예시에서는 자식 프로세스의 PID가 29138로 설정되었다.

  • 커널은 부모와 자식의 메모리 공간을 분리하여 각각 독립적으로 실행될 수 있도록 한다.

  • 커널은 fork()의 반환 값을 각각 다르게 설정한다.

    • 부모 프로세스: fork()의 반환 값으로 자식 프로세스의 PID(29138)가 저장된다.
    • 자식 프로세스: fork()의 반환 값으로 0이 저장된다.

(c) 부모와 자식 모두 fork() 반환 후 각각 실행

  • fork() 호출이 완료되면, 부모와 자식 프로세스는 각각 자신의 메모리 공간에서 독립적으로 실행된다.

  • 부모 프로세스:

    • pid > 0 조건을 만족하므로, 부모 프로세스는 자식 프로세스의 PID를 이용하여 필요한 작업을 계속 진행한다.
    • 반환 값은 자식 프로세스의 PID인 29138이다.
  • 자식 프로세스:

    • pid == 0 조건을 만족하므로, 자식 프로세스는 자신만의 독립적인 작업을 시작한다.
    • 반환 값은 0이다.

4.2 프로세스 오버레이와 exec()

프로세스 오버레이란?

  • 프로세스 오버레이현재 실행 중인 프로세스의 메모리 공간에 새로운 응용프로그램을 덮어쓰는 방식이다.

  • 새로운 프로그램을 실행하기 위해 exec 패밀리 시스템 호출을 사용한다.

  • 프로세스의 PID는 변경되지 않으며, 기존 프로세스의 메모리 공간이 새로운 프로그램으로 대체된다.


exec 패밀리 시스템 호출

  • exec 패밀리는 프로세스 오버레이를 수행하기 위해 사용하는 시스템 호출로, 새로운 프로그램을 현재 프로세스의 메모리 공간에 덮어씌운다.

  • exec 패밀리의 대표적인 함수:

    • execlp(): 인자들을 하나씩 나열하여 프로그램을 실행하며, 실행 파일의 경로를 검색하여 실행.
    • execv(): 인자들을 배열로 전달하여 프로그램을 실행하며, 전체 경로를 제공해야 함.
    • execvp(): 인자들을 배열로 전달하여 프로그램을 실행하며, 실행 파일의 경로를 검색하여 실행.

프로세스 오버레이의 특징

  • PID 유지: 기존 프로세스의 PID는 그대로 유지되며, 새로운 프로그램이 덮어씌워져도 프로세스 ID는 변하지 않는다.

  • 메모리 영역 덮어쓰기: 코드, 데이터, 힙, 스택 영역이 새로운 응용프로그램으로 덮어써지면서 기존 프로그램은 메모리에서 제거된다.

  • 프로세스의 대체 실행: 기존 프로세스가 새로운 프로그램을 실행할 필요가 있을 때, 프로세스 오버레이를 통해 메모리를 효율적으로 사용하며 새로운 프로그램을 실행할 수 있다.


fork()와 exec()의 조합

  • 일반적으로 fork()에 의해 생성된 자식 프로세스는 생성 직후 새로운 프로그램을 실행하기 위해 exec()를 호출하는 경우가 많다.

  • fork()는 부모 프로세스를 복제하여 자식 프로세스를 생성하지만, 자식 프로세스가 곧바로 다른 프로그램을 실행할 필요가 있는 경우, exec()를 호출하여 새로운 프로그램으로 자식 프로세스의 메모리 공간을 덮어쓴다.

  • fork(): 부모 프로세스가 자식 프로세스를 생성한다.

  • exec(): 자식 프로세스가 새로운 프로그램(/bin/ls)을 적재하여 실행한다.

  • wait(): 부모 프로세스가 자식의 종료를 기다린다.

  • exit(): 자식 프로세스가 프로그램 실행을 마치고 종료한다.


4.3 프로세스 종료와 프로세스 종료 대기

프로세스 종료 방법

  1. exit() 시스템 호출

    • exit(종료코드) 함수를 호출하여 프로세스를 종료한다.
    • exit() 호출 시, 프로세스는 종료 코드(종료 상태)를 지정할 수 있으며, 이 값은 부모 프로세스에게 전달된다.
  2. main() 함수의 return

    • main() 함수에서 return 종료코드; 구문으로도 프로세스를 종료할 수 있다.
    • return 문을 통해 반환된 종료 코드는 exit() 호출과 동일하게 부모 프로세스에 전달된다.

종료 과정

  1. 모든 자원 반환

    • 프로세스는 종료 시, 할당받은 코드, 데이터, 스택, 힙 등의 모든 메모리 자원을 반환한다.
    • 또한, 열어 놓은 파일, 소켓 등 모든 입출력 자원도 닫는다. 이는 시스템 자원이 더 이상 사용되지 않도록 하는 중요한 단계이다.
  2. PCB 업데이트

    • PCB(Process Control Block)에 프로세스 상태를 Terminated로 변경하고, 종료 코드를 저장한다.
    • 이로 인해 운영체제는 해당 프로세스가 종료되었음을 인식하고, 종료 상태를 관리할 수 있게 된다.
  3. 자식 프로세스의 입양

    • 종료되는 프로세스가 자식 프로세스를 가지고 있을 경우, 자식 프로세스는 init 프로세스에게 입양된다.
    • init 프로세스는 시스템의 최상위 프로세스로, 고아가 된 자식 프로세스를 관리하는 역할을 한다.
  4. 부모 프로세스에 SIGCHLD 신호 전송

    • 종료된 프로세스는 부모 프로세스에게 SIGCHLD 신호를 전송하여 자신의 종료 상태를 알린다.
    • SIGCHLD 신호를 받은 부모 프로세스는 wait() 시스템 호출을 통해 자식의 종료 코드를 확인할 수 있으며, 이로 인해 자식 프로세스가 좀비 상태로 남지 않도록 관리한다.

종료 코드의 범위와 의미

  1. 종료 코드의 범위

    • POSIX 표준에 따르면, 종료 코드는 0~255 사이의 1바이트 정수 값으로 표현된다.
    • 0: 정상 종료를 의미한다.
    • 1~255: 개발자가 임의로 정의하여 특정 오류 상황이나 상태를 나타내는 데 사용된다.
  2. 종료 코드의 사용 목적

    • 부모 프로세스는 자식 프로세스가 종료될 때 종료 코드를 통해 자식 프로세스의 종료 상태나 원인을 알 수 있다.

종료 코드 사용 시 유의할 점

  • 255 이상의 값 사용

    • main() 함수나 exit() 함수에서 255 이상의 값을 반환하려고 할 때 주의해야 한다.
    • 예를 들어, return 300; 또는 exit(300);와 같이 255를 초과하는 값을 반환하려고 하면, 실제 종료 코드는 300 % 256 = 44로 변환되어 44로 전달된다.
    • 이는 255 이상의 값이 1바이트로 표현되기 때문에 값이 순환되며 발생하는 결과이다.
  • 1을 반환하는 경우

    • return -1; 혹은 exit(-1);을 호출하면 1이 1바이트 정수로 변환된다.
    • 1은 16진수로 0xFF로 변환되며, 양수 255로 인식되어 종료 코드 255로 전달된다.

0개의 댓글