성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>윈도우 운영체제의 시간 함수 (1) - GetTickCount와 timeGetTime의 차이점</h1> <p> 윈도우의 기본 타이머 정확도는 15.625ms(15,625,000ns)입니다. 이는 타이머 디바이스가 PIC 컨트롤러의 (우선 순위가 가장 높은) IRQ 0번을 통해,<br /> <br /> <img alt='gettick_1.png' src='/SysWebRes/bbs/gettick_1.png' /><br /> <br /> 인터럽트하는 횟수에 기반을 둡니다. 이 타이머 디바이스를 PIT(Programmable Interval Timer)라고도 하는데 이름에서처럼 PIT 컨트롤러의 레지스터에 주기를 설정해 인터럽트 발생 횟수를 제어할 수 있습니다. 다시 말해, 윈도우는 발생 주기의 기본 값을 64Hz로 설정하고 있는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 주기 = 1초 / 64hz = 0.015625(sec) </pre> <br /> 타이머의 주기는 다음의 Win32 API를 이용해 임의로 조정하는 것이 가능한데요. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > timeBeginPeriod function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod'>https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod</a> NtSetTimerResolution ; <a target='tab' href='http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html'>http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FTime%2FNtSetTimerResolution.html</a> ; <a target='tab' href='https://web.archive.org/web/20220715225118/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</a> </pre> <br /> 실제로 일부 프로그램들은 이를 사용하고 있습니다. 이제는 아니라고 하지만 과거에는 그랬던 Google의 크롬 웹 브라우저나, Microsoft SQL Server 등이 그렇다고 합니다. (제 컴퓨터에서 자주 사용하는 것 중에는 snagit이 1ms로 주기를 설정합니다.)<br /> <br /> 그 예로, 제 데스크톱 컴퓨터에서 <a target='tab' href='https://learn.microsoft.com/en-us/sysinternals/downloads/clockres'>sysinternals의 clockres.exe</a>를 실행해 보면 다음과 같이 (초당 1000번의 타이머 인터럽트가 발생하도록) 1ms로 조정된 것을 확인할 수 있습니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 <span style='color: blue; font-weight: bold'>Current timer interval: 1.001 ms</span> </pre> <br /> 반면, timer interrupt에 영향을 주는 프로그램이 실행되지 않은 상태에서는 기본값 그대로 15.625로 맞춰져 있습니다.<br /> <br /> 데스크톱 PC 같은 환경이라면 주기의 조정이 별로 문제가 안되는데, 노트북의 경우 초당 인터럽트가 64번에서 1,000번으로 늘어난다는 것은 곧 배터리의 수명이 그만큼 줄어든다는 것을 의미합니다.<br /> <br /> 어떤 프로그램들이 여러분들의 전원 소비를 높이는지 찾고 싶다면 다음의 글에 나온 "powercfg -energy duration 5"와 같은 명령어를 통해 분석할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Windows Timer Resolution: Megawatts Wasted ; <a target='tab' href='https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/'>https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/</a> </pre> <br /> 참고로, 최소 타이머 간격인 0.5ms가 타이머 디바이스의 제약은 아닙니다. PIT 컨트롤러의 내부 클럭은 1.193182Mhz로 동작하는데 이는 약 838.095 나노초 단위가 됩니다. 하지만, 운영체제에서는 타이머 인터럽트에 기반해 수행되는 작업의 부담으로 0.5ms까지만 가능하게 정한 듯합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 기본 타이머 정확도가 15.625ms라는 것은 과연 어떤 의미가 있을까요?<br /> <br /> 일반적으로 CPU는 GHz의 속도로 동작하며 기계어를 실행시킵니다. 하지만, 운영체제 입장에서는 스레드 스케줄링 등의 작업을 위해 CPU의 GHz 단위에 맞춰 작업을 할 수 없습니다. 그랬다가는 운영체제를 실행하는 것만으로 CPU가 바쁠 것이기 때문입니다.<br /> <br /> 따라서, 운영체제는 타이머 인터럽트에 맞춰 스케줄링 등의 작업을 합니다. 또한 내부적으로 운영체제가 부팅 이후 지난 시간을 기록하는 변수를 업데이트하기도 합니다. 이를 달리 말하면, "부팅 이후 지난 시간이 기록된 변수"의 값이 15.625ms가 지나서야 한 번씩 업데이트된다는 것입니다.<br /> <br /> 일례로 윈도우의 GetTickCount 함수는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > GetTickCount function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount'>https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount</a> </pre> <br /> 운영체제가 시작된 후 흐른 시간을 밀리초 단위로 반환하지만, 정작 운영체제는 15.625ms가 지나야 GetTickCount가 반환할 시간 값 변수를 업데이트하므로 타이머 인터럽트를 처리하는 중간에 GetTickCount를 호출하게 되면 동일한 값을 반환하다가 그다음 타이머 인터럽트에서는 대략 15.625ms가 지난 값을 반환할 것으로 예상할 수 있습니다.<br /> <br /> 눈으로 확인하기 위해 GetTickCount를 다음과 같이 사용해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" #include <Windows.h> int main() { int count = 100; while (count -- > 0) { printf("%d\n", ::GetTickCount()); } return 0; } </pre> <br /> 출력 결과는 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 510617234 ...[생략: 61번 같은 값 반복]... 510617250 ...[생략]... </pre> <br /> 같은 값이 반복되다가 값이 한번 바뀐 시점의 차이를 보면 510617250 - 510617234 = 16이 나오고 예상할 수 있듯이 단위는 밀리초입니다. 즉, 타이머 인터럽트 시간인 15.625ms에 맞춰서 업데이트가 된 것입니다.<br /> <br /> 이런 특성을 이해하지 못하는 일부 개발자들은, GetTickCount가 밀리초 단위의 값을 반환하므로 윈도우에서 밀리초 단위로 시간을 잴 수 있다고 말하기도 합니다. 정확히 말하면, 밀리초 단위로 값을 반환받기는 하지만 1 밀리초 단위로 업데이트되는 것은 아니라는 점입니다.<br /> <br /> 재미있는 점이 하나 있는데, GetTickCount는 변화된 Timer Interrupt에는 영향을 받지 않는다는 것입니다. (혹시 이유를 정확히 아시는 분은 덧글 부탁드립니다. ^^) 아마도 GetTickCount가 Windows 95/98 때부터 있었던 Win32 API라서 하위 호환성을 위해 이후의 운영체제에서는 내부적인 보정을 거치는 것 같은데요.<br /> <br /> 일례로, "Current timer interval"이 "1.0ms"로 설정된 PC에서 테스트를 해봐도 여전히 약 16ms 단위로 값이 변경되는 것을 볼 수 있습니다. <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount'>문서상으로는 GetTickCount</a>가 "The resolution of the GetTickCount function is limited to the resolution of the system timer"라고 되어 있는데 "system timer"는 장치 관리자에서 봤던 것처럼 IRQ 0번에 물린 PIT를 의미한다고 했을 때 다소 혼란스러운 면이 있습니다.<br /> <br/> (<a target='tab' href='https://litux.nl/mirror/kerneldevelopment/0672327201/ch10lev1sec3.html'>리눅스의 jiffies 값</a>이 윈도우의 GetTickCount가 반환하는 값과 유사한 개념이고, 보다 정확히는 아래에서 설명할 timeGetTime이 반환하는 값과 같습니다.) <br /> <hr style='width: 50%' /><br /> <br /> 오히려, timer interrupt에 직접적으로 반응하는 것은 timeGetTime 함수입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > timeGetTime function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timegettime'>https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timegettime</a> </pre> <br /> 이것 역시 GetTickCount처럼 윈도우가 시작된 이후 흐른 시간을 밀리초 단위로 반환하는 역할을 합니다. 따라서, (clockres.exe가 15.625ms로 반환하는) 기본 Timer Interrupt를 갖는 윈도우에서 다음의 코드를 실행하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" #include <Windows.h> #pragma comment(lib, "winmm.lib") int main() { int count = 100; while (count -- > 0) { printf("%d\n", ::<span style='color: blue; font-weight: bold'>timeGetTime</span>()); } return 0; } </pre> <br /> 출력 결과는 이렇고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 2478593 ...[생략: 43번 같은 값 반복]... 2478608 ...[생략: 46번 같은 값 반복]... 2478624 ...[생략]... </pre> <br /> 중간에 튀는 값을 보면 15, 16으로 GetTickCount와 마찬가지로 15.625ms 단위로 변화하는 것을 볼 수 있습니다.<br /> <br /> 이전에 제가 타이머 인터럽트 정확도를 timeBeginPeriod 함수를 이용해 변경할 수 있다고 했는데요. 이를 1ms 단위로 설정해 주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" #include <Windows.h> #pragma comment(lib, "winmm.lib") int main() { int count = 100; <span style='color: blue; font-weight: bold'>timeBeginPeriod(1);</span> // 주기를 1ms로 설정 while (count -- > 0) { printf("%d\n", ::timeGetTime()); } <span style='color: blue; font-weight: bold'>timeEndPeriod(1);</span> // 설정된 주기를 해제 return 0; } </pre> <br /> 이제 timeGetTime의 출력 결과는 1ms 단위로 변화하는 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 2801569 2801570 ...[생략]... 2801570 2801571 2801571 2801572 2801572 2801572 2801573 2801573 2801573 2801574 2801574 2801574 2801575 2801575 2801575 2801576 ...[생략]... </pre> <br /> 주의할 것은, timeBeginPeriod 설정은 시스템 전역적으로 설정되기 때문에 여러분들의 프로그램에서 호출하지 않았다고 해도 현재 실행되고 있는 다른 프로그램이 timeBeginPeriod(1)을 호출한 상태라면 timeGetTime은 그런 경우에도 1ms 단위의 정밀도로 동작하게 됩니다.<br /> <br /> 즉, 어떤 프로그램이든지 timeBeginPeriod로 timer interrupt를 변경하게 되면 clockres.exe의 결과로도 확인할 수 있는 것입니다.<br /> <br /> 이 정도면... GetTickCount와 timeGetTime의 차이점이 명확해졌겠죠?!!! ^^<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1065&boardid=331301885'>첨부 파일은 이 글의 테스트 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> ClockRes.exe의 리눅스 버전은 단순히 config 파일에서 조회할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > $ <span style='color: blue; font-weight: bold'>cat /boot/config-`uname -r` | grep CONFIG_HZ</span> # 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 </pre> <br /> 즉, 위의 시스템은 1/250=0.004로 약 4ms마다 스레드 스케줄링을 하는 것입니다. (<a target='tab' href='https://www.programmersought.com/article/65045842885/'>Linux kernel time management and kernel timer</a>)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2104
(왼쪽의 숫자를 입력해야 합니다.)