학습 목표 
- 프로세스가 생성된 후 어떤 상태 변화를 거치는지 알아본다.
- 프로세스 제어 블록의 구성과 문맥 교환 시 동작 과정을 알아본다.
- 프로세스의 생성과 복사, 전환 과정을 알아본다.
- 스레드의 개념을 이해하고 멀티스레드 시스템의 장점을 알아본다.
     

    목차

    • 프로세스의 구조
    • 프로세스의 생성과 복사
    • 프로세스의 전환
    • 프로세스의 계층 구조

    프로세스의 구조

    프로세스의 구조 테이블

    프로세스 요리 설명
    코드 영역 요리책 스파게티를 만들려면 레시피가 적힌 요리책을 봐야 한다. 프로세스에 대한 동작을 구성하는 코드는 마치 레시피가 적힌 요리책과 같다. 이는 코드 영역에 저장된다.
    데이터 영역 재료 요리를 완성하려면 재료가 있어야 한다. 코드를 작성하며 할당되는 일반 데이터 영역과 힙 영역이 바로 이에 해당된다.
    스택 영역 조리 도구 도마나 칼같은 도구는 스택 영역에 해당된다. 스택 영역은 코드를 작동하기 위해 운영체제가 부수적으로 관리하는 데이터 영역이다. 사용자에게는 스택의 내용이 보이지 않는다.

     

    워드 프로그램을 예로 들어보자, 워드 프로그램이 실행되면 코드 영역에 탑재된다. 워드 프로그램에서 작성중인 문서는 데이터 영역에 탑재된다. 워드 프로그램을 구동하기 위한 각종 부가적인 데이터는 스택 영역에 관리된다. 

     

    코드 영역

    읽기 전용으로 생성되는 텍스트 영역이다.

     

    데이터 영역

    코드가 실행되면서 사용되는 변수나 파일 등 각종 데이터를 모아놓는 곳이다.

     

    스택 영역

    운영체제가 프로세스를 실행하기 위해 부수적으로 필요한 데이터를 모아놓은 곳이다.

     


     

    프로세스의 생성과 복사

    프로세스는 프로그램을 실행할 때 생성된다. 프로세스가 생성되는데에는 다음과 같은 순서를 거친다.

     

    1. 운영체제가 프로그램(코드)를 메모리로 가져와서 코드 영역에 넣는다.
    2. 프로세스 제어 블록을 생성한다.
    3. 이후 필요한 메모리에 데이터 영역과 스택 영역을 확보한다
    4. 프로세스를 실행한다.

     

    만약 새로운 프로세스를 만들어야하는 상황이 왔을 때 위 과정을 모두 거치고 생성하는게 아닌 완성된 프로세스를 복사하는 방식을 사용할 수 있다. 

     

    fork() 시스템 호출

    커널에서 제공되는 함수이며, 실행 중인 프로세스로부터 새로운 프로세스를 복사하는 함수이다. 

     

    fork() 시스템 호출은 실행중인 프로세스를 복사하는 커널 함수이다. 이 때 실행하던 프로세스는 부모 프로세스, 새로 생긴 프로세스는 자식 프로세스가 되며, 부모-자식 관계를 갖게된다.

     

     

    fork() 시스템 호출의 동작 과정

     

    프로세스 제어 블록이 상속된다. 일부 변경되는 내용도 있다. 제어 블록 내에 자식 프로세스를 가리키는 영역이 각자 수정된다. 그리고 자식 프로세스의 구분자도 새로 할당된다.

     

     fork() 시스템 호출의 장점
    • 프로세스의 생성 속도가 빠르다.
    • 추가 작업 없이 자원 상속이 가능하다.
    • 시스템 관리에 효율적이다.

     

    fork() 시스템 호출의 예

     

    unistd.h 헤더파일이 필요하다. fork()함수에 의해 자식 프로세스가 정상적으로 생성되면 부모 프로세스에 0보다 큰 값을 반환하고 자식 프로세스에 0을 반환한다. 만약 생성에 실패하면 0보다 작은 값을 반환한다. fork()문 이전에 선언된 변수들은 모두 자식 프로세스에 상속되고 이후 서로 독립적이기 때문에 child와 parent중 어느게 먼저 출력될지는 알 수 없다.

     

     


    프로세스의 전환

    exec() 시스템 호출의 개념

    exec() 시스템 호출은 기존의 프로세스를 새로운 프로세스로 전환하는 커널 함수이다. 일종의 프로세스의 수정 작업이라고 할 수 있다. 새로운 프로세스를 만들려면 생성 과정을 반복해야하는데, 그 과정없이 수정만을 원할 경우 exec() 시스템 호출을 하면 된다.

     

     

    exec() 시스템 호출의 동작 과정

    exec() 시스템 호출은 기존의 코드 영역과 데이터 영역, 스택영역에 있는 내용은 전부 지우고 새로운 코드로 대체시킨다. 기존의 제어 블록 내용은 변하지 않는다. 

     

     

    exec() 시스템 호출의 예

    fork()로 자식 프로세스를 생성한 후 wait() 문을 실행하고 자식 프로세스가 끝날 때 까지 대기한다. wait() 함수는 자식 프로세스와의 동기화를 위한 코드이며, 자식 프로세스가 끝날 때 까지 부모 프로세스가 기다린다. execlp가 실행되는 순간 자식 프로세스의 코드 영역이 mplayer로 바뀌며 처음부터 다시 실행된다.

     

    유닉스는 exec() 시스템 호출이 parameter에 따라 execl(), execlp(), execv(), execvp()등 다양한 함수가 있다. 기능은 모두 동일하다. 

     


     

    프로세스의 계층 구조

    유닉스의 프로세스 계층 구조

    유닉스에서 커널이 처음 메모리에 올라와 부팅되면 커널 관련 프로세스를 여러개 만든다. init 프로세스는 전체 프로세스의 시작점이 된다. 이후 나머지 프로세스를 init프로세스의 자식 프로세스로 만든다. 운영체제에 있는 모든 프로세스는 init 프로세스의 자식이 된다. 트리 구조이다.

     

    프로세스 계층 구조의 장점

    계층 구조는 동시에 여러 작업을 하는 다중 프로그래밍 시스템을 가능하게 하고 종료된 프로세스의 자원을 회수하는데 유용하다.

     

     

    다중 프로그래밍 시스템

    login 프로세스는 인증을 거쳐 컴퓨터에 접속하는 과정을 나타낸다. 유닉스 운영체제는 여러 사용자를 동시에 처리하기 위해 fork() 시스템 호출로 login 프로세스를 여러 개 만든 후 사용자에게 나누어준다. 새로운 사용자가 올 때 마다 이러한 작업을 반복하면 여러 사용자를 동시에 처리할 수 있다.

     

    login 프로세스가 작업을 마치면 메모리 공간과 프로세스 제어 블록을 exec()로 재활용해서 shell 프로세스를 생성한다. shell 프로세스는 운영체제에 명령을 내리고 받는 프로세스이다. 물론 부모 - 자식의 관계도 그대로 상속된다.

     

    용이한 자원 회수
    프로세스를 계층 구조로 만들면 시스템 관리가 수월하다. 그리고 자원을 회수(garbage collection)할 때 특히 편하다. 모든 프로세스가 독립적이라면 각 프로세스가 종료될 때마다 운영체제가 관여해서 직업 자원을 회수해야 하기 때문에 작업이 복잡해진다. 그러나 계층 구조처럼 부모-자식 관계의 프로세스 구조라면 자식 프로세스가 작업을 마쳤을때 사용하던 자원을 부모 프로세스가 회수하면 된다.

     

     

    고아 프로세스

    부모 프로세스는 자원을 회수하기 위해 자식 프로세스가 종료될 때 까지 기다려야한다. 만약 부모 프로세스가 먼저 종료되거나 자식 프로세스가 비정상적으로 종료되면 자원이 그대로 남게 되버린다. 이렇게 자원이 회수되지 않은 상태로 남아버린 프로세스를 고아 프로세스(orphan process)또는 좀비 프로세스(zombie process)라고 한다. 

     

     

    exit() or return문 

     

    exit() 또는 return문은 부모 프로세스에 자식 프로세스가 정상적으로 종료되었다는걸 알려주는 용도이다. 요새는 return문을 적지 않아도 문제가 생겨 좀비 프로세스가 되거나 하진 않는다. 

     

    프로세스의 복사, 상속, 계층 구조는 현대 객체지향 프로그래밍 언어의 기본이다. 자바의 경우 객체가 상위 객체의 모든 특징을 상속하며, 모든 객체의 최상위에 Object클래스가 있다. 자바는 자원 회수가 잘되는 프로그래밍 언어중 하나이다.