Notice
Recent Posts
Recent Comments
05-18 01:37
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

Byeol Lo

3.3 Operations on Processes 본문

OS/OS Design

3.3 Operations on Processes

알 수 없는 사용자 2024. 4. 12. 02:30

 대부분 시스템에서 프로세스들은 동시에 실행될 수 있으며, 동적으로 생성 및 삭제가 될 수 있다. 따라서 이러한 시스템의 프로세스 생성, 종료를 위한 메커니즘을 제공해야 한다. 프로세스 생성에 관련된 메커니즘을 보자.

 

3.3.1 Process Creation

실행 과정 중에 프로세스는 여러 개의 새로운 프로세스를 생성할 수 있다. 생성되는 프로세스는 '부모 프로세스', 부모 프로세스에서 syscall로 생성되는 프로세스를 '자식 프로세스'라고 한다. 이러한 프로세스들은 프로세스 트리(tree)를 형성하게 된다. 대부분의 운영체제에서 프로세스는 일반적으로 정수 숫자인 고유한 프로세스 식별자(pid, process identifie)를 가지며, pid시스템 내의 각 프로세스에 대한 primary key가 되어 커널 내에서 프로세스의 다양한 속성에 액세스하는 데 사용될 수 있다.

 리눅스 운영체제의 전형적인 프로세스 트리를 보여주며, 각 프로세스의 이름과 해당 pid를 표시한다. systemd 프로세스(pid가 항상 1임), 그래서 모든 프로세스의 항상 최상위 프로세스, 부모 프로세스임)는 모든 사용자 프로세스의 루트 부모 프로세스(root parent process)로 작동하며, 시스템 부팅시 생성되는 첫 번째 사용자 프로세스이다. 시스템이 부팅되면 systemd 프로세스는 웹이나 프린트 서버, ssh 서버 등의 추가적인 서비스를 제공하는 자식 프로세스들을 더 생성한다. 위의 예시에서는 logind 프로세스에서 클라이언트가 로그인을 했을때, 이 프로세스가 bash 프로세스의 셸을 클라이언트에게 제공하고, 클라이언트가 다시 ps 프로세스와 vim 에디터를 생성한 것을 볼 수 있다.

 UNIX, Linux 시스템에서는 ps 명령어를 사용해 프로세스 목록을 얻을 수 있다.

ps -el

이 명령은 시스템에서 현재 활성화된 모든 프로세스에 대한 완전한 정보들을 나열하게 된다(linux에서는 pstree 명령어 또한 제공한다).

 일반적으로, 프로세스가 자식 프로세스를 생성할 때, 해당 자식 프로세스는 작업을 수행하는 데 필요한 특정 리소스가 필요하다. 자식 프로세스는 이러한 리소스를 운영 체제에서 직접 얻을 수 있거나, 부모 프로세스의 리소스 하위 집합으로 제한될 수 있다. 부모는 자식들 사이에서 리소스를 분할해야 할 수도 있고, 일부 리소스를 여러 자식 프로세스들 사이에 공유를 하게 할 수 있다. 자식 프로세스를 부모의 리소스 하위 집합으로 제한함으로써, 너무 많은 자식 프로세스를 생성하여 시스템을 과부하시키는 것을 방지할 수 있다. 다양한 물리적, 논리적 리소스를 제공하는 것 외에도, 부모 프로세스는 초기화 데이터(입력 데이터, 매개변수)를 자식 프로세스에게 전달할 수 있다.

 프로세스가 새로운 프로세스를 생성할 때 두 경우가 있다.

  1. 부모 프로세스가 자식 프로세스와 동시에 실행
  2. 부모 프로세스가 자식 프로세스 중 일부 또는 모두가 종료될 때까지 기다림

또 새로운 프로세스에 대한 두 가지 주소 공간을 가지는 경우도 있다.

  1. 자식 프로세스는 부모 프로세스의 복제본임(프로그램과 데이터가 부모와 동일함)
  2. 자식 프로세스에 새로운 프로그램이 로드

 이 차이점들을 설명하기 전에, UNIX 운영체제를 먼저 보자. UNIX에서 각 프로세스는 고유한 정수인 프로세스 식별자로 식별된다. 새 프로세스는 fork() 시스템 호출에 의해 생성이되고, 새 프로세스는 원래 프로세스의 주소 공간 복사본으로 구성(위의 새 프로세스에 대한 첫 번째 경우임)이 된다. 이 메커니즘을 통해 부모 프로세스는 자식 프로세스와 쉽게 통신할 수 있다. 두 프로세스(부모, 자식) 모두 fork() 이후의 명령어에서 실행이 계속되지만 fork()의 반환 코드는 새로운 프로세스의 경우에는 0이고, 자식의 (0이 아닌) 프로세스 식별자는 부모에게 반환된다. 즉, fork 시스템 호출은 새로운 프로세스를 생성하고 부모 프로세스와 자식 프로세스가 동시에 실행되는 것을 야기한다. 하지만, fork() 반환 값은 두 프로세스 간에 차이가 있는데, 부모 프로세스는 새로 생성된 자식 프로세스의 프로세스 식별자를 반환 받고, 자식 프로세스는 0을 반환한다.

 fork() 시스템 호출 후에, 두 프로세스(부모, 자식) 중 하나는 일반적으로 exec() 시스템 호출을 사용하여 프로세스의 메모리 공간을 새 프로그램으로 대체한다. 즉, exec() 시스템 호출은 프로그램 관련의 이진 파일을 메모리에 로드하고(exec() 시스템 호출을 포함한 프로그램의 메모리 이미지를 파괴함) 실행을 시작한다. 이 방식으로 두 프로세스는 통신한 다음 각자의 길로 갈 수 있다. 부모는 그 후에 추가적인 자식을 생성할 수 있고, 자식이 실행되는 동안 할 일이 없다면 자식의 종료까지 wait queue에서 자신을 제외시킬 wait() 시스템 호출을 발생시킬 수 있다. exec() 호출이 프로세스의 주소 공간을 새 프로그램으로 오버레이하므로 오류가 발생하지 않는 한 exec()는 제어를 반환하지 않는다.

 위는 UNIX 시스템 호출을 보여주는 예제이다. 동일한 프로그램의 복사본을 실행중인 두가지 다른 프로세스를 가지고 있으며, 유일한 차이점은 자식 프로세스의 변수 pid 값이 0이고, 부모의 경우에는 0보다 큰 정수 값이다(자식 프로세스의 pid 값이다). 자식 프로세스는 부모로부터 권한 및 스케줄링 속성 뿐만 아니라 일부 리소스를 상속한다. 그런 다음 자식 프로세스는 execlp() 시스템 호출을 사용하여 UNIX 명령어 /bin/ls 로 주소 공간을 오버레이한다(현재 실행중인 프로세스를 새로운 프로세스로 교체한다는 것, execlp()는 exec() 시스템 호출의 버전임). 부모는 wait() 시스템 호출로 자식 프로세스가 완료될 때까지 기다린다. 자식 프로세스가 완료되면(암묵적 혹은 명시적으로 exit()를 호출하여), 부모 프로세스는 wait() 호출에서 재개되어 exit() 시스템 호출을 사용하여 완료된다.

 자식이 exec()를 호출하지 않고 부모 프로세스의 복사본으로 계속 실행하는 것을 막을 수는 없다. 이 경우 부모와 자식은 동시에 실행되는 동일한 코드 명령을 실행하는 동시성 프로세스이다. 자식이 부모의 복사본이기 때문에 각 프로세스는 데이터 각각의 복사본을 갖는다.

  •  

 다른 방식으로 Windows에서의 프로세스 생성 메커니즘인데, 프로세스는 Windows API를 사용하여 CreateProcess() 함수로 생성되고, fork()와 유사하게 부모가 새로운 자식 프로세스를 생성한다. 하지만 fork()가 자식 프로세스에게 부모의 주소 공간을 상속하는 반면, CreateProcess()는 프로세스 생성시 자식 프로세스의 주소 공간에 지정된 프로그램을 로드해야 한다. 또한, fork()는 매개변수를 전달 받지 않지만, CreateProcess() 는 최소 열 개의 매개변수를 예상한다.

 CreateProcess() 함수에 전달되는 두 매개변수는 STARTUPINFOPROCESS_INFORMATION 구조체들의 인스턴스들이다. STARTUPINFO는 새 프로세스의 많은 속성을 지정하는데 사용되며, 창 크기 및 모양, 표준 입력 및 출력 파일의 핸들 등이 포함되게 된다. PROCESS_INFORMATION 구조체에는 새로 생성된 프로세스 및 해당 스레드의 핸들과 식별자가 포함되어 있다. CreateProcess()를 진행하기 전에 각 구조체에 메모리를 할당하기 위해 ZeroMemory() 함수를 호출한다.

 CreateProcess()에 전달되는 처음 두 프라마터들은 응용 프로그램 이름과 명령행 매개변수이다. 응용 프로그램 이름이 NULL인 경우, 명령행 매개변수가 로드할 응용 프로그램을 지정한다. 위의 예에서는 Windows의 mspaint.exe 응용프로그램을 로드하게 된다. 이 두 초기 매개변수 이외에도 프로세스 및 스레드 핸들을 상속하는 데 사용되는 기본 매개변수와 생성 플래그가 없음을 지정하는 매개변수를 사용한다. 또한 부모의 기존 환경 블록과 시작 디렉터리를 사용한다. 마지막으로, 프로그램의 시작 부분에서 생성된 STARTUPINFO 및 PROCESS_INFORMATION 구조체에 대한 두 개의 포인터를 제공한다. 위 그림에서 부모 프로세스는 자식 프로세스가 완료될 때까지 wait() 시스템 호출을 사용하여 대기한다. Windows에서 이와 같은 수행하는 것은 WaitForSingleObject() 함수이며, 이 함수는 자식 프로세스의 핸들인 pi.hProcess를 전달받고 해당 프로세스가 완료될 때까지 기다린다. 자식 프로세스가 종료되면 부모 프로세스에서 WaitForSingleObject() 함수에서 제어가 반환된다.

 

3.3.2 Process Termination

 프로세스는 최종 명령문을 실행하고 exit() 시스템 호출을 사용하여 운영체제에게 삭제를 요청할 때 종료된다. 그 지점에서 프로세스는 종종 상태 값(일반적으로 정수)를 기다리는 부모 프로세스에게 반환할 수 있다(wait() 호출을 통해서). 프로세스의 모든 리소스는 운영체제에 의해 할당되고 회수 된다.

 종료는 다른 상황에서도 발생할 수 있다. 프로세스는 적절한 시스템 호출을 통해 다른 프로세스의 종료를 유도할 수 있다. 일반적으로 이러한 시스템 호출은 종료될 프로세스의 부모만이 호출할 수 있다. 그렇지 않으면 사용자나 잘못된 응용 프로그램이 다른 사용자의 프로세스를 임의로 종료시킬 수 있다. 부모는 자식 프로세스를 종료하려면 그들의 식별자를 알아야 한다. 따라서 하나의 프로세스가 새로운 프로세스를 생성할 때, 새로 생성된 프로세스의 식별자는 부모에게 전달된다.

 부모는 다양한 이유로 자식의 실행을 종료할 수 있다.

  • 자식이 할당 받은 일부 리소스의 사용량을 초과하거나
  • 자식에게 할당된 작업이 더 이상 필요하지 않거나
  • 부모가 종료되고 운영체제에서 부모가 종료되면 자식이 계속 실행되지 않도록 막거나

 일부 시스템에서는 부모가 종료되면 자식이 존재할 수 없고, 프로세스가 종료되면 해당 모든 자식 프로세스도 종료되어야 할 것이다. 이러한 현상을 연쇄 종료(cascading termination) 이라고 한다.

프로세스 실행 및 종료를 설명하기 위해 Linux, UNIX 시스템에서는 exit()를 호출하여 프로세스를 종료할 수 있다. 이때 종료 상태를 매개변수로 제공하게 된다.

exit(1);

 사실 정상 종료의 경우는 위와 같이 입력되거나 C 런타임 라이브러리가 기본적으로 exit()를 호출한다. 부모 프로세스는 wait()를 사용하여 자식 프로세스의 종료를 기다릴 수 있다. wait() 시스템 호출은 매개변수로 자식의 종료 상태를 얻을 수 있도록 해야한다. 이 시스템 호출은 또한 종료된 자식의 프로세스 식별자를 반환하여 부모가 어떤 자식이 종료되었는지 알 수 있게 한다.

pid_t pid;
int status;
pid = wait (&status); // 자식 프로세스의 종료상태를 저장하는 status 변수

  프로세스가 종료되면 운영체제에 의해 해당 리소스가 할당 해제(deallocated) 된다. 하지만 프로세스 테이블에는 프로세스의 종료 상태가 포함되기 때문에 부모가 wait()를 호출할 때까지 프로세스 테이블에 해당 항목이 유지된다. 부모가 wait()를 호출하지 않은 상태에서 종료된 프로세스zombie process 라고 하며, 모든 프로세스는 종료될 때 이 상태로 전환되지만, 일반적으로 그들은 잠깐 동안만 zombie로 존재한다. 즉, wait()를 통해 부모 프로세스가 자식 프로세스의 종료 상태를 확인하기 위해 사용되는 시스템 호출이다. 하지만 부모 프로세스가 자식 프로세스가 종료되었는지 확인하고 종료상태를 수집하려면 wait()를 호출해야 하고, 이때 부모가 wait()를 호출하면 좀비 프로세스의 프로세스 식별자와 프로세스 테이블 항목이 해제된다.

 이제 부모가 wait()를 호출하지 않고 종료되고, 자식 프로세스가 orphans가 되는 상황을 생각해보자. 기존 UNIX 시스템은 이러한 시나리오를 다루기 위해 운영체제에서 고아 프로세스에게 새로운 부모로 init 프로세스(init은 프로세스 계층 중 최상위 루트를 말함)를 지정한다. init 프로세스는 주기적으로 wait()를 호출하여 고아 프로세스의 종료 상태를 수집하고 고아 프로세스의 식별자와 프로세스 테이블 항목을 해제한다.

 대부분의 리눅스 시스템은 init을 systemd로 대체했지만, 후자 프로세스도 동일한 역할을 수행할 수 있다. 리눅스는 systemd 이외의 프로세스도 고아 프로세스를 상속하고 그 종료를 관리할 수 있도록 허용한다.

'OS > OS Design' 카테고리의 다른 글

3.5 IPC in Shared-Memory Systems  (0) 2024.04.12
3.4 Interprocess Communication  (1) 2024.04.12
3.2 Process Scheduling  (0) 2024.04.10
3.1 Process Concept  (0) 2024.04.09
2.8 Operating-System Structure  (0) 2024.04.09
Comments