Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 2개 있습니다.)
(시리즈 글이 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




윈도우 운영체제의 시간 함수 (4) - RTC, TSC, PM Clock, HPET Timer

이번에는, 윈도우 운영체제에서 그 외에 제공되는 하드웨어 타이머 관련 장치를 한번 알아볼 텐데요.

Hardware timer info
; https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#hardware-timer-info

우선, 위의 문서 목록에는 없지만 "RTC"에 대해 잠깐 알아보겠습니다. 이는 장치 관리자의 "System CMOS/real time clock" 항목으로 나오는데,

hardware_timer_0.png

IRQ 8번을 통해 인터럽트 신호를 발생시킵니다. 보통 RTC(real time clock)라고 불리는데 별도로 64바이트의 CMOS RAM을 가지고 있어 "System CMOS/real time clock"으로 불리는 것입니다. 자칫 RTC를 IRQ 0번과 맞물린 PIT와 헷갈리는 경우가 종종 있으니 용어상 주의를 해야 합니다.

RTC가 "Hardware timer info" 문서의 관련 장치에서 빠진, 즉 윈도우가 정밀 시간 측정의 도구로 RTC를 사용하지 않는 것은 그럴만한 이유가 있습니다. RTC의 최고 주파수가 8kHz를 넘지 않는다는 것 외에도, RTC의 IRQ가 8번과 물려 있으므로 다른 인터럽트에 의해 가로채기 당할 수 있으므로 스케줄링 등의 작업에 사용하기에는 안정적이지 않다는 결정적인 이유가 있습니다. 따라서 IRQ 0번의 PIT가 설정되는 환경에서 굳이 RTC를 정밀 시간을 얻는 용도로 사용할 이유가 없습니다.

대신 RTC는 별도의 전원을 통해 항상 켜져 있기 때문에 시스템의 시간을 컴퓨터의 전원이 내려간 상태에서도 유지해주는 역할을 합니다. 이 때문에 보통 RTC의 인터럽트 주기는 1hz로 설정한다고 하며, CMOS에 저장된 시간은 년/월/일/DayOfWeek/DayOfMonth/시/분/초로 0x70, 0x71 I/O 포트를 통해 이 값을 활용하는 것이 전부입니다.

자, 그럼 다음으로 PM Clock과 HPET Timer를 설명해 보겠습니다.

마이크로소프트의 문서에서 이 2가지를 합쳐서 "platform counter"라고 일컫습니다.

PM Clock: The ACPI timer, also known as the PM clock, was added to the system architecture to provide reliable time stamps independently of the processors speed. Because this was the single goal of this timer, it provides a time stamp in a single clock cycle, but it doesn't provide any other functionality.
HPET Timer: The High Precision Event Timer (HPET) was developed jointly by Intel and Microsoft to meet the timing requirements of multimedia and other time-sensitive applications. HPET support has been in Windows since Windows?Vista, and Windows?7 and Windows?8 Hardware Logo certification requires HPET support in the hardware platform.


HPET의 경우 장치 관리자에 "High precision event timer"라고 나옵니다. (PM Timer의 경우 장치 관리자에서 다른 ACPI 장치에 엮인 것인지 독립적인 열거는 없는 것 같습니다.)

hardware_timer_1.png

순서상으로 보면 ACPI 타이머가 먼저 나온 듯하고, 이후에 좀 더 정밀한 시간을 위해 Intel과 Microsoft 간의 협력으로 HPET가 나온 것 같습니다. 그 말인즉, 일반적인 메인보드에 ACPI 타이머가 장착되어 있을 가능성이 Intel과의 협력으로 만들어진 HPET보다 더 높다는 의미가 됩니다.

그런데 윈도우는 QPC(QueryPerformanceCounter) 함수의 기반으로 주로 (잠시 뒤에 설명할) TSC를 사용합니다. 단지, platform counter(HPET or ACPI timer)를 전격적으로 QPC에 도입한 적이 있는데 바로 Windows Vista와 2008이었습니다. 그 외의 윈도우에서는 QPC의 기반을 TSC로 하고 있으며, TSC를 사용할 수 없을 때의 보조적인 용도로 platform counter를 활용합니다.

이에 대해서는 다음의 문서에 나옵니다.

QPC support in Windows versions
; https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#qpc-support-in-windows-versions

자, 그럼 이제부터 말도 많고 탈도 많았던 TSC(Time Stamp Counter)에 대해 알아보겠습니다.

Time Stamp Counter
; https://en.wikipedia.org/wiki/Time_Stamp_Counter

TSC도 HPET와 마찬가지로 최초 제공자는 Intel이었습니다. 구현 방식은 매우 단순한데, CPU에 64비트의 용량으로 "Time Stamp Counter"라는 이름의 레지스터를 하나 제공해 이곳에 CPU Clock cycle의 수를 담고 있는 것이 전부입니다. 근래의 GHz 수준의 CPU를 생각했을 때 cycle 수를 의미하는 TSC는 그야말로 최고의 정밀도를 자랑한다고 해도 무방합니다. 게다가 rdtsc라는 기계어로 CPU 차원에서 그 값을 얻을 수 있도록 해주므로 그 실행 속도 또한 기존의 platform counter에 비해 월등히 빠릅니다. 그럴 수밖에 없는 것이 HPET나 PM Clock은 메인보드에 있는 장치이므로 이 값을 접근하려면 시스템 버스를 타고 들어가는 부하가 발생하기 때문입니다. 실제로 Wikipedia에서 인용한 benchmark에 따르면 천만 번 읽기에 대해 HPET는 12초, ACPI PM timer는 24초가 걸렸지만 tsc는 0.6초라고 하니 실시간 작업에서는 무시 못 할 정도입니다.

그런데, TSC의 문제는 바로 그 cycle을 담고 있다는 특성으로 인해 다중 코어/프로세서 시대가 열리면서 부각되기 시작했습니다.

왜 그런지 간단한 예를 하나 들어볼까요? ^^

__int64 t1 = __rdtsc();
Sleep(1000);
__int64 t2 = __rdtsc();

위와 같은 코드에서 t1을 구하는 시점에 스레드는 0번 코어(또는 프로세서)에서 실행된다고 가정해 보겠습니다. 하지만 Sleep으로 인해 스케줄링 작업이 다시 발생하게 되고 다시 RUN 상태로 진입했을 때는 다른 코어(또는 프로세서)에서 실행되는 것이 가능합니다. TSC 레지스터의 특성상 코어/프로세서별로 있게 되는데, 그럴 수밖에 없는 것이 해당 코어/프로세서의 cycle 수를 담고 있기 때문입니다. 각각의 코어/프로세서가 동작한 시점이 cycle 레벨로 내려가면 정확하게 일치하지 않기 때문에 값의 왜곡이 발생할 수 있습니다. 즉, t1에서는 10이라는 값을 반환받았지만 t2에서는 오히려 8이라는 값이 나올 수 있는 것입니다. (이런 문제를 대응하기 위해 Thread Affinity를 명시하라는 권고를 종종 웹상에서 볼 수 있습니다.)

이를 포함해 TSC의 갖가지 문제점에 대해서는 "Hardware timer info" 문서에 잘 나와 있습니다.

  • 모든 프로세서가 TSC 레지스터를 갖는 것은 아니므로 소스 코드의 이식성이 떨어짐.
  • 일부 프로세서는 (예를 들어, 인텔의 Turbo Boost처럼 특정 조건에서 동적으로 CPU 클럭이 변하는 환경에서) TSC clock 값의 업데이트도 가변으로 바뀐다. 이런 TSC를 "non-invariant TSC"라고 한다. (반면, 동적인 CPU 클럭의 변화에서도 일정한 TSC clock 증가를 하는 경우 이런 TSC를 "invariant TSC" 또는 "constant TSC"라고 한다.)
  • 다중 코어/프로세서 환경에서 일부 시스템은 각각의 코어 간의 TSC 값을 동기화할 수 없다.
  • 프로세서가 너무 많은 경우 프로세서 각각이 "invariant TSC"를 지원한다고 해도 모든 프로세서를 동기화할 수 없는 경우가 있다.
  • 일부 프로세서는 명령의 실행 순서를 바꾸는 기능을 갖는데, 이런 경우 시간을 재기 위해 사용한 rdtsc 명령어가 올바르지 않는 CPU cycle 값을 반환할 수 있다. (이 문제의 대안으로 AMD는 rdtscp 기계어를 도입했고 이후 Intel도 이를 받아들여 자사의 CPU에 적용함.)

이런 이유로 인해 초기 웹상의 자료에서는 TSC 사용에 부정적인 의견을 내는 경우가 많고, 아울러 이를 기반으로 동작하는 윈도우의 QPC(QueryPerformanceCounter)도 사용을 하지 않는 것이 좋다는 의견도 종종 보입니다.

하지만, "QPC support in Windows versions" 문서를 보면 생각이 달라질 수 있습니다.

  • Windows XP and Windows 2000: QPC는 Windows XP와 Windows 2000에 적용되어 있으며 대부분의 시스템에서 잘 동작함. 그러나, 일부 시스템의 경우 BIOS에서 CPU 특성을 제대로 인지하지 못하거나 일부 멀티 코어/프로세서 시스템에서는 코어들 간에 TSC를 동기화하지 못하는 경우가 있음. 펌웨어의 결함을 갖는 시스템에서는 QPC가 TSC를 기반으로 동작하는 경우 코어가 달라지면 동일한 QPC를 제공하지 않을 수 있음.
  • Windows Vista and Windows Server 2008: QPC의 기반으로 TSC가 아닌 platform counter를 사용. 당연히 TSC에 비해 속도가 느리며, 다중 프로세서에서 동시에 호출되는 경우 QPC의 확장을 제한하게 됨.
  • Windows 7 and Windows Server 2008?R2: 이 시절이 되어 나온 대부분의 컴퓨터는 "constant-rate TSC"를 장착. 운영체제가 시스템 초기화 작업 중에 모든 프로세서들의 개별 TSC를 동기화할 수 있는 단일 클럭 시스템이라면 QPC의 기반으로 TSC를 사용함. 그렇지 않은 경우 윈도우는 자동으로 platform counter를 QPC의 기반으로 사용.
  • Windows 8, Windows 8.1, Windows Server 2012, and Windows Server 2012 R2: QPC의 기반으로 TSC를 사용함. 이 운영체제들의 TSC 동기화 알고리즘이 크게 개선되어 다수의 프로세서를 장착한 시스템에서도 보다 더 잘 동작하게 됨.

즉, Windows XP/2000 운영체제가 설치된 구형 컴퓨터를 제외하고는 TSC가 불안하다고 판정되는 경우 platform counter를 사용하도록 되어 있으므로 QPC의 안정성이 보장받게 되는 것입니다. 이런 보완을 모르고 있는 웹상의 많은 자료가 QueryPerformanceCounter를 사용하려면 Thread Affinity를 지정해야 한다고 설명합니다. 물론, 이것은 초기 구형 시스템 및 Windows XP/2000 이전 시절이라면 옳은 의견이지만 그 이후부터는 그럴 필요가 없습니다.

마이크로소프트 역시 가이드 문서에서 일반적으로는 QPC가 다중 코어/프로세서 환경에서 (심지어 서로 다른 스레드 및 프로세스에서 측정한다고 해도) 모든 프로세서들의 경계를 넘어 일관된 결과 값을 반환한다고 명시하고 있습니다. 단지, 다음과 같은 2가지 예외 사항이 있다고 하는데요.

하나는, Vista 나오기 이전의 운영체제에서 "non-invariant TSC"를 가진 CPU를 제대로 인식하지 못해 운영체제의 TSC 동기화 알고리즘이 다수의 프로세서를 가진 시스템을 제대로 동기화하지 못하는 경우가 있다고 합니다. 나머지 하나는 사실 하드웨어적인 특성에서 오는 것으로 오류라고 보기는 애매한 것인데, tick 카운터의 디지털적인 특성으로 인해 서로 다른 스레드에서 동시에 TSC를 얻는 경우 ± 1의 오차가 발생할 수 있다는 것입니다. (두 번째 문제에 대해서는 "마이크로소프트의 가이드 문서"에서 잘 설명하고 있습니다.)

결론을 내리면, 초기 웹상의 자료에서 지적하는 문제와는 달리 QPC의 소스로 TSC가 사용된 경우 값이 왜곡될 수 있는 가능성은 구형 PC의 Windows XP/2000이 아니라면 걱정하지 않아도 된다는 것입니다.




그럼, 이쯤 해서 간략하게 정리해볼까요? ^^

우선, TSC의 경우 CPU 차원에서 지원된다는 특성이 있어 빠릅니다. PM Timer와 HPET의 경우 메인보드 차원에서 지원되므로 TSC보다는 다소 느린 접근 속도를 보여주지만 초기 모델에서 TSC가 가진 단점을 보완합니다. 그 외에 운영체제 차원에서 인터럽트 발생 시 내부적인 시간 카운트 값을 보관하고 있으며 이를 GetTickCount나 timeGetTime으로 제공합니다.

이 정도면 타이머의 속성들이 좀 파악되시죠?!!! ^^

그래서 선택 가이드도 분명해집니다. 1us(마이크로 초) 수준의 정밀도가 필요하고, 외부 타임(예: UTC)과의 연동이 필요 없다면 QueryPerformanceCounter를 이용하면 됩니다. 만약 외부 타임으로의 변환이 필요하다면 (Windows 8부터 지원되는) GetSystemTimePreciseAsFileTime으로 간단하게 해결할 수 있습니다.

그보다 낮은 1ms ~ 15ms의 정밀도가 필요하면 timeBeginPeriod + { timeGetTime | QueryInterruptTime } 조합으로 해결합니다. 단지 외부 타임과의 연동이 필요하면 GetSystemTimeAsFileTime을 이용하면 됩니다.

하지만, 10~16밀리 초 이하의 정밀도만 필요하다면 GetTickCount64를 사용합니다.




하드웨어를 잘 아는 듯한 개발자가 남긴 덧글에 보면,

RTC vs PIT for scheduler
; http://stackoverflow.com/questions/20712085/rtc-vs-pit-for-scheduler

APIC timer가 장착된 대부분의 (i586부터라고 하니) 컴퓨터 환경으로 바뀌었으므로, PIT와 RTC 장치는 이미 구식이라고 합니다. 특히나 HPET가 있는 경우에는 PIT가 물리적으로도 장착되지 않는다고 합니다. 왜냐하면 HPET로도 PIT와 RTC 인터럽트를 대체할 수 있기 때문이라고.




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

[연관 글]






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

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

비밀번호

댓글 작성자
 



2018-02-06 05시35분
[이성환] 안녕하세요~
운영체제의 시간 함수를 다시 정독하고 있습니다. 자세한 분석과 쉬운 설명 감사드립니다. 큰 도움이 되고 있습니다.

그런데 마지막 윈도우 운영체제의 시간 함수 (5) - TSC(Time Stamp Counter)와 QueryPerformanceCounter 이 링크가 현재 문서로 잘못 연결되어 있네요.
수정부탁드립니다. (__)
[guest]
2018-02-07 12시03분
모두 수정했습니다. 감사합니다. ^^
정성태

... [61]  62  63  64  65  66  67  68  69  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12089정성태12/23/201911155디버깅 기술: 146. gflags와 _CrtIsMemoryBlock을 이용한 Heap 메모리 손상 여부 체크
12088정성태12/23/201910128Linux: 28. Linux - 윈도우의 "Run as different user" 기능을 shell에서 실행하는 방법
12087정성태12/21/201910602디버깅 기술: 145. windbg/sos - Dictionary의 entries 배열 내용을 모두 덤프하는 방법 (do_hashtable.py) [1]
12086정성태12/20/201912607디버깅 기술: 144. windbg - Marshal.FreeHGlobal에서 발생한 덤프 분석 사례
12085정성태12/20/201910320오류 유형: 586. iisreset - The data is invalid. (2147942413, 8007000d) 오류 발생 - 두 번째 이야기 [1]
12084정성태12/19/201910988디버깅 기술: 143. windbg/sos - Hashtable의 buckets 배열 내용을 모두 덤프하는 방법 (do_hashtable.py) [1]
12083정성태12/17/201912309Linux: 27. linux - lldb를 이용한 .NET Core 응용 프로그램의 메모리 덤프 분석 방법 [2]
12082정성태12/17/201912056오류 유형: 585. lsof: WARNING: can't stat() fuse.gvfsd-fuse file system
12081정성태12/16/201913771개발 환경 구성: 465. 로컬 PC에서 개발 중인 ASP.NET Core 웹 응용 프로그램을 다른 PC에서도 접근하는 방법 [5]
12080정성태12/16/201911795.NET Framework: 870. C# - 프로세스의 모든 핸들을 열람
12079정성태12/13/201912895오류 유형: 584. 원격 데스크톱(rdp) 환경에서 다중 또는 고용량 파일 복사 시 "Unspecified error" 오류 발생
12078정성태12/13/201912952Linux: 26. .NET Core 응용 프로그램을 위한 메모리 덤프 방법 [3]
12077정성태12/13/201912428Linux: 25. 자주 실행할 명령어 또는 초기 환경을 "~/.bashrc" 파일에 등록
12076정성태12/12/201910662디버깅 기술: 142. Linux - lldb 환경에서 sos 확장 명령어를 이용한 닷넷 프로세스 디버깅 - 배포 방법에 따른 차이
12075정성태12/11/201911532디버깅 기술: 141. Linux - lldb 환경에서 sos 확장 명령어를 이용한 닷넷 프로세스 디버깅
12074정성태12/10/201911105디버깅 기술: 140. windbg/Visual Studio - 값이 변경된 경우를 위한 정지점(BP) 설정(Data Breakpoint)
12073정성태12/10/201912968Linux: 24. Linux/C# - 실행 파일이 아닌 스크립트 형식의 명령어를 Process.Start로 실행하는 방법
12072정성태12/9/201910269오류 유형: 583. iisreset 수행 시 "No such interface supported" 오류
12071정성태12/9/201912650오류 유형: 582. 리눅스 디스크 공간 부족 및 safemode 부팅 방법
12070정성태12/9/201914750오류 유형: 581. resize2fs: Bad magic number in super-block while trying to open /dev/.../root
12069정성태12/2/201911208디버깅 기술: 139. windbg - x64 덤프 분석 시 메서드의 인자 또는 로컬 변수의 값을 확인하는 방법
12068정성태11/28/201914387디버깅 기술: 138. windbg와 Win32 API로 알아보는 Windows Heap 정보 분석 [3]파일 다운로드2
12067정성태11/27/201911215디버깅 기술: 137. 실제 사례를 통해 Debug Diagnostics 도구가 생성한 닷넷 웹 응용 프로그램의 성능 장애 보고서 설명 [1]파일 다운로드1
12066정성태11/27/201911049디버깅 기술: 136. windbg - C# PInvoke 호출 시 마샬링을 담당하는 함수 분석 - OracleCommand.ExecuteReader에서 OpsSql.Prepare2 PInvoke 호출 분석
12065정성태11/25/201910018디버깅 기술: 135. windbg - C# PInvoke 호출 시 마샬링을 담당하는 함수 분석파일 다운로드1
12064정성태11/25/201912124오류 유형: 580. HTTP Error 500.0/500.33 - ANCM In-Process Handler Load Failure
... [61]  62  63  64  65  66  67  68  69  70  71  72  73  74  75  ...