2012-07-27 76 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时间。