스케줄링 별 운영체제 종류

선점형 운영체제

 선점형 운영체제에서는 프로세스가 CPU를 할당받아서 실행 중인 상태에서 OS에 의해서 실행을 중지하고 CPU를 강제

 점유 할 수 있습니다. 즉 OS의 관리에 따라서 CPU 자원을 배분합니다.

비선점형 운영체제

 비선점형 운영체제에서는 프로세스가 CPU를 할당 받아서 실행 중인 상태에서는 해당 프로세스가 CPU를 자발적으로

 포기 할 때까지 점유합니다.

WINDOW에서 CPU 배분 방식

 윈도우는 선점형 운영체제로 라운드 로빈 방식으로 CPU를 배분합니다. OS는 퀀텀이라는 단위시간을 스레드에

 배분하여 활성화된 스레드들에게 공평하게 배분되도록 합니다.

 스레드가 OS에게 배분받은 단위시간을 다 사용하면 OS는 해당 스레드에서 CPU 사용을 중지 시키고 다른 스케줄이

 가능한 스레드에게 CPU를 배분하는데 이때 스레드의 컨텍스트 스위칭 작업이 발생합니다.

스레드 컨텍스트 스위칭

 스레드의 컨텍스트 스위칭이란 무엇이고 어떻게 진행 될까요?

 간단한 실생활에서 예를 든다면 스레드 컨텍스트 스위칭은 도서관의 학생이 책을 바꿔 읽는 것과 같습니다.

 학생은 CPU에 해당하고 책1과 책2는 스레드에 해당합니다.

컨텍스트 스위치 예제

 위의 예제에서는 학생은 책1의 120 Page의 15번째 줄까지 읽었고 이제 책2를 읽으려고 합니다. 

 학생은 책2을 읽기 전에 현재 읽고 있던 책1의 읽은 위치(컨텍스트)를 기록해둔 후 책2을 읽기 시작합니다.

 학생이 다시 책1을 읽게 되면 예전에 기록해 둔 책1의 읽은 위치(컨텍스트)를 확인하고 그 위치로 이동해서 

 책을 읽기 시작합니다. 이런식으로 진행되어야 여러권을 책을 돌아가며 읽을 수 있을 것입니다. 

 스레드와 CPU도 비슷하게 동작합니다. 자신에게 할당받은 시간을 다 사용한 스레드는

 현재 시점의 CPU 레지스터 정보를 컨텍스트 구조체 형태로 자신의 스레드 커널 오브젝트에 저장합니다. 

 시간이 지나 해당 스레드가 CPU를 할당 받으면 스레드 커널 오브젝트에서 CPU 레지스터 정보를 로드하여 이전에 

 수행하던 시점부터 수행을 시작합니다. 이러한 내부작업 때문에 너무 잦은 스레드 전환을 하게 되면 

 컨텍스트 스위치 비용때문에 문제가 될 수 있습니다. 

스레드의 정지와 수행 

 스레드 커널 오브젝트내에는 정지 카운트 값이 존재합니다. 스레드가 생성되면 초기값은 1로 셋팅됩니다. 

 이후 스레드가 초기화 작업이 끝난 후 생성 플레그값으로 CREATE_SUSPENDED가 전달되었는지 확인 후에

 해당 플레그가 전달 되지 않았다면 정지 카운트를 0으로, 플레그가 전달되면 정지 카운트값을 변경하지 않습니다.

 스레드의 정지 카운트 값이 0이 되면 스케줄링이 가능한 상태가 되며 CPU를 할당 받을 수 있습니다.

 정지 카운트 값이 0 보다 크면 스케줄링이 불가능한 상태이며 해당 스레드는 수행되지 않습니다. 

// 스레드의 정지 카운트를 감소시킵니다.
// 리턴값 : 해당 함수 수행전 스레드의 정지카운트
DWORD ResumeThread(HANDLE hThread);

// 스레드의 정지 카운트를 증가시킵니다.
// 리턴값 : 해당 함수 수행전 스레드의 정지카운트
DWORD SuspendThread(HANDLE hThread);

 스레드가 생성된 후에는 위의 ResumeThread와 SuspendThread 함수를 사용해서 정지 카운트를 변경할 수 있습니다.

 정지카운트는 최대 127(0x7f)까지 중첩될 수 있습니다.

 만약 정지 카운트가 4라면 ResumeThread함수가 4회 호출되어야 수행가능한 상태가 됩니다. 

 SuspendThread 함수를 호출 할때는 주의해야 하는데 수행중인 스레드가 어느 시점에 정지 될 지 예측 할 수 없기

 때문에 각종 문제를 예방할 수 있는 상태에서 사용 되어야 합니다. 

Sleep 함수

 어플리케이션 개발시 Sleep 함수는 자주 보게되는 친숙한 함수입니다. 

// 입력값은 밀리초 단위입니다. 
// 해당 스레드를 입력된 시간만큼 스케줄 불가능한 상태로 유지합니다.
void Sleep(DWORD dwMilliseconds)

 Sleep함수의 주요 동작 방식

  • Sleep을 호출하면 스레드는 자신에게 주어진 타임 슬라이스(퀀텀)를 포기합니다. 
  • 입력된 시간만큼 스레드를 스케줄 불가능한 상태로 유지합니다.
    • 만약 200 밀리초를 입력받으면 그 시간동안 스케줄이 불가능한 상태가 유지됩니다.
    • 하지만 주의할 점은 200 밀리초가 지났다고 무조건 스레드가 깨어나지 않습니다.
    • 시스템 상태에 따라서 지연된 시간동안 스케줄이 불가능하게 될 수도 있습니다. 
  • Sleep함수의 입력값을 0으로 전달하면 스레드의 남은 슬라이스를 포기하고 다른 스레드가 스케줄 되도록 합니다.
    • 이때 Sleep함수를 호출한 스레드의 우선순위와 같거나 높은 스레드들중 스케줄 가능한 스레드가 없다면 Sleep함수를 호출한 스레드가 다시 스케줄 될 수 있습니다. 

 Sleep 함수의 매개변수를 0으로 호출 할 때는 주의를 해야하는데 루프문을 통해서 Sleep(0)을 호출하게 되면

 CPU 리소스 자원이 로직 수행 없이도 낭비 될 수 있습니다. 

 (이렇게 루프문을 사용해야 할 때는 동기화 함수를 사용해서 스레드가 활성화 되야 할 때만 깨어나도록 작성해야

 효율적입니다.)

	while (true)
	{
    	// 수행 로직 
		... 
		Sleep(0);
	}

 

Sleep(0) 논리적 프로세서
CPU사용률

 위 그림은 루프에서 Sleep(0)이 수행중인 필자의 컴퓨터의 CPU 상태 및 프로세스 상태 창인데

 논리적 CPU하나가 점유당한 상태로 벗어 나지 못하고 있습니다. 

	ElapsedTimeOutput elapsed("SleepTime");
	DWORD dwCheck = 0;
	while (true)
	{
		dwCheck++;
		if (dwCheck == 100000) break;
		Sleep(0);
	}

	elapsed.milliSecPrint();
	std::cout << "dwCheck = " << dwCheck << std::endl;

수행횟수

 Sleep(0)인 경우 얼마나 자주 루프가 수행되는지 확인하기 위해서 위의 로직을 테스트로 수행해보았는데

 I7-4790인 CPU에서 22밀리초(10의 -3승 )에 10만 회가 수행되는 것을 확인하였고 1회당 수행시간을 환산하였더니

 220나노초(10의 -9승 초)에 1회씩 수행되는 것을 확인 하였습니다.

SwitchToThread함수

 이 함수를 호출하면 시스템은 일정시간 동안 CPU시간을 받지 못한 스레드가 있는지 확인 합니다.

 해당하는 스레드가 없다면 바로 반환되고 해당하는 스레드가 있다면 해당 스레드를 스케줄합니다.

 Sleep(0)과 유사하지만 다른점은 호출한 스레드보다 낮은 우선순위 스레들도 수행될 수 있습니다. 

'c++ > windows' 카테고리의 다른 글

[windows] 멀티 프로세서 환경에서의 CPU 캐시라인  (1) 2020.02.15
[window c++] InterLocked 함수들  (2) 2020.02.12
[window c++] 스레드  (0) 2020.02.04
[windows c++] 프로세스  (1) 2020.01.31
[Window c++] 커널 오브젝트  (0) 2020.01.20

+ Recent posts