ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2. 프로세스 관리
    Computer Science/Operating System 2022. 2. 28. 02:41
    728x90

    하드디스크와 같은 파일 시스템에 있는 프로그램이 먼저 프로세스가 되기까지의 과정을 살펴본 후 프로세스에 대한 전반적인 흐름을 확인해보려 한다.

     

     

    프로그램 실행

     

     

      - 파일 시스템에 저장되어 있는 실행 파일을 실행시키면 메모리에 적재되어 프로세스가 된다.

     


      -  메모리에 적재되기 이전에 실행 파일마다 개별적인 가상 메모리가 존재하며 필요한 페이지만 물리적 메모리에 적재되며, 올라가지 않은 페이지는 스왑 영역에 올라간다.

     

    이때, 각 프로세스별 가상 메모리는 0번지부터 시작하고 실제 물리 메모리에 쌓이는 주소와는 다른데, 가상 메모리의 논리적 주소에서 물리 주소로 변환하는 과정을 거친다.

     

    스왑 영역이란 쫓겨난 프로세스가 저장장치의 특별한 공간을 모아두는데 이러한 영역을 의미하며, 스왑 영역에서 메모리로 데이터를 가져오는 작업은 스왑 인이고 반대는 스왑 아웃이다. 스왑 영역은 메모리 관리자가 관리를 하며 크기는 메모리의 크기와 같다. 

     




    가상 메모리

     

     

    크기가 다른 물리 메모리에서 일관되게 프로세스를 실행할 수 있는 환경을 제공해주며, 물리적 메모리를 효율적으로 사용하기 위한 가상의 메모리이다. 가상 메모리는 프로그램의 크기에 맞춰진다.

     

    스택 영역 : 운영체제가 프로세스를 실행하기 위해 부수적으로 필요한 데이터를 모아놓은 곳으로 지역 변수 및 함수에 관한 정보(호출, 리턴값)가 담기며 호출 깊이에 따라 아래의 방향으로 확장된다.

     

    힙 영역 : 동적으로 메모리가 할당되는 영역으로써 할당되는 크기에 따라 위로 확장된다.

     

    데이터 영역 : 데이터가 보관되는 영역으로 메모리 데이터가 올라가는데, 여기서의 데이터들은 전역 변수나 프로그램 종료까지 사라지지 않는 데이터를 말한다.


    코드 영역 : 실제 CPU가 실행할 기계어가 올라가는 읽기 전용 텍스트 영역이다. 코드에 작성된 함수의 종류로는 사용자 정의 함수 또는 라이브러리 함수가 포함될 수 있다.

     

     


    부팅시 운영체제가 실행되면서 실제 메모리 첫 부분에 적재되는 커널도 가상 메모리의 구조와 비슷하다. 


    커널 코드에는 인터럽트 처리 코드, 자원관리를 위한 코드, 커널 함수(fork(), exec() 등)이 있고 데이터 영역에는 프로세스 제어 블록과 같은 정보가 담겨있다. 커널 스택에선 사용자 프로세스별로 실행한 운영체제 함수 호출 정보가 담겨있으며 이처럼 시스템 콜을 하는 경우에는 CPU 제어권이 커널로 넘어가 Mode bit도 바뀌게 된다.

     

    프로세스 제어 블록(Process Control Block)는 운영체제가 메모리에 있는 각 프로세스를 관리하기 위해 프로세스별 유지하는 정보로 OS가 관리상 사용하는 정보, CPU 수행 관련 하드웨어 값, 메모리 관련, 파일 관련 내용들이 담겨있는데, 자세한 내용은 아래 그림과 같다.

     

     

     

     

    프로세스

     

     

    위에서 언급하였듯이 프로그램이 메모리에 올라가 실행되면 프로세스라 하였으며, 프로세스의 문맥과 상태에 대해 알아보겠다. 

     


    프로세스 문맥 


    - CPU 수행 상태를 나타내는 하드웨어 문맥(Program Counter, register)
    - 프로세스 주소 공간(code, data, stack)
    - 프로세스 관련 자료 구조(PCB, 커널 스택) 

     


    프로세스 상태

     

     


    생성(New) : 프로그램이 프로세스가 되어 메모리에 올라와 준비를 완료한 상태로써, PCB가 생성된다.

    준비(Ready) : CPU 준비 큐에서 프로세스가 CPU를 기다리는 상태 

    실행(Running) : CPU가 할당되어 명령어를 수행중인 상태로 시분할 시스템에서는 일정 시간동안 CPU를 사용할 권한을 갖게 된다.

    대기(Blocked) : 프로세스 자신이 요청한 입출력이 즉시 만족되지 않아 입출력이 완료될 때까지 기다리는 상태이거나 디스크에서 파일을 읽어와야 하는 경우(HW, SW 인터럽트 공존)에 해당한다.


    입출력 대기인 경우 입출력 관리자가 DMA를 통해 메모리에 접근해서 입출력을 완료했을 때 인터럽트를 걸어 CPU가 처리한 후 해당 프로세스를 다시 실행시키기 위해 준비 큐 끝에 넣는다. 

    보류(Suspended) : 외부적인 원인에 의해서 프로세스의 수행이 정지된 상태이며 해당 프로세스는 메모리에서 쫓겨나 임시로 스왑영역에 보관된다. 이는 외부(사용자 또는 운영체제)에서 다시 재개시켜줘야 활성화된다. 


    예를 들어, 사용자가 프로그램을 의도적으로 일시 정지시킨 경우(break key)가 있거나, 시스템이 메모리에 너무 많은 프로세스가 적재된 것과 같은 이유로 프로세스를 잠시 중단시킬 때가 있다.

    완료(Exit) : 프로세스 수행을 마친 상태로 프로세스 제어 블록이 사라진다.

     


    CPU가 할당된 한 프로세스에서 다른 프로세스로 CPU를 넘겨줄 때는 문맥 교환(Context Switch)이 일어난다. 즉, 현재 진행중인 작업의 상태를 저장하고, 다음 작업의 상태를 적용하는 과정이라 할 수 있다.

     

    기존 프로세스의 상태를 그 프로세스의 PCB에 저장시키고, CPU가 새롭게 프로세스를 얻어 작업을 수행할 수 있도록 새로운 프로세스의 상태 또는 레지스터 정보가 담겨있는 PCB로 교환하는 과정을 거친다.

     

    단, 시스템콜이나 인터럽트 발생 시 반드시 문맥교환이 일어나는 건 아니다. 이 경우 CPU 수행 정보 등 문맥의 일부만 PCB에 저장해야 하지만 문맥 교환을 하는 경우엔 오버헤드가 차지하는 게 상대적으로 더 크다.

     

     

     

    스케줄러의 종류와 역할

     

     

    장기 스케줄러 (Job scheduler)


    - 생성 프로세스 중 어떤 걸 준비 큐로 보낼지 결정해주는데, 이때 생성에서 준비상태로 넘어갈 때 admited 해주는 작업

    - 메모리에 올라가는 프로세스의 개수인 멀티 프로그래밍의 정도를 제어
    - 시분할 시스템에는 장기 스케줄러가 일반적으로 없으며(생성 후 바로 준비상태) 이를 해결해주기 위해 중기 스케줄러를 이용한다.

    단기 스케줄러 (CPU scheduler)


    - 어떤 프로세스를 다음번에 실행시킬지 결정, 프로세스에 CPU를 주는 문제
    - millisecond 단위로 충분히 빨라야 한다.


    CPU 스케줄러는 프로세스의 전 상태에 관여해 모든 프로세스의 작업이 원만하게 이뤄지도록 관리한다. 즉, 프로세스가 생성된 후 종료될 때까지 모든 상태 변화를 조정한다.

     

     

       [CPU 스케줄러가 하는 작업]


     타임 아웃 : 프로세스가 자신에게 주어진 하나의 타임 슬라이스 동안 작업을 끝내지 못해 타이머 인터럽트를 걸어 CPU 할당 시간이 만료되면 다시 준비 상태로 돌아가는 것.


     디스패치 : 준비 큐에 있던 프로세스 중 하나를 실행 상태로 바꾸는 것.


     블록 : 실행 상태에 있는 프로세스가 입출력 요청을 하는 경우 해당 프로세스를 대기 상태로 옮기는 것.


     웨이크업 : 대기 상태에 있는 프로세스 중 입출력 처리가 완료되어 해당 프로세스의 PCB가 준비 상태로 이동하게 되는 것.

     


    중기 스케줄러 (Swapper)


    - 시스템 성능이 떨어지는 경우 메모리의 여유 공간 마련을 위해서 일부 프로세스를 메모리에서 스왑영역으로 쫓아낸다.
    - 멀티 프로그래밍의 정도를 제어

     

     

     

    프로세스의 작업단위, 스레드

     

     

    스레드는 프로세스의 코드에 정의된 절차에 따라 CPU에 작업 요청을 하는 실행 단위로 프로세스 내부에서 스레드 간 서로 강하게 연결되어 있다. LWP(Light Weight Process)라는 가벼운 프로세스라고도 불린다.

     

    시스템콜로 인한 메모리 낭비를 방지하기 위해 하나의 프로세스 내에 여러 개의 스레드를 만들어 사용한다. 각 스레드는 프로그램 카운터, 레지스터 값, 스택 공간으로 구성되어 있고, 코드, 데이터 영역 및 OS 자원은 스레드 간 공유하는 부분이다.

     

    멀티스레드란 이처럼 프로세스 내 작업을 여러 개의 스레드로 분할시킴으로써 작업의 부담을 줄이는 프로세스 운영 기법이다.

     



    프로세스 내 공유가 가능한 부분을 제외하고 실행과 관련된 부분을 스레드로 나누어 관리하면 자원의 중복 사용을 피해 효율적으로 작업할 수 있으며, 동일한 작업을 수행한다면 여러 개의 스레드가 협력하여 높은 처리율과 성능 및 병렬성을 향상시킬 수 있다.

     

    다만, 프로세스 내 모든 스레드가 자원을 공유하므로 한 스레드에 문제가 생긴다면 하나의 프로세스 전체에 영향을 미칠 수 있다. 

     

     

    프로세스의 생성, 종료

     


    프로세스 생성은 부모 프로세스가 운영체제로부터 시스템콜을 통해 똑같은 자식 프로세스를 생성하는데 프로세스를 만들어내는 시스템콜의 종류는 아래와 같다.

    fork()

     

    부모 프로세스와 같은 내용을 복제해 새로운 주소 공간을 만들어내는 함수이다.

     

    아예 처음 프로세스를 생성할 때는 하드디스크로부터 프로세스를 생성하는 과정이 있기에 시간이 걸리지만 이후에는 fork() 시스템콜로 복사를 하므로 생성 속도가 훨씬 빠르다.

     

    기존 프로세스는 부모 프로세스가 되며 새로 복사된 프로세스는 자식 프로세스가 된다. 자원 관리는 부모 프로세스에서 하며 프로세스는 서로 독립적이다.

     

     

    왼쪽 부모 프로세스의 코드에서 fork()가 호출되는 시점에서 자식 프로세스가 생성된다. 이때 부모 프로세스의 pid는 0보다 큰 상태로 parent에 해당하는 출력문이 실행되고, 자식 프로세스는 pid가 0이기에 child에 해당하는 출력문이 실행된다.



    exec()

     

    fork()를 통해 주소 공간을 만들고 내용만 전환하고 싶을 때, 실행 중인 자식 프로세스로부터 새로운 프로세스로 만들어내는 함수이다.


    이미 만들어진 PCB, 부모-자식 관계, PID와 같은 정보들이 변경되지 않기에 재활용성이 뛰어난 편이다. 코드 영역의 내용은 목적에 맞게 새롭게 바뀌면서 데이터 영역과 스택 영역은 리셋되어 새로운 변수를 사용하게 된다.

     

     

    왼쪽에서 자식 프로세스로 생성된 코드에서 pid가 0인 경우 execlp() 함수가 호출되면 자식 프로세스로부터 오른쪽 코드처럼 새로운 프로세스가 생성된다. exec()으로 생성해 낸 프로세스는 기존 자식 프로세스의 부모 프로세스와의 관계도 유지된다.

     

     

    부모, 자식 프로세스의 특징에는 다음과 같다. 

     

      - 프로세스의 트리 형성으로 계층구조를 이룸
      - 프로세스는 자원을 필요로 하는데, 운영체제로부터 받으며, 부모와 공유하는 경우도 있다.
      - 모든 자원을 공유, 일부 공유, 전혀 공유하지 않는 모델 세 가지로 나뉘어 자원의 공유를 구분지을 수 있고, 추가적으로 부모와 자식이 공존하여 수행되는 모델도 있다. 이는 자식이 종료될 때까지 부모가 기다리는 경우에 해당한다.

     


    프로세스 종료는 프로세스가 마지막 명령을 수행한 후 운영체제에게 이를 알려주는 exit, 부모 프로세스가 자식의 수행을 종료시키는 abort 두 가지의 종료 상황이 있다. 자식 프로세스의 입출력 요구 시 일시적인 프로세스 종료에 사용되는 wait 시스템 콜도 사용된다.


    exit()

     

    프로세스가 종료되고 각종 자원들을 운영체제에게 반납하게 하는 시스템 콜이다. 모든 자원들을 반환하고 부모 프로세스에게 알리는 역할을 한다.

     

    마지막 명령문이 수행된 후 해당 시스템 콜을 통해 프로그램을 종료시키거나, 프로그램에 명시적으로 작성해주지 않아도 main 함수가 반환되는 위치에 컴파일러가 넣어주는 자발적 종료가 있다.

     

    또한, 부모 프로세스가 자식 프로세스를 강제 종료하는 경우, 키보드를 통해 kill, break를 친 경우의 비자발적 종료가 있다. 


    abort() 

     

    abort() 시스템 콜은 프로세스를 중단하는 역할을 한다. 예컨대, 자식이 할당 자원의 한계치를 넘거나 작업의 필요성이 사라지는 경우 그리고 부모가 종료하는 경우가 있는데 이는 운영체제가 부모 프로세스의 종료를 통해 자식 프로세스의 수행을 막기에 단계적으로 종료된다.

     

    wait()

     

    부모 프로세스가 wait() 시스템 콜을 호출하면 커널은 fork()로 복제된 자식 프로세스가 종료될 때까지 부모 프로세스를 sleep시켜 block 상태를 만든다. 이때, block의 원인으로는 디스크 I/O, 키보드 I/O과 같은 이유이다. 자식 프로세스가 종료되면 커널은 다시 부모 프로세스를 깨워 준비상태로 옮긴다.

    728x90

    댓글

Designed by Tistory.