22

CLOCK_MONOTONIC 似乎不可用,因此 clock_gettime 已过期。

我在某些地方读到 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/ http://www.meandmark.com/timing.pdf

谢谢!布雷特

4

3 回答 3

27

Mach 内核提供对系统时钟的访问,其中至少有一个 ( SYSTEM_CLOCK) 被文档宣传为单调递增。

#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当前的实现相同。

文档clock_get_time说时钟是单调递增的,除非有人调用clock_set_time. clock_set_time鼓励调用,因为它可能会破坏时钟的单调性,事实上,当前的实现KERN_FAILURE不做任何事情就返回了。

于 2012-07-27T03:31:29.960 回答
9

在为此查找了几个不同的答案后,我最终定义了一个在马赫上模拟 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;
}
于 2013-12-26T17:54:16.363 回答
4

只需使用Mach Time
它是公共 API,可以在 macOS、iOS 和 tvOS 上运行,并且可以在沙箱中运行。

Mach Time 返回一个我通常称之为“时钟滴答”的抽象时间单位。时钟滴答的长度是系统特定的,取决于 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);

由于马赫时间与任何挂钟时间无关,您可以随意调整系统日期和时间设置,这不会对马赫时间产生任何影响。

但是,有一个特殊的考虑因素可能会使 Mach Time 不适用于某些用例:当您的系统处于睡眠状态时,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 时间那时已经在忙着运行一个线程。

更新 (2018-09-26)

由于 macOS 10.12 (Sierra) 也存在mach_continuous_time. mach_continuous_time和之间的唯一区别mach_absolute_time是,当系统处于睡眠状态时,持续时间也会提前。因此,如果到目前为止这是一个问题并且是不使用 Mach Time 的原因,10.12 及更高版本提供了解决此问题的方法。用法与上面描述的完全相同。

同样从 macOS 10.9 (Mavericks) 开始,有一个mach_approximate_time,在 10.12 中还有一个mach_continuous_approximate_time. 这两个是相同的mach_absolute_timemach_continuous_time唯一的区别是它们更快但不太准确。标准函数需要调用内核,因为内核负责马赫时间。这样的调用有点昂贵,尤其是在已经有Meltdown修复的系统上。近似版本不必总是调用内核。他们在用户空间中使用一个只与内核时钟不时同步的时钟,以防止它运行得太远不同步,但总是可能出现小的偏差,因此它只是“近似的”马赫时间。

于 2016-08-26T09:58:41.930 回答