Microsoft MVP성태의 닷넷 이야기
글쓴 사람
홈페이지
첨부 파일

윈도우 운영체제의 시간 함수 (1) - GetTickCount와 timeGetTime의 차이점

윈도우 운영체제의 시간 함수 (1) - GetTickCount와 timeGetTime의 차이점
; https://www.sysnet.pe.kr/2/0/11063

윈도우 운영체제의 시간 함수 (2) - Sleep 함수의 동작 방식
; https://www.sysnet.pe.kr/2/0/11065

윈도우 운영체제의 시간 함수 (3) - QueryInterruptTimePrecise, QueryInterruptTime 함수
; https://www.sysnet.pe.kr/2/0/11066

윈도우 운영체제의 시간 함수 (4) - RTC, TSC, PM Clock, HPET Timer
; https://www.sysnet.pe.kr/2/0/11067

윈도우 운영체제의 시간 함수 (5) - TSC(Time Stamp Counter)와 QueryPerformanceCounter
; https://www.sysnet.pe.kr/2/0/11068

윈도우의 기본 타이머 정확도는 15.625ms(15,625,000ns)입니다. 이는 타이머 디바이스가 PIC 컨트롤러의 (우선 순위가 가장 높은) IRQ 0번을 통해,

gettick_1.png

인터럽트하는 횟수에 기반을 둡니다. 이 타이머 디바이스를 PIT(Programmable Interval Timer)라고도 하는데 이름에서처럼 PIT 컨트롤러의 레지스터에 주기를 설정해 인터럽트 발생 횟수를 제어할 수 있습니다. 다시 말해, 윈도우는 발생 주기의 기본 값을 64Hz로 설정하고 있는 것입니다.

주기 = 1초 / 64hz = 0.015625(sec)

타이머의 주기는 다음의 Win32 API를 이용해 임의로 조정하는 것이 가능한데요.

timeBeginPeriod function
; https://msdn.microsoft.com/en-us/library/windows/desktop/dd757624(v=vs.85).aspx

NtSetTimerResolution
; http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html

실제로 일부 프로그램들은 이를 사용하고 있습니다. 이제는 아니라고 하지만 과거에는 그랬던 Google의 크롬 웹 브라우저나, Microsoft SQL Server 등이 그렇다고 합니다. (제 컴퓨터에서 자주 사용하는 것 중에는 snagit이 1ms로 주기를 설정합니다.)

그 예로, 제 데스크톱 컴퓨터에서 sysinternals의 clockres.exe를 실행해 보면 다음과 같이 (초당 1000번의 타이머 인터럽트가 발생하도록) 1ms로 조정된 것을 확인할 수 있습니다.

D:\Tools>Clockres.exe

ClockRes v2.0 - View the system clock resolution
Copyright (C) 2009 Mark Russinovich
SysInternals - www.sysinternals.com

Maximum timer interval: 15.625 ms
Minimum timer interval: 0.500 ms
Current timer interval: 1.001 ms

반면, timer interrupt에 영향을 주는 프로그램이 실행되지 않은 상태에서는 기본값 그대로 15.625로 맞춰져 있습니다.

데스크톱 PC 같은 환경이라면 주기의 조정이 별로 문제가 안되는데, 노트북의 경우 초당 인터럽트가 64번에서 1,000번으로 늘어난다는 것은 곧 배터리의 수명이 그만큼 줄어든다는 것을 의미합니다.

어떤 프로그램들이 여러분들의 전원 소비를 높이는지 찾고 싶다면 다음의 글에 나온 "powercfg -energy duration 5"와 같은 명령어를 통해 분석할 수 있습니다.

Windows Timer Resolution: Megawatts?Wasted
; https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/

참고로, 최소 타이머 간격인 0.5ms가 타이머 디바이스의 제약은 아닙니다. PIT 컨트롤러의 내부 클럭은 1.193182Mhz로 동작하는데 이는 약 838.095 나노초 단위가 됩니다. 하지만, 운영체제에서는 타이머 인터럽트에 기반해 수행되는 작업의 부담으로 0.5ms까지만 가능하게 정한 듯합니다.




기본 타이머 정확도가 15.625ms라는 것은 과연 어떤 의미가 있을까요?

일반적으로 CPU는 GHz의 속도로 동작하며 기계어를 실행시킵니다. 하지만, 운영체제 입장에서는 스레드 스케줄링 등의 작업을 위해 CPU의 GHz 단위에 맞춰 작업을 할 수 없습니다. 그랬다가는 운영체제를 실행하는 것만으로 CPU가 바쁠 것이기 때문입니다.

따라서, 운영체제는 타이머 인터럽트에 맞춰 스케줄링 등의 작업을 합니다. 또한 내부적으로 운영체제가 부팅 이후 지난 시간을 기록하는 변수를 업데이트하기도 합니다. 이를 달리 말하면, "부팅 이후 지난 시간이 기록된 변수"의 값이 15.625ms가 지나서야 한 번씩 업데이트된다는 것입니다.

일례로 윈도우의 GetTickCount 함수는,

GetTickCount function
; https://msdn.microsoft.com/en-us/library/windows/desktop/ms724408(v=vs.85).aspx

운영체제가 시작된 후 흐른 시간을 밀리초 단위로 반환하지만, 정작 운영체제는 15.625ms가 지나야 GetTickCount가 반환할 시간 값 변수의 주소를 업데이트하므로 타이머 인터럽트를 처리하는 중간에 GetTickCount를 호출하게 되면 동일한 값을 반환하다가 그다음 타이머 인터럽트에서는 대략 15.625ms가 지난 값을 반환할 것으로 예상할 수 있습니다.

눈으로 확인하기 위해 GetTickCount를 다음과 같이 사용해 보면,

#include "stdafx.h"
#include <Windows.h>

int main()
{
    int count = 100;

    while (count -- > 0)
    {
        printf("%d\n", ::GetTickCount());
    }

    return 0;
}

출력 결과는 다음과 같습니다.

510617234
...[생략: 61번 같은 값 반복]...
510617250
...[생략]...

같은 값이 반복되다가 값이 한번 바뀐 시점의 차이를 보면 510617250 - 510617234 = 16이 나오고 예상할 수 있듯이 단위는 밀리초입니다. 즉, 타이머 인터럽트 시간인 15.625ms에 맞춰서 업데이트가 된 것입니다.

이런 특성을 이해하지 못하는 일부 개발자들은, GetTickCount가 밀리초 단위의 값을 반환하므로 윈도우에서 밀리초 단위로 시간을 잴 수 있다고 말하기도 합니다. 정확히 말하면, 밀리초 단위로 값을 반환받기는 하지만 1 밀리초 단위로 업데이트되는 것은 아니라는 점입니다.

재미있는 점이 하나 있는데, GetTickCount는 변화된 Timer Interrupt에는 영향을 받지 않는다는 것입니다. (혹시 이유를 정확히 아시는 분은 덧글 부탁드립니다. ^^) 아마도 GetTickCount가 Windows 95/98 때부터 있었던 Win32 API라서 하위 호환성을 위해 이후의 운영체제에서는 내부적인 보정을 거치는 것 같은데요.

일례로, "Current timer interval"이 "1.0ms"로 설정된 PC에서 테스트를 해봐도 여전히 약 16ms 단위로 값이 변경되는 것을 볼 수 있습니다. 문서 상으로는 GetTickCount가 "The resolution of the GetTickCount function is limited to the resolution of the system timer"라고 되어 있는데 "system timer"는 장치 관리자에서 봤던 것처럼 IRQ 0번에 물린 PIT를 의미한다고 했을 때 다소 혼란스러운 면이 있습니다.




오히려, timer interrupt에 직접적으로 반응하는 것은 timeGetTime 함수입니다.

timeGetTime function
; https://msdn.microsoft.com/en-us/library/windows/desktop/dd757629(v=vs.85).aspx

이것 역시 GetTickCount처럼 윈도우가 시작된 이후 흐른 시간을 밀리초 단위로 반환하는 역할을 합니다. 따라서, (clockres.exe가 15.625ms로 반환하는) 기본 Timer Interrupt를 갖는 윈도우에서 다음의 코드를 실행하면,

#include "stdafx.h"
#include <Windows.h>

#pragma comment(lib, "winmm.lib")

int main()
{
    int count = 100;

    while (count -- > 0)
    {
        printf("%d\n", ::timeGetTime());
    }

    return 0;
}

출력 결과는 이렇고,

2478593
...[생략: 43번 같은 값 반복]...
2478608
...[생략: 46번 같은 값 반복]...
2478624
...[생략]...

중간에 튀는 값을 보면 15, 16으로 GetTickCount와 마찬가지로 15.625ms 단위로 변화하는 것을 볼 수 있습니다.

이전에 제가 타이머 인터럽트 정확도를 timeBeginPeriod 함수를 이용해 변경할 수 있다고 했는데요. 이를 1ms 단위로 설정해 주면,

#include "stdafx.h"
#include 

#pragma comment(lib, "winmm.lib")

int main()
{
    int count = 100;

    timeBeginPeriod(1); // 주기를 1ms로 설정

    while (count -- > 0)
    {
        printf("%d\n", ::timeGetTime());
    }

    timeEndPeriod(1); // 설정된 주기를 해제

    return 0;
}

이제 timeGetTime의 출력 결과는 1ms 단위로 변화하는 것을 볼 수 있습니다.

2801569
2801570
...[생략]...
2801570
2801571
2801571
2801572
2801572
2801572
2801573
2801573
2801573
2801574
2801574
2801574
2801575
2801575
2801575
2801576
...[생략]...

주의할 것은, timeBeginPeriod 설정은 시스템 전역적으로 설정되기 때문에 여러분들의 프로그램에서 호출하지 않았다고 해도 현재 실행되고 있는 다른 프로그램이 timeBeginPeriod(1)을 호출한 상태라면 timeGetTime은 그런 경우에도 1ms 단위의 정밀도로 동작하게 됩니다.

즉, 어떤 프로그램이든지 timeBeginPeriod로 timer interrupt를 변경하게 되면 clockres.exe의 결과로도 확인할 수 있는 것입니다.

이 정도면... GetTickCount와 timeGetTime의 차이점이 명확해졌겠죠?!!! ^^

(첨부 파일은 이 글의 테스트 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]





[최초 등록일: ]
[최종 수정일: 2/6/2018 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 



2018-06-08 03시05분
[질문드립니닷] 안녕하세요, 윈도우 운영체제 함수 글 모두 정독하고있습니다. 정말 감사드립니다.
질문드리고 싶은 부분이 있는데요, time period를 1로 설정하고 게시하신 코드대로 timeGetTime() 함수를 실행하여보면
밀리초 단위로 변화하다가 크게 튀는 부분이 있어서.. 어떠한 원인으로 이런 문제가 발생할수 있는지 혹시 알수 있을까요?
값이 아래와 같이 튑니다.
270082480 (x회 반복..)
270082481 (y회 반복..)
270082482 (z회 반복.., 이하는 생략하겠습니다.)
270082483
270082493 (이부분)
270082494
270082498 (이부분)
270082499
270082500
270082501
270082515 (이부분)
270082524 (이부분)
...

현재 1ms 단위로 특정 명령을를 실행하는 함수를 만드려고 하는데
위와 같이 튀게되면 쉽지않을 것 같아 질문드립니다! 감사합니닷
[손님]
2018-06-08 03시55분
글쎄요. 일단 console out을 제거하고 테스트해 보세요. (메모리 버퍼에 플래그를 만들고 일정 시간 mod 연산으로 연속 숫자가 설정되는지 확인하는 식으로.) 그래도 끊기면 해당 스레드의 스케쥴링이 다른 스레드에 의해 점유당한 걸 수도 있습니다. 가령 시스템에 운영 중인 다른 스레드 중 우선 순위가 높은 것이 있어서 CPU 자원을 점유하려들면 방법이 없습니다. 윈도우나 리눅스같은 운영체제는 RTOS가 아니기 때문에 언제나 저런 식으로 동작하는 것을 보장할 수 없습니다.

해볼 수 있는 거라면 스레드의 우선 순위를 높여보는 정도일 것입니다.
정성태
2018-06-08 07시40분
[질문드립니닷] 답변 감사드립니다. 폴링으로 진행하는 것은 역시 한계가 있군요.
Windows 와 RTOS 환경을 연동하려다보니 고려해야할 사항이 많네요. 정보 정말 감사합니닷.
[손님]
2019-02-25 06시17분
[초보자] 이걸 이용해서 혹시 시스템 시간을 변경하면(즉, 윈도우 상에서 시간/날짜 조정) cpu 시간과 비교해 이상징후를 체크 할 수 있을까요 ??

예를 들면 프로그램 시작전 임의의 사용자가 윈도우 상에서 시간/날짜 조정을 이상하게 하여 실제 cpu에 킨 시간과 내용이 상의 할때 캐치가 가능할까요?
[손님]
2019-02-25 06시24분
@초보자 간단하게 테스트해보시면 답이 나오지 않을까요?
정성태

... [46]  47  48  49  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
11127정성태1/8/20176194기타: 63. (개발자를 위한) Visual Studio의 "with MSDN" 라이선스 설명
11126정성태1/7/20179988기타: 62. Edge 웹 브라우저의 즐겨찾기(Favorites)를 편집/백업/복원하는 방법 [1]파일 다운로드1
11125정성태1/7/20176898개발 환경 구성: 310. IIS - appcmd.exe를 이용해 특정 페이지에 클라이언트 측 인증서를 제출하도록 설정하는 방법
11124정성태1/4/20178360개발 환경 구성: 309. 3년짜리 유효 기간을 제공하는 StartSSL [2]
11123정성태1/4/20176743.NET Framework: 629. .NET Core의 dotnet.exe CLI 명령어 확장 방법 [1]
11122정성태1/3/20177034.NET Framework: 628. TransactionScope에 사용자 정의 트랜잭션을 참여시키는 방법 [2]파일 다운로드1
11121정성태1/1/20175927개발 환경 구성: 308. "ASP.NET Core Web Application (.NET Core)"와 "ASP.NET Core Web Application (.NET Framework)" 차이점
11120정성태12/12/20199483개발 환경 구성: 307. ASP.NET Core Web Application을 IIS에서 호스팅하는 방법
11119정성태12/23/201628019개발 환경 구성: 306. Visual Studio Code에서 Python 개발 환경 구성 [1]
11118정성태12/22/201610414오류 유형: 374. Python 64비트 설치 시 0x80070659 오류 발생 [1]
11117정성태12/21/20166354웹: 35. nopCommerce 예제 사이트 구성 방법
11116정성태12/21/20168455디버깅 기술: 84. NopCommerce의 Autofac 부하(CPU, Memory) [2]
11115정성태12/21/201610452Windows: 133. 윈도우 서버 2016에서 플래시가 동작하지 않는 경우 [2]
11114정성태12/19/201611673Windows: 132. 역 슬래시(backslash) 문자가 왜 통화 표기 문자(한글인 경우 "\")로 보일까요? [2]
11113정성태12/19/20165411오류 유형: 373. ICOMAdminCatalog::GetCollection에서 CO_E_ISOLEVELMISMATCH(0x8004E02F) 오류 발생파일 다운로드1
11112정성태11/23/201611192오류 유형: 372. MySQL 서비스가 올라오지 않는 경우 - Error 1067
11111정성태2/26/202015718.NET Framework: 627. C++로 만든 DLL을 C#에서 사용하기 [2]
11110정성태11/17/20165710.NET Framework: 626. Commit 메모리가 낮은 상황에서도 메모리 부족(Out-of-memory) 예외 발생
11109정성태11/17/20165394.NET Framework: 625. ASP.NET에서 System.Web.HttpApplication 인스턴스는 다중으로 생성됩니다.
11108정성태11/13/20166058.NET Framework: 624. WPF - Line 요소를 Canvas에 위치시켰을 때 흐림(blur) 현상파일 다운로드1
11107정성태11/10/20166261오류 유형: 371. Post cache substitution is not compatible with modules in the IIS integrated pipeline that modify the response buffers.파일 다운로드1
11106정성태11/8/20167483.NET Framework: 623. C# - PeerFinder를 이용한 Wi-Fi Direct 데이터 통신 예제파일 다운로드1
11105정성태11/8/20166090.NET Framework: 622. PeerFinder Wi-Fi Direct 통신 시 Read/Write/Dispose 문제
11104정성태11/8/20166156개발 환경 구성: 305. PeerFinder로 Wi-Fi Direct 연결 시 방화벽 문제
11103정성태11/8/20165906오류 유형: 370. PeerFinder.ConnectAsync의 결과 값인 Task.Result를 호출할 때 System.AggregateException 예외 발생
11102정성태11/8/20166254오류 유형: 369. PeerFinder.FindAllPeersAsync 호출 시 System.UnauthorizedAccessException 예외 발생
... [46]  47  48  49  50  51  52  53  54  55  56  57  58  59  60  ...