Getting OS Information - The KUSER_SHARED_DATA Structure
Windows Research Kernel @ HPIEver asked how Windows API retrieves the current time, the version of the OS, or whether an evaluation period has expired? This structure will answers some of these questions.
Recently, a colleague of mine, Martin von Löwis, found an article
in Google about Windows timing and this article used an artifact of
the KUSER_SHARED_DATA
structure. Well, as we have the
sources, let's have a closer look to that structure (see
public\sdk\inc\ntexapi.h). (As the WRK license agreement limits the
number of lines of code that can be shown in one piece to 50 lines,
I left out the comments.)
02229 typedef struct _KUSER_SHARED_DATA { 02237 ULONG TickCountLowDeprecated; 02238 ULONG TickCountMultiplier; 02244 volatile KSYSTEM_TIME InterruptTime; 02250 volatile KSYSTEM_TIME SystemTime; 02256 volatile KSYSTEM_TIME TimeZoneBias; 02264 USHORT ImageNumberLow; 02265 USHORT ImageNumberHigh; 02271 WCHAR NtSystemRoot[260]; 02277 ULONG MaxStackTraceDepth; 02283 ULONG CryptoExponent; 02289 ULONG TimeZoneId; 02290 ULONG LargePageMinimum; 02291 ULONG Reserved2[7]; 02297 NT_PRODUCT_TYPE NtProductType; 02298 BOOLEAN ProductTypeIsValid; 02309 ULONG NtMajorVersion; 02310 ULONG NtMinorVersion; 02316 BOOLEAN ProcessorFeatures[PROCESSOR_FEATURE_MAX]; 02322 ULONG Reserved1; 02323 ULONG Reserved3; 02329 volatile ULONG TimeSlip; 02335 ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture; 02344 LARGE_INTEGER SystemExpirationDate; 02350 ULONG SuiteMask; 02356 BOOLEAN KdDebuggerEnabled; 02362 UCHAR NXSupportPolicy; 02368 volatile ULONG ActiveConsoleId; 02376 volatile ULONG DismountCount; 02384 ULONG ComPlusPackage; 02392 ULONG LastSystemRITEventTickCount; 02399 ULONG NumberOfPhysicalPages; 02405 BOOLEAN SafeBootMode; 02413 ULONG TraceLogging; 02422 ULONGLONG TestRetInstruction; 02423 ULONG SystemCall; 02424 ULONG SystemCallReturn; 02425 ULONGLONG SystemCallPad[3]; 02431 union { 02432 volatile KSYSTEM_TIME TickCount; 02433 volatile ULONG64 TickCountQuad; 02434 }; 02440 ULONG Cookie; 02446 ULONG Wow64SharedInformation[MAX_WOW64_SHARED_ENTRIES]; 02447 02448 } KUSER_SHARED_DATA, *PKUSER_SHARED_DATA;
While I cannot say the meaning and usage of all fields, I will concentrate on the most interesting ones.
Windows Times
Windows maintains three types of time: The interrupt time (line
2244), the system time (line 2250), and the system's tick count
(lines 2431 thru 2434). While the first ones represent a real time
value in units of 100 ns, the latter is just a counter starting at
zero when the machine boots. In tests using the
KUSER_SHARED_DATA
we figured out that the tick count is
incremented each 15.625 ms.
The interrupt time is the only Windows clock that
guarantees to be monotonous that is, its value only increases over
timer. Its value represents the time in units of 100 ns since the
system was booted. The interrupt time is the base clock for all
timers in Windows (see my recent article
A Bug in Windows Timer Management). It is updated every clock
interrupt.
In contrast, the system time represents the time in units
of 100 ns since January 1, 1601. It always represents your local
time, not UTC (Universal Coordinated Time). The system time does not
guarantee to be monotonous, which means it might "jump" backwards.
(Just think of the Daylight Saving time adjustment or NTP.)
Now, let's have a closer look at the data structure that is used to hold the interrupt time and the system time (see public\sdk\inc\ntkeapi.h):
00048 typedef struct _KSYSTEM_TIME { 00049 ULONG LowPart; 00050 LONG High2Time; 00051 LONG High3Time; 00052 } KSYSTEM_TIME, *PKSYSTEM_TIME;
As you can see, the time is stored as a 64-bit value:
High2Time:LowPart
. But why is there another HighPart
(High3Time
) field in the structure (line 51)? To be
short, it is for synchronization purposes. The three time values are
updated directly by the clock interrupt service routine (ISR). An
ISR must not acquire any lock because it must complete as fast as
possible and thus must not block. However, to ensure applications
(Win32) to read a consistent time value, the order in which the
members of KSYSTEM_TIME
are updated has a very strict
manner. The ISR first updates High3Time
, then
LowPart
, and finally High2Time
. A
consuming application must read the structure the same strict but
inverse order, that is, it first reads High2Time
, then
LowPart
, and finally High3Time
. If
High2Time
and High3Time
are equal, the
High2Time:LowPart
is a consistent value. Otherwise the
application was interrupted by the clock interrupt and needs to read
the structure again.
Access the Structure in Your Application
The most important thing I need to tell you about the
KUSER_SHARED_DATA
structure is that it is exported to
user address space. See files base\ntos\inc\i386.h or
base\ntos\inc\amd64.h, respectively, depending on your target
architecture. Refer to the definition of
KI_USER_SHARED_DATA
. This constant holds the virtual
address, where the single instance of KUSER_SHARE_DATA
is mapped to. For example, on an x86 architecture, it is mapped to
0xFFDF0000
. But be careful with using this structure!
It is subject to change and it might have a completely different
structure on Windows XP or Windows Vista. Although I think this is
pretty improbable because of its strong usage inside the kernel, you
must be aware of that fact.
If you already have experiences with KUSER_SHARED_DATA, don't hesitate to comment and share your knowledge among the community.
Comments
5 Responses to "Getting OS Information - The KUSER_SHARED_DATA Structure"
This structure is documented in the WDK, along with the full structure and comments up to Vista… there's no use in using the WRK for this or hiding any details.
[...] For details on how to access USER_SHARED_DATA check this Post , for example. >>>>>>PASTE>>>>>>>>>> // // Define [...]
[...] it already: http://afatkulin.blogspot.com/ He has some good Oracle internals information in there,Windows Research Kernel @ HPI - news, howtos and …Windows 7 and Server 2008 R2 Kernel Changes (TechEd Europe 2009) Resolved: A Performance … [...]
"The interrupt time is the only Windows clock that guarantees to be monotonous that is, its value only increases over time"
Shouldn't the tick count also be a monotonically increasing value?
@Dan: If the tick count is based on the same data as GetTickCount (or the other way around, as I believe it to be), then there can be a separate tick count per processor, and they are not guaranteed to be synchronized (and in fact usually are not). If two calls are made to obtain tick counts from different processors, the second one can have a (slightly) smaller value than the first call, i.e. tick counts can go backwards.
This is a serious problem for code that needs a simple incrementing tick count, such as the code I write. The only workable solution I have found so far is to use a separate timer thread that sleeps for 1 ms, then wakes and updates a global variable with the current tick count. The key here is to then limit the processor affinity of that thread. However, some environments may not support the affinity calls. (I have been told that the Windows affinity calls in apps running under Amazon S3 may not be ignored, but I have not confirmed this yet.) So I have been looking for a Windows-supported simple tick count for some time. I was unaware of this KUSER_SHARED_DATA structure and I'm hoping the InterruptTime can be used instead of GetTickCount without requiring processor affinity limitations in order for it to be a simple increment.