2012-07-27 75 views
18

CLOCK_MONOTONIC看起來不可用,所以clock_gettime不存在。OSX上的單調時鐘

我在某些地方看過mach_absolute_time()可能是正確的路要走,但在讀完它是'cpu依賴值'之後,它立即讓我懷疑它是否在下面使用了rtdsc。因此,即使它是單調的,該值也會隨時間漂移。此外,線程關聯問題可能會導致調用該函數的有意義的不同結果(使其在所有內核中都不是單調的)。

當然,這只是猜測。有誰知道mach_absolute_time實際上是如何工作的?實際上,我正在尋找替代clock_gettime(CLOCK_MONOTONIC ...)或類似的OSX的東西,不管什麼時鐘源,我期望至少有毫秒的精度和毫秒的準確度。瞭解什麼時鐘可用,哪些時鐘是單調的,如果某些時鐘漂移,具有線程相關性問題,在所有Mac硬件上都不支持,或者執行「超高」數量的cpu週期來執行。我能找到關於這個主題的鏈接(有些已經是死鏈接,在archive.org上找不到):

https://developer.apple.com/library/mac/#qa/qa1398/_index.html http://www.wand.net .nz /〜smr26/wordpress/2009/01/19/monotonic-time-in-mac-os-x Brett

+0

據我所知,'CLOCK_MONOTONIC'不保證該值不會漂移,也不存在線程關聯問題。 – zneak 2012-07-27 02:05:56

+3

稍微超過我的腦海,但'mach_absolute_time'確實使用rtdsc,可以從[source](http://www.opensource.apple.com/source/Libc/Libc-320.1.3/i386/mach /mach_absolute_time.c「mach_absolute_time.c」)。 – cobbal 2012-07-27 02:06:54

+0

@cobbal:感謝您的發現!這絕對是爲我規定mach_absolute_time。它可用於快速,短時的測量,因爲該值可能會漂移。 – Brett 2012-07-27 02:11:26

回答

22

Mach內核提供對系統時鐘的訪問,其中至少有一個(SYSTEM_CLOCK)爲advertised by the documentation爲單調遞增。

#include <mach/clock.h> 
#include <mach/mach.h> 

clock_serv_t cclock; 
mach_timespec_t mts; 

host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); 
clock_get_time(cclock, &mts); 
mach_port_deallocate(mach_task_self(), cclock); 

mach_timespec_t具有納秒精度。不過,我不確定其準確性。

Mac OS X支持三個時鐘:

  • SYSTEM_CLOCK返回自啓動時間的時間;
  • CALENDAR_CLOCK返回自1970-01-01以來的UTC時間;
  • REALTIME_CLOCK已過時,與當前實施中的SYSTEM_CLOCK相同。

documentation for clock_get_time說時鐘是單調遞增,除非有人撥打clock_set_time。致電clock_set_time的是discouraged,因爲它可能打破時鐘的單調特性,實際上,the current implementation returns KERN_FAILURE沒有做任何事情。

+0

看起來'mach_task_self()'不再存在,有'mach_task_self_'。感謝你的回答! – Zhao 2015-08-25 23:51:57

+0

這是哪個操作系統? El Capitan? – zneak 2015-08-26 11:34:29

+0

對不起,這是約塞米蒂10.10.5。我正在做iOS開發。我安裝了最新的iOS 9 SDK。我的部署目標是8.0 – Zhao 2015-08-26 18:23:53

8

找了幾個不同的答案對這個後,我最終確立其模擬clock_gettime馬赫頭:

#include <sys/types.h> 
#include <sys/_types/_timespec.h> 
#include <mach/mach.h> 
#include <mach/clock.h> 

#ifndef mach_time_h 
#define mach_time_h 

/* The opengroup spec isn't clear on the mapping from REALTIME to CALENDAR 
being appropriate or not. 
http://pubs.opengroup.org/onlinepubs/009695299/basedefs/time.h.html */ 

// XXX only supports a single timer 
#define TIMER_ABSTIME -1 
#define CLOCK_REALTIME CALENDAR_CLOCK 
#define CLOCK_MONOTONIC SYSTEM_CLOCK 

typedef int clockid_t; 

/* the mach kernel uses struct mach_timespec, so struct timespec 
    is loaded from <sys/_types/_timespec.h> for compatability */ 
// struct timespec { time_t tv_sec; long tv_nsec; }; 

int clock_gettime(clockid_t clk_id, struct timespec *tp); 

#endif 

和mach_gettime.c

#include "mach_gettime.h" 
#include <mach/mach_time.h> 

#define MT_NANO (+1.0E-9) 
#define MT_GIGA UINT64_C(1000000000) 

// TODO create a list of timers, 
static double mt_timebase = 0.0; 
static uint64_t mt_timestart = 0; 

// TODO be more careful in a multithreaded environement 
int clock_gettime(clockid_t clk_id, struct timespec *tp) 
{ 
    kern_return_t retval = KERN_SUCCESS; 
    if(clk_id == TIMER_ABSTIME) 
    { 
     if (!mt_timestart) { // only one timer, initilized on the first call to the TIMER 
      mach_timebase_info_data_t tb = { 0 }; 
      mach_timebase_info(&tb); 
      mt_timebase = tb.numer; 
      mt_timebase /= tb.denom; 
      mt_timestart = mach_absolute_time(); 
     } 

     double diff = (mach_absolute_time() - mt_timestart) * mt_timebase; 
     tp->tv_sec = diff * MT_NANO; 
     tp->tv_nsec = diff - (tp->tv_sec * MT_GIGA); 
    } 
    else // other clk_ids are mapped to the coresponding mach clock_service 
    { 
     clock_serv_t cclock; 
     mach_timespec_t mts; 

     host_get_clock_service(mach_host_self(), clk_id, &cclock); 
     retval = clock_get_time(cclock, &mts); 
     mach_port_deallocate(mach_task_self(), cclock); 

     tp->tv_sec = mts.tv_sec; 
     tp->tv_nsec = mts.tv_nsec; 
    } 

    return retval; 
} 
+0

將此複製到要點以供將來參考:https://gist.github.com/alfwatt/3588c5aa1f7a1ef7a3bb – alfwatt 2014-08-29 17:40:40

+1

但是,預先劃分mt_timebase可能會對準確性產生不利影響。不在x86上,數字和denom都是1,但在ARM上。 – Aktau 2014-09-16 09:54:45

2

只需使用馬赫時間
它是公共API,它可以在macOS,iOS和tvOS上運行,並且可以在沙箱內運行。

馬赫時間返回一個抽象時間單位,我通常稱其爲「時鐘刻度」。時鐘滴答的長度是系統特定的,取決於CPU。在當前的英特爾系統上,時鐘節拍實際上只有一個納秒,但您不能依賴於此(對於ARM可能不同,對於PowerPC CPU它肯定不同)。該系統還可以告訴您轉換因子,將時鐘滴答轉換爲納秒和納秒以作爲時鐘滴答(這個因子是靜態的,在運行時不會改變)。當系統啓動時,時鐘開始於0,然後隨着每個時鐘週期單調增加,因此您還可以使用Mach Time來獲得系統的正常運行時間(當然,正常運行時間是單調的!)。

下面是一些代碼:

#include <stdio.h> 
#include <inttypes.h> 
#include <mach/mach_time.h> 

int main () { 
    uint64_t clockTicksSinceSystemBoot = mach_absolute_time(); 
    printf("Clock ticks since system boot: %"PRIu64"\n", 
     clockTicksSinceSystemBoot 
    ); 

    static mach_timebase_info_data_t timebase; 
    mach_timebase_info(&timebase); 
    // Cast to double is required to make this a floating point devision, 
    // otherwise it would be an interger division and only the result would 
    // be converted to floating point! 
    double clockTicksToNanosecons = (double)timebase.numer/timebase.denom; 

    uint64_t systemUptimeNanoseconds = (uint64_t)(
     clockTicksToNanosecons * clockTicksSinceSystemBoot 
    ); 
    uint64_t systemUptimeSeconds = systemUptimeNanoseconds/(1000 * 1000 * 1000); 
    printf("System uptime: %"PRIu64" seconds\n", systemUptimeSeconds); 
} 

你也可以把一個線程睡眠狀態,直到一定時間馬赫已經達到。下面是一些代碼:

// Sleep for 750 ns 
uint64_t machTimeNow = mach_absolute_time(); 
uint64_t clockTicksToSleep = (uint64_t)(750/clockTicksToNanosecons); 
uint64_t machTimeIn750ns = machTimeNow + clockTicksToSleep; 
mach_wait_until(machTimeIn750ns); 

馬赫時間沒有聯繫任何時鐘時間,你可以玩你的系統日期和時間設置,只要你喜歡,這不會對馬赫時間任何影響。

雖然有一個特殊的考慮,可能會使馬赫時間不適合某些使用情況:T 當您的系統睡着時CPU時鐘不運行!因此,如果您讓線程等待5分鐘,1分鐘後系統進入睡眠狀態並保持睡眠30分鐘,則線程在系統醒來後仍等待4分鐘,因爲30分鐘的睡眠時間不會計數!在那段時間CPU時鐘也在休息。然而在其他情況下,這正是你想要發生的事情。

馬赫時間也是測量時間的一種非常精確的方法。下面是顯示該任務的一些代碼:再次甚至

// Measure time 
uint64_t machTimeBegin = mach_absolute_time(); 
sleep(1); 
uint64_t machTimeEnd = mach_absolute_time(); 
uint64_t machTimePassed = machTimeEnd - machTimeBegin; 
uint64_t timePassedNS = (uint64_t)(
    machTimePassed * clockTicksToNanosecons 
); 
printf("Thread slept for: %"PRIu64" ns\n", timePassedNS); 

你會看到該線程不睡了整整一秒,這是因爲它需要一些時間來把一個線程睡眠,喚醒回來當喚醒時,如果當前所有核心已經在忙於運行線程,它將不會立即獲得CPU時間。