Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 10개 있습니다.)
(시리즈 글이 5개 있습니다.)
Windows: 120. 윈도우 운영체제의 시간 함수 (1) - GetTickCount와 timeGetTime의 차이점
; https://www.sysnet.pe.kr/2/0/11063

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

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

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

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




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

윈도우의 기본 타이머 정확도는 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://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod

NtSetTimerResolution
; http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html
; https://web.archive.org/web/20220715225118/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://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount

운영체제가 시작된 후 흐른 시간을 밀리초 단위로 반환하지만, 정작 운영체제는 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://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timegettime

이것 역시 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의 차이점이 명확해졌겠죠?!!! ^^

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




ClockRes.exe의 리눅스 버전은 단순히 config 파일에서 조회할 수 있습니다.

$ cat /boot/config-`uname -r` | grep CONFIG_HZ
# CONFIG_HZ_PERIODIC is not set
# CONFIG_HZ_100 is not set
CONFIG_HZ_250=y
# CONFIG_HZ_300 is not set
# CONFIG_HZ_1000 is not set
CONFIG_HZ=250

즉, 위의 시스템은 1/250=0.004로 약 4ms마다 스레드 스케줄링을 하는 것입니다. (Linux kernel time management and kernel timer)




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/14/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at 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 단위로 특정 명령을를 실행하는 함수를 만드려고 하는데
위와 같이 튀게되면 쉽지않을 것 같아 질문드립니다! 감사합니닷
[guest]
2018-06-08 03시55분
글쎄요. 일단 console out을 제거하고 테스트해 보세요. (메모리 버퍼에 플래그를 만들고 일정 시간 mod 연산으로 연속 숫자가 설정되는지 확인하는 식으로.) 그래도 끊기면 해당 스레드의 스케쥴링이 다른 스레드에 의해 점유당한 걸 수도 있습니다. 가령 시스템에 운영 중인 다른 스레드 중 우선 순위가 높은 것이 있어서 CPU 자원을 점유하려들면 방법이 없습니다. 윈도우나 리눅스같은 운영체제는 RTOS가 아니기 때문에 언제나 저런 식으로 동작하는 것을 보장할 수 없습니다.

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

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

... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...
NoWriterDateCnt.TitleFile(s)
11801정성태1/1/201913512개발 환경 구성: 427. Netsh의 네트워크 모니터링 기능 [3]
11800정성태12/28/201812869오류 유형: 509. WCF 호출 오류 메시지 - System.ServiceModel.CommunicationException: Internal Server Error
11799정성태12/19/201813684.NET Framework: 804. WPF(또는 WinForm)에서 UWP UI 구성 요소 사용하는 방법 [3]파일 다운로드1
11798정성태12/19/201812879개발 환경 구성: 426. vcpkg - "Building vcpkg.exe failed. Please ensure you have installed Visual Studio with the Desktop C++ workload and the Windows SDK for Desktop C++"
11797정성태12/19/201810296개발 환경 구성: 425. vcpkg - CMake Error: Problem with archive_write_header(): Can't create '' 빌드 오류
11796정성태12/19/20189937개발 환경 구성: 424. vcpkg - "File does not have expected hash" 오류를 무시하는 방법
11795정성태12/19/201812296Windows: 154. PowerShell - Zone 별로 DNS 레코드 유형 정보 조회 [1]
11794정성태12/16/20189753오류 유형: 508. Get-AzureWebsite : Request to a downlevel service failed.
11793정성태12/16/201811338개발 환경 구성: 423. NuGet 패키지 제작 - Native와 Managed DLL을 분리하는 방법 [1]
11792정성태12/11/201812106Graphics: 34. .NET으로 구현하는 OpenGL (11) - Per-Pixel Lighting파일 다운로드1
11791정성태12/11/201812080VS.NET IDE: 130. C/C++ 프로젝트의 시작 프로그램으로 .NET Core EXE를 지정하는 경우 닷넷 디버깅이 안 되는 문제 [1]
11790정성태12/11/201810489오류 유형: 507. Could not save daemon configuration to C:\ProgramData\Docker\config\daemon.json: Access to the path 'C:\ProgramData\Docker\config' is denied.
11789정성태12/10/201820260Windows: 153. C# - USB 장치의 연결 및 해제 알림을 위한 WM_DEVICECHANGE 메시지 처리 [2]파일 다운로드2
11788정성태12/4/201810265오류 유형: 506. SqlClient - Value was either too large or too small for an Int32.Couldn't store <2151292191> in ... Column
11787정성태11/29/201814195Graphics: 33. .NET으로 구현하는 OpenGL (9), (10) - OBJ File Format, Loading 3D Models파일 다운로드1
11786정성태11/29/201811001오류 유형: 505. OpenGL.NET 예제 실행 시 "Managed Debugging Assistant 'CallbackOnCollectedDelegate'" 예외 발생
11785정성태11/21/201813369디버깅 기술: 120. windbg 분석 사례 - ODP.NET 사용 시 Finalizer에서 System.AccessViolationException 예외 발생으로 인한 비정상 종료
11784정성태11/18/201813020Graphics: 32. .NET으로 구현하는 OpenGL (7), (8) - Matrices and Uniform Variables, Model, View & Projection Matrices파일 다운로드1
11783정성태11/18/201811147오류 유형: 504. 윈도우 환경에서 docker가 설치된 컴퓨터 간의 ping IP 주소 풀이 오류
11782정성태11/18/201810930Windows: 152. 윈도우 10에서 사라진 "Adapters and Bindings" 네트워크 우선순위 조정 기능 - 두 번째 이야기
11781정성태11/17/201813109개발 환경 구성: 422. SFML.NET 라이브러리 설정 방법 [1]파일 다운로드1
11780정성태11/17/201814602오류 유형: 503. vcpkg install bzip2 빌드 에러 - "Error: Building package bzip2:x86-windows failed with: BUILD_FAILED"
11779정성태11/17/201814928개발 환경 구성: 421. vcpkg 업데이트 [1]
11778정성태11/14/201812664.NET Framework: 803. UWP 앱에서 한 컴퓨터(localhost, 127.0.0.1) 내에서의 소켓 연결
11777정성태11/13/201811786오류 유형: 502. Your project does not reference "..." framework. Add a reference to "..." in the "TargetFrameworks" property of your project file and then re-run NuGet restore.
11776정성태11/13/201811005.NET Framework: 802. Windows에 로그인한 계정이 마이크로소프트의 계정인지, 로컬 계정인지 알아내는 방법
... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...