02 프로세스의 관리
본 설명은 책 “그림으로 배우는 구조와 원리 운영체제 개정 3판”를 읽으며 제 나름대로 해석하고 정리해 보았습니다.😉
공룡책 버전의 설명이 궁금하다면 ? (https://hoyeonkim795.github.io/posts/프로세스의이해/)
1. 프로세스의 구조
운영체제는 프로세스에서 생성, 종료, 제거, 중단, 재시작, 우선순위 변경, 대기, 문맥 교환 등 다양한 작업을 수행할 수 있다. 먼저 해당 작업들을 알아가기 전에 프로세스의 구조에 대해 살펴보자. 프로세스는 실행 중에 프로세스 생성 시스템 호출을 이용하여 새로운 프로세스를 생성할 수 있고 이는 프로세스 간에 부모 - 자식 관계를 갖게 된다. 따라서 말그대로 프로세스를 생성하는 프로세스는 부모 프로세스이고 생성되는 프로세스는 자식 프로세스 혹은 서브 프로세스라고 명칭한다. 이렇게 부모 프로세스는 자식 프로세스를 생성하는 과정을 반복하고 계층 구조를 형성하게 된다.
2. 프로세스의 생성
이렇게 운영체제나 응용 프로그램에서 요청을 받아 프로세스를 생성하면 운영체제는 해당 프로세스에서 프로세스 제어 블록을 만들어 주소 공간을 할당한다. (프로세스 제어 블록이 궁금하다면? https://hoyeonkim795.github.io/posts/pcb/ )
여기서 중요한 것은 일괄 처리 환경은 ready queue 에 작업이 도착할때 프로세스를 생성하고, 대화형 환경에서는 새로운 사용자가 로그온할 때 프로세스를 생성한다.
프로세스의 생성을 세부적으로 더 살펴본다면, 아래와 같은 순서로 작업을 진행한다.
- 새로운 프로세스에 프로세스 식별자를 할당한다.
- 프로세스의 모든 구성 요소를 포함할 수 있는 주소 공간과 프로세스 제어 블록 공간을 할당한다.
- 프로세스 제어 블록을 초기화 한다. 프로세스 상태, 프로그램 카운터 등 초기화, 자원 요청, 프로세스 제어 정보(우선순위) 등을 포함한다.
- 링크를 건다. (해당 큐에 삽입한다.)
이런 일련의 작업을 거쳐 프로세스가 생성이 된다. 그리고 이렇게 생성된 프로세스들이 작업을 수행하기 위해서는 프로세서 점유 시간, 메모리, 파일, 입출력 장치 등 자원이 필요하다. 자식 프로세스 같은 경우 필요한 자원을 직접 얻는 경우도 있고 부모 프로세스의 자원을 일부 사용하기도 한다. 그런데 특정 프로세스가 자식 프로세스를 너무 많이 생성하게 되어 시스템에 부담을 주는 것을 막기 위해서 부모 프로세스는 자식 프로세스가 사용하는 자원을 제한하기도 한다.
일단, 프로세스 생성에서 추가로 알아야 할 개념은 fork 이다.
fork 는 일단 시스템 호출 명령어이며 프로세스가 자기 자신을 복제하는 동작이다. 이는 일반적으로 시스템 호출의 일종이며, 커널 안에서 구현된다. 포크는 유닉스 계열 운영 체제에서 프로세스를 만드는 주된 방식이다. 복제의 대상을 부모 프로세스라 하고 그 결과물을 자식 프로세스라 한다.
그래서, 첫 번째는 fork 명령어로 부모와 동일한 자식을 복제하는 것이고 두 번째 경우는 fork 명령어를 호출한 후 exec 명령어를 연달아 호출하여 자식 프로세스의 주소 공간을 별도 프로그램 주소 공간으로 덮어쓰는 것이다.
그리고 프로세스가 새로운 프로세스를 생성할 때 다음 두 가지 실행이 발생할 수 있다.
- 부모 프로세스와 자식 프로세스를 동시에 실행한다.
- 부모 프로세스는 자식 프로세스를 모두 종료할 때까지 기다린다.
3. 프로세스의 종료
이제 프로세스가 제 할일을 마치고 모든 명령을 실행하면 종료하여 운영체제에게 프로세스의 삭제를 요청한다. 일괄 처리 환경에서는 작업의 종료 의미로 인터럽트를 발생하거나 시스템 호출로 중단 명령을 전달하여 프로세스를 종료한다. 대화형 환경에서는 사용자가 로그오프 혹은 터미널을 닫아 프로세스를 종료하고 이외에도 오류로 프로세스를 종료하기도 한다. 또 abort 명령어로도 프로세스를 종료할 수 있따. 그럼 아까 말했던, 부모-자식 관계에 있는 프로세스들은 어떠한 방식으로 프로세스를 종료하는지 살펴보자.
일단 먼저, 자식 프로세스는 부모가 아닌 다른 프로세스가 임의로 해당 프로세스를 중단할 수 없다. 그리고 부모 프로세스는 다음 상황에서 자식 프로세스를 종료할 수 있다.
- 자식 프로세스가 할당된 자원을 초과하여 자원을 사용할 때
- 자식 프로세스에 할당한 작업이 더 이상 없을 때
그리고, 프로세스는 다음과 같은 예에서 프로세스를 종료한다. (일부일 뿐 다양함)
- 정상 종료 : 프로세스가 운영체제의 서비스를 호출할때 (exit)
- 시간 초과: 프로세스가 명시된 시간을 초과하여 실행하거나 어떤 이벤트를 기다릴때
- 실패 : 파일 검색 실패, 입출력이 명시된 회수 초과해서 실패할 때
- 산술 오류, 보호 오류, 데이터 오류 등
- 메모리 부족, 액세스 위반 등
4. 프로세스의 제거
프로세스 제거는 종료와 다르게 제거할 경우 사용하던 자원을 시스템에게 돌려주고 시스템 리스트나 테이블에서 사라져 프로세스 제어 블록을 회수하지만 프로그램은 여전히 디스크에 저장한다.
5. 프로세스의 중단과 재시작
위의 프로세스의 준비, 실행, 대기 상태만으로는 유휴 상태인 프로세스가 많아지는 문제가 생길 수 있다. 시스템의 유휴시간 문제는 프로세스 중단 상태를 이용하여 해결할 수 있다. 여기서 유휴시간이 무엇인지 잠깐 짚자면, 유휴시간 이란 어떠한 프로그램에 의해서도 사용되지 않는 상태를 말한다. (유휴시간 : https://ko.wikipedia.org/wiki/유휴_(CPU))
운영체제는 새로운 프로세스를 생성하여 실행하거나 실행 중인 프로세스를 중단했다가 다시 실행하여 사용할 수 있다. 그리고 프로세스를 중단했다가 다시 실행 하는 방법을 이용할 경우 시스템 전체 부하를 증가시키지 않고 서비스를 제공할 수 있다. 왜냐하면 실행에서 대기가 아닌 중단 상태를 추가할 경우 해당 이벤트가 발생할때 즉시 실행 상태로 바꿀 수 있기 때문이다.
즉, 프로세스 중단과 재시작은 시스템 부하를 조절하는 데 상당히 중요하고, 다음과 같은 상황에서 많이 발생한다.
- 시스템에 장애가 발생할 경우 실행 중인 프로세스를 잠시 중단하고 시스템 기능이 회복될때 재시작한다.
- 프로세스에 의심이가는 부분이 있다면 실행 중인 프로세스를 중단한 상태에서 확인한 후 재시작 혹은 종료한다.
- 처리할 작업이 많아 시스템에 부하가 늘면 프로세스 몇개를 중단했다가 다시 시스템이 정상적으로 돌아올 경우 재시작한다.
사실 대다수 시스템은 프로세스를 실행하기 전에 자원을 할당하고 실행하지만, 다중 프로그래밍 환경에서는 효율성을 이유로 자원을 동적으로 할당한다. 이때, 자원을 할당 반드려고 하는 상태가 대기이고 할당받은 자원을 기다리는 상태가 중단이다. 따라서 이 두개의 상태를 추가하게 되면 아래와 같은 모습이다.
6. 프로세스의 우선순위 변경
프로세스 스케줄러는 PCB에 있는 우선순위를 이용하여 준비 리스트의 프로세스를 처리한다. 준비 리스트의 프로세스는 프로세서 중심 프로세스와 입출력 중심 프로세스가 있다. 입출력 중심 프로세스는 속도가 느리면서 빠른 응답을 요구하는 프로세스에 높은 우선순위를 부여하고, 속도가 빠른 디스크 입출력 프로세스에는 낮은 우선순위를 부여한다. 우선순위가 낮은 프로세스에는 많은 시간을 높은 프로세스에는 적은 시간을 할당한다.
7. 프로세스의 문맥 교환
보통 인터럽트가 발생하면 인터럽트 처리 루틴으로 제어가 넘어가고 시스템 관리와 관련된 작업을 한 뒤 인터럽트 유형에 따라 관련 루틴으로 분기한다.
- 입출력 인터럽트 : 입출력 동작이 발생했음을 확인하고 이벤트를 기다리는 프로세스를 준비 상태로 바꾼 뒤 실행할 프로세스를 결정
- 클록 인터럽트 : 현재 실행 중인 프로세스의 할당 시간을 조사해서 실행 중인 프로세스를 준비 상태로 바꾸고, 다른 프로세스를 실행 상태로 바꾼다.
이렇게 실행 중인 프로세스에 인터럽트가 발생하면 OS가 다른 프로세스를 실행 상태로 바꾸고 제어를 넘겨주어 프로세스 문맥 교환이 일어난다. 결국, 이전 프로세스의 상태 레지스터 내용을 보관하고 다른 프로세스의 레지스터를 적재하여 프로세스를 교환하는데 이러한 일련의 과정들을 문맥 교환이라고 하는 것이다.
이렇게 문맥교환이 일어날 때는 시간 비용이 들어가는 오버헤드이고, 이 오버헤드는 메모리 속도, 레지스터 수, 특수 명령어의 유무에 따라 시스템마다 다르다. 따라서 운영체제를 설계할때 이러한 불필요한 문맥교환을 줄이는 것을