[OS] 프로세스 (2) - 프로세스 상태

doongidoong·2024년 1월 17일
0

운영체제

목록 보기
3/8

✅ 프로세스 생명 주기

프로세스의 상태는 실행(RUNNING), 준비(READY), 봉쇄(BLOCKED, WAIT, SLEEP)의 세 가지로 구분할 수 있다.
이외에도 생성 중인 상태를 시작(NEW) 상태, 종료 중인 상태를 완료(TERMINATED)라고 부르기도 한다.


✔️ 프로세스 상태

  • New

    • 프로세스가 생성 중인 상태
    • 프로세스가 시작되어 프로세스를 위한 각종 자료구조는 생성되었지만 아직 메모리 획득을 승인 받지 못한 상태
    • fork
  • Ready

    • 프로세스가 CPU 할당 받는 것을 대기 중인 상태
    • CPU만 보유하면 당장 명령을 실행할 수 있지만 CPU를 할당받지 못한 상태이다.
    • Ready Queue에 위치한다.
  • Running

    • 프로세스 실행 중인 상태, CPU를 보유하고 기계어 명령을 실행하고 있는 상태를 가리킨다.
    • CPU가 하나인 컴퓨터느 시스템은 여러 프로세스가 동시에 수행된다고 해도 실제로 실행되는 프로세스는 매 시점 하나뿐이다.
      • 추후 다룰 동시성과 관련된 내용
  • Waiting

    • 봉쇄 상태는 CPU를 할당받더라도 당장 명령을 실행할 수 없는 상태이다.
    • 프로세스가 I/O completion 또는 시그널 등을 기다리는 중이다.
      • 예를 들면, 입출력 인터럽트가 발생한 경우
    • 프로세스가 Waiting Queue에 위치해 있다.
  • Terminated

    • 프로세스 종료 상태
    • 프로세스가 종료되었으나 아직 운영체제가 그 프로세스와 관련된 자료구조를 완전히 정리하지 못한 상태

✔️ 프로세스 상태 변화

하나의 프로세스는 앞서 말한 한 가지의 상태에 놓여있게 되며 시간의 흐름에 따라 상태는 변하게 된다.

예를 들어, 실행 상태에서 인터럽트가 발생하면 CPU 제어권은 운영체제로 이관되며 원래 프로세스는 준비 상태가 된다.

  • New → Ready
    • Secondary Storage에 저장되어 있는 실행 파일이 메모리에 적재되는 과정
  • Ready → Running
    • 메모리에는 여러 개의 프로세스가 있을 수 있고 (Ready Queue) 그 중 어떤 프로세스를 실행시킬 것인지는 CPU 스케쥴링 (dispatcher)가 결정한다.
    • Running 프로세스는 하나만 존재할 수 있다.
  • Running → Ready
    • 실행 중인 프로세스는 Time Quantum을 모두 소모하는 경우 CPU 점유권을 빼앗긴다.
      • 이러한 OS를 선점형 OS(preemption OS)라고 한다.
  • Running → Waiting
    • I/O 요청이 발생하면 Device Queue에 위치한다.
  • Waiting → Ready
    • I/O 작업이 완료되면 다시 Ready Queue에 들어간다. (Running으로 가는 것이 아니다.)


✅ 문맥 교환(CONTEXT SWITCHING)

  • 하나의 사용자 프로세스로부터 다른 사용자 프로세스로 CPU 제어권이 이양되는 과정

    CPU를 할당받고 프로세스가 실행되던 와중 타이머 인터럽트가 발생하면 CPU 제어권이 운영체제에게 넘어간다. 그리고 운영체제는 현재 실행 중인 프로세스의 문맥을 저장한다. 이후 현재 프로세스는 준비 상태가 되고 새로운 프로세스가 실행 상태가 된다.

  • 위와 같이 프로세스 간 전환이 일어날 때 현재 프로세스의 문맥(정보)을 저장하고 새로운 문맥으로 교환하는 것을 컨텍스트 스위칭(문맥 교환)이라고 한다.

    또한 준비 상태의 프로세스들 중 할당받을 프로세스를 선택하고 실제로 CPU 제어권을 넘겨받는 과정을 디스패치(DISPATCH)라고 한다.

  • 타이머 인터럽트 이외에도 입출력이나 조건을 충족하지 못해 봉쇄되는 경우도 문맥교환이라 칭한다.

  • 다만, 인터럽트로 운영 체제의 커널 코드가 실행될 때도 문맥을 저장하지만 이를 문맥 교환이라 칭하지는 않는다.

    • 하나의 프로세스의 실행모드가 사용자 모드에서 커널 모드로 변경되는 것 뿐이다. (문맥 교환이 오버헤드가 훨씬 크다)

프로세스들의 시간 할당량은 시스템 성능에 중요한 역할을 한다.

  • 시간 할당량이 커지면 Context Switching이 줄어들어 오버헤드가 감소하지만 여러 프로세스가 동시에 실행되는 느낌을 받지 못한다.
  • 시간 할당량이 줄어들면 Context Switching이 많아져 오버헤드가 증가하지만 여러 프로세스가 동시에 실행되는 느낌을 받는다.


✅ 프로세스 API

프로세스를 생성할 때, 운영체제가 프로세스 전부를 생성한다고 생각할 수 있지만 실제로는 그렇지 않다.
최초의 프로세스는 운영체제가 직접 생산하지만 그 이후는 부모 프로세스를 복제하여 자식 프로세스를 생성하는 구조이다. 따라서 프로세스는 트리(계층) 구조를 띈다.

✔️ fork()

  • fork() 함수는 함수를 호출한 프로세스를 복사하는 기능
    • 이때 원래 진행되던 프로세스는 부모 프로세스(parent), 복사된 프로세스를 자식 프로세스(child) 라고 한다.
    • 부모 프로세스와 자식 프로세느는 주소 공간을 따로 갖게되지만 주소 공간 내에서는 동일한 내용을 가지게 된다.
  • fork 함수는 프로세스 id, 즉 pid 를 반환한다.
    • 이때 부모 프로세스에서는 자식 pid가 반환되고 자식 프로세스에서는 0이 반환
    • 만약 fork() 함수 실행이 실패하면 -1을 반환
    • fork()의 반환 값으로 부모인지 자식인지 구분한다.
  • fork는 heavy-weight한 시스템 콜이다.
    - 왜냐하면 부모 프로세스의 전체 복사본을 생성하고, 이를 자식 프로세스로 실행시키기 때문이다.
    - 따라서 이 수행에 필요한 노력을 줄이기 위해 copy-on-write 기술을 사용한다.

    💡 copy-on-write

    • 복사하는 작업을 부모나 자식이 page에 쓰기 작업을 하기 전까지 copy 작업을 지연시킴으로써 효율성을 높이는 기술
    • 부모 프로세스가 fork하여 생긴 자식 프로세스 page를 공유하다가 자식이 page에 쓰기 작업을 할 때 해당 page만을 copy하는 방식
    • 프로세스들이 일반적으로 메모리에서 page의 일부분만을 사용한다는 사실을 이용

✔️ exec()

  • fork 다음으로 수행되는 시스템 콜으로, 새로운 프로그램을 메모리에 올려 실행함.
  • 어떤 프로그램을 완전히 새로운 프로세스로 태어나도록하는 역할을 하며, exec() 시스템콜을 통해 다른 프로그램을 수행할 수 있다.
    • 기존엔 부모 프로세스를 그대로 복사한 상태지만, exec() 시스템 콜로 인해 다른 새 프로그램으로 덮어 씌워진다. 이것으로 자식 프로그램은 독자적인 일을 할 수 있게 된다.
    • 즉, 자신의 복사본이 아닌 다른 프로그램을 실행해야 할 경우에는 바로 exec() 시스템 콜이 그 일을 한다.
  • 해당 실행 파일의 코드와 정적 데이터를 읽어 들여 현재 실행 중인 프로세스의 코드 세그멘트와 정적 데이터 부분을 덮어 쓴다.
    • 힙과 스택 및 프로그램의 다른 주소 공간들로 새로운 프로그램의 실행을 위해 다시 초기화한다.

✔️ wait()

  • wait( ) 시스템 콜은, 만약 프로세스 A가 wait( )를 호출하면 커널은 자식이 종료될 때까지 A를 Sleep(blocked)시킨다.
    • 자식 프로세스가 종료되면 커널이 A를 깨워 Ready 상태로 만든다.
    • 따라서 만약 자식이 먼저 수행되기를 원하면 wait( )를 넣어주면 된다

💡 자식 프로세스를 만들 때 왜 fork와 exec을 분리할까?

  • fork와 exec을 분리해야 그 사이에 로직을 넣을 수 있다.
    • prompt> wc p3.c > newfile.txt
      • 위의 예에서 wc 프로그램의 출력은 newfile.txt로 재지정된다. 이러한 작업을 수행할 때, fork와 exec을 분리하면 자식을 생성하고 exec이 호출되기전에, 표준 출력 파일(STDOUT)을 닫고 newfile.txt를 연다. 이런 작업을 하면, wc의 출력은 파일로 보내진다.

✔️ exit() & abort()

  • 프로세스는 원칙적으로 부모 프로세스가 종료되기 전에 모든 자식 프로세스가 종료되어야 된다. 이와 관련해 프로세스 종료는 두 가지로 나뉜다.

  • exit()

    • 프로세스가 마지막 명령을 수행한 후 운영체제에게 이를 알려 이뤄지는 자발적 종료
    • 프로세스는 명령을 모두 수행한 후 마쳐지는 코드 부분에 exit()을 호출한다.
      • 이를 통해 운영체제에게 자신이 종료됨을 알리고 운영체제는 프로세스로부터 자원을 회수하고 프로세스를 정리한다.
    • exit() 함수는 개발자가 명시적으로 호출하지 않아도 컴파일러에서 자동으로 삽입한다.
  • abort()

    • 비자발적 종료로 부모 프로세스가 자식 프로세스를 강제로 종료시키는 것

    • 강제 종료가 발생하는 경우는 다음과 같이 나누어 볼 수 있다.

      1. 자식 프로세스가 한계치를 넘는 자원을 요구할 때
      2. 자식 프로세스에게 할당된 작업이 더 이상 필요하지 않을 때
      3. 부모 프로세스가 종료되는 경우

      부모 프로세스를 종료 이후에도 자식 프로세스의 업무가 수행되어야 한다면 이를 다른 프로세스로 이관시키는 작업이 필요하다. 이렇게 하면 원칙을 지킬 수 있다.

      • 만약 로그아웃이 이후에도 로그아웃 이전에 수행한 업무가 계속 진행되어야 하는 경우

➕ Zombie process & Orphan process

  • Zombie process (Defunct process)
    • 프로세스가 종료되었으나, 부모 프로세스의 wait() 호출을 통해 아직 회수되지 않은 경우
    • 프로세스가 종료되었으나, process table에 남아있게 된다.
    • 보통 버그나 잘못된 코드로 인해 발생한다.
      • 예를 들어 부모 프로세스가 무한 루프에 걸려 종료된 자식 프로세스를 회수하지 못하는 경우가 있을 수 있다.
    • 다만 모든 프로세스는 아주 잠깐 좀비일 수 있음.
       
  • Orphan process
    • 자식 프로세스의 수행 중, 부모 프로세스가 wait() 호출 전에 종료된 경우
    • 부모가 먼저 죽어버린 경우
    • init process가 주기적으로 wait()를 호출하여 고아 프로세스들의 exit status를 회수한다.

💡 Daemon 프로세스
멀티태스킹 운영 체제에서 데몬은 사용자가 직접적으로 제어하지 않고, 백그라운드에서 돌면서 여러 작업을 하는 프로세스
- 데몬은 일반적으로 자식 프로세스를 포크(fork)하여 자기 자신을 복사, 생성한 후 자기 자신은 삭제하여 해당 프로세스를 고아 프로세스로 만든 후 이를 init이 자신의 자식 프로세스로 받아들이도록 하는 과정을 통하여 만들어지며 이를 'fork off and die'라 표현

profile
안녕하세요! 신입 개발자입니다.

0개의 댓글