一文搞懂应用开发所需 Linux 系统时间的相关知识点

秒级精度

time_t 保存自 UTC 1970 年 1 月 1 日 00:00 以来的秒数(不包括闰秒),对应于 POSIX time Unix 和 POSIX 兼容系统将 time_t 类型作为带符号整数(通常为 32 或 64 位宽)来实现,它表示自 Unix 时间开始以来的秒数

time_t 和 struct tm 之间的转换接口

struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

time_t mktime(struct tm *tm);
struct tm {
   int tm_sec;    /* Seconds (0-60) */
   int tm_min;    /* Minutes (0-59) */
   int tm_hour;   /* Hours (0-23) */
   int tm_mday;   /* Day of the month (1-31) */
   int tm_mon;    /* Month (0-11) */
   int tm_year;   /* Year - 1900 */
   int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
   int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
   int tm_isdst;  /* Daylight saving time */
};

获取当前时间的 time_t

#include <time.h>

NAME
       time - get time in seconds

SYNOPSIS
       #include <time.h>
       time_t time(time_t *tloc);

DESCRIPTION
       time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).
       If tloc is non-NULL, the return value is also stored in the memory pointed to by tloc.

当前时间转 struct tm 打印

std::tm* local_time = localtime(&current_time);

// Print the local time
std::cout << "Current local time is: "
          << (local_time->tm_year + 1900) << "-"
          << (local_time->tm_mon + 1) << "-"
          << local_time->tm_mday << " "
          << local_time->tm_hour << ":"
          << local_time->tm_min << ":"
          << local_time->tm_sec << std::endl;

纳秒精度 struct timespec

struct timespec {
   time_t   tv_sec;        /* seconds */
   long     tv_nsec;       /* nanoseconds */
};
int clock_gettime(clockid_t clk_id, struct timespec *tp);
int clock_settime(clockid_t clk_id, const struct timespec *tp);

关于 clk_id

clk_id 参数是要对其采取行动的特定时钟的标识符。时钟可以是系统范围的,因此对所有进程都可见,或者如果它仅在单个进程内测量时间,则可以对每个进程都可见。

所有实现都支持系统范围的实时时钟,该时钟由 CLOCK_REALTIME 标识。其时间表示自纪元以来的秒数和纳秒数。当其时间改变时,相对间隔的计时器不受影响,但绝对时间点的计时器会受到影响。

可以实现更多时钟。相应时间值的解释和对计时器的影响尚未指定。

足够新的 glibc 版本和 Linux 内核支持以下时钟:

CLOCK_REALTIME

测量实际(即挂钟)时间的系统范围时钟。设置此时钟需要适当的权限。此时钟受系统时间不连续跳跃的影响(例如,如果系统管理员手动更改时钟),以及 adjtime(3) 和 NTP 执行的增量调整。

CLOCK_REALTIME_COARSE(自 Linux 2.6.32 起;特定于 Linux)

CLOCK_REALTIME 的更快但精度较低的版本。当您需要非常快但不是细粒度的时间戳时使用。需要每个架构支持,并且可能还需要 vdso(7) 中对此标志的架构支持。

CLOCK_MONOTONIC

无法设置的时钟,表示自 POSIX 描述的“过去某个未指定的点”以来的单调时间。在 Linux 上,该点对应于系统自启动以来运行的秒数。

CLOCK_MONOTONIC 时钟不受系统时间不连续跳跃的影响(例如,如果系统管理员手动更改时钟),但会受到 adjtime(3) 和 NTP 执行的增量调整的影响。此时钟不计算系统暂停的时间。

CLOCK_MONOTONIC_COARSE(自 Linux 2.6.32 起;Linux 专用)

CLOCK_MONOTONIC 的更快但精度更低的版本。当您需要非常快速但不是细粒度的时间戳时使用。需要每个架构支持,并且可能还需要 vdso(7) 中对此标志的架构支持。

CLOCK_MONOTONIC_RAW(自 Linux 2.6.28 起;Linux 专用)

与 CLOCK_MONOTONIC 类似,但提供对不受 NTP 调整或 adjtime(3) 执行的增量调整影响的原始硬件时间的访问。此时钟不计算系统暂停的时间。

CLOCK_BOOTTIME(自 Linux 2.6.39 起;Linux 专用)

与 CLOCK_MONOTONIC 相同,但它还包括系统暂停的任何时间。这允许应用程序获得可感知暂停的单调时钟,而无需处理 CLOCK_REALTIME 的复杂性,如果使用 settimeofday(2) 或类似方法更改时间,则可能会出现不连续性。

CLOCK_PROCESS_CPUTIME_ID(自 Linux 2.6.12 起)

每个进程的 CPU 时间时钟(测量进程中所有线程所消耗的 CPU 时间)。

CLOCK_THREAD_CPUTIME_ID(自 Linux 2.6.12 起)

线程专用的 CPU 时间时钟。

根据时间戳设置系统时间

// Your existing code to get the timestamp_us
uint64_t timestamp_us = 1629374305000000; // Example timestamp in microseconds

// Convert the timestamp to seconds and microseconds
struct timespec new_time;
new_time.tv_sec = static_cast<time_t>(timestamp_us / 1000000);
new_time.tv_nsec = (timestamp_us % 1000000) * 1000;

// CLOCK_REALTIME is the identifier for the system-wide realtime clock
int result = clock_settime(CLOCK_REALTIME, &new_time);
if (result == -1) {
    std::cerr << "Failed to set system time: " << strerror(errno) << std::endl;
    return 1;
}

std::cout << "System time updated to: "
          << new_time.tv_sec << "s and " << new_time.tv_nsec << "ns" << std::endl;

微秒精度 struct timeval

struct timeval {
   time_t      tv_sec;     /* seconds */
   suseconds_t tv_usec;    /* microseconds */
};
struct timezone {
   int tz_minuteswest;     /* minutes west of Greenwich */
   int tz_dsttime;         /* type of DST correction */
};
#include <sys/time.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);

int settimeofday(const struct timeval *tv, const struct timezone *tz);

struct timevalstruct timespec 都是POSIX标准中定义的时间结构体,用于表示时间点或时间间隔。它们在很多系统调用中用于获取或设置时间。以下是这两个结构体的详细说明:

struct timeval

struct timeval 用于表示一个时间间隔,它包含两个字段:

  • tv_sec: 表示秒(time_t 类型),time_t 通常是一个长整型(long),表示自Unix纪元(1970年1月1日 00:00:00 UTC)以来的秒数。
  • tv_usec: 表示微秒(suseconds_t 类型,通常是一个长整型),表示tv_sec后的微秒数。

这个结构体通常用于需要时间间隔的场景,比如在某些系统调用中指定超时时间。

struct timespec

struct timespec 用于表示一个具体的时间点,它同样包含两个字段:

  • tv_sec: 表示秒(time_t 类型),同struct timeval中的tv_sec
  • tv_nsec: 表示纳秒(long 类型),表示tv_sec后的纳秒数。

struct timespec 提供了比 struct timeval 更高的时间精度(纳秒级对比微秒级),因此它更适合用于需要高分辨率时间点的场景。

使用场景

  • struct timeval 常用于设置或获取某个操作的超时时间,例如在使用 select()poll() 或者 gettimeofday() 函数时。
  • struct timespec 常用于需要设定或查询具体时间点的场合,例如使用 clock_gettime() 或 clock_settime() 函数。
#include <iostream>
#include <ctime>
#include <sys/time.h>

int main() {
    // 使用struct timeval获取当前时间
    struct timeval now_val;
    if (gettimeofday(&now_val, NULL) == -1) {
        perror("gettimeofday");
        return 1;
    }
    std::cout << "Current time (usec): "
              << now_val.tv_sec << "s and " << now_val.tv_usec << "us since the epoch" << std::endl;

    // 使用struct timespec获取当前时间
    struct timespec now_spec;
    if (clock_gettime(CLOCK_REALTIME, &now_spec) == -1) {
        perror("clock_gettime");
        return 1;
    }
    std::cout << "Current time (nsec): "
              << now_spec.tv_sec << "s and " << now_spec.tv_nsec << "ns since the epoch" << std::endl;

    return 0;
}