【问题标题】:Getting iOS system uptime, that doesn't pause when asleep获取iOS系统正常运行时间,睡着时不会暂停
【发布时间】:2012-09-11 09:27:41
【问题描述】:

我正在寻找一种方法来在 iOS 上获得绝对的、始终增加的系统正常运行时间。

它应该返回自设备上次重新启动以来的时间,并且不受系统日期更改的影响。

我能找到的所有方法要么在设备睡眠时暂停(CACurrentMediaTime[NSProcessInfo systemUptime]mach_absolute_time),要么在系统日期更改时更改(sysctl/KERN_BOOTTIME)。

有什么想法吗?

【问题讨论】:

    标签: ios kernel clock uptime


    【解决方案1】:

    我想我解决了。

    time() 在设备休眠时继续递增,但当然可以由操作系统或用户操作。但是,内核启动时间(系统上次启动时间的时间戳)也会随着系统时钟的变化而变化,因此即使这两个值都不是固定的,它们之间的偏移量也是如此。

    #include <sys/sysctl.h>
    
    - (time_t)uptime
    {
        struct timeval boottime;
        int mib[2] = {CTL_KERN, KERN_BOOTTIME};
        size_t size = sizeof(boottime);
        time_t now;
        time_t uptime = -1;
    
        (void)time(&now);
    
        if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0) {
            uptime = now - boottime.tv_sec;
        }
    
        return uptime;
    }
    

    【讨论】:

    • 干得好,伙计。在此之前,我将不得不进行服务器日期/本地日期偏移比较...非常感谢。
    • 如果您碰巧在系统时间更新后但在启动时间更新之前调用它,是否存在竞争条件,或者是否有某种东西可以保证它们都会自动更新?
    • 我不确定。这已经在生产应用程序中使用了几年,我从未见过竞争条件问题。所以,有趣的是,这似乎很好。
    • 是的,存在竞争条件。 “从未见过竞争条件问题”是无效的,因为: 1. 您的应用程序实际上可能并不依赖于时钟的单调性 2. 您可能没有在现场实际捕获此类问题的机制(即您的用户是击中它,你只是不知道) 3. 你没有大量的用户安装基数或者你的安装基数不能代表其他用户(例如你的用户从不改变他们的手动时钟)。 4. 您的用户可能只是走运了,但从未碰过这个。
    【解决方案2】:

    正如其中一位 cmets 所述,POSIX'sclock_gettime() 在 iOS 10 和 macOS 10.12 中实现。当与CLOCK_MONOTONIC 参数一起使用时,它似乎确实返回了正常运行时间值。但是,文档不能保证这一点:

    对于这个时钟,clock_gettime() 返回的值表示 自未指定点以来的时间量(以秒和纳秒为单位) 过去(例如,系统启动时间或 Epoch)。这 点在系统启动时间后不会改变。

    以及来自相应 macOS 手册页的摘录:

    CLOCK_MONOTONIC 单调递增的时钟,跟踪从任意点开始的时间,并在系统休眠时继续递增。

    CLOCK_MONOTONIC_RAW 单调递增的时钟,跟踪从任意点(如 CLOCK_MONOTONIC)开始的时间。但是,此时钟不受频率或时间调整的影响。不应将其与其他系统时间源进行比较。

    CLOCK_MONOTONIC_RAW_APPROX 类似于 CLOCK_MONOTONIC_RAW,但读取系统在上下文中缓存的值 转变。这可以更快地读取,但会损失准确性,因为它可能会返回毫秒前的值。

    CLOCK_UPTIME_RAW 单调递增的时钟,其方式与 CLOCK_MONOTONIC_RAW 相同,但在系统休眠时递增。应用适当的 mach_timebase 转换后,返回的值与 mach_absolute_time() 的结果相同。

    请注意,CLOCK_MONOTONIC 以微秒精度返回,而 CLOCK_MONOTONIC_RAW 以纳秒精度返回。

    Swift 代码:

    func uptime() -> timespec {
    
        var uptime = timespec()
        if 0 != clock_gettime(CLOCK_MONOTONIC_RAW, &uptime) {
            fatalError("Could not execute clock_gettime, errno: \(errno)")
        }
    
        return uptime
    }
    
    print(uptime()) // timespec(tv_sec: 636705, tv_nsec: 750700397)
    

    对于那些仍然对在 Swift 中获取内核启动时间感兴趣的人:

    func kernelBootTime() -> timeval {
    
        var mib = [ CTL_KERN, KERN_BOOTTIME ]
        var bootTime = timeval()
        var bootTimeSize = MemoryLayout<timeval>.size
    
        if 0 != sysctl(&mib, UInt32(mib.count), &bootTime, &bootTimeSize, nil, 0) {
            fatalError("Could not get boot time, errno: \(errno)")
        }
    
        return bootTime
    }
    
    print(kernelBootTime()) // timeval(tv_sec: 1499259206, tv_usec: 122778)
    

    【讨论】:

    • CLOCK_MONOTONIC_RAW怎么会放弃时间?文档说明它来自未指定的任意时间点
    • 我在这里看到了上面的文档:unix.com/man-page/mojave/3/clock_getres。它似乎适用于 macOS Mojave。
    【解决方案3】:

    在列出的所有示例中都存在竞争条件:如果时间发生变化(NTP 或用户修改),代码将竞争并且时钟不再单调。

    正确的实现是:

    #include <sys/sysctl.h>
    
    static int64_t us_since_boot() {
        struct timeval boottime;
        int mib[2] = {CTL_KERN, KERN_BOOTTIME};
        size_t size = sizeof(boottime);
        int rc = sysctl(mib, 2, &boottime, &size, NULL, 0);
        if (rc != 0) {
          return 0;
        }
        return (int64_t)boottime.tv_sec * 1000000 + (int64_t)boottime.tv_usec;
    }
    
    - (int64_t)us_uptime
    {
        int64_t before_now;
        int64_t after_now;
        struct timeval now;
    
        after_now = us_since_boot();
        do {
            before_now = after_now;
            gettimeofday(&now, NULL);
            after_now = us_since_boot();
        } while (after_now != before_now);
    
        return (int64_t)now.tv_sec * 1000000 + (int64_t)now.tv_usec - before_now;
    }
    

    【讨论】:

    • 我已经对此进行了测试,它看起来就像一个魅力。我找不到它的任何缺陷。谢谢!作为记录,最后有一个适用于 OS10 及更高版本的非尴尬解决方案。 clock_gettime API 终于可用了,使用 CLOCK_MONOTONIC 应该支持单调定时器的要求,该定时器以挂钟速率增加并具有相当的准确性。
    • 这很好,除了函数名us_since_boot。这只是一个启动时间,而不是自启动以来经过的时间。
    • 请注意,在执行boottime.tv_sec * 1000000now.tv_sec * 1000000 时可能会发生溢出(在 32 位平台上很容易捕获)。为了避免这个问题,你必须做一个直接投射。
    • @KeewonSeo 你是对的。将其称为 boot_timestamp() 可能更好。
    • @IlikeSerena 你是对的。从性能的角度来看,CLOCK_MONOTONIC_RAW_APPROX 可能是更好的 API,如果您需要绝对准确的时间戳,则 CLOCK_MONOTONIC_RAW 可能是更好的 API(我使用高精度时间戳,我认为很难找到 RAW 是更好选择的场景)。听起来 CLOCK_MONOTONIC 可能仍会受到时间/频率调整的影响。
    【解决方案4】:

    如果需要更高的精度,那么下面是接受答案的修改版本。

    #include <sys/sysctl.h>
    
    - (NSTimeInterval)uptime
    {
        struct timeval boottime;
        int mib[2] = {CTL_KERN, KERN_BOOTTIME};
        size_t size = sizeof(boottime);
    
        struct timeval now;
        struct timezone tz;
        gettimeofday(&now, &tz);
    
        double uptime = -1;
    
        if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
        {
            uptime = now.tv_sec - boottime.tv_sec;
            uptime += (double)(now.tv_usec - boottime.tv_usec) / 1000000.0;
        }
        return uptime;
    }
    

    【讨论】:

      【解决方案5】:

      我会评论 Leszek 的回答,但没有足够的代表...... Leszek 的解决方案返回秒数。

      如果有人需要毫秒精度,以下是 Leszek 答案的修改版本。

      #include <sys/sysctl.h>
      
      + (long long int)uptime 
      {
          struct timeval boottime;
          int mib[2] = {CTL_KERN, KERN_BOOTTIME};
          size_t size = sizeof(boottime);
      
          struct timeval now;
          struct timezone tz;
          gettimeofday(&now, &tz);
      
          long long int uptime = -1;
      
          if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
          {
              uptime = ((long long int)(now.tv_sec - boottime.tv_sec)) * 1000;
              uptime += (now.tv_usec - boottime.tv_usec) / 1000;
          }
          return uptime;
      }
      

      【讨论】:

        【解决方案6】:

        为什么不使用systemUptime

        系统正常运行时间 返回计算机重新启动以来的时间。

        • (NSTimeInterval)系统正常运行时间 返回值 一个 NSTimeInterval,指示计算机重新启动后的时间。

        可用性 在 iOS 4.0 及更高版本中可用。 宣布于 NSProcessInfo.h

        我已经测试并证明,至少在带有 iOS 7.1.1 的 iPhone 5 上,当您的手机被锁定时,systemUptime 不会停止。所以任何人都不相信这可以自己测试。

        【讨论】:

        • 在我的 iOS 7.1.x 测试中。设备锁定且屏幕关闭时不会停止。
        • 它确实受到睡眠/清醒的影响。也许您使用可防止睡眠的连接电缆对其进行了测试。或者其他一些原因在您测试时阻止您的设备休眠...
        • 是的,我重新测试过,它停止了,对不起。
        • 发件人:devetc.org/code/2014/01/21/timers-clocks-and-cocoa.html 单调时间基本上是一个计数器,当物理定时器通过定时器中断向 CPU 发出信号时,它会递增……几个函数包括转换为秒:-[NSProcessInfo systemUptime]、CACurrentMediaTime( ) 等……但是,由于 CPU 会增加此计数器,因此单调时钟会在 CPU 断电时停止 - 包括系统“睡眠”时。
        • @Speakus 非常感谢!你是对的 systemUpTime 是用拔掉的电缆重新启动的!我整天都在为此苦苦挣扎,但我没有注意到它......
        猜你喜欢
        • 2016-07-12
        • 2013-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多