ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [공룡책] Ch 3. 프로세스
    공부/운영체제(OS) 2022. 4. 26. 14:20
    반응형

    이 글은 공룡책을 읽고 정리한 글입니다.


     

     

     

    0. 소개


    프로세스란 공식적으로는 모든 CPU의 활동을 말하고, 비공식적으로는 실행 중인 프로그램을 말한다.

    운영체제가 복잡해질수록 커널 안에서 다양한 시스템 작업을 최선으로 처리할 필요가 있다. 그래서 하나의 시스템은 프로세스의 집합체로 일부는 사용자 코드를 실행하고, 일부는 운영체제 코드를 실행한다.

    모든 프로세스는 잠재적으로 병행 실행이 가능하고, CPU는 이들 프로세스 가운데서 다중화된다.

    * 다중화란 장애가 발생해도 시스템 기능을 계속 수행할 수 있도록 하는 것

     

    이번 장에서는 프로세스가 정확히 무엇인지, 운영체제에서 어떻게 표현되는지, 어떻게 작동하는지에 대해 다룰 예정이다.


     

     

     

    1. 프로세스 개념


    운영체제를 논의할 때 모든 CPU 활동들을 프로세스라고 부르도록 결정하였다.


    1) 프로세스

    프로세스의 현재 활동의 상태는 프로그램 카운터 값과 프로세서 레지스터의 내용으로 나타낸다.

     

    프로세스의 메모리 배치는 위와 같은 사진으로 되어 있다.

    - 스택: 함수를 호출할 때 임시 데이터 저장장소

    - 힙: 프로그램 실행 중에 동적으로 할당되는 메모리

    - 데이터: 전역 변수

    - 텍스트: 실행 코드

     

    데이터와 텍스트의 경우에는 크기가 고정되어 있지만, 스택과 힙크기가 동적으로 바뀌게 된다.

    이때 스택은 아래 방향으로, 힙은 윗 방향으로 크기가 커지는데 운영체제는 서로 겹치지 않도록 만들어야 한다.

     

    프로그램과 프로세스를 혼동하지 말아야 하는데, 프로그램은 명령어 리스트를 내용으로 가진 디스크에 저장된 파일과 같은 수동적인 존재이다. 프로세스는 다음에 실행할 명령어를 지정하는 프로그램 카운터와 관련 자원의 집합을 가진 능동적인 존재이다. 그러므로 실행 파일이 메모리에 적재될 때 프로그램은 프로세스가 된다.

     

    한 프로세스는 반드시 반드시 순차적으로 수행되어야 한다. 동일한 프로그램에 여러 프로세스가 연관될 수 있는데, 각각의 프로세스를 별도의 실행 순서로 간주한다. (해당 내용은 Ch.1 5.1과 Ch.3 3.4에 설명되어 있다.)


    2) 프로세스 상태

     

    프로세스는 실행되면서 상태가 변하게 되는데 위 그림과 같다.

    - new: 프로세스 생성

    - ready: 프로세스가 처리기에 할당되기를 기다림

    - running: 명령어들이 실행

    - waiting: 프로세스가 어떤 이벤트가 일어나기를 기다림

    - terminated: 프로세스 종료

     

    저 이름은 운영체제마다 변하지만 저런 상태는 모든 시스템에서 찾아 볼 수 있다.

    어느 한 순간에 한 처리기 코어에서는 오직 하나의 프로세스만이 실행된다는 것이 중요하다.


    PCB

     

    3) 프로세스 제어 블록

     

    프로세스는 운영체제에서 프로세스 제어 블록(PCB, 태스크 제어 블록)에 의해 표현된다.

     

    - 프로세스 상태: 새로운(new), 준비(ready), 실행(running), 대기(waiting), 정지(halted) 상태, ... 같은 것들이 있다.

    - 프로세스 번호: 프로세스의 고유 번호이다.

    - 프로그램 카운터: 해당 프로세스가 다음에 실행할 명령어의 주소를 가르킨다.

    - CPU 레지스터들: 여러 레지스터와 상태 정보가 포함된다. 프로그램 카운터와 함께 상태 정보는 나중에 프로세스가 다시 스케줄 될 때 계속 올바르게 실행되도록 하기 위해서 인터럽트 발생 시 저장되어야 한다.

    - CPU-스케줄링 정보: 프로세스 우선순위, 스케줄 큐에 대한 포인터와 다른 스케줄 매개변수를 가지고 있다.

    - 메모리 관리 정보: 운영체제에 의해 사용되는 메모리 시스템에 따라 기준 레지스터와 한계 레지스터의 값, 운영체제가 사용하는 메모리 시스템에 따라 페이지 테이블 또는 세그먼트 테이블 등과 같은 정보를 포함한다.

    - 회계 정보(accounting): CPU 사용 시간과 경과된 실시간, 시간 제한, 계정 번호, 잡 또는 프로세스 번호 등을 공유한다. 

     

    그래서 프로세스 제어 블록(PCB)는 회계 데이터와 함께 프로세스를 시작시키거나 다시 시작 시키는데 필요한 모든 데이터를 위한 저장소 역할을 한다고 생각하면 된다.


    4) 스레드(Threads)

     

    - 단일 스레드

    만약 한 스레드가 워드를 실행하면, 실행되는 명령어의 단일 스레드가 존재한다. 이 단일 제어 스레드는 프로세스가 한 번에 한 가지 일만 실행하도록 허용하기 때문에 사용자는 문자를 입력하면서 동시에 철자 검사기를 실행할 수 없게 된다.

     

    - 멀티 스레드

    현대 운영체제는 한 프로세스에 다수의 실행 스레드를 가질 수 있도록 한다. 그러면 프로세스가 한 번에 하나 이상의 일을 수행할 수 있게 된다. 이런 경우 여러 스레드가 병렬로 실행될 수 있기 때문에 워드를 실행하면, 하나의 스레드에 사용자 입력 관리를 맡기는 동안 다른 스레드가 철자 검사기를 수행하도록 만들 수 있다.

    스레드를 지원하는 시스템에서는 PCB가 각 스레드에 관한 정보를 포함하도록 확장되는데, 이런 스레드에 대한 자세한 내용은 4장에서 설명할 예정이다.


     

     

     

    2. 프로세스 스케줄링


    다중 프로그래밍의 목적

    CPU 이용을 최대화하기 위해 항상 어떤 프로세스가 실행되도록 하기 위해

     

    시분할의 목적

    시분할: 여러 명의 사용자가 사용하는 시스템에서 컴퓨터가 사용자들의 프로그램을 번갈아가며 처리함으로써 각 사용자에게 컴퓨터를 사용하는 느낌을 주는 것(라운드 로빈 방식)

    →사람은 1초에 1문장을 작성해도 컴퓨터 입장에서는 느리기 때문에 타자를 치는 사이사이 공백마다 CPU가 다른 일을 하게 함

    각 프로그램이 실행되는 동안 사용자가 상호 작용할 수 있도록 프로세스들 사이에서 CPU 코어를 자주 교체하기 위해

     

    위 두 목적을 달성하기 위해 프로세스 스케줄러는 코어에서 실행 가능한 여러 프로세스 중에서 하나의 프로세스를 선택한다. 각 CPU 코어는 한 번에 하나의 프로세스를 실행할 수 있다. 그래서 단일 CPU 코어는 한 번에 하나의 프로세스만 실행할 수 있고, 다중 CPU 코어는 한 번에 여러 프로세스를 선택할 수 있게 된다. 만약 프로세스 수가 코어의 수보다 많으면 초과 프로세스는 다시 스케줄 될 때까지 기다린다.


    1) 스케줄링 큐

     

    아래에 언급된 큐는 모두 연결 리스트로 보통 구현되어 있는 편이다.

    프로세스가 시스템에 들어가면 준비 큐에 들어가서 준비 상태가 되어 CPU 코어에서 실행되기를 기다린다.

    특정 이벤트가 발생하기를 기다리는 프로세스의 경우에는 대기 큐에 삽입을 한다.

    연결 리스트에는 head와 tail이 있는데 head는 첫 번째 PCB를 가리키고, tail은 마지막 PCB를 가리킨다.


    2) CPU 스케줄링

     

    프로세스는 수명주기 동안 준비 큐와 다양한 대기 큐로 이동한다.

    여기서 CPU 스케줄링의 역할준비 큐에 있는 프로세스 중에서 하나의 프로세스에 CPU 코어를 할당하는 것이다.

    이때 CPU 스케줄러는 효율을 위해 CPU를 할당하기 위한 새 프로세스를 자주 선택한다.

     

    일부 운영체제는 스와핑으로 알려진 중간 형태의 스케줄링을 가지고 있는데, 스와핑의 핵심 아이디어는 때때로 메모리에서 프로세스를 제거(스왑아웃)하여 다중 프로그래밍의 정도를 감소시키는 것이 유리하다고 생각하는 것이다. 나중에 프로세스를 메모리에 다시 적재(스왑인)될 수 있으며 중단된 위치에서 실행을 계속할 수 있다.

    스와핑은 일반적으로 메모리가 초과 사용되어 가용 공간을 확보해야 할 때 필요하다.


    3) 문맥 교환 (Context Switch)

     

    인터럽트는 운영체제가 CPU 코어를 현재 작업에서 뺏고, 커널 루틴을 실행할 수 있게 한다. 인터럽트가 발생하면 시스템은 인터럽트 처리가 끝난 후에 문맥을 복구할 수 있도록 현재 실행중인 프로세스의 현재 문맥을 저장할 필요가 있다. 이것은 결국엔 프로세스를 중단한 것을 재개하는 작업인데, 문맥은 프로세스의 PCB에 표현된다.

     

    이건 커널모드나 사용자모드나 CPU의 현재 상태를 저장하는 작업을 수행하고, 나중에 상태 복구 작업을 수행한다.

    CPU 코어를 다른 프로세스로 교환하려면 이전의 프로세스의 상태를 보관하고, 새로운 프로세스의 보관된 상태를 복구하는 작업이 필요하다. 이런 작업을 문맥 교환이라고 한다.

    문맥 교환이 일어나면 커널은 과거 프로세스의 문맥을 PCB에 저장하고, 실행이 스케줄된 새로운 프로세스의 저장된 문맥을 복구한다. 문맥 교환이 진행될 동안 시스템이 아무런 유용한 일을 하지 못하기 때문에 문맥 교환 시간은 순수 오버헤드이다.


     

     

     

    3. 프로세스에 대한 연산


    대부분 시스템 내의 프로세스는 병행 실행될 수 있고, 동적으로 생성 및 제거가 되어야 한다. 그러므로 운영체제는 프로세스 생성 및 종료를 위한 기법을 제공해야 한다.


    1) 프로세스 생성

     

    프로세스는 실행되는 동안 여러 개의 프로세스를 생성할 수 있다. 이때 원래 있던 프로세스는 부모 프로세스라고 하고, 생성된 프로세스들을 자식 프로세스라고 한다. 이 구조는 트리를 형성한다.

    현대 운영체제들은 정수로된 유일한 프로세스 식별자(pid)를 사용하여 프로세스를 구분한다. pid는 시스템의 각 프로세스에 고유한 값을 가지도록 할당된다. 그리고 이 식별자를 통해 커널이 유지하고 있는 프로세스의 다양한 속성에 접근하기 위한 찾아보기(index)로 사용된다.

     

    자식 프로세스는 임무를 달성하기 위한 자원을 운영체제나 부모 프로세스한테 얻을 수 있고, 부모 프로세스는 자원을 분할하여 자식 프로세스들에게 나누어 주거나, 자식 프로세스들이 같이 사용할 수 있게 한다.

     

    프로세스가 새로운 프로세스를 만들 때, 두 프로세스를 실행시키는데 두 가지 방법이 존재한다.

    • 부모는 자식과 병행하게 실행을 계속한다.
    • 부모는 일부 또는 모든 자식이 실행을 종료할 때까지 기다린다.

    2) 프로세스 종료

     

    프로세스가 실행을 끝내고, exit 시스템 콜을 사용하여 운영체제에 자신의 삭제를 요청하면 종료한다. 이때 프로세스는 부모 프로세스에 상태 값을 반환할 수 있으며, 모든 자원이 할당 해제가 되고 운영체제로 반납된다.

    프로세스를 종료하는 다른 방법으로는 한 프로세스가 적당한 시스템 콜을 통해 다른 프로세스의 종료를 유발할 수 있다. 이런 경우는 부모 프로세스만 할 수 있다. 이때 부모는 자식의 pid를 알고 있어야 한다.

     

    부모 프로세스가 자식 프로세스를 종료하는 이유

    • 자식이 자신에게 할당된 자원을 초과하여 사용할 때
    • 자식에게 할당된 태스크가 더 이상 필요가 없을 때
    • 부모가 exit을 하는데, 운영체제가 자식이 실행을 계속하는 것을 허용하지 않을 경우 (연쇄식 종료)

    3) 좀비와 고아

     

    프로세스가 종료하면 사용하던 자원은 운영체제가 되찾아간다고 위에서 언급을 하였다. 하지만 프로세스의 종료 상태가 저장되는 프로세스 테이블의 해당 항목은 부모 프로세스가 wait()를 호출할 때까지 남아있게 된다.

     

    좀비 프로세스종료되었지만 부모 프로세스가 아직 wait() 호출을 하지 않은 프로세스이다. 종료하게 되면 모든 프로세스가 좀비 상태가 되지만, 아주 짧은 시간만 해당 상태가 된다.

     

    고아 프로세스부모 프로세스가 wait()를 호출하는 대신 종료하는 경우에 남아있는 자식 프로세스이다.

    UNIX는 고아 프로세스의 새로운 부모 프로세스를 init 프로세스를 지정하여 문제를 해결하고, init 프로세스는 주기적으로 wait()를 호출한다. Linux는 init 대신 systemd로 대체되어, systemd 이외의 프로세스가 고아 프로세스를 관리하도록 하였다.


     

     

     

    4. 프로세스 간 통신


    운영체제 내에서 실행되는 병행 프로세스들은 독립적이거나 협력적인 프로세스들이다.

    • 독립적인 프로세스: 프로세스가 시스템에서 실행중인 다른 프로세스들과 데이터를 공유하지 않는 프로세스
    • 협력적인 프로세스: 프로세스가 시스템에서 실행중인 다른 프로세스들에 영향을 주거나 받는 프로세스

     

    프로세스 협력을 허용하는 환경을 제공하는 이유는 다음과 같다.

    • 정보 공유: 여러 프로그램들이 동일한 정보에 접근할 수도 있다.
    • 계산 가속화: 특정 태스크를 빨리 실행하려고 하면 서브 태스크로 나누어 병렬로 실행되게 한다.
    • 모듈성: 시스템 기능을 별도의 프로세스들 또는 스레드로 나누어 모듈 형식으로 시스템을 구성한다.

    협력적 프로세스들은 데이터를 교환할 수 있는 프로세스 간 통신(IPC) 기법이 필요하다.

    IPC에는 기본적으로 공유 메모리메시지 전달 두 가지 모델이 있다.

    • 공유 메모리: 공유 되는 메모리의 영역을 구축하여 프로세스들이 그 영역을 읽고 쓰며 정보를 교환한다.
    • 메시지 전달: 통신이 협력 프로세스들 사이에 교환되는 메시지를 통하여 이루어진다.

    운영체제에서는 공유 메모리와 메시지 전달 두 가지를 모두 구현한다.

     

    - 메시지 전달 모델

    장점)

    충돌을 회피할 필요가 없기 때문에 적응 양의 데이터를 교환하는데 유용하다.

    분산 시스템에서 공유 메모리보다 구현하기 쉽다.

    단점)

    메시지 전달 시스템은 통상 시스템 콜을 사용하여 구현되어 있어서 시간이 좀 소비되기 떄문에 공유 메모리 모델이 더 빠르다.

     

    - 공유 메모리 모델

    장점)

    공유 메모리 시스템에서는 공유 메모리를 구축할 때만 시스템 콜이 필요하다.

    메모리 구축이 되면 모든 접근은 일반적인 메모리 접근으로 취급되어 커널의 도움이 필요없다.


     

     

     

    5. 공유 메모리 시스템에서의 프로세스 간 통신


    위에서 언급했듯이 공유 메모리를 사용하는 프로세스 간 통신은 통신하는 프로세스들이 공유 메모리 영역을 구축해야 한다.

    공유 메모리 영역은 공유 메모리 세그먼트를 생성하는 프로세스의 주소 공간에 위치하고, 이 세그먼트는 통신할 다른 프로세스의 주소 공간에 추가해야 한다. 이전에 운영체제는 한 프로세스가 다른 프로세스의 메모리 접근을 접근하는 것을 금지한다고 하였는데, 공유 메모리는 이 제약 조건을 제거하는 것에 동의가 필요하다.


    생산자 - 소비자 문제

    생산자 프로세스는 정보를 생산하고, 소비자 프로세스는 정보를 소비한다.

    (ex. 서버를 생산자, 클라이언트를 소비자)

     

    생산자 - 소비자의 문제의 해결책중 하나는 공유 메모리를 사용하는 것이다. 생산자와 소비자가 병행으로 실행되려면, 생산자가 정보를 채워넣고, 소비자가 소모할 수 있는 버퍼가 반드시 사용 가능해야 한다. 그리고 당연하지만 해당 버퍼는 공유 메모리에 있어야 한다.

    여기서 버퍼는 무한 버퍼와 유한 버퍼가 사용되는데, 이때 무한/유한은 버퍼의 크기를 뜻한다.


     

     

     

    6. 메시지 전달 시스템에서의 프로세스 간 통신


    메시지 전달 방식은 공유 메모리처럼 주소 공간을 공유하지 않고, 프로세스들이 통신을 하여 그들의 동작을 동기화할 수 있도록 허용하는 기법을 제공한다. 이 방식은 통신하는 프로세스들이 네트워크로 연결되어 있는 분산 환경의 다른 컴퓨터들과 통신할 때 유용하다.

     

    메시지 전달 시스템은 send(message)와 receive(message)를 제공하는데, 이 메시지의 길이는 고정일 수도 있고 가변일 수도 있다. 그리고 이 send()와 receive() 연산을 논리적으로 구현하는 방법은 아래와 같다.

    • 직접 / 간접 통신 (명명, Naming)
    • 동기식 / 비동기식 통신 (동기화, Synchronization)
    • 자동 / 명시적 버퍼링 (버퍼링, buffering)

    1) 명명

    직접 통신

     

    통신을 원하는 프로세스들은 서로를 가리킬 방법이 있어야 한다.

    직접 통신에서는 각 프로세스는 통신의 수신자와 송신자를 명시해야 한다. 이렇게 서로 이름을 명시하는 것으로 대칭성을 보인다.

    • send(P, message) - 프로세스 P에 메시지를 전송한다.
    • receive(Q, message) - 프로세스 Q로부터 메시지를 수신한다.

    비대칭성을 사용한다면 수신자는 송신자의 이름을 제시하지 않고, 송신자만 수신자의 이름을 지명하면 된다.

    • send(P, message) - 메시지를 프로세스 P에 전달한다. (송신자)
    • receive(Q, message) - 임의의 프로세스로부터 메시지를 수신한다. (수신자)

     

    간접 통신

     

    간접 통신에서 메시지들은 메일박스, 포트로 송신되고, 그것으로부터 수신된다. 각 메일박스는 고유한 id를 가지고 있어서 다수의 메일박스를 통해 다른 프로세스들과 통신할 수 있다.

    • send(A, message) - 메시지를 메일박스 A로 송신한다.
    • receive(A, message) - 메시지를 메일박스 A로부터 수신한다.

    2) 동기화

    프로세스간 통신이 send와 receive의 호출에 의해 발생할 때, 메시지 전달은 봉쇄형(동기식, blocking), 비봉쇄형(비동기식, nonblocking) 방식으로 전달된다.

    • 봉쇄형 보내기: 송신하는 프로세스는 메시지가 수신 프로세스나 메일 박스에 의해 수신될 때까지 봉쇄된다.
    • 비봉쇄형 보내기: 송신하는 프로세스가 메시지를 보내고 작업을 재시작한다.
    • 봉쇄형 받기: 메시지가 이용 가능할 때까지 수신 프로세스가 봉쇄된다.
    • 비봉쇄형 받기: 송신하는 프로세스가 유효한 메시지나 널(null)을 받는다.

    여기서 송신자와 수신자가 모두 봉쇄형이라면 랑데부라고 부른다.


    3) 버퍼링

    프로세스에 의해 교환되는 메시지는 임시 큐에 들어가 있는데, 임시 큐를 구현하는 방식은 아래 세 가지가 있다.

    • 무용량: 큐의 최대 길이가 0이라서 송신자는 수신자가 메시지를 수신할 때까지 기다려야 한다.
    • 유한용량: 큐에 메시지를 전송한다. 이때 큐의 용량이 꽉차면 송신자는 큐에 공간이 생길 때까지 봉쇄된다.
    • 무한용량: 얼마든지 메시지를 큐에 전송할 수 있어서 송신자는 절대 봉쇄되지 않는다.

     

     

     

    8. 클라이언트 서버 환경에서 통신


    공유 메모리와 메시지 전달 기법은 프로세스간 통신뿐만 아니라, 클라이언트 서버 시스템의 통신에서도 사용할 수 있다. 여기서 소켓(socket)과 원격 프로시저 호출(RPC)에 대해 설명한다.


    1) 소켓

    소켓은 통신의 극점(endpoint)를 뜻한다. 두 프로세스가 네트워크상에서 통신을 하려면 양 프로세스마다 하나의 소켓이 필요하다. 이때 소켓은 IP 주소와 포트 번호 두 가지를 합쳐서 구별한다. (ex. 웹 서버는 HTTP 서버이므로 80번 포트를 사용)

     

    일반적으로 소켓은 클라이언트 - 서버 구조를 사용한다. 서버는 지정된 포트에 클라이언트 요청 메시지가 도착하기를 기다리게 되는데, 요청이 수신되면 서버는 클라이언트 소켓으로부터 연결 요청을 수락하여 연결이 완성된다.

     

    소켓을 이용한 통신은 분산된 프로세스 간에 널리 사용되고 효율적이지만, 스레드 간에 구조화되지 않은 바이트 스트림만을 통신하기 때문에 수준이 낮다. 그래서 구조화하여 해석하는 것이 서버 / 클라이언트의 책임이므로 더욱 높은 수준인 원격 프로시저 호출(RPC)를 사용한다.


    2) 원격 프로시저 호출(RPC)

    프로세스들이 서로 다른 시스템 위에서 돌아가기 때문에 원격 서비스를 제공하기 위해서는 메시지 기반 통신을 해야한다. RPC는 IPC와 많은 측면에서 유사하며 실제로 IPC 기반 위에서 만들어진다. 하지만 RPC 통신에서 전달되는 메시지는 구조화되어 있어서 데이터의 패킷 수준을 넘어서게 된다.

     

    RPC는 원격 프로세스간 통신 기능이 쉽게 구현 가능하여 분산 처리 환경에서 많이 사용하고 있으며, 최근에는 MSA(Micro Service Archtecture)에서 마이크로 서비스간에 사용되는 형식이기도 하다.


     

    반응형

    '공부 > 운영체제(OS)' 카테고리의 다른 글

    [공룡책] Ch.6 동기화 도구  (0) 2022.04.27
    [공룡책] Ch 5. CPU 스케줄링  (0) 2022.04.27
    [공룡책] Ch 4. 스레드와 병행성  (0) 2022.04.26
    [공룡책] Ch 2. 운영체제 구조  (0) 2022.04.19
    [공룡책] Ch 1. 서론  (0) 2022.04.12

    댓글

Designed by Tistory.