上一篇的原始碼看得十分無趣,官方文件跟黑心棉一樣渣。
這一篇講講windows作業系統上的時間戳實現,由於類的宣告,方法解釋上一篇都貼過了,所以這次直接上對應版本的程式碼。
windows與mac很不一樣,實現了一個新的Clock類來管理時間,如下。
// We implement time using the high-resolution timers so that we can get // timeouts which are smaller than 10-15ms. To avoid any drift, we // periodically resync the internal clock to the system clock. class Clock final { public: Clock() : initial_ticks_(GetSystemTicks()), initial_time_(GetSystemTime()) {} Time Now() { /* */ } Time NowFromSystemTime() { /* */ } private: static TimeTicks GetSystemTicks() { /* */ } static Time GetSystemTime() { /* */ } TimeTicks initial_ticks_; Time initial_time_; Mutex mutex_; };
從註釋和方法名可以看出,windows完全用這個新類代替了老的Time、TimeTicks,因為這個方法擁有更好的效能,這個類同時會週期性的與系統時間同步資料。
下面正式開始。
先從Now方法看起,看windows系統是如何獲取本地的時間戳。
DEFINE_LAZY_LEAKY_OBJECT_GETTER(Clock, GetClock) #define DEFINE_LAZY_LEAKY_OBJECT_GETTER(T, FunctionName, ...) \ T* FunctionName() { \ static ::v8::base::LeakyObject<T> object{__VA_ARGS__}; \ return object.get(); \ } Time Time::Now() { return GetClock()->Now(); }
這個方法的定義也不一般,直接用了一個特殊巨集,巨集就不展開了,簡單說就是懶載入,呼叫的時候會分配空間生成一個Clock類,初始化完後第二次呼叫就直接返回了,當成一個單例來理解。
直接看巨集的返回型別,剛好是上面的Clock,該類只有一個無參建構函式,初始化兩個時間戳屬性。
先看後那個,也就是系統時間的時間戳。
static Time GetSystemTime() { FILETIME ft; ::GetSystemTimeAsFileTime(&ft); return Time::FromFiletime(ft); }
這裡的FILETIME和GetSystemTimeAsFileTime都是windowsAPI,可以獲取當前系統的日期和時間,但是返回值很奇怪。
typedef struct _FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME, *PFILETIME, *LPFILETIME;
Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC).
這是結構體的宣告與解釋,High、Low分別代表時間的高位與地位,而那個方法就是配合這個使用的。
可以從上面看到,這個API返回的時間竟然是從1601年1月1日開始算的,不知道那一年發生了什麼。
下面寫一個測試程式碼。
int main() { FILETIME ft; LARGE_INTEGER t; ::GetSystemTimeAsFileTime(&ft); t.LowPart = ft.dwLowDateTime; t.HighPart = ft.dwHighDateTime; cout << t.QuadPart << endl; }
得到的輸出為132034487665022709,由於單位是100納秒,所以這個數字乘以100的,然後換算一下。
由於基準是1601年,而Date是從1970年開始算,所以年份上差了369年,剛好是2019,很合理。
來看看V8的處理。
// Time between windows epoch and standard epoch. static const int64_t kTimeToEpochInMicroseconds = int64_t{11644473600000000}; Time Time::FromFiletime(FILETIME ft) { // 特殊情況處理 if (ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0) { return Time(); } if (ft.dwLowDateTime == std::numeric_limits<DWORD>::max() && ft.dwHighDateTime == std::numeric_limits<DWORD>::max()) { return Max(); } // 換算 int64_t us = (static_cast<uint64_t>(ft.dwLowDateTime) + (static_cast<uint64_t>(ft.dwHighDateTime) << 32)) / 10; return Time(us - kTimeToEpochInMicroseconds); }
前面的特殊情況看看就行了,主要是換算這一步,就是簡單的將高低位的數值拼到了一起,除以10之後,單位從100納秒變成了微秒。
最後的計算,也是為了平衡標準的時間戳和windows時間戳兩者的差異,如下。
為什麼不是1970 - 1601 = 369年整呢?因為中間有閏年,很合理。
最後得到微秒單位的標準時間戳,將該數值賦到類的屬性上。
回到最初的Now方法,初始化完後,會呼叫Clock自身的Now方法獲取最終的時間戳,如下。
Time Now() { // 一個誤差臨界值 const TimeDelta kMaxElapsedTime = TimeDelta::FromMinutes(1); // 我目前不想解析所有關於鎖的東西 MutexGuard lock_guard(&mutex_); // 再次獲取當前的硬體時間戳與本地時間戳 TimeTicks ticks = GetSystemTicks(); Time time = GetSystemTime(); // 這裡進行誤差修正 TimeDelta elapsed = ticks - initial_ticks_; // 1.當前時間小於初始化時間 可參考上一篇中類方法的註釋(the system might adjust its clock...) // 2.硬體時間戳的時間差超過臨界值 這種情況基本可以認定初始化的時間完全不可信了 if (time < initial_time_ || elapsed > kMaxElapsedTime) { initial_ticks_ = ticks; initial_time_ = time; return time; } return initial_time_ + elapsed; }
雖然在建構函式中獲取了時間戳,但是V8考慮到由於函式呼叫、系統修正等原因導致的誤差(比如第一次初始化),再次進行了修正,具體操作和原因可以直接看註釋,最後返回的時間戳是計算獲得的理論本地時間戳加上硬體時間戳差值。
至於NewFromSystemTime就比較簡單了,在mac中這兩個方法是一個,在windows裡如下。
Time NowFromSystemTime() { MutexGuard lock_guard(&mutex_); // 更新兩個時間戳 initial_ticks_ = GetSystemTicks(); initial_time_ = GetSystemTime(); // 直接返回最新獲得的時間戳 return initial_time_; }
不計算任何東西,直接返回系統API的時間戳,可以配合註釋來理解這兩個方法。
尷尬了,沒想到V8在Time階段把兩個時間戳全用上了。稍微看了一下TimeTicks的實現,發現還有點意思,所以這一篇先這樣了,太長了寫的累。