본문 바로가기

Robotics/Software Tech.

Windows에서 최대한 Realtime을 지켜보자

Windows는 실시간 OS가 아니기 때문에 근본적으로 실시간은 지켜질수 없다.

그러나 여러 방법을 사용하면 가능할 수도 있다.

방법으론 다음과 같이 크게 3가지가 있을수 있다.


1. RTC(RealTime Clock) 인터럽트를 이용하는 방법

2. 실시간을 위한 윈도우 써드파트를 이용하는 방법

3. 윈도우의 scheduling과 Process Class./Thread Priority를 이용하는 방법


1번은 DDK로 IRQ8번을 후킹하여 RTC가 갖고있는  Period Interrupt기능을 이용할 수 있다.

이를 위해서는 디바이스 드라이버 프로그래밍을 할 줄 알아야 하는 번거로움이 있고

IDT(INterrupt Descriptor Table)을 직접 조작할수 있는 기술이나 다른 사람이 짠 프로그램을

빼끼는 능력이 있어야 하지만 내가 해본바로는 하드웨어 인터럽트기 때문에 hard Realtime을

구현할 수 있다. 이에 대한 내용은 이미 내 블로그에 찾아보면 있을 것이다.

대신에 RTOS처럼 여러 여러 쓰레드를 사용할수 없고 2의 배수로 나눈 시간으로 밖에

주기를 설정할 수 없는 단점이 있다.  나는 512Hz로 현재 모션 컨트롤러를 제어 하고 있다.


둘째로 돈이 가장 많이 드는 방법으로 Third Part를 사서 사용하는 방법이다.

이것들은 내가 본것만도 몇가지 되지만 그중 가장 괜찮다고 하는게 INTime이라는 것이다.

이것이 설치되면 Window커널위에 INTime RT 커널이 위치해서 윈도를 완전히 장악 하고

있는것 같다.  Realtime의 구현은 일반 Application에서 되는것이 아니고 따로 RT module을

작성해야하며 Application과 다양한 방법의 통신으로 운영되는 것 같다.


셋째로 윈도우 자체 기능을 최대한 활용하는것으로 Hard RealTime은 될수 없지만

어느정도는 잘되는듯 하다.

일단 시스템의 타이밍 해상도를 1ms로 바꾸기 위해 timeBeginPeriod(1)을 사용하다.

이렇게 되면 스케쥴링을 위한 최소 타임이 Win2K인경우 10ms에서 1ms으로 변경된다.

그리고 프로그램의 첨 시작에


  SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS);


을 삽입하여 현 프로그램에 대한 프로세서 우선순위를 최고로 올려놓는다.

다음으로 실시간을 구현하고자 하는  쓰레드를 가장 높은 우선순위로 다음과 같이 생성한다.


  AfxBeginThread(MyThread,this,THREAD_PRIORITY_TIME_CRITICAL);


이렇게 되면 일단 모든 준비는 완료된 것이다.

다음으로 쓰레드에서 반드시 해야할 일은 Sleep을 넣는것인데 이것을 넣지 않으면

윈도우는 완전히 멈춰버리고 오직 이쓰레드만 동작하게 된다.

Sleep은 다음과 같은 방법으로 넣는다.  예를들어 100Hz로 정확하게 구동하는 쓰레드를

만들고 싶다면 다음과 같은 구조로 짠다.


#define HZ  100.0


double pretime, ctime;

pretime = GetPrecisionTime();


while(flag)

{

    ctime = GetPrecisionTime();

    if (ctime-pretime >= 1.0/Hz)

   {

       pretime = ctime;


       // 여기에 원하는 코드를 넣는다,


       Sleep(7);   // <<------- 매우 중요한 부분이다.

   }

}


위에서 사용된 GetPrecisionTime은 timeGetTime이나 GetTickCount가 1ms단위로 리턴하는것에

비해 nanosec단위로 리턴하기때문에 아주 정확하다. 함수의 원형은 아래와 같다.


double GetPrecisionTime(void)
{
 LARGE_INTEGER lpFrequency;
 LARGE_INTEGER lpPerformanceCount;
 QueryPerformanceFrequency(&lpFrequency);
 QueryPerformanceCounter(&lpPerformanceCount);
 return  (double)lpPerformanceCount.QuadPart /(double)lpFrequency.QuadPart;
}


여기서 의문이 왜 Sleep에 7이라고 했으며 왜 내 코드를 수행하는 부분 안에 넣었을까이다.

자 그럼 의문을 하나하나 풀어가보자.

timeBeginPeriod로 1ms 설정했기 때문에 Sleep의 최소단위는 1ms이다. 우리는 반드시

Sleep(1)이상을 넣어야 하는 의무가 있는 조건이다. 그럼 매번 Sleep(1)을 호출한다면

최소 1ms는 안지켜질 확률이 매우 커진다. 우린 1ms이상의 시간을 지켜야 하는 사명이 있다.

그렇다고 Sleep을 안쓰게 되면 윈도는 멈춰버린다. 해결책은 어짜피 우린 100Hz (10ms)로만

동작하면 되고 또 우리의 코드가 그리많은 시간이 필요로 하지 않는다. 아하~.. 그렇다면

우리 의 코드가 수행된다음 다음 시간까진 아직 많은 시간이 남아 있기 때문에

그때 Sleep을 주면 되겠다는 생각이 떠오른다.  그래서 시간을 측정하고 내 코드가 수행되는

그 블록안에 Sleep이 위치 해 있는것이다.

SLeep(7)은 왜했을까 ?. Sleep(1)을 하는게 생각으론 좋겠지만 그렇게 된다면 이 쓰레드 이외의

다른 작업에 겨우 1ms의 시간밖에 할당을 안해주기 때문에 전체적으로 윈도우가 느려짐을 느낄

수 있다.  그렇다고 Sleep(10)을 주면 100Hz를 달성 할 수 없다.  이것저것해보니 7~8이 가장

적당한 듯 하다. 


대충 의문이 풀렸는가?.. 그냥 쉽게 말해 윈도우는 평소에 아무짓도 안하고 시간만 보고 있고

시간이 되면 내작업하고 7ms동안만 윈도우의 하던 작업을 하게 시간을 내주는것이다.


이것이 Idea는 단순하지만 하드디스크를 억세스 하지 않는 조건이라면 아주 잘 지켜진다.

단 시험해본바로 98/2K에서만 잘되는것 같다. XP는 내부적으로 먼가 틀린가 보다.

똑같은 코드로 돌려봐도 잘 안지켜 지는것 같다.


아무튼..귀찮은 실시간을 조금이나마 구현하고자 하는데 도움이 됬으면 좋겠다.