Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

윈도우 운영체제의 시간 함수 (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분
@초보자 간단하게 테스트해보시면 답이 나오지 않을까요?
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12339정성태9/21/202028오류 유형: 655. 코어 모드의 윈도우는 GUI 모드의 윈도우로 교체가 안 됩니다.
12338정성태9/21/202014오류 유형: 654. 우분투 설치 시 "CHS: Error 2001 reading sector ..." 오류 발생
12337정성태9/21/202015오류 유형: 653. Windows - Time zone 설정을 바꿔도 반영이 안 되는 경우
12336정성태9/21/202073.NET Framework: 942. C# - WOL(Wake On Lan) 구현
12335정성태9/21/202016Linux: 31. 우분투 20.04 초기 설정 - 고정 IP 및 SSH 설치
12334정성태9/21/20209오류 유형: 652. windbg - !py 확장 명령어 실행 시 "failed to find python interpreter"
12333정성태9/20/202067.NET Framework: 941. C# - 전위/후위 증감 연산자에 대한 오버로딩 구현 (2)
12332정성태9/18/202045.NET Framework: 940. C# - Windows Forms ListView와 DataGridView의 예제 코드파일 다운로드1
12331정성태9/18/202045오류 유형: 651. repadmin /syncall - 0x80090322 The target principal name is incorrect.
12330정성태9/20/2020162.NET Framework: 939. C# - 전위/후위 증감 연산자에 대한 오버로딩 구현 [2]파일 다운로드1
12329정성태9/16/202065오류 유형: 650. ASUS 메인보드 관련 소프트웨어 설치 후 ArmouryCrate.UserSessionHelper.exe 프로세스 무한 종료 현상
12328정성태9/16/202048VS.NET IDE: 150. TFS의 이력에서 "Get This Version"과 같은 기능을 Git으로 처리한다면?
12327정성태9/12/2020108.NET Framework: 938. C# - ICS(Internet Connection Sharing) 제어파일 다운로드1
12326정성태9/12/2020101개발 환경 구성: 516. Azure VM의 Network Adapter를 실수로 비활성화한 경우
12325정성태9/12/202093개발 환경 구성: 515. OpenVPN - 재부팅 후 ICS(Internet Connection Sharing) 기능이 동작 안하는 문제
12324정성태9/11/2020138개발 환경 구성: 514. smigdeploy.exe를 이용한 Windows Server 2016에서 2019로 마이그레이션 방법
12323정성태9/11/2020119오류 유형: 649. Copy Database Wizard - The job failed. Check the event log on the destination server for details.
12322정성태9/11/2020142개발 환경 구성: 513. Azure VM의 RDP 접속 위치 제한
12321정성태9/11/2020119오류 유형: 648. netsh http add urlacl - Error: 183 Cannot create a file when that file already exists.
12320정성태9/11/2020176개발 환경 구성: 512. RDP(원격 데스크톱) 접속 시 비밀 번호를 한 번 더 입력해야 하는 경우
12319정성태9/10/2020131오류 유형: 647. smigdeploy.exe를 Windows Server 2016에서 실행할 때 .NET Framework 미설치 오류 발생
12318정성태9/9/2020133오류 유형: 646. OpenVPN - "TAP-Windows Adapter V9" 어댑터의 "Network cable unplugged" 현상
12317정성태9/10/2020137개발 환경 구성: 511. Beats 용 Kibana 기본 대시 보드 구성 방법
12316정성태9/8/2020300디버깅 기술: 170. WinDbg Preview 버전부터 닷넷 코어 3.0 이후의 메모리 덤프에 대해 sos.dll 자동 로드
12315정성태9/7/2020248개발 환경 구성: 510. Logstash - FileBeat을 이용한 IIS 로그 처리 [2]
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...