1/* 2 * tclWinTime.c -- 3 * 4 * Contains Windows specific versions of Tcl functions that 5 * obtain time values from the operating system. 6 * 7 * Copyright 1995-1998 by Sun Microsystems, Inc. 8 * 9 * See the file "license.terms" for information on usage and redistribution 10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES. 11 * 12 * RCS: @(#) $Id: tclWinTime.c,v 1.14.2.11 2007/04/21 19:52:15 kennykb Exp $ 13 */ 14 15#include "tclWinInt.h" 16 17#define SECSPERDAY (60L * 60L * 24L) 18#define SECSPERYEAR (SECSPERDAY * 365L) 19#define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY) 20 21/* 22 * Number of samples over which to estimate the performance counter 23 */ 24#define SAMPLES 64 25 26/* 27 * The following arrays contain the day of year for the last day of 28 * each month, where index 1 is January. 29 */ 30 31static int normalDays[] = { 32 -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 33}; 34 35static int leapDays[] = { 36 -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 37}; 38 39typedef struct ThreadSpecificData { 40 char tzName[64]; /* Time zone name */ 41 struct tm tm; /* time information */ 42} ThreadSpecificData; 43static Tcl_ThreadDataKey dataKey; 44 45/* 46 * Data for managing high-resolution timers. 47 */ 48 49typedef struct TimeInfo { 50 51 CRITICAL_SECTION cs; /* Mutex guarding this structure */ 52 53 int initialized; /* Flag == 1 if this structure is 54 * initialized. */ 55 56 int perfCounterAvailable; /* Flag == 1 if the hardware has a 57 * performance counter */ 58 59 HANDLE calibrationThread; /* Handle to the thread that keeps the 60 * virtual clock calibrated. */ 61 62 HANDLE readyEvent; /* System event used to 63 * trigger the requesting thread 64 * when the clock calibration procedure 65 * is initialized for the first time */ 66 67 HANDLE exitEvent; /* Event to signal out of an exit handler 68 * to tell the calibration loop to 69 * terminate */ 70 71 LARGE_INTEGER nominalFreq; /* Nominal frequency of the system 72 * performance counter, that is, the value 73 * returned from QueryPerformanceFrequency. */ 74 75 /* 76 * The following values are used for calculating virtual time. 77 * Virtual time is always equal to: 78 * lastFileTime + (current perf counter - lastCounter) 79 * * 10000000 / curCounterFreq 80 * and lastFileTime and lastCounter are updated any time that 81 * virtual time is returned to a caller. 82 */ 83 84 ULARGE_INTEGER fileTimeLastCall; 85 LARGE_INTEGER perfCounterLastCall; 86 LARGE_INTEGER curCounterFreq; 87 88 /* 89 * Data used in developing the estimate of performance counter 90 * frequency 91 */ 92 Tcl_WideUInt fileTimeSample[SAMPLES]; 93 /* Last 64 samples of system time */ 94 Tcl_WideInt perfCounterSample[SAMPLES]; 95 /* Last 64 samples of performance counter */ 96 int sampleNo; /* Current sample number */ 97 98 99} TimeInfo; 100 101static TimeInfo timeInfo = { 102 { NULL }, 103 0, 104 0, 105 (HANDLE) NULL, 106 (HANDLE) NULL, 107 (HANDLE) NULL, 108#ifdef HAVE_CAST_TO_UNION 109 (LARGE_INTEGER) (Tcl_WideInt) 0, 110 (ULARGE_INTEGER) (DWORDLONG) 0, 111 (LARGE_INTEGER) (Tcl_WideInt) 0, 112 (LARGE_INTEGER) (Tcl_WideInt) 0, 113#else 114 0, 115 0, 116 0, 117 0, 118#endif 119 { 0 }, 120 { 0 }, 121 0 122}; 123 124CONST static FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE }; 125 126/* 127 * Declarations for functions defined later in this file. 128 */ 129 130static struct tm * ComputeGMT _ANSI_ARGS_((const time_t *tp)); 131static void StopCalibration _ANSI_ARGS_(( ClientData )); 132static DWORD WINAPI CalibrationThread _ANSI_ARGS_(( LPVOID arg )); 133static void UpdateTimeEachSecond _ANSI_ARGS_(( void )); 134static void ResetCounterSamples _ANSI_ARGS_(( 135 Tcl_WideUInt fileTime, 136 Tcl_WideInt perfCounter, 137 Tcl_WideInt perfFreq 138 )); 139static Tcl_WideInt AccumulateSample _ANSI_ARGS_(( 140 Tcl_WideInt perfCounter, 141 Tcl_WideUInt fileTime 142 )); 143 144/* 145 *---------------------------------------------------------------------- 146 * 147 * TclpGetSeconds -- 148 * 149 * This procedure returns the number of seconds from the epoch. 150 * On most Unix systems the epoch is Midnight Jan 1, 1970 GMT. 151 * 152 * Results: 153 * Number of seconds from the epoch. 154 * 155 * Side effects: 156 * None. 157 * 158 *---------------------------------------------------------------------- 159 */ 160 161unsigned long 162TclpGetSeconds() 163{ 164 Tcl_Time t; 165 Tcl_GetTime( &t ); 166 return t.sec; 167} 168 169/* 170 *---------------------------------------------------------------------- 171 * 172 * TclpGetClicks -- 173 * 174 * This procedure returns a value that represents the highest 175 * resolution clock available on the system. There are no 176 * guarantees on what the resolution will be. In Tcl we will 177 * call this value a "click". The start time is also system 178 * dependant. 179 * 180 * Results: 181 * Number of clicks from some start time. 182 * 183 * Side effects: 184 * None. 185 * 186 *---------------------------------------------------------------------- 187 */ 188 189unsigned long 190TclpGetClicks() 191{ 192 /* 193 * Use the Tcl_GetTime abstraction to get the time in microseconds, 194 * as nearly as we can, and return it. 195 */ 196 197 Tcl_Time now; /* Current Tcl time */ 198 unsigned long retval; /* Value to return */ 199 200 Tcl_GetTime( &now ); 201 retval = ( now.sec * 1000000 ) + now.usec; 202 return retval; 203 204} 205 206/* 207 *---------------------------------------------------------------------- 208 * 209 * TclpGetTimeZone -- 210 * 211 * Determines the current timezone. The method varies wildly 212 * between different Platform implementations, so its hidden in 213 * this function. 214 * 215 * Results: 216 * Minutes west of GMT. 217 * 218 * Side effects: 219 * None. 220 * 221 *---------------------------------------------------------------------- 222 */ 223 224int 225TclpGetTimeZone (currentTime) 226 Tcl_WideInt currentTime; 227{ 228 int timeZone; 229 230 tzset(); 231 timeZone = _timezone / 60; 232 233 return timeZone; 234} 235 236/* 237 *---------------------------------------------------------------------- 238 * 239 * Tcl_GetTime -- 240 * 241 * Gets the current system time in seconds and microseconds 242 * since the beginning of the epoch: 00:00 UCT, January 1, 1970. 243 * 244 * Results: 245 * Returns the current time in timePtr. 246 * 247 * Side effects: 248 * On the first call, initializes a set of static variables to 249 * keep track of the base value of the performance counter, the 250 * corresponding wall clock (obtained through ftime) and the 251 * frequency of the performance counter. Also spins a thread 252 * whose function is to wake up periodically and monitor these 253 * values, adjusting them as necessary to correct for drift 254 * in the performance counter's oscillator. 255 * 256 *---------------------------------------------------------------------- 257 */ 258 259void 260Tcl_GetTime(timePtr) 261 Tcl_Time *timePtr; /* Location to store time information. */ 262{ 263 struct timeb t; 264 265 int useFtime = 1; /* Flag == TRUE if we need to fall back 266 * on ftime rather than using the perf 267 * counter */ 268 269 /* Initialize static storage on the first trip through. */ 270 271 /* 272 * Note: Outer check for 'initialized' is a performance win 273 * since it avoids an extra mutex lock in the common case. 274 */ 275 276 if ( !timeInfo.initialized ) { 277 TclpInitLock(); 278 if ( !timeInfo.initialized ) { 279 timeInfo.perfCounterAvailable 280 = QueryPerformanceFrequency( &timeInfo.nominalFreq ); 281 282 /* 283 * Some hardware abstraction layers use the CPU clock 284 * in place of the real-time clock as a performance counter 285 * reference. This results in: 286 * - inconsistent results among the processors on 287 * multi-processor systems. 288 * - unpredictable changes in performance counter frequency 289 * on "gearshift" processors such as Transmeta and 290 * SpeedStep. 291 * 292 * There seems to be no way to test whether the performance 293 * counter is reliable, but a useful heuristic is that 294 * if its frequency is 1.193182 MHz or 3.579545 MHz, it's 295 * derived from a colorburst crystal and is therefore 296 * the RTC rather than the TSC. 297 * 298 * A sloppier but serviceable heuristic is that the RTC crystal 299 * is normally less than 15 MHz while the TSC crystal is 300 * virtually assured to be greater than 100 MHz. Since Win98SE 301 * appears to fiddle with the definition of the perf counter 302 * frequency (perhaps in an attempt to calibrate the clock?) 303 * we use the latter rule rather than an exact match. 304 */ 305 306 if ( timeInfo.perfCounterAvailable 307 /* The following lines would do an exact match on 308 * crystal frequency: 309 * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 1193182 310 * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 3579545 311 */ 312 && timeInfo.nominalFreq.QuadPart > (Tcl_WideInt) 15000000 ) { 313 314 /* 315 * As an exception, if every logical processor on the system 316 * is on the same chip, we use the performance counter anyway, 317 * presuming that everyone's TSC is locked to the same 318 * oscillator. 319 */ 320 321 SYSTEM_INFO systemInfo; 322 unsigned int regs[4]; 323 GetSystemInfo( &systemInfo ); 324 if ( TclWinCPUID( 0, regs ) == TCL_OK 325 326 && regs[1] == 0x756e6547 /* "Genu" */ 327 && regs[3] == 0x49656e69 /* "ineI" */ 328 && regs[2] == 0x6c65746e /* "ntel" */ 329 330 && TclWinCPUID( 1, regs ) == TCL_OK 331 332 && ( (regs[0] & 0x00000F00) == 0x00000F00 /* Pentium 4 */ 333 || ( (regs[0] & 0x00F00000) /* Extended family */ 334 && (regs[3] & 0x10000000) ) ) /* Hyperthread */ 335 && ( ( ( regs[1] & 0x00FF0000 ) >> 16 ) /* CPU count */ 336 == systemInfo.dwNumberOfProcessors ) 337 338 ) { 339 timeInfo.perfCounterAvailable = TRUE; 340 } else { 341 timeInfo.perfCounterAvailable = FALSE; 342 } 343 344 } 345 346 /* 347 * If the performance counter is available, start a thread to 348 * calibrate it. 349 */ 350 351 if ( timeInfo.perfCounterAvailable ) { 352 DWORD id; 353 InitializeCriticalSection( &timeInfo.cs ); 354 timeInfo.readyEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); 355 timeInfo.exitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); 356 timeInfo.calibrationThread = CreateThread( NULL, 357 256, 358 CalibrationThread, 359 (LPVOID) NULL, 360 0, 361 &id ); 362 SetThreadPriority( timeInfo.calibrationThread, 363 THREAD_PRIORITY_HIGHEST ); 364 365 /* 366 * Wait for the thread just launched to start running, 367 * and create an exit handler that kills it so that it 368 * doesn't outlive unloading tclXX.dll 369 */ 370 371 WaitForSingleObject( timeInfo.readyEvent, INFINITE ); 372 CloseHandle( timeInfo.readyEvent ); 373 Tcl_CreateExitHandler( StopCalibration, (ClientData) NULL ); 374 } 375 timeInfo.initialized = TRUE; 376 } 377 TclpInitUnlock(); 378 } 379 380 if ( timeInfo.perfCounterAvailable ) { 381 /* 382 * Query the performance counter and use it to calculate the 383 * current time. 384 */ 385 386 LARGE_INTEGER curCounter; 387 /* Current performance counter */ 388 389 Tcl_WideInt curFileTime; 390 /* Current estimated time, expressed 391 * as 100-ns ticks since the Windows epoch */ 392 393 static LARGE_INTEGER posixEpoch; 394 /* Posix epoch expressed as 100-ns ticks 395 * since the windows epoch */ 396 397 Tcl_WideInt usecSincePosixEpoch; 398 /* Current microseconds since Posix epoch */ 399 400 posixEpoch.LowPart = 0xD53E8000; 401 posixEpoch.HighPart = 0x019DB1DE; 402 403 EnterCriticalSection( &timeInfo.cs ); 404 405 QueryPerformanceCounter( &curCounter ); 406 407 /* 408 * If it appears to be more than 1.1 seconds since the last trip 409 * through the calibration loop, the performance counter may 410 * have jumped forward. (See MSDN Knowledge Base article 411 * Q274323 for a description of the hardware problem that makes 412 * this test necessary.) If the counter jumps, we don't want 413 * to use it directly. Instead, we must return system time. 414 * Eventually, the calibration loop should recover. 415 */ 416 if ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart 417 < 11 * timeInfo.curCounterFreq.QuadPart / 10 ) { 418 419 curFileTime = timeInfo.fileTimeLastCall.QuadPart 420 + ( ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart ) 421 * 10000000 / timeInfo.curCounterFreq.QuadPart ); 422 timeInfo.fileTimeLastCall.QuadPart = curFileTime; 423 timeInfo.perfCounterLastCall.QuadPart = curCounter.QuadPart; 424 usecSincePosixEpoch = ( curFileTime - posixEpoch.QuadPart ) / 10; 425 timePtr->sec = (long) ( usecSincePosixEpoch / 1000000 ); 426 timePtr->usec = (unsigned long ) ( usecSincePosixEpoch % 1000000 ); 427 useFtime = 0; 428 } 429 430 LeaveCriticalSection( &timeInfo.cs ); 431 } 432 433 if ( useFtime ) { 434 /* High resolution timer is not available. Just use ftime */ 435 436 ftime(&t); 437 timePtr->sec = (long)t.time; 438 timePtr->usec = t.millitm * 1000; 439 } 440} 441 442/* 443 *---------------------------------------------------------------------- 444 * 445 * StopCalibration -- 446 * 447 * Turns off the calibration thread in preparation for exiting the 448 * process. 449 * 450 * Results: 451 * None. 452 * 453 * Side effects: 454 * Sets the 'exitEvent' event in the 'timeInfo' structure to ask 455 * the thread in question to exit, and waits for it to do so. 456 * 457 *---------------------------------------------------------------------- 458 */ 459 460static void 461StopCalibration( ClientData unused ) 462 /* Client data is unused */ 463{ 464 SetEvent( timeInfo.exitEvent ); 465 WaitForSingleObject( timeInfo.calibrationThread, INFINITE ); 466 CloseHandle( timeInfo.exitEvent ); 467 CloseHandle( timeInfo.calibrationThread ); 468} 469 470/* 471 *---------------------------------------------------------------------- 472 * 473 * TclpGetTZName -- 474 * 475 * Gets the current timezone string. 476 * 477 * Results: 478 * Returns a pointer to a static string, or NULL on failure. 479 * 480 * Side effects: 481 * None. 482 * 483 *---------------------------------------------------------------------- 484 */ 485 486char * 487TclpGetTZName(int dst) 488{ 489 size_t len; 490 char *zone, *p; 491 TIME_ZONE_INFORMATION tz; 492 Tcl_Encoding encoding; 493 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 494 char *name = tsdPtr->tzName; 495 496 /* 497 * tzset() under Borland doesn't seem to set up tzname[] at all. 498 * tzset() under MSVC has the following weird observed behavior: 499 * First time we call "clock format [clock seconds] -format %Z -gmt 1" 500 * we get "GMT", but on all subsequent calls we get the current time 501 * zone string, even though env(TZ) is GMT and the variable _timezone 502 * is 0. 503 */ 504 505 name[0] = '\0'; 506 507 zone = getenv("TZ"); 508 if (zone != NULL) { 509 /* 510 * TZ is of form "NST-4:30NDT", where "NST" would be the 511 * name of the standard time zone for this area, "-4:30" is 512 * the offset from GMT in hours, and "NDT is the name of 513 * the daylight savings time zone in this area. The offset 514 * and DST strings are optional. 515 */ 516 517 len = strlen(zone); 518 if (len > 3) { 519 len = 3; 520 } 521 if (dst != 0) { 522 /* 523 * Skip the offset string and get the DST string. 524 */ 525 526 p = zone + len; 527 p += strspn(p, "+-:0123456789"); 528 if (*p != '\0') { 529 zone = p; 530 len = strlen(zone); 531 if (len > 3) { 532 len = 3; 533 } 534 } 535 } 536 Tcl_ExternalToUtf(NULL, NULL, zone, (int)len, 0, NULL, name, 537 sizeof(tsdPtr->tzName), NULL, NULL, NULL); 538 } 539 if (name[0] == '\0') { 540 if (GetTimeZoneInformation(&tz) == TIME_ZONE_ID_UNKNOWN) { 541 /* 542 * MSDN: On NT this is returned if DST is not used in 543 * the current TZ 544 */ 545 dst = 0; 546 } 547 encoding = Tcl_GetEncoding(NULL, "unicode"); 548 Tcl_ExternalToUtf(NULL, encoding, 549 (char *) ((dst) ? tz.DaylightName : tz.StandardName), -1, 550 0, NULL, name, sizeof(tsdPtr->tzName), NULL, NULL, NULL); 551 Tcl_FreeEncoding(encoding); 552 } 553 return name; 554} 555 556/* 557 *---------------------------------------------------------------------- 558 * 559 * TclpGetDate -- 560 * 561 * This function converts between seconds and struct tm. If 562 * useGMT is true, then the returned date will be in Greenwich 563 * Mean Time (GMT). Otherwise, it will be in the local time zone. 564 * 565 * Results: 566 * Returns a static tm structure. 567 * 568 * Side effects: 569 * None. 570 * 571 *---------------------------------------------------------------------- 572 */ 573 574struct tm * 575TclpGetDate(t, useGMT) 576 TclpTime_t t; 577 int useGMT; 578{ 579 const time_t *tp = (const time_t *) t; 580 struct tm *tmPtr; 581 time_t time; 582 583 if (!useGMT) { 584 tzset(); 585 586 /* 587 * If we are in the valid range, let the C run-time library 588 * handle it. Otherwise we need to fake it. Note that this 589 * algorithm ignores daylight savings time before the epoch. 590 */ 591 592 if (*tp >= 0) { 593 return localtime(tp); 594 } 595 596 time = *tp - _timezone; 597 598 /* 599 * If we aren't near to overflowing the long, just add the bias and 600 * use the normal calculation. Otherwise we will need to adjust 601 * the result at the end. 602 */ 603 604 if (*tp < (LONG_MAX - 2 * SECSPERDAY) 605 && *tp > (LONG_MIN + 2 * SECSPERDAY)) { 606 tmPtr = ComputeGMT(&time); 607 } else { 608 tmPtr = ComputeGMT(tp); 609 610 tzset(); 611 612 /* 613 * Add the bias directly to the tm structure to avoid overflow. 614 * Propagate seconds overflow into minutes, hours and days. 615 */ 616 617 time = tmPtr->tm_sec - _timezone; 618 tmPtr->tm_sec = (int)(time % 60); 619 if (tmPtr->tm_sec < 0) { 620 tmPtr->tm_sec += 60; 621 time -= 60; 622 } 623 624 time = tmPtr->tm_min + time/60; 625 tmPtr->tm_min = (int)(time % 60); 626 if (tmPtr->tm_min < 0) { 627 tmPtr->tm_min += 60; 628 time -= 60; 629 } 630 631 time = tmPtr->tm_hour + time/60; 632 tmPtr->tm_hour = (int)(time % 24); 633 if (tmPtr->tm_hour < 0) { 634 tmPtr->tm_hour += 24; 635 time -= 24; 636 } 637 638 time /= 24; 639 tmPtr->tm_mday += (int)time; 640 tmPtr->tm_yday += (int)time; 641 tmPtr->tm_wday = (tmPtr->tm_wday + (int)time) % 7; 642 } 643 } else { 644 tmPtr = ComputeGMT(tp); 645 } 646 return tmPtr; 647} 648 649/* 650 *---------------------------------------------------------------------- 651 * 652 * ComputeGMT -- 653 * 654 * This function computes GMT given the number of seconds since 655 * the epoch (midnight Jan 1 1970). 656 * 657 * Results: 658 * Returns a (per thread) statically allocated struct tm. 659 * 660 * Side effects: 661 * Updates the values of the static struct tm. 662 * 663 *---------------------------------------------------------------------- 664 */ 665 666static struct tm * 667ComputeGMT(tp) 668 const time_t *tp; 669{ 670 struct tm *tmPtr; 671 long tmp, rem; 672 int isLeap; 673 int *days; 674 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 675 676 tmPtr = &tsdPtr->tm; 677 678 /* 679 * Compute the 4 year span containing the specified time. 680 */ 681 682 tmp = (long)(*tp / SECSPER4YEAR); 683 rem = (LONG)(*tp % SECSPER4YEAR); 684 685 /* 686 * Correct for weird mod semantics so the remainder is always positive. 687 */ 688 689 if (rem < 0) { 690 tmp--; 691 rem += SECSPER4YEAR; 692 } 693 694 /* 695 * Compute the year after 1900 by taking the 4 year span and adjusting 696 * for the remainder. This works because 2000 is a leap year, and 697 * 1900/2100 are out of the range. 698 */ 699 700 tmp = (tmp * 4) + 70; 701 isLeap = 0; 702 if (rem >= SECSPERYEAR) { /* 1971, etc. */ 703 tmp++; 704 rem -= SECSPERYEAR; 705 if (rem >= SECSPERYEAR) { /* 1972, etc. */ 706 tmp++; 707 rem -= SECSPERYEAR; 708 if (rem >= SECSPERYEAR + SECSPERDAY) { /* 1973, etc. */ 709 tmp++; 710 rem -= SECSPERYEAR + SECSPERDAY; 711 } else { 712 isLeap = 1; 713 } 714 } 715 } 716 tmPtr->tm_year = tmp; 717 718 /* 719 * Compute the day of year and leave the seconds in the current day in 720 * the remainder. 721 */ 722 723 tmPtr->tm_yday = rem / SECSPERDAY; 724 rem %= SECSPERDAY; 725 726 /* 727 * Compute the time of day. 728 */ 729 730 tmPtr->tm_hour = rem / 3600; 731 rem %= 3600; 732 tmPtr->tm_min = rem / 60; 733 tmPtr->tm_sec = rem % 60; 734 735 /* 736 * Compute the month and day of month. 737 */ 738 739 days = (isLeap) ? leapDays : normalDays; 740 for (tmp = 1; days[tmp] < tmPtr->tm_yday; tmp++) { 741 } 742 tmPtr->tm_mon = --tmp; 743 tmPtr->tm_mday = tmPtr->tm_yday - days[tmp]; 744 745 /* 746 * Compute day of week. Epoch started on a Thursday. 747 */ 748 749 tmPtr->tm_wday = (long)(*tp / SECSPERDAY) + 4; 750 if ((*tp % SECSPERDAY) < 0) { 751 tmPtr->tm_wday--; 752 } 753 tmPtr->tm_wday %= 7; 754 if (tmPtr->tm_wday < 0) { 755 tmPtr->tm_wday += 7; 756 } 757 758 return tmPtr; 759} 760 761/* 762 *---------------------------------------------------------------------- 763 * 764 * CalibrationThread -- 765 * 766 * Thread that manages calibration of the hi-resolution time 767 * derived from the performance counter, to keep it synchronized 768 * with the system clock. 769 * 770 * Parameters: 771 * arg -- Client data from the CreateThread call. This parameter 772 * points to the static TimeInfo structure. 773 * 774 * Return value: 775 * None. This thread embeds an infinite loop. 776 * 777 * Side effects: 778 * At an interval of 1 s, this thread performs virtual time discipline. 779 * 780 * Note: When this thread is entered, TclpInitLock has been called 781 * to safeguard the static storage. There is therefore no synchronization 782 * in the body of this procedure. 783 * 784 *---------------------------------------------------------------------- 785 */ 786 787static DWORD WINAPI 788CalibrationThread( LPVOID arg ) 789{ 790 FILETIME curFileTime; 791 DWORD waitResult; 792 793 /* Get initial system time and performance counter */ 794 795 GetSystemTimeAsFileTime( &curFileTime ); 796 QueryPerformanceCounter( &timeInfo.perfCounterLastCall ); 797 QueryPerformanceFrequency( &timeInfo.curCounterFreq ); 798 timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime; 799 timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime; 800 801 ResetCounterSamples( timeInfo.fileTimeLastCall.QuadPart, 802 timeInfo.perfCounterLastCall.QuadPart, 803 timeInfo.curCounterFreq.QuadPart ); 804 805 /* 806 * Wake up the calling thread. When it wakes up, it will release the 807 * initialization lock. 808 */ 809 810 SetEvent( timeInfo.readyEvent ); 811 812 /* Run the calibration once a second */ 813 814 for ( ; ; ) { 815 816 /* If the exitEvent is set, break out of the loop. */ 817 818 waitResult = WaitForSingleObjectEx(timeInfo.exitEvent, 1000, FALSE); 819 if ( waitResult == WAIT_OBJECT_0 ) { 820 break; 821 } 822 UpdateTimeEachSecond(); 823 } 824 825 /* lint */ 826 return (DWORD) 0; 827} 828 829/* 830 *---------------------------------------------------------------------- 831 * 832 * UpdateTimeEachSecond -- 833 * 834 * Callback from the waitable timer in the clock calibration thread 835 * that updates system time. 836 * 837 * Parameters: 838 * info -- Pointer to the static TimeInfo structure 839 * 840 * Results: 841 * None. 842 * 843 * Side effects: 844 * Performs virtual time calibration discipline. 845 * 846 *---------------------------------------------------------------------- 847 */ 848 849static void 850UpdateTimeEachSecond() 851{ 852 853 LARGE_INTEGER curPerfCounter; 854 /* Current value returned from 855 * QueryPerformanceCounter */ 856 857 FILETIME curSysTime; /* Current system time */ 858 859 LARGE_INTEGER curFileTime; /* File time at the time this callback 860 * was scheduled. */ 861 862 Tcl_WideInt estFreq; /* Estimated perf counter frequency */ 863 864 Tcl_WideInt vt0; /* Tcl time right now */ 865 Tcl_WideInt vt1; /* Tcl time one second from now */ 866 867 Tcl_WideInt tdiff; /* Difference between system clock and 868 * Tcl time. */ 869 870 Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time 871 * into step over 1 second */ 872 873 /* 874 * Sample performance counter and system time. 875 */ 876 877 QueryPerformanceCounter( &curPerfCounter ); 878 GetSystemTimeAsFileTime( &curSysTime ); 879 curFileTime.LowPart = curSysTime.dwLowDateTime; 880 curFileTime.HighPart = curSysTime.dwHighDateTime; 881 882 EnterCriticalSection( &timeInfo.cs ); 883 884 /* 885 * Several things may have gone wrong here that have to 886 * be checked for. 887 * (1) The performance counter may have jumped. 888 * (2) The system clock may have been reset. 889 * 890 * In either case, we'll need to reinitialize the circular buffer 891 * with samples relative to the current system time and the NOMINAL 892 * performance frequency (not the actual, because the actual has 893 * probably run slow in the first case). Our estimated frequency 894 * will be the nominal frequency. 895 */ 896 897 /* 898 * Store the current sample into the circular buffer of samples, 899 * and estimate the performance counter frequency. 900 */ 901 902 estFreq = AccumulateSample( curPerfCounter.QuadPart, 903 (Tcl_WideUInt) curFileTime.QuadPart ); 904 905 /* 906 * We want to adjust things so that time appears to be continuous. 907 * Virtual file time, right now, is 908 * 909 * vt0 = 10000000 * ( curPerfCounter - perfCounterLastCall ) 910 * / curCounterFreq 911 * + fileTimeLastCall 912 * 913 * Ideally, we would like to drift the clock into place over a 914 * period of 2 sec, so that virtual time 2 sec from now will be 915 * 916 * vt1 = 20000000 + curFileTime 917 * 918 * The frequency that we need to use to drift the counter back into 919 * place is estFreq * 20000000 / ( vt1 - vt0 ) 920 */ 921 922 vt0 = 10000000 * ( curPerfCounter.QuadPart 923 - timeInfo.perfCounterLastCall.QuadPart ) 924 / timeInfo.curCounterFreq.QuadPart 925 + timeInfo.fileTimeLastCall.QuadPart; 926 vt1 = 20000000 + curFileTime.QuadPart; 927 928 /* 929 * If we've gotten more than a second away from system time, 930 * then drifting the clock is going to be pretty hopeless. 931 * Just let it jump. Otherwise, compute the drift frequency and 932 * fill in everything. 933 */ 934 935 tdiff = vt0 - curFileTime.QuadPart; 936 if ( tdiff > 10000000 || tdiff < -10000000 ) { 937 timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart; 938 timeInfo.curCounterFreq.QuadPart = estFreq; 939 } else { 940 driftFreq = estFreq * 20000000 / ( vt1 - vt0 ); 941 if ( driftFreq > 1003 * estFreq / 1000 ) { 942 driftFreq = 1003 * estFreq / 1000; 943 } 944 if ( driftFreq < 997 * estFreq / 1000 ) { 945 driftFreq = 997 * estFreq / 1000; 946 } 947 timeInfo.fileTimeLastCall.QuadPart = vt0; 948 timeInfo.curCounterFreq.QuadPart = driftFreq; 949 } 950 951 timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart; 952 953 LeaveCriticalSection( &timeInfo.cs ); 954 955} 956 957/* 958 *---------------------------------------------------------------------- 959 * 960 * ResetCounterSamples -- 961 * 962 * Fills the sample arrays in 'timeInfo' with dummy values that will 963 * yield the current performance counter and frequency. 964 * 965 * Results: 966 * None. 967 * 968 * Side effects: 969 * The array of samples is filled in so that it appears that there 970 * are SAMPLES samples at one-second intervals, separated by precisely 971 * the given frequency. 972 * 973 *---------------------------------------------------------------------- 974 */ 975 976static void 977ResetCounterSamples( Tcl_WideUInt fileTime, 978 /* Current file time */ 979 Tcl_WideInt perfCounter, 980 /* Current performance counter */ 981 Tcl_WideInt perfFreq ) 982 /* Target performance frequency */ 983{ 984 int i; 985 for ( i = SAMPLES-1; i >= 0; --i ) { 986 timeInfo.perfCounterSample[i] = perfCounter; 987 timeInfo.fileTimeSample[i] = fileTime; 988 perfCounter -= perfFreq; 989 fileTime -= 10000000; 990 } 991 timeInfo.sampleNo = 0; 992} 993 994/* 995 *---------------------------------------------------------------------- 996 * 997 * AccumulateSample -- 998 * 999 * Updates the circular buffer of performance counter and system 1000 * time samples with a new data point. 1001 * 1002 * Results: 1003 * None. 1004 * 1005 * Side effects: 1006 * The new data point replaces the oldest point in the circular 1007 * buffer, and the descriptive statistics are updated to accumulate 1008 * the new point. 1009 * 1010 * Several things may have gone wrong here that have to 1011 * be checked for. 1012 * (1) The performance counter may have jumped. 1013 * (2) The system clock may have been reset. 1014 * 1015 * In either case, we'll need to reinitialize the circular buffer 1016 * with samples relative to the current system time and the NOMINAL 1017 * performance frequency (not the actual, because the actual has 1018 * probably run slow in the first case). 1019 */ 1020 1021static Tcl_WideInt 1022AccumulateSample( Tcl_WideInt perfCounter, 1023 Tcl_WideUInt fileTime ) 1024{ 1025 Tcl_WideUInt workFTSample; /* File time sample being removed 1026 * from or added to the circular buffer */ 1027 1028 Tcl_WideInt workPCSample; /* Performance counter sample being 1029 * removed from or added to the circular 1030 * buffer */ 1031 1032 Tcl_WideUInt lastFTSample; /* Last file time sample recorded */ 1033 1034 Tcl_WideInt lastPCSample; /* Last performance counter sample recorded */ 1035 1036 Tcl_WideInt FTdiff; /* Difference between last FT and current */ 1037 1038 Tcl_WideInt PCdiff; /* Difference between last PC and current */ 1039 1040 Tcl_WideInt estFreq; /* Estimated performance counter frequency */ 1041 1042 /* Test for jumps and reset the samples if we have one. */ 1043 1044 if ( timeInfo.sampleNo == 0 ) { 1045 lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo 1046 + SAMPLES - 1 ]; 1047 lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo 1048 + SAMPLES - 1 ]; 1049 } else { 1050 lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo - 1 ]; 1051 lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo - 1 ]; 1052 } 1053 PCdiff = perfCounter - lastPCSample; 1054 FTdiff = fileTime - lastFTSample; 1055 if ( PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10 1056 || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10 1057 || FTdiff < 9000000 1058 || FTdiff > 11000000 ) { 1059 ResetCounterSamples( fileTime, perfCounter, 1060 timeInfo.nominalFreq.QuadPart ); 1061 return timeInfo.nominalFreq.QuadPart; 1062 1063 } else { 1064 1065 /* Estimate the frequency */ 1066 1067 workPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo ]; 1068 workFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo ]; 1069 estFreq = 10000000 * ( perfCounter - workPCSample ) 1070 / ( fileTime - workFTSample ); 1071 timeInfo.perfCounterSample[ timeInfo.sampleNo ] = perfCounter; 1072 timeInfo.fileTimeSample[ timeInfo.sampleNo ] = (Tcl_WideInt) fileTime; 1073 1074 /* Advance the sample number */ 1075 1076 if ( ++timeInfo.sampleNo >= SAMPLES ) { 1077 timeInfo.sampleNo = 0; 1078 } 1079 1080 return estFreq; 1081 } 1082} 1083 1084/* 1085 *---------------------------------------------------------------------- 1086 * 1087 * TclpGmtime -- 1088 * 1089 * Wrapper around the 'gmtime' library function to make it thread 1090 * safe. 1091 * 1092 * Results: 1093 * Returns a pointer to a 'struct tm' in thread-specific data. 1094 * 1095 * Side effects: 1096 * Invokes gmtime or gmtime_r as appropriate. 1097 * 1098 *---------------------------------------------------------------------- 1099 */ 1100 1101struct tm * 1102TclpGmtime( tt ) 1103 TclpTime_t_CONST tt; 1104{ 1105 CONST time_t *timePtr = (CONST time_t *) tt; 1106 /* Pointer to the number of seconds 1107 * since the local system's epoch */ 1108 /* 1109 * The MS implementation of gmtime is thread safe because 1110 * it returns the time in a block of thread-local storage, 1111 * and Windows does not provide a Posix gmtime_r function. 1112 */ 1113 return gmtime( timePtr ); 1114} 1115 1116/* 1117 *---------------------------------------------------------------------- 1118 * 1119 * TclpLocaltime -- 1120 * 1121 * Wrapper around the 'localtime' library function to make it thread 1122 * safe. 1123 * 1124 * Results: 1125 * Returns a pointer to a 'struct tm' in thread-specific data. 1126 * 1127 * Side effects: 1128 * Invokes localtime or localtime_r as appropriate. 1129 * 1130 *---------------------------------------------------------------------- 1131 */ 1132 1133struct tm * 1134TclpLocaltime( tt ) 1135 TclpTime_t_CONST tt; 1136{ 1137 CONST time_t *timePtr = (CONST time_t *) tt; 1138 /* Pointer to the number of seconds 1139 * since the local system's epoch */ 1140 1141 /* 1142 * The MS implementation of localtime is thread safe because 1143 * it returns the time in a block of thread-local storage, 1144 * and Windows does not provide a Posix localtime_r function. 1145 */ 1146 return localtime( timePtr ); 1147} 1148