공룡책 Ch 3 요약 (원서를 읽고 요약하는 과정에서 잘못된 내용이 있을 수 있습니다.)

img


1. Process Concept

1-1. The Process

  • 프로세스는 메모리에 올라가 실행중인 프로그램을 의미함(program in execution)
  • 프로세스의 현재 실행에 관한 정보는 프로그램 카운터와 프로세서의 레지스터 구성으로 나타낼 수 있음
  • 프로세스의 메모리 구조는 크게 텍스트, 데이터, 힙&스택 영역으로 나눌 수 있음
    • 텍스트 영역 : 실행 가능한 코드 데이터가 저장됨
    • 데이터 영역: 정적/전역 변수가 저장됨
    • 힙 영역: 프로그램 런타임 시점에 동적으로 생성된 객체 등이 저장됨
    • 스택 영역: 함수 호출시 일시적으로 매개변수, 리턴 주소, 지역변수 등이 저장됨
image
  • 텍스트 및 데이터 영역의 크기는 고정되어 있으며, 스택과 힙 영역은 프로그램 실행 시점에 크기가 가변적임

    • 함수가 호출될 때마다 지역변수, 매개변수, 리턴주소 등을 포함한 activation record 가 스택에 추가(push)됨
    • 비슷하게 객체가 생성되면 힙 메모리 영역에 적재되며, 쓰임이 다한 후 메모리의 해제외 함께 힙 영역의 크기도 줄어듦
  • 프로세스 자체가 다른 코드의 실행 환경이 될 수도 있음

    • JVM 이 대표적인 예로, 가상머신이 로드된 자바 코드를 읽어들인 후 가상머신 자체의 명령어를 통해 코드 대신 행동을 취하는 방식임

      The JVM executes as a process that interprets the loaded Java code and takes actions(via native machine instructions) on be half that code.

    • 예를 들어 java Program 이라는 명령을 실행했을 때, java 는 JVM을 프로세스로서 실행시킨다는 것을 의미하며, Program 은 JVM 환경에서 해당 프로그램을 실행시킨다는 것을 의미함

1-2. Process State

image
  • 프로세스가 실행된다는 것은 프로세스의 상태(state)가 바뀐다는 것을 의미
  • 프로세스의 상태는 생성(New) , 실행(Running) , 대기(Waiting) , 준비(Ready) , 종료(Terminated) 의 5가지로 나뉨
    • 생성: 프로세스가 말 그대로 새롭게 생성된 상태를 의미
    • 실행 : 명령(Instructions)들이 실행되는 상태를 의미
    • 대기 : 프로세스가 입출력과 같은 특정 이벤트를 위해 대기하는 상태를 의미
    • 준비 : 프로세스가 실행을 위해 프로세서에 할당되는 것을 기다리는 상태를 의미
    • 종료 : 프로세스가 모든 실행을 종료한 상태를 의미
  • 프로세스 혹은 코어 하나당 하나의 프로세스가 실행 상태가 될 수 있음

1-3. Process Control Block

image
  • 각각의 프로세스는 운영체제 상에서 프로세스 제어 블록(PCB) 으로 대표되며, 여기에는 개별 프로세스의 구체적인 정보들이 포함되어 있음

  • 프로세스 상태(Process State) : 바로 위에서 언급한 생성,준비,실행,대기,종료 등의 상태 정보

  • 프로그램 카운터(Program Counter) : 카운터는 프로세스 내에서 다음에 수행되야 할 명령의 주소를 가리킴

  • 일반 레지스터 및 CPU 레지스터(Registers) : 레지스터 는 프로세서에서 자료를 보관하기 위한 장소로 사용되며, 위의 구조에서는 일반적인 레지스터와 함께 스택 포인터나 상태 코드 정보 등을 저장하는 레지스터가 포함되있음

    • 인터럽트 발생시 레지스터에 상태 정보가 제대로 저장되어 있어야, 나중에 프로세스가 재개될 때 정확한 상태로 수행될 수 있음
  • CPU 스케줄링 정보(CPU-scheduling information) : 스케줄링 관련 정보를 포함하며, 여기에는 프로세스 우선순위나 스케줄링 큐에서 가리킬 포인터 등이 있음

  • 메모리 관리 정보(Memory-management information) : 페이지 및 세그먼트 테이블이나 레지스터의 공간 제한 등의 프로세스 메모리 관련 정보를 포함하고 있음

  • 프로세스 계정 정보(Accounting information) : CPU 사용량, 시간 제한, 계정 혹은 프로세스 번호 등의 정보를 포함하고 있음

  • 입출력 상태 정보(I/O status information) : 프로세스에 할당된 입출력 디바이스 목록이나, 열린 파일 목록 등의 정보를 포함하고 있음


1-4. Threads

  • 스레드는 프로세스 내에서 개별 실행 흐름의 작은 단위를 의미함

  • 지금 까지 언급된 프로세스는 엄밀히 말해 싱글 스레드 기반의 수행(single thread of execution) 을 말하는 것이며, 오늘날 보통의 프로세스 컨셉은 프로세스가 다수의 스레드를 사용하는 것을 허용함

  • 이는 특히 멀티코어 환경에서 여러 스레드가 병렬적으로 작업을 수행하게 할 수 있다는 점에서 이점이 있음

  • 멀티 스레딩을 지원하는 환경에서 프로세스 제어 블록은 개별 스레드의 정보까지 포함하는 식으로 확장됨


2. Process Scheduling

  • 멀티 프로그래밍의 목적은 결국 CPU 자원의 활용을 최대한으로 높이고자 프로세스가 항상 실행될 수 있도록 하는 데 있음
  • 이를 위해 프로세스 스케줄러(process scheduler) 은 적절한 프로세스들을 선택하여 자원을 분배하여 실행될 수 있도록 함
  • 코어 하나 당 하나의 프로세스를 실행시킬 수 있기 때문에, 실행되야 할 프로세스의 수가 코어의 수를 초과하면 나머지 프로세스들은 사용 가능한 코어가 나올 때 까지 대기 상태로 있어야 하는데, 스케줄러가 여기서 자원 분배 과정을 조정하는 것
    • 여기서 메모리에 올라와 있는 프로세스의 수를 degree of multiprogramming 으로 표현함

2-1. Scheduling Queues

image
  • 일단 프로세스가 실행을 위해 메모리에 올라가면 준비 큐(ready queue)에 올라가서 CPU 코어와 같은 자원을 배분받기 위해 기다리게 됨
  • 준비 큐 외에 대기 큐(wait queue) 라는 것도 존재하는데, 이는 특정 이벤트의 발생을 위해 대기 상태로 있는 프로세스들이 보관된 큐임
  • 프로세스가 처음 생성되면 준비 큐로 들어가 실행 상태가 될 때 까지 기다리며, 자원을 배분받으면(dispatched) 실행 도중에 여러 이벤트를 발생시킴
  • 이후 입출력 요청과 같은 이벤트가 발생하여 프로세스는 대기 큐로 들어가 이벤트가 끝날 때 까지 대기할 수도 있고, 프로세스가 자식 프로세스(child process) 를 생성하여 자식 프로세스가 종료될 때까지 대기할 수도 있음
  • 혹은 실행중이던 프로세스가 코어에서 인터럽트 발생이나 시간 초과 등으로 인해 강제로 제거될 경우에는 다시 준비 큐로 들어갈 수도 있음

2-2. CPU Scheduling

  • CPU 스케줄러의 역할은 앞에서 언급했듯 준비 큐에 있는 프로세스 중 적절한 것들을 선택해서 자원을 분배하는 것임
  • 최대한 많은 프로세스들이 실행될 수 있도록, 스케줄러는 빈번히 실행되면서 특정 프로세스로부터 강제로 자원을 회수하여 다른 프로세스에 할당할 수도 있음
  • 문제는 메모리에 올라갈 수 있는 프로세스의 수는 한정되있기 때문에, 기존에 너무 많은 메모리가 실행 혹은 대기 상태로 있을 경우에 새로운 프로세스를 준비 큐에 올리지 못하는 상황이 발생할 수 있음
  • 이를 위해 몇몇 운영체제들은 메모리에서 특정 프로세스를 제거하여 멀티프로그래밍의 정도를 줄인 후 나중에 프로세스가 재개되야 할 시점에 이를 다시 메모리에 올릴 수 있도록 하는데, 이를 가리켜 swapping 이라고 표현함
    • 프로세스를 메모리로부터 제거하는 것은 swapping out 이라고 표현
  • 스와핑은 결국 메모리 공간이 부족하여 가용 공간을 확보해야 하는 상황에 유용한 것

2-3. Context Switch

image
  • 앞에서 언급했듯이 인터럽트가 발생하면 운영체제는 CPU 코어를 현재 작업을 수행하던 것에서 커널 실행을 위한 모드를 바꾸도록 변경함
  • 이 경우 시스템 상에서 변경된 코어에서 실행중이던 프로세스의 상황 정보(context of process)를 추후 프로세스의 재개를 위해 저장해둘 필요가 있는데, 이는 프로세스 제어 블록에 저장됨
    • 다시 말해 상황 정보에는 앞서 언급한 프로세스 제어 블록에 저장되는 상태 정보와 같은 것들이 포함되는 것
  • 이렇게 프로세스에서 자원을 회수하고 상황 정보를 잠시 프로세스 제어 블록에 저장한 후, 자원을 다시 다른 프로세스에 배분하여 실행될 수 있도록 하는 것을 Context Switching 이라고 표현함
  • 커널이 기존 프로세스의 상황 정보를 프로세스 제어 블록에 저장시킨 후, 새로 실행될 프로세스의 상황 정보를 로드해서 실행될 수 있도록 하는 것
  • 컨텍스트 스위칭이 수행되는 시간은 이것이 수행되는 동안 시스템이 다른 작업을 수행하지 않기 때문에 완전한 오버헤드로 간주됨

3. Operations on Processes

3-1. Process Creation

  • 프로세스가 새로운 프로세스를 생성할 수 있는데, 여기서 만드는 것이 부모 프로세스이고 만들어진 것이 자식 프로세스에 해당됨

    • 부모 프로세스가 자식 프로세스를 생성하는 과정은 위의 그림과 같이 결과적으로 트리 형태로 나타남
  • 유닉스 계열의 경우 보통 새로운 프로세스는 fork() 시스템 호출을 통해 생성된 후 실행되며, 실행이 끝나면 exec() 호출을 통해 실행이 끝났음을 알린 후 프로세스의 메모리 공간에 기존의 프로그램을 새로운 프로그램으로 대체하여 새로운 작업을 실행할 수 있도록 함

    (After a fork() system call, one of the two processes typically uses the exec() system call to replace the process’s memory space with a new program.)

  • 대부분의 운영체제는 여러 프로세스를 프로세스 식별자(Process Identifie, PID)를 통해 구분하는데 보통 정수값(integer value)을 가짐

    • 리눅스의 경우 시스템이 시장되면 systemd 프로세스가 여러 프로세를 생성하게 되는데, 이것이 곧 최초 부모 프로세스에 해당되며 pid 값이 1이 됨
  • 프로세스가 보통 자식 프로세스를 생성하게 되면, 자식 프로세스는 필요한 자원을 운영체제로부터 얻을 수도 있고 부모 프로세스의 자원을 일부 얻어 한정적으로 사용할 수도 있음

  • 자식 프로세스의 실행은 보통 부모 프로세스와 자식 프로세스들이 동시적으로 실행되거나(execute concurrently) , 자식 프로세스가 모두 종료될 때 까지 부모 프로세스가 대기하는 형태가 될 수도 있음

    • 만일 부모 프로세스를 대기시켜야 하는 경우라면 wait() 시스템 호출을 통해 자식 프로세스가 종료될 때까지 대기시킴
  • 또한, 자식 프로세스 생성으로 인한 주소 공간(Address space)은 부모 프로세스와 동일한 프로글매 및 데이터를 복제하여 적재하거나, 자식 프로세스가 새로운 프로그램을 메모리에 적재하여 실행하는 형태가 될 수도 있음

    • 부모 프로세스를 복제하는 것은 부모 프로세스와 자식 프로세스 간의 통신(communication)을 더욱 쉽게 한다는 이점이 있음

3-2. Process Termination

  • 위에서 언급했듯 프로세스는 마지막 구문(final statement)의 실행이 끝나면 종료 상태가 되면서 운영체제로 하여금 exit() 시스템 호출을 통해 스스로를 제거할 것을 요청함

  • 자식 프로세스가 종료되는 경우에는 정수 형태의 상태값을 wait() 을 통해 대기중인 부모 프로세스에게 반환하고 물리, 가상 메모리와 입출력 머퍼 등을 포함한 모든 프로세스 자원은 운영체제에 의해 메모리에서 해제됨

  • 프로세스의 종료는 윈도우의 TerminateProcess() 호출과 같이 부모 프로세스에 의해 종료되거나, 유저나 잘못 실행중인 프로세스가 임의로 다른 유저의 프로세스를 종료할 수도 있음

    • 부모 프로세스에 의해 종료될 경우에는 부모 프로세스가 반드시 자식 프로세스의 신원을 알아야 하기 때문에 자식 프로세스를 새롭게 생성할 때 pid와 같은 신원정보가 전달되야 함
  • 부모 프로세스가 자식 프로세스를 종료하는 것은 보통 다음과 같은 경우로 나눌 수 있음

    • 자식 프로세스가 허용된 자원의 범위를 초과하여 사용한 경우
    • 자식 프로세스에 할당된 작업이 더 이상 필요한 것이 아니라고 판단될 경우
    • 부모 프로세스가 종료되야 할 때, 운영체제가 자식 프로세스가 계속해서 실행되는 것을 허용하지 않는 경우(Cascading termination)
  • wait() 호출은 자식 프로세스가 종료되면서 남긴 상태값을 얻을 수 있도록 하는 매개변수(parameter)을 전달하는데, 결과적으로 종료된 자식 프로세스의 pid와 같은 신원을 리턴하도록 함으로써 부모 프로세스가 어떤 자식 프로세스가 종료되었는 지 알 수 있도록 함

    pid_t pid;
    int status;
    pid = wait(&status);
    
    • 만일 자식 프로세스의 실행이 끝났는데도 부모 프로세스가 wait() 을 호출하지 않은 경우에는 이를 zombie process 라고 부름
    • 유닉스 계열에서는 이런 식으로 자식 프로세스가 사실상 고아 상태로 방치될 경우 init 프로세스를 이러한 프로세스의 새로운 부모 프로세스가 되도록 하고 있음
    • init 프로세스가 주기적으로 wait() 을 호출하여 실행이 끝난 후 exit() 을 호출한 상태인 프로세스를 모아 이들의 pid와 프로세스 테이블 항목을 모두 할당 해제하는 것

4. Interprocess Communication

image
  • 보통 프로세스가 다른 프로세스와 그 어떤 데이터도 공유하지 않을 경우 두 프로세스가 서로 독립적이라고 표현함(independant)
  • 반대로 프로세스가 실행을 통해 서로에게 영향을 끼치는 경우에는 두 프로세스 간에 협력한다고 표현함(cooperating)
    • 프로세스가 서로 협력적인 데에는 정보 교환, 더욱 빠른 작업 수행, 시스템 모듈화 등의 이유가 있음
  • 협력적인 프로세스는 프로세스 간 통신 매커니즘(Interpersonal communication mechanism, IPC)을 필요로 하는데, 이는 간단히 말해 프로세스 간에 데이터를 서로 공유할 수 있는 매커니즘을 의미함
  • 이러한 IPC는 크게 공유 메모리 모델(Shared memory)과 메시지 전달 모델(Message passing)로 나눠볼 수 있음
  • 공유 메모리 모델은 운영체제에 의해 프로세스들이 특정 메모리 공간을 공유하는 형태 이며, 여기서 프로세스들은 해당 공간에 있는 데이터를 읽거나 씀으로써 서로 간에 데이터를 교환하게 됨
  • 메시지 전달 모델에서는 프로세스들이 서로 간에 메시지를 보내는 방식으로 커뮤니케이션이 이뤄짐
  • 비교적 작은 크기의 데이터를 교환하는 경우에는 충돌을 피해야 하는 경우가 적기 때문에 메시지 전달이 적합하며, 데이터의 크기가 비교적 크거나 빠른 시간 동안 커뮤니케이션이 이루어져야 하는 경우에는 공유 메모리 모델이 적합함
    • 공유 메모리 모델에서는 공유 영역에 대한 쓰기 결과가 즉시 보여지며, 커널의 간섭도 없기 때문

5. IPC in Shared-Memory Systems

  • 공유 메모리 모델 하에서 프로세스 간 통신은 우선적으로 공유 메모리 영역을 구축하는 것으로부터 시작됨
  • 보통 운영체제는 프로세스가 다른 프로세스의 메모리 영역에 접근하는 것을 허용하지 않지만, 공유 메모리 영역을 사용할 경우에는 두 프로세스 간에 이러한 제한을 없애게 됨
  • 따라서 공유 메모리 영역의 데이터 및 위치 등의 형식은 운영 체제가 아닌 프로세스에 의해 결정됨
  • 공유 메모리 영역에의 읽기 및 쓰기를 통해 데이터의 교환이 이루어지지만, 두 프로세스가 동시에 같은 곳에 쓰기를 실행하면 안됨 (동기화 문제)
  • 두 프로세스 간의 관계를 생산자-소비자 관계로 보는 생산자-소비자 문제 (consumer-producer problem) 는 버퍼 사용을 통해 이러한 동기화 문제 해결의 대표적인 매커니즘임
    • 데이터를 생산하는 쪽이 생산자, 소비하는 쪽이 소비자가 되며 보통 웹서비스에서 웹서버가 생산자, 클라이언트가 소비자로 흔히 비유됨
  • 생산자가 데이터를 생성하면 이를 공유 메모리 영역의 버퍼에 보관하게 되며, 소비자는 버퍼에서 데이터를 꺼내 소비하는 구조로 데이터의 생성이 완료된 후에 소비가 이루어지는 순서가 담보됨으로써 동기화가 가능해지는 것
  • 여기서 버퍼는 버퍼 사이즈의 제한 유무에 따라 비한정 버퍼( unbounded buffer ) 와 한정 버퍼( bounded buffer ) 의 두 가지로 나뉘며, 한정 버퍼를 사용할 경우 소비자와 생산자는 각각 버퍼가 비어있거나 가득차있을 때 대기해야 함
    • 현실 세계에서 버퍼의 사이즈는 당연히 제한적이기 때문에, 생산자-소비자 문제를 다른 말로 한정 버퍼 문제로 부르기도 함

6. IPC in Message-Passing Systems

  • 메시지 교환 모델에서는 공유 메모리 영역을 사용하지 않고 두 프로세스 간의 커뮤니케이션 과정에서의 동기화를 이룸
  • 이는 커뮤니케이션을 하는 두 프로세스가 네트워크로 연결된 서로 다른 두 컴퓨터에 존재하는 등의 분산 환경에서 동기화를 실현할 수 있다는 점에서 유용함
  • 교환되는 메시의 크기는 고정될 수도, 가변적일 수도 있으며 보통 send()receive() 의 두 연산의 실행을 사용함
  • 두 프로세스가 메시지를 교환하기 위해서는 우선적으로 커뮤니케이션 링크(communication link) 가 둘 사이에 반드시 존재해야 함

6-1. Naming

Direct Communication

  • 두 프로세스가 직접적으로 커뮤니케이션할 때, 기본적으로 이름과 같은 서로의 신원 정보를 같이 넘기도록 대칭적(symmetric)으로 구성됨
  • 여기서 커뮤니케이션 링크는 자동적으로 단 한개만 정확히 구축되며, 이로 인해 프로세스는 커뮤니케이션을 위해서 오로지 신원정보만 알면 됨
send(P, message) // 프로세스 P에게 전송
receive(Q, message) // 프로세스 Q로부터 수신
  • 여기서 조금 변형을 가할 경우, 송신자만 수신자 정보를 넘기고 수신자는 송신자 정보를 꼭 넘기지 않아도 되도록 비대칭적(asymmetric)으로 구성할 수도 있음
send(P, message) // 프로세스 P에게 전송
receive(id, message) // 일단 들어온 메시지를 무조건 수신(송신자는 모름)
  • 위의 코드에서 id 변수는 커뮤니케이션이 발생한 프로세스의 이름으로 설정되는 것

    The variable id is set to the name of the process with which communication has taken place

  • 직접 커뮤니케이션 방식은 프로세스의 신원 정보가 바뀌면 이와 연관된 커뮤니케이션상의 다른 프로세스들도 모두 수정해야 한다는 번거로움이 있음

Indirect Communication

  • 두 프로세스의 간접적인 커뮤니케이션은 메일박스(mailbox) 혹은 포트의 사용으로 설명할 수 있음
  • 메일박스들은 고유의 신원정보를 가지며, 프로세스는 서로 다른 메일박스의 수 만큼 다른 프로세스와 통신할 수 있음
    • 단, 두 프로세스가 서로 메일 박스를 공유하고 있다는 전제 하에서만 정상적인 커뮤니케이션이 일어나는 것이며 커뮤니케이션 링크 또한 공유되는 메일박스가 있어야 구축됨
send(A, message) // 메일박스 A로 전송
receive(A, message) // 메일박스 A로부터 수신
  • 단, 하나의 메일박스를 세 개 이상의 프로세스가 바라보고 있는 경우에는 메일박스로부터 메시지를 수신할 때 어떤 프로세스가 이를 가져갈 것인 지를 정해야 함

  • 메일박스의 소유권이 프로세스에 있을 경우에는 메시지를 받기만 하는 소유자와 보내기만 하는 사용자를 반드시 구분해야 하고, 운영체제에 소유권이 있을 경우에는 메일 박스는 그 자체로 독립적이며 그 어떤 프로세스에도 종속되지 않음

    • 이는 다시 말해, 메일 박스가 특정 프로세스에 종속되어 있을 경우 해당 프로세스가 종료되면 메일박스도 사라짐을 의미함
  • 보통은 프로세스가 메일박스를 생성할 경우, 해당 프로세스가 소유권을 가져가는 것이 기본이지만 필요에 따라 적절한 시스템 호출을 통해 다른 프로세스로 소유권을 넘길 수도 있음

    The process that creates a new mailbox is that mailbox’s owner by default. Initially, the owner is the only process that c an receive messages through this mailbox. However, the ownership and receiving privilege may be passed to other processes through appropriate system calls.

6-2. Synchronization

  • 보통은 send() 연산과 receive() 연산의 두 가지가 기본이지만, 이를 동기적(synchronous) 혹은 비동기적(asynchronous) 인 방식으로 세분화할 수도 있음
  • 동기적 송신(Blocking send) : 메시지가 완전히 수신되기 전까지 송신할 수 없음
  • 비동기적 송신(Nonblocking send) : 송신 과정이 일어나면서 다른 작업도 수행할 수 있음
  • 동기적 수신(Blocking receive) : 메시지를 온전히 수신할 수 있을 때까지 다른 작업을 차단하는 것
  • 비동기적 수신(Nonblocking receive) : 수신자는 메시지가 유효하지 않거나 null 이더라도 다른 작업을 차단하지 않음

6-3. Buffering

  • 커뮤니케이션의 직, 간접 여부에 관계없이 교환되는 메시지들은 모두 일시적인 큐에 저장되는데, 이를 버퍼라고 부름
  • 버퍼의 사이즈가 0일 경우에는 데이터를 보관할 수 없는 상태이기 때문에 송신자는 반드시 수신자가 메시지를 받을 때 까지 차단해야 함
  • 버퍼의 사이즈가 0보다 큰 값으로 한정된 경우에는 버퍼가 가득 차지 않는 이상 송신자는 계속 메시지를 보낼 수 있으며, 가득 찰 경우에는 데이터를 넣을 여유 공간이 확보될 때까지 차단해야 함
  • 버퍼의 사이즈가 무한한 경우에는 메시지가 끊임 없이 큐에 저장될 수 있기 때문에 송신자가 굳이 차단할 필요 없음

Reference