windows scheduling

2022. 1. 19. 01:46게임서버/멀티쓰레드 이론

320x100

안녕하세요 대학생 개발자입니다.

이번 글에서는 저번 글에 이어서 윈도우즈의 스케쥴링 방식에 대해서 얘기를 더 해볼겁니다.

 

우선 스케쥴링이란게 뭔지 알아야될겁니다.

스케쥴링이란건... 뭐 간단히 따지면 어떤 쓰레드를 실행할 것이냐에 대한 부분인거겠죠

 

스케쥴링의 주체가 누구인지를 먼저 알아야 될 것 같은데요...

사실 윈도우가 스케쥴링 한다가 맞는말이겠지만... 좀더 구체적으로 어떤 계기로 스케쥴링이 발생하느냐 그리고 누가 직접 스케쥴링을 하느냐 에 대한 부분을 따지고 든다면 cpu자체가 스케쥴링의 주체이자 대상이 될겁니다.

 

무슨말인지 잘 이해가 안되실 수도있습니다.

간단히 설명하면 os는 코드에 불과합니다 결국 코드라는건 하드웨어 의존없이 혼자서 작동될 수 없는 것인데요...

결국 os의 스케쥴링이란것도 시스템 타이머라는 녀석의 인터럽트에 의해서 실행됩니다.

결국 cpu가 언제 스케쥴링을 해야되는지 알려주는 것이고 os는 스케쥴링을 하는 상황을 인터럽트로 다시 전파를 하게되는거죠.

 

그리고 인터럽트는 그 어떤 동작보다 우선순위가 높습니다.

A라는 함수를 실행중인 쓰레드가 돌고있던 중 인터럽트가 걸리게 된다면...

그 즉시 인터럽트를 처리하러 달려갑니다. 물론 이 과정은 컨텍스트 스위칭이 아닙니다. 이유는 좀있다가 설명 드리겠습니다.

음... 그 즉시라고 하니까 어떻게 해석하느냐에 따라서 틀렸다라고 판단하실 부분이 존재합니다.

그 즉시가 아니라 cpu에서 행동하는 최소 단위 행동이 끝난 직후 바로 달려갑니다 그러니까 MOV와 MOV가 있다면 그 사이에 들어갈 수 있는거죠. 그리고 나서 인터럽트 이후에 내 로직이 다시 돌아가는 식으로 되어있습니다.

그럼 그 쓰레드의 입장을 한번 살펴보도록 하죠...

난 열심히 일하고 있었는데 갑자기 인터럽트가 와서 내가 일할시간을 뺏겨버렸습니다... 너무 억울하잖아요 내가 할당받은 quantum time이 20ms인데 그중 5ms가 인터럽트로 뺏기게 된다면 나는 15ms밖에 못쓰게되는건데...(뭐 실제로 인터럽트가 이렇게 많은 시간을 잡아먹지는 않습니다.)

물론 인터럽트가 걸리게 되면 os에서 당연히 quantum time을 보정해주고 그 만큼 더 돌수있습니다.(쓰레드입장에서 봤을땐 더 돈다고 하기도 그렇네요 ㅋㅋ)

 

그리고 인터럽트에 대한 얘기를 잠시 하고 넘어가야 되겠는데요...

인터럽트가 아까 컨텍스트 스위칭이 아니라고 말씀드렸습니다.

그 이유를 말씀드리자면... 우선 컨텍스트 스위칭은 cpu에 있는 TCB(쓰레드 컨텍스트 블럭이라고 부릅니다) 들을 세팅해주는 과정이라고 했습니다. 하지만 인터럽트는 따로 이런 것들이 존재하지 않습니다. 그저 IRQ라는 곳에 인터럽트 번호가 매겨져 있고 그에 해당하는 핸들러가 달려있을 뿐입니다.

 

음... 잠시 다른길로 들렀다가 가보자면 예전에 도스 시절에는 이 IRQ도 그렇고 핸들러를 일일이 수작업으로 등록해줬어야 됬습니다.

상당한 불편함이 존재했죠... 윈도우즈에서는 뭐... 인터럽트를 자동으로 세팅해줍니다. 이건 확실히 좋은거 같아요.

기본으로 정해져있는 인터럽트들 시스템 타이머라던가 키보드마우스등을 제외하고 NIC(최근에는 NIC도 메인보드에 붙어서 나와서... 기본으로 설정된 경우가 많습니다)같은 경우는 따로 붙을 수도있겠죠.

뭐... 메모리에 인터럽트 테이블 같은게 존재하고 핸들러 또한 그에 상응하는 핸들러 테이블이 존재하게 됩니다.

 

뭐... 인터럽트라고 하니까 좀 뭔가 되게 포괄적으로 들리실수도 있을텐데요...

예를 들자면 네트워크 IO의 경우에는 네트워크를 타고온 무언가가 이더넷 인터럽트를 발생시키고 그에 해당하는 드라이버가 작동되는 그런 과정들을 처리하는 IRQ와 핸들러가 테이블로 따로 존재한다 정도로... 생각하시면 될 것 같습니다.

 

뭐... 전 글에서도 언급했듯 이상황은 절대 컨텍스트 스위칭은 아니구요...

컨텍스트 스위칭이란 것은 시스템 타이머에 의해서 인터럽트가 왔을때 스케쥴러가 quantum time을 다 소모했는지 확인하고 Dispatcher라는 녀석을 통해서 Ready Queue에 들어가게되고 그 과정에서 사용 됬었던 레지스터 셋을 TCB로 어딘가에 저장하는 과정... 이게 컨텍스트 스위칭이지 저 경우와 같이 핸들러로 바로 튀어버리는 상황은 컨텍스트 스위칭 상황이 아닙니다.

네... 뭐 제가 스케쥴러와 디스패쳐를 녀석이라고 하니까 하나의 객체 혹은 하나의 업무를 수행하는자라고 생각하실 수있지만 뭐... 결국 이 행위는 cpu가 하는 행위입니다. 스스로 인터럽트를 걸고 cpu에 있는 데이터를 어딘가에 빼고 어딘가에 저장하는것도 결국 cpu가 하는 행동이니까요... os는 그저 인터럽트에 의해서 무언가가 왔을때 cpu가 무슨행동을 해야되는지 방향을 알려주는 그런 정도로 생각하고 있습니다 저는.

 

근데 궁금한게 있습니다. 핸들러라는것도 결국에는 함수입니다. 그러면 당연히 레지스터가 필요하지 않을까 혹은 메모리가 필요하지 않을까 생각 할 수있습니다. 이게 당연한거구요. 그럼 얘는 어디에 저장이 되느냐... 바로 Non Paged Pool이라는 메모리에 저장됩니다. 이 메모리들은 당연히 page out되면 안되는 메모리일거구요... page out이 되어서 다시 page in 시키기위해 스탑을 시킬수도없습니다. 왜냐면 인터럽트를 통한 리액션인거지 쓰레드가 도는 개념과는 다르기 때문입니다...

네 즉... 한줄로 말하면

백업이 스택에 되기 때문에 이녀석은 항상 메모리에 상주해야 되는 녀석이고 그래서 비 페이징 풀에 저장 된다. 라는겁니다.

두줄이네요 ^_^...

그리고 일반적으로 드라이버가 논페이지드 풀에 없다면... 인터럽트 테이블에 있는 곳으로 열심히 달려가서 실행하려고 하는데 page out이 되어있는 상황이라면... 그 즉시 os는 먹통이됩니다 블루스크린에 걸린다는거죠 이유는 그리고 에러코드를 보신다면 높은확률로 PAGE_FAULT 일 겁니다...

 

그럼 IRQ에서 아주 중요한... 0번 우선순위를 가지는 시스템 타이머에 대한 얘기를 조금만 하고 정리를 해볼까합니다.

cpu가 context switching을 하는 계기인 만들어주는 녀석이구요... scheduling또한 이 인터럽트에 의해서 발생됩니다.

이 interupt가 뛸때마다 스케쥴러는 돌고있는 쓰레드의 quantum time을 모두 소진했는지 확인할 겁니다. 다썻으면 dispatch interupt를 발생시킬것이구요... 이 인터럽트가 발생이 되면 running중이던 쓰레드는 ready queue로 들어가거나 block되어서 다시 깨워지기를 기다리게 될겁니다.

아마... 예전에 타이머 함수 관련 얘기를 할때말이죠... timeBeginPeriod 함수에 대한 얘기를 드렸던 적이 있을겁니다.

timeBeginPeriod라는 함수는요... 이 시스템 타이머의 인터럽트 주기를 바꾸는 함수입니다.

GetTickCount 함수의 경우에는 시스템 타이머의 인터럽트 주기와 동일한 시간 해상도를 가지고 있는 함수인데요...

윈도우의 기본 시스템 타이머 인터럽트 주기는 64frame/sec로 되어있습니다 즉 약 15.6ms당 한번씩 인터럽트를 주는데요 timeBeginPeriod를 이용해 이를 1로 만들게되면 1ms마다  인터럽트를 주게되겠구요... 그와동시에 스케쥴러의 일도 늘어나게 될겁니다. 물론 그만큼 전력소모는 커지겠지만요 ㅋㅋ... 어차피 cpu가 노는것보다 최대한 다쓰는쪽이 더 이득이 클겁니다...

뭐 물론 timeBeginPeriod를 1로세팅해서 1ms마다 스케쥴링이 된다고 하더라도 어마어마하게 cpu를 먹는다거나 그런건 없습니다. 일반적으로 우리가 설치하는 프로그램들중 일부는 이미 시스템 타이머의 주기를 1ms로 만들어 놓는 코드가 삽입이 되어있으니까요...

 

그리고 여기까지 말씀드렸으면 한가지 정리가 된 사항이 있겠죠...

스케쥴링은 thread가 하는것도 아니고 os가 하는것도 아닙니다. 물론 스케쥴링을 하는 코드는 os가 가지고 있을겁니다만... 실제로 스케쥴링을 관리하는 녀석은 cpu 그 자체라는겁니다. 다른 매니저가 존재하는것도 아니고 cpu자체에서 인터럽트들을 통해서 자신이 가진 TCB를 백업하고 다른 레디큐에 넣고... 인터럽트가 생기면 또 거기를 찾아가서 핸들러를 실행하고 이모든걸 cpu가 하는거죠... os는 그저 여기서 거들뿐입니다. 직접 행동을 하는건 결국 cpu라는거에요.

 

자... 그러면 여기서 쪼금더 들어가본다면

스케쥴러는 과연 실제 context switching에 관여를 하는가입니다.

이때까지 설명에서는 스케쥴러가 컨텍스트 스위칭에 관여를 하는 것 처럼 말해놓고 갑자기 왜이러냐... 하실분들이 계실것 같습니다만...

사실 스케쥴러는 진짜 모든 스케쥴링을 담당하는 부분이 아닙니다.

스케쥴러는 그저 quantum time을 다 소진했는지 확인하는 정도의 작업만을 수행하고 있구요... 실제로는 Dispatcher라는 녀석이 실제 스케쥴링(컨텍스트 스위칭)을 담당하고 있습니다.

여기서도 녀석이라고 했는데 그저 dispatch interupt를 발생시키는 행위에 불과합니다.

즉 실제 스케쥴링의 업무는 Dispatcher가 담당하고 있다는 겁니다. 그리고 이것은 또한 인터럽트를 기반으로 돌고있고 이작업은 cpu에 의해서 일어납니다.

네... quantum time을 모두 소진하게 되면 dispatch interupt가 발생하구요... 그 인터럽트가 발생이 된다면... dispatcher는 TCB를 어느 ready queue에 넣는게 좋을지 결정을 할겁니다. 그리고 백업을 하면 ready queue에 있는 가장 우선순위가 높은 TCB가 그자리를 꿰차고 들어갈겁니다. 이게 컨텍스트 스위칭이에요... 뭐 그렇게 어마어마한게 아니에요.

 

근데 스케쥴링을 할때 어떤 cpu에 어떤 ready queue에 어떤 TCB가 들어갈 것이냐를 생각해본다면...

조건이 몇가지가 있는데요... 첫째는 배정의 우선순위는 cpu가 비어있다는 가정하에 선호도가 가장 우선이 됩니다. 물론 이것은 강제는 아닙니다. 당연하겠죠... 강제가 되어버린다면 지금 사용할 수 없는 cpu에 들어가게 될 수도있으니까요

그리고 두번째로 쓰레드가 만들어질 때 내부에 선호도를 결정한다고 하는데... 이부분은 ms에 언급이 되긴 합니다... 하지만 제가 체감을 해본 부분이 아니기 때문에 무슨 의미인지 정확히 잘 모르겠습니다.

뭐... 대충 문맥을 읽어보면 이런 느낌입니다. 쓰레드 생성 순서에 따라서 홀수 혹은 짝수번째 논리 코어로 배정해준다 그니까 물리적인 코어를 띄워 놓는 식으로 배정을 해준다는 거겠죠... 뭐 윈도우가 이렇게 한다고 하니까... 그런거겠죠

 

사실 스케쥴링을 하는 상황에서 베스트 상황은 이런거겠죠.

지금 물고있던 코어의 ready queue가 비어있고(혹은 안비어있어도 지금 쓰레드가 가장 우선순위가 높거나) 그 ready queue에 다시 꽂아 버린다면... 사용하던 캐시도 그래도 유지되어도 되고 얼마나 좋습니까.

그래서 Dispatcher는 만약 ready queue가 비어있다면 다시 거기다가 꽂아버린답니다.

이 경우에는 실제 context switching에 해당하는 부분이긴 합니다만... 이걸 context switching으로 보지는 않습니다.

 

그리고 선점형 방식이기 때문에 가능한 스케쥴링 방법으로 이미 running 상태인 쓰레드가 quantum time을 모두 소진하지 않았음에도 불구하고 ready queue에 더 priority가 높은 TCB가 들어온다면... 더 높은게 바로 자리를 뺏아버리는 경우가 발생합니다. 즉 이경우 context switching이 발생하게됩니다.

 

자... 그럼 우리는 우선순위에 대한 얘기를 좀 해봤습니다. 그리고 이는 우리가 설정을 할 수있고 API로도 조작이 가능합니다. 하지만 Priority에 의도적인 개입이 효과가 있을지 없을지에 대한 부분을 고민해 봐야됩니다.

만약 process의 경우라면 어떨까요

CPU 가용율이 100%라면 priority 개입으로 성능향상을 기대해볼 수 있을겁니다. 당연히 내가 지정한 프로세스가 더 우선 돌게될거고... 그러면 그 프로세스가 100%중 다수를 차지하게 될텐데... 그경우라면 내가 생각했던 성능 향상의 효과를 톡톡히 볼 수 있을것입니다.

뭐... 가용율이 100%가 아니라면 성능 차이는 큰 의미가 없을것 같습니다.

Blocking 후 ready queue에 dispatch 되었을때 cpu를 우선 획득하게 될것이구요... 이경우 이전에 돌고있던 쓰레드의 자리르 뺏어 버리게 되겠지만... 어차피 cpu를 100% 사용하지 않기 때문에 남는 시간동안 다른 쓰레드를 돌리게 될겁니다.

아주 약간의 반응성 향상 정도는 기대해볼 수 있겠습니다만... 뭐 성능이 향상된다거나 그런건 딱히 없을 것 같습니다. 어차피 남는시간에 다른 쓰레드 돌려버리면 결국 똑같이 도는거니까요 ㅋㅋ...

 

그럼 쓰레드의 경우에는 priority에 의도적인 개입이 좋은가에 대한 여부를 판단해봐야 될것같습니다...

음... 로직이나 io 관련해서 쓰레드의 선호도를 높혀서 성능 향상을 기대해 볼 수는 있을 것 같긴합니다.

그런데 이걸 서버에도 적용할 건이 있을까 싶습니다.

서버라는건... 결국 io가 일어나면 반드시 로직이 작동해야되고 로직이 작동했으면 대부분 다시 io를 통해서 그 결과를 클라이언트에게 알려주게 되는데요... 뭐 사실 그리고 서버에 안중요하고 중요하고 그런 부분이 어딨겠습니까... 다중요하고 다 빨랐으면 좋겠는걸요...

 

뭐... 대신 이런용도로 사용해 볼 수는 있을 것 같습니다.

인텔의 경우에는 h processor, u processor, p processor등 고효율코어, 고성능코어 등으로 나눠두는데요... 이런 느낌으로다가 고성능이 요구되는 worker thread들의 경우에는 쓰레드 하나당 코어 하나의 선호도를 지정해 물려주고 그 메인함수(다른 쓰레드들이 꺼지지 않게만 해주는녀석), 그리고 모니터링 서버에 데이터를 보내는 쓰레드라던가... 이런 녀석들은 한 쓰레드로 선호도를 묶어버리는겁니다... 이렇게 한다면 뭐... 쪼금이나마 선호도를 가지고 덕을 볼 건덕지가 생길 여지는 있을 것 같긴합니다.

 

뭐... 우선순위 건에서 아까 말씀드렸던 쓰레드 기아 현상과 dynamic priority boosting에 대한 얘기를 조금만 해보고 글을 정리하겠습니다

컨텍스트 스위칭이 일어나게 된다면 dispatcher가 다시 ready queue에 넣는 작업을 할텐데 이 쓰레드의 우선순위가 가장 높은 상태로 계속 유지된다면 그 외에 다른 우선순위가 밀리는 쓰레드들은 영원히 실행되지 않을겁니다... 당연히 이러면 안되겠죠... 그렇기 때문에 윈도우에서는 특정 시간이상 머무리는 TCB에 대해서 부스팅을 하게됩니다 물론 모든 TCB를 확인하진 않습니다. 뭐... 16개인가 그정도 확인한다고 듣긴 했는데 정확히는 모르겠습니다.

boosting의 경우에도 10개 내외로 한다고 듣기만 했지 정확히 숙지하고 있지는 않습니다.

그리고 Blocking 당해있는 TCB또한 boosting의 대상이 될 수 있습니다.

왜냐면... io에 막힌 thread거나 혹은 event를 기다리는 thread였다고 한다면 이벤트가 발생했을때 빨리 처리하는게 좋기 때문일거구요...

 

마지막으로 저번 글에서 quantum time이 일반 windows 보다 windows server의 경우 2배정도 더 길게 잡힌다고 했었습니다. 뭐... 그이유는 일단 일반 윈도우(유저가 사용하는거)는 일반적으로 한 os내부에서 많은 프로세스를 돌리고 있을겁니다 그렇기 떄문에 더 짧은 quantum time이 요구되는 것이구요... 서버의 경우에는 특정 완벽하게 안전하다고 판단되는 몇개의 프로세스만을 켜놓을 것입니다. 그러면 quantum time이 긴게 오히려 더 이득일 수도있습니다.

이건... 뭐 os 차별이 아니라 각 용도가 다른 os이기 때문에 그런 식으로 설계가 된것이구요... 뭐 ms사에서도 많은 실험끝에 도출해낸 결과 아니겠습니까??... 그게 더 성능면으로도 좋다고 판단했기 때문일겁니다.

 

후... 이정도 하면 윈도우의 스케쥴링 관련 건으로는... 대충 마무리가 된 것 같습니다...

제가 필기를 바탕으로 적다보니까 중간에 겹치는 내용도 한두건씩 나오게 되고... 그리고 이내용 저내용 막 섞여있다 보니까 다른 글들처럼 좀 깔끔하게 정리가 안되어있는 편입니다만...

 

일단은 이렇게 정리를 해놓고 나중에 다시 볼때 이상하다 싶으면 수정하겠습니다...

그럼 오늘도 긴글 읽어주셔서 감사하구요... 

다음 글에서는 동기화 객체에 대한 내용을 정리해볼겁니다... 물론 아마 이정리는 저혼자 보게되겠죠ㅠㅠ

이 재미없고 긴글을 누가 읽어줄까요... 째뜬... 다들 안녕히계세요

 

 

 

 

320x100

'게임서버 > 멀티쓰레드 이론' 카테고리의 다른 글

Synchronization2  (0) 2022.01.21
Synchronization  (0) 2022.01.21
Thread  (0) 2022.01.20
Synchronization object  (0) 2022.01.19
Windows preemptive priority based round-robin scheduling  (0) 2022.01.18