[UNIX] Process Scheduling 간략히 살펴보기

process scheduling은 커널의 서브시스템으로써 유한한 리소스인 프로세서의 시간을 시스템 내의 프로세스에 나눠준다. 다시 말해 프로세스 스케줄러는 다음에 실행할 프로세스를 선택하기 위한 커널의 구성 요소다. 어떤 프로세스가 언제 실행되어야 하는지 결정하는 과정에서 스케줄러는 여러 프로세스가 동시에 매끄럽게 동작하고 있다는 착각을 심어줌과 동시에 프로세서를 최대한으로 활용해야 하는 책임을 지고 있다.

실행 가능한 프로세스는 블록되지 않은 프로세스로, 블록된 프로세스는 자고 있거나 커널로부터 입출력을 기다리고 있는 프로세스다. 어떤 프로세스가 사용자와 상호 작용을 하거나, 파일을 자주 읽고 쓴다거나, 혹은 요청한 리소스가 사용 간으할 때까지 오랫동안 기다려야 하는 네트워크 이벤트를 처리한다면 그 기간 동안에는 실행 가능한 상태가 아니다. 실행 가능한 프로세스가 하나만 주어진다면 그냥 그 프로세스를 실행하기만 하면 되므로 스케줄러의 임무는 그리 중요하지 않을 것이다. 하지만 프로세서의 개수보다 실행 가능한 프로세스가 더 많이 존재할 때는 스케줄러가 필요하다. 이 경우, 다른 프로세스가 차례를 기다리는 동안 몇몇 프로세스만 실행될 것이다. 어떤 프로세스가 언제 실행되고, 또 얼마나 오랫동안 실행될지를 결정하는 것은 스케줄러의 기본 책임이다.

멀리태스킹 운영체제는 선점형과 비선점형, 이렇게 두 가지로 나눌 수 있다. 리눅스는 특정 프로세스가 언제 실행을 멈추고 다른 프로세스가 실행을 재개할지를 스케줄러가 결정하는 선점형 멀티태스킹을 구현한다. 다른 프로세스를 위해 실행 중인 프로세스를 멈추는 행위를 선점이라고 한다. 스케줄러가 선점하기 전까지 프로세스에 허락된 실행 시간을 프로세스 타임 슬라이스라고 하는데 스케줄러가 프로세서의 시간의 '조각'을 할당하기 때문이다.

반대로, 비선점형 멀티태스킹에서는 프로세스가 스스로 실행을 멈추기 전까지 계속 실행된다. 이때 프로세스가 자발적으로 실행을 잠시 쉬는 것을 양보(yield)라고 한다. 이상적으로는 프로세스가 자주 양보하면 좋겠지만 운영체제에서 이를 강제할 수는 없다. 프로그램이 욕심이 많거나 문제가 생긴 채로 너무 오랫동안 실행되고 있으면 멀티태스킹이 유지되지 못하거나 심지어는 무기한으로 계속 실행되면 전체 시스템을 다운시킬 수도 있다. 비선점형 멀티태스킹 방식의 이런 단점 때문에 최신 운영체제는 거의 선점형 멀티태스킹 방식을 사용하고 있으며 리눅스도 예외는 아니다.

리눅스의 경우 현재 스케줄러는 커널 2.6.23 버전부터 등장한 스케줄러로, CFS(Completely Fair Scheduler)라고 한다. 이 스케줄러의 이름은 리소스에 대한 동등한 접근을 강제하는 스케줄러의 알고리즘에서 따온 것이다. CFS스케줄러는 이전에 사용되는 O(1)스케줄러를 비롯, 다른 유닉스의 스케줄러와는 철저하게 다르다.
O(1) -> Big O 표기법이다. 간단히 말하면 스케줄러가 입력의 크기에 상관없이 일정한 시간 안에 작업을 수행한다는 뜻이다.
O(1) 스케줄러는 대화형 프로세스가 없는 커다란 서버 작업에는 이상적이었지만, 대화형 어플리케이션이 그 존재 이유가 되는 데스크톱 시스템에서는 평균 이하의 성능을 보여줌

선점형 멀티태스킹(Preemptive multitasking)에서는 프로세스 실행을 언제 중단하고 다른 프로세스를 실행할지를 스케줄러가 결정한다. 실행중인 프로세스를 강제로 중지시키는 작업을 선점(Preemption)이라고 한다. 프로세스가 선점되기 전까지 프로세스에 주어지는 시간은 보통 미리 정해져 있으며, 이를 프로세스의 타임 슬라이스(timeslice)라고 한다.
Timeslice는 프로세서 동작 시간을 실행 가능한 프로세스에게 잘라서 나눠준다는 뜻이다.

< 프로세스 우선순위 >
프로세스에 할당되는 CPU 시간은 nice값에 의해서 가중치가 적용된다. 유닉스는 전통적으로 이런 우선순위를 nice값이라고 불렀는데, 프로세스의 우선순위를 낮춰 다른 프로세스가 CPU시간을 좀 더 사용할 수 있도록 '친절하게 대한다 be nice'라는 의미가 있다.

nice값은 경계 값을 포함해서 -20부터 19까지 쓸 수 있고 기본값은 0이다. nice값이 적을수록 우선순위가 높아지며 타임 슬라이스도 커진다. 

< nice() >
#include
int nice(int inc);
root만이 inc에 음수를 넘겨서 nice값을 감소시켜 우선순위를 높일 수 있다. 따라서 root가 아닌 프로세스는 nice값을 증가시켜 우선순위를 낮추는 작업만 가능하다.

에러가 발생하면 -1을 반환한다. 하지만 nice()는 새로운 nice값을 반환하므로 -1은 성공했을 때의 정상적인 반환값이기도 하다. 이 둘의 차이를 구분하기 위해 nice()를 호출하기 전에 다음 예제처럼 errno를 0으로 초기화한 다음에 이 값을 검사하자.
int ret;
errno = 0;
ret = nice(10); /* nice값을 10만큼 증가 */
if(ret == -1 && errno != 0)
     perror("nice");
else
     printf("nice value is now %d\n", ret);


inc에 0을 넘겨서 쉽게 현재 nice값을 얻을 수 있다. 

댓글

가장 많이 본 글