Get the precise time of system bootup on iOS/OS X

2019-01-22 22:57发布

Is there an API to obtain the NSDate or NSTimeInterval representing the time the system booted? Some APIs such as [NSProcessInfo systemUptime] and Core Motion return time since boot. I need to precisely correlate these uptime values with NSDates, to about a millisecond.

Time since boot ostensibly provides more precision, but it's easy to see that NSDate already provides precision on the order of 100 nanoseconds, and anything under a microsecond is just measuring interrupt latency and PCB clock jitter.

The obvious thing is to subtract the uptime from the current time [NSDate date]. But that assumes that time does not change between the two system calls, which is, well, hard to accomplish. Moreover if the thread is preempted between the calls, everything is thrown off. The workaround is to repeat the process several times and use the smallest result, but yuck.

NSDate must have a master offset it uses to generate objects with the current time from the system uptime, is there really no way to obtain it?

5条回答
神经病院院长
2楼-- · 2019-01-22 23:37

Refer to this category

NSDate+BootTime.h

#import <Foundation/Foundation.h>

@interface NSDate (BootTime)

+ (NSDate *)bootTime;

+ (NSTimeInterval)bootTimeTimeIntervalSinceReferenceDate;

@end

NSDate+BootTime.m

#import "NSDate+BootTime.h"

#include <sys/types.h>
#include <sys/sysctl.h>

@implementation NSDate (BootTime)

+ (NSDate *)bootTime {
    return [NSDate dateWithTimeIntervalSinceReferenceDate:[NSDate bootTimeTimeIntervalSinceReferenceDate]];
}

+ (NSTimeInterval)bootTimeTimeIntervalSinceReferenceDate {
    return getKernelTaskStartTime();
}

////////////////////////////////////////////////////////////////////////
#pragma mark - Private
////////////////////////////////////////////////////////////////////////

#define COUNT_ARRAY_ELEMS(arr) sizeof(arr)/sizeof(arr[0])

static CFAbsoluteTime getKernelTaskStartTime(void) {
    enum { MICROSECONDS_IN_SEC = 1000 * 1000 };
    struct kinfo_proc   info;
    bzero(&info, sizeof(info));

    // Initialize mib, which tells sysctl the info we want, in this case
    // we're looking for information about a specific process ID = 0.
    int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, 0};

    // Call sysctl.
    size_t size = sizeof(info);
    const int sysctlResult = sysctl(mib, COUNT_ARRAY_ELEMS(mib), &info, &size, NULL, 0);
    if (sysctlResult != -1) {

        const struct timeval * timeVal = &(info.kp_proc.p_starttime);
        NSTimeInterval result = -kCFAbsoluteTimeIntervalSince1970;
        result += timeVal->tv_sec;
        result += timeVal->tv_usec / (double)MICROSECONDS_IN_SEC;
        return result;

    }

    return 0;
}

@end
查看更多
一夜七次
3楼-- · 2019-01-22 23:44

There is another way. It could give result slightly different (less or more) than accepted answer

I have compared them. I get difference -7 second for OSX 10.9.3 and +2 second for iOS 7.1.1

As i understand this way gives same result if wall clock changed, but accepted answer gives different results if wall clock changed...

Here code:

static CFAbsoluteTime getKernelTaskStartTime(void) {
    enum { MICROSECONDS_IN_SEC = 1000 * 1000 };
    struct kinfo_proc   info;
    bzero(&info, sizeof(info));

    // Initialize mib, which tells sysctl the info we want, in this case
    // we're looking for information about a specific process ID = 0.
    int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, 0};

    // Call sysctl.
    size_t size = sizeof(info);
    const int sysctlResult = sysctl(mib, COUNT_ARRAY_ELEMS(mib), &info, &size, NULL, 0);
    assert(0 != sysctlResult);

    const struct timeval * timeVal = &(info.kp_proc.p_starttime);
    NSTimeInterval result = -kCFAbsoluteTimeIntervalSince1970;
    result += timeVal->tv_sec;
    result += timeVal->tv_usec / (double)MICROSECONDS_IN_SEC;
    return result;
}
查看更多
够拽才男人
4楼-- · 2019-01-22 23:47

The routines inside mach/mach_time.h are guaranteed to be monotonically increasing, unlike NSDate.

查看更多
别忘想泡老子
5楼-- · 2019-01-22 23:51

In OSX you could use sysctl(). This is how the OSX Unix utility uptime does it. Source code is available - search for boottime.

Fair warning though, in iOS i have no idea if this would work.

UPDATE: found some code :)

#include <sys/types.h>
#include <sys/sysctl.h>  

#define MIB_SIZE 2  

int mib[MIB_SIZE];
size_t size;
struct timeval  boottime;

mib[0] = CTL_KERN;
mib[1] = KERN_BOOTTIME;
size = sizeof(boottime);
if (sysctl(mib, MIB_SIZE, &boottime, &size, NULL, 0) != -1)
{
    // successful call
    NSDate* bootDate = [NSDate dateWithTimeIntervalSince1970:
                               boottime.tv_sec + boottime.tv_usec / 1.e6];
}

see if this works...

查看更多
狗以群分
6楼-- · 2019-01-22 23:55

The accepted answer, using systcl, works, but the values returned by sysctl for KERN_BOOTTIME, at least in my testing (Darwin Kernel Version 11.4.2), are always in whole seconds (the microseconds field, tv_usec, is 0). This means the resulting time may be up to 1 second off, which is not very accurate.

Also, having compared that value, to one derived experimentally from the difference between the REALTIME_CLOCK and CALENDAR_CLOCK, they sometimes differ by a couple seconds, so its not clear whether the KERN_BOOTTIME value corresponds exactly to the time-basis for the uptime clocks.

查看更多
登录 后发表回答