📄 erl_time_sup.c
字号:
/* ``The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved via the world wide web at http://www.erlang.org/. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Initial Developer of the Original Code is Ericsson Utvecklings AB. * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings * AB. All Rights Reserved.'' * * $Id$ *//*** Support routines for the timer wheel**** This code contains two strategies for dealing with ** date/time changes in the system. ** If the system has some kind of high resolution timer (HAVE_GETHRTIME),** the high resolution timer is used to correct the time-of-day and the** timeouts, the base source is the hrtimer, but at certain intervals the ** OS time-of-day is checked and if it is not within certain bounds, the ** delivered time gets slowly adjusted for each call until** it corresponds to the system time (built-in adjtime...). ** The call gethrtime() is detected by autoconf on Unix, but other ** platforms may define it in erl_*_sys.h and implement ** their own high resolution timer. The high resolution timer** strategy is (probably) best on all systems where the timer have ** a resolution higher or equal to gettimeofday (or what's implemented** is sys_gettimeofday()). The actual resolution is the interesting thing,** not the unit's thats used (i.e. on VxWorks, nanoseconds can be** retrieved in terms of units, but the actual resolution is the same as ** for the clock ticks).** If the systems best timer routine is kernel ticks returned from ** sys_times(), and the actual resolution of sys_gettimeofday() is** better (like most unixes that does not have any realtime extensions), ** another strategy is used. The tolerant gettimeofday() corrects ** the value with respect to uptime (sys_times() return value) and checks ** for correction both when delivering timeticks and delivering nowtime.** this strategy is slower, but accurate on systems without better timer ** routines. The kernel tick resolution is not enough to implement** a gethrtime routine. On Linux and other non solaris unix-boxes the second ** strategy is used, on all other platforms we use the first.** ** The following is expected (from sys.[ch] and erl_*_sys.h):**** 64 bit integers. So it is, and so it will be.**** sys_init_time(), will return the clock resolution in MS and** that's about it. More could be added of course** If the clock-rate is constant (i.e. 1 ms) one can define ** SYS_CLOCK_RESOLUTION (to 1),** which makes erts_deliver_time/erts_time_remaining a bit faster.**** if HAVE_GETHRTIME is defined:** sys_gethrtime() will return a SysHrTime (long long) representing ** nanoseconds, sys_init_hrtime() will do any initialization.** else** a long (64bit) integer type called Sint64 should be defined.**** sys_times() will return clock_ticks since start and ** fill in a SysTimes structure (struct tms). Instead of CLK_TCK, ** SYS_CLK_TCK is used to determine the resolution of kernel ticks.**** sys_gettimeofday() will take a SysTimeval (a struct timeval) as parameter** and fill it in as gettimeofday(X,NULL).***/#ifdef HAVE_CONFIG_H# include "config.h"#endif#include "sys.h"#include "erl_vm.h"#include "global.h"static erts_smp_mtx_t erts_timeofday_mtx;static SysTimeval inittv; /* Used everywhere, the initial time-of-day */static SysTimes t_start; /* Used in elapsed_time_both */static SysTimeval gtv; /* Used in wall_clock_elapsed_time_both */static SysTimeval then; /* Used in get_now */#ifdef HAVE_GETHRTIMEint erts_disable_tolerant_timeofday;static SysHrTime hr_init_time, hr_last_correction_check, hr_correction, hr_last_time;static void init_tolerant_timeofday(void){ /* Should be in sys.c */#if defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_CONF) if (sysconf(_SC_NPROCESSORS_CONF) > 1) { char b[1024]; int maj,min,build; os_flavor(b,1024); os_version(&maj,&min,&build); if (!strcmp(b,"sunos") && maj <= 5 && min <= 7) { erts_disable_tolerant_timeofday = 1; } }#endif hr_init_time = sys_gethrtime(); hr_last_correction_check = hr_last_time = hr_init_time; hr_correction = 0;}static void get_tolerant_timeofday(SysTimeval *tv){ SysHrTime diff_time, curr; if (erts_disable_tolerant_timeofday) { sys_gettimeofday(tv); return; } *tv = inittv; diff_time = ((curr = sys_gethrtime()) + hr_correction - hr_init_time) / 1000; if (curr < hr_init_time) { erl_exit(1,"Unexpected behaviour from operating system high " "resolution timer"); } if ((curr - hr_last_correction_check) / 1000 > 1000000) { /* Check the correction need */ SysHrTime tv_diff, diffdiff; SysTimeval tmp; int done = 0; sys_gettimeofday(&tmp); tv_diff = ((SysHrTime) tmp.tv_sec) * 1000000 + tmp.tv_usec; tv_diff -= ((SysHrTime) inittv.tv_sec) * 1000000 + inittv.tv_usec; diffdiff = diff_time - tv_diff; if (diffdiff > 10000) { SysHrTime corr = (curr - hr_last_time) / 100; if (corr / 1000 >= diffdiff) { ++done; hr_correction -= ((SysHrTime)diffdiff) * 1000; } else { hr_correction -= corr; } diff_time = (curr + hr_correction - hr_init_time) / 1000; } else if (diffdiff < -10000) { SysHrTime corr = (curr - hr_last_time) / 100; if (corr / 1000 >= -diffdiff) { ++done; hr_correction -= ((SysHrTime)diffdiff) * 1000; } else { hr_correction += corr; } diff_time = (curr + hr_correction - hr_init_time) / 1000; } else { ++done; } if (done) { hr_last_correction_check = curr; } } tv->tv_sec += (int) (diff_time / ((SysHrTime) 1000000)); tv->tv_usec += (int) (diff_time % ((SysHrTime) 1000000)); if (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; tv->tv_sec += 1; } hr_last_time = curr;}#define correction (hr_correction/1000000)#else /* !HAVE_GETHRTIME */#if !defined(CORRECT_USING_TIMES) #define init_tolerant_timeofday() #define get_tolerant_timeofday(tvp) sys_gettimeofday(tvp)#elsetypedef Sint64 Milli;static clock_t init_ct;static Sint64 ct_wrap;static Milli init_tv_m;static Milli correction_supress; static Milli last_ct_diff;static Milli last_cc; static clock_t last_ct;/* sys_times() might need to be wrapped and the values shifted (right) a bit to cope with newer linux (2.5.*) kernels, this has to be taken care of dynamically to start with, a special version that uses the times() return value as a high resolution timer can be made to fully utilize the faster ticks, like on windows, but for now, we'll settle with this silly workaround */#ifdef ERTS_WRAP_SYS_TIMES #define KERNEL_TICKS() (sys_times_wrap() & \ ((1UL << ((sizeof(clock_t) * 8) - 1)) - 1)) #elseSysTimes dummy_tms;#define KERNEL_TICKS() (sys_times(&dummy_tms) & \ ((1UL << ((sizeof(clock_t) * 8) - 1)) - 1)) #endifstatic void init_tolerant_timeofday(void){ last_ct = init_ct = KERNEL_TICKS(); last_cc = 0; init_tv_m = (((Milli) inittv.tv_sec) * 1000) + (inittv.tv_usec / 1000); ct_wrap = 0; correction_supress = 0;}static void get_tolerant_timeofday(SysTimeval *tvp){ clock_t current_ct; SysTimeval current_tv; Milli ct_diff; Milli tv_diff; Milli current_correction; Milli act_correction; /* long shown to be too small */ Milli max_adjust; if (erts_disable_tolerant_timeofday) { sys_gettimeofday(tvp); return; }#ifdef ERTS_WRAP_SYS_TIMES #define TICK_MS (1000 / SYS_CLK_TCK_WRAP)#else#define TICK_MS (1000 / SYS_CLK_TCK)#endif current_ct = KERNEL_TICKS(); sys_gettimeofday(¤t_tv); /* I dont know if uptime can move some units backwards on some systems, but I allow for small backward jumps to avoid such problems if they exist...*/ if (last_ct > 100 && current_ct < (last_ct - 100)) { ct_wrap += ((Sint64) 1) << ((sizeof(clock_t) * 8) - 1); } last_ct = current_ct; ct_diff = ((ct_wrap + current_ct) - init_ct) * TICK_MS; /* * We will adjust the time in milliseconds and we allow for 1% * adjustments, but if this function is called more often then every 100 * millisecond (which is obviously possible), we will never adjust, so * we accumulate small times by setting last_ct_diff iff max_adjust > 0 */ if ((max_adjust = (ct_diff - last_ct_diff)/100) > 0) last_ct_diff = ct_diff; tv_diff = ((((Milli) current_tv.tv_sec) * 1000) + (current_tv.tv_usec / 1000)) - init_tv_m; current_correction = ((ct_diff - tv_diff) / TICK_MS) * TICK_MS; /* trunc */ /* * We allow the current_correction value to wobble a little, as it * suffers from the low resolution of the kernel ticks. * if it hasn't changed more than one tick in either direction, * we will keep the old value. */ if ((last_cc > current_correction + TICK_MS) || (last_cc < current_correction - TICK_MS)) { last_cc = current_correction; } else { current_correction = last_cc; } /* * As time goes, we try to get the actual correction to 0, * that is, make erlangs time correspond to the systems dito. * The act correction is what we seem to need (current_correction) * minus the correction suppression. The correction supression * will change slowly (max 1% of elapsed time) but in millisecond steps. */ act_correction = current_correction - correction_supress; if (max_adjust > 0) { /* * Here we slowly adjust erlangs time to correspond with the * system time by changing the correction_supress variable. * It can change max_adjust milliseconds which is 1% of elapsed time */ if (act_correction > 0) { if (current_correction - correction_supress > max_adjust) { correction_supress += max_adjust; } else { correction_supress = current_correction; } act_correction = current_correction - correction_supress; } else if (act_correction < 0) { if (correction_supress - current_correction > max_adjust) { correction_supress -= max_adjust; } else { correction_supress = current_correction; } act_correction = current_correction - correction_supress; } } /* * The actual correction will correct the timeval so that system * time warps gets smothed down. */ current_tv.tv_sec += act_correction / 1000; current_tv.tv_usec += (act_correction % 1000) * 1000; if (current_tv.tv_usec >= 1000000) { ++current_tv.tv_sec ; current_tv.tv_usec -= 1000000; } else if (current_tv.tv_usec < 0) { --current_tv.tv_sec; current_tv.tv_usec += 1000000; } *tvp = current_tv;#undef TICK_MS}#endif /* CORRECT_USING_TIMES */#endif /* !HAVE_GETHRTIME *//*** Why this? Well, most platforms have a constant clock resolution of 1,** we dont want the deliver_time/time_remaining routines to waste ** time dividing and multiplying by/with a variable that's always one.** so the return value of sys_init_time is ignored on those platforms.*/ #ifndef SYS_CLOCK_RESOLUTIONstatic int clock_resolution;#define CLOCK_RESOLUTION clock_resolution#else#define CLOCK_RESOLUTION SYS_CLOCK_RESOLUTION#endif/*** The clock resolution should really be the resolution of the ** time function in use, which on most platforms ** is 1. On VxWorks the resolution shold be ** the number of ticks per second (or 1, which would work nicely to).**** Setting lower resolutions is mostly interesting when timers are used** instead of something like select.*/#if defined(ERTS_TIMER_THREAD)static ERTS_INLINE void init_erts_deliver_time(const SysTimeval *inittv) { }static ERTS_INLINE void do_erts_deliver_time(const SysTimeval *current) { }#elsestatic SysTimeval last_delivered; static void init_erts_deliver_time(const SysTimeval *inittv){ /* We set the initial values for deliver_time here */ last_delivered = *inittv; last_delivered.tv_usec = 1000 * (last_delivered.tv_usec / 1000); /* ms resolution */}static void do_erts_deliver_time(const SysTimeval *current){ SysTimeval cur_time; long elapsed; /* calculate and deliver appropriate number of ticks */ cur_time = *current; cur_time.tv_usec = 1000 * (cur_time.tv_usec / 1000); /* ms resolution */ elapsed = (1000 * (cur_time.tv_sec - last_delivered.tv_sec) + (cur_time.tv_usec - last_delivered.tv_usec) / 1000) / CLOCK_RESOLUTION; /* Sometimes the time jump backwards, resulting in a negative elapsed time. We compensate for this by simply pretend as if the time stood still. :) */ if (elapsed > 0) { do_time_add(elapsed); last_delivered = cur_time; }}#endifint erts_init_time_sup(void){ erts_smp_mtx_init(&erts_timeofday_mtx, "timeofday");#ifndef SYS_CLOCK_RESOLUTION clock_resolution = sys_init_time();#else (void) sys_init_time();#endif sys_gettimeofday(&inittv); #ifdef HAVE_GETHRTIME sys_init_hrtime();#endif init_tolerant_timeofday(); init_erts_deliver_time(&inittv); gtv = inittv; then.tv_sec = then.tv_usec = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -