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분
@초보자 간단하게 테스트해보시면 답이 나오지 않을까요?
정성태

1  2  3  4  5  6  7  8  9  [10]  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13373정성태6/19/20234399오류 유형: 865. 파이썬 - pymssql 설치 관련 오류 정리
13372정성태6/15/20233113개발 환경 구성: 682. SQL Server TLS 통신을 위해 사용되는 키 길이 확인 방법
13371정성태6/15/20233132개발 환경 구성: 681. openssl - 인증서 버전(V1 / V3)
13370정성태6/14/20233297개발 환경 구성: 680. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 - TLS 1.2 지원
13369정성태6/13/20233096개발 환경 구성: 679. PyCharm(을 비롯해 JetBrains에 속한 여타) IDE에서 내부 Window들의 탭이 없어진 경우
13368정성태6/13/20233225개발 환경 구성: 678. openssl로 생성한 인증서를 SQL Server의 암호화 인증서로 설정하는 방법
13367정성태6/10/20233333오류 유형: 864. openssl로 만든 pfx 인증서를 Windows Server 2016 이하에서 등록 시 "The password you entered is incorrect" 오류 발생
13366정성태6/10/20233131.NET Framework: 2128. C# - 윈도우 시스템에서 지원하는 암호화 목록(Cipher Suites) 나열파일 다운로드1
13365정성태6/8/20232897오류 유형: 863. MODIFY FILE encountered operating system error 112(failed to retrieve text for this error. Reason: 15105)
13364정성태6/8/20233682.NET Framework: 2127. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 [1]
13363정성태6/7/20233244스크립트: 49. 파이썬 - "Transformers (신경망 언어모델 라이브러리) 강좌" - 1장 2절 코드 실행 결과
13362정성태6/1/20233165.NET Framework: 2126. C# - 서버 측의 요청 제어 (Microsoft.AspNetCore.RateLimiting)파일 다운로드1
13361정성태5/31/20233639오류 유형: 862. Facebook - ASP.NET/WebClient 사용 시 graph.facebook.com/me 호출에 대해 403 Forbidden 오류
13360정성태5/31/20233037오류 유형: 861. WSL/docker - failed to start shim: start failed: io.containerd.runc.v2: create new shim socket
13359정성태5/19/20233354오류 유형: 860. Docker Desktop - k8s 초기화 무한 반복한다면?
13358정성태5/17/20233662.NET Framework: 2125. C# - Semantic Kernel의 Semantic Memory 사용 예제 [1]파일 다운로드1
13357정성태5/16/20233464.NET Framework: 2124. C# - Semantic Kernel의 Planner 사용 예제파일 다운로드1
13356정성태5/15/20233769DDK: 10. Device Driver 테스트 설치 관련 오류 (Code 37, Code 31) 및 인증서 관련 정리
13355정성태5/12/20233688.NET Framework: 2123. C# - Semantic Kernel의 ChatGPT 대화 구현 [1]파일 다운로드1
13354정성태5/12/20233958.NET Framework: 2122. C# - "Use Unicode UTF-8 for worldwide language support" 설정을 한 경우, 한글 입력이 '\0' 문자로 처리
13352정성태5/12/20233569.NET Framework: 2121. C# - Semantic Kernel의 대화 문맥 유지파일 다운로드1
13351정성태5/11/20234074VS.NET IDE: 185. Visual Studio - 원격 Docker container 내에 실행 중인 응용 프로그램에 대한 디버깅 [1]
13350정성태5/11/20233323오류 유형: 859. Windows Date and Time - Unable to continue. You do not have permission to perform this task
13349정성태5/11/20233663.NET Framework: 2120. C# - Semantic Kernel의 Skill과 Function 사용 예제파일 다운로드1
13348정성태5/10/20233570.NET Framework: 2119. C# - Semantic Kernel의 "Basic Loading of the Kernel" 예제
13347정성태5/10/20233935.NET Framework: 2118. C# - Semantic Kernel의 Prompt chaining 예제파일 다운로드1
1  2  3  4  5  6  7  8  9  [10]  11  12  13  14  15  ...