7个设置/获取接口了解Linux时间管理

2024-11-23  本文已影响0人  开源519

7个设置/获取接口了解Linux时间管理

[TOC]

引言

  最近的项目开发中,频繁遇到了时间戳相关的问题,如时间回退至1970年、时区错误及时间同步不准确等。鉴于此前仅对时间接口的使用有所了解而未深入探究其原理,本篇文章进行一次系统性整理,以便后续参考。文章若存在一些错误,可在留言区明确指出。

<span style="font-size: 12px;">
<span style="color: blue;"> 注:文末提供本文源码获取方式。文章不定时更新,喜欢本公众号系列文章,可以星标公众号,避免遗漏干货文章。源码开源,如果对您有帮助,帮忙分享、点赞加收藏喔!</span>
</span>

基础概念

Linux 中的时间形式主要以两种形式呈现:

相关结构体

  时间编程中常用要用到的时间结构体有time_ttimevaltimespectm。《Unix环境高级编程》中一张图准确的反应出time_ttm之间的关系:

时间函数之间的关系.png

相关函数

时间获取

时间设置

时间转换

时间格式化

时区设置

  时区会影响到本地时间与UTC时间之间的转换(即本地时间 = UTC + 时区)。
  查阅了一些文档,目前Ubuntu上时区记录在路径/etc/localtime,其通常为软链接,指向具体的时区文件,例如 /etc/localtime -> /usr/share/zoneinfo/Asia/Shanghai。通过修改/etc/localtime指向即可修改为对应的时区(/etc/timezone也会记录当前时区,但似乎仅用于显示)。

实例测试

测试time/stime

time

void TestGetTime()
{
    // time UTC时间戳
    time_t tmt1 = time(NULL);
    printf("timestamp  : %ld\n", tmt1);

    // ctime_r UTC时间戳转换为本地时间字符串
    char cbuf[50] = {0};
    ctime_r(&tmt1, cbuf);
    printf("ctime_r    : %ld(%6d) %s", tmt1, 0, cbuf);

    // gmtime_r UTC时间戳转换为UTC时间字符串
    tm gtm;
    time_t tmt2;
    char gbuf[50] = {0};
    gmtime_r(&tmt1, &gtm);
    asctime_r(&gtm, gbuf);
    tmt2 = mktime(&gtm);    // mktime 会自动减时区
    printf("gmtime_r   : %ld(%6ld) %s %s", tmt2, tmt2-tmt1, gtm.tm_zone, gbuf);

    // 将时间戳转换为本地时间
    tm ltm;
    time_t tmt3;
    char lbuf[50] = {0};
    localtime_r(&tmt1, &ltm);
    asctime_r(&ltm, lbuf);
    tmt3 = mktime(&ltm);
    printf("localtime_r: %ld(%6ld) %s %s", tmt3, tmt3-tmt1, ltm.tm_zone, lbuf);

    char buf3[50] = {0};
    strftime(buf3, 50, "%Z %a %b %d %H:%M:%S %Y", &ltm);
    printf("strftime   : %ld(%6ld) %s\n", tmt3, tmt3-tmt1, buf3);
}

测试结果

timestamp  : 1732450363
ctime_r    : 1732450363(     0) Sun Nov 24 20:12:43 2024
gmtime_r   : 1732421563(-28800) CST Sun Nov 24 12:12:43 2024
localtime_r: 1732450363(     0) CST Sun Nov 24 20:12:43 2024
strftime   : 1732450363(     0) CST Sun Nov 24 20:12:43 2024

gmtime_r 打印的是UTC时间戳,与本地时间相差28800s (8h),即本地与UTC时间相差8h

测试gettimeofday/settimeofday

void Testgettimeofday()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    printf("tv_sec: %ld, tv_usec: %ld\n", (long)tv.tv_sec, (long)tv.tv_usec);
}

void Testsettimeofday()
{
    Testgettimeofday();
    struct timeval tv1;
    tv1.tv_sec = 1731985300;
    tv1.tv_usec = 100;
    int ret = settimeofday(&tv1, NULL);
    if (ret == -1) {
        perror("settimeofday");
    }
    Testgettimeofday();
}

测试结果

tv_sec: 1732450828, tv_usec: 890873
tv_sec: 1731985300, tv_usec: 150

注意在调用设置时间接口时,需要root权限执行,否则会设置失败。

测试clock_gettime/clock_settime

void Testclock_gettime()
{
    std::string name[] = {
        "CLOCK_REALTIME",
        "CLOCK_MONOTONIC",
        "CLOCK_PROCESS_CPUTIME_ID",
        "CLOCK_THREAD_CPUTIME_ID",
        "CLOCK_MONOTONIC_RAW",
        "CLOCK_REALTIME_COARSE",
        "CLOCK_MONOTONIC_COARSE",
        "CLOCK_BOOTTIME",
        "CLOCK_REALTIME_ALARM",
        "CLOCK_BOOTTIME_ALARM",
    };

    // printf("Test clock_gettime\n");
    printf("%-25s  %10s  %10s\n", "CLOCK TYPE", "SEC", "NSEC");
    printf("-----------------------------------------------------------------------------\n");
    for (int i = 0; i <= CLOCK_BOOTTIME_ALARM; i++) {
        struct timespec ts;
        clock_gettime(i, &ts);
        printf("%-25s: %10ld, %10ld\n", name[i].c_str(), (long)ts.tv_sec, (long)ts.tv_nsec);
    }
    printf("-----------------------------------------------------------------------------\n");
}

void Testclock_settime()
{
    Testclock_gettime();

    // Only CLOCK_REALTIME is allowed to be set
    struct timespec ts1;
    ts1.tv_sec = 1731985300;
    ts1.tv_nsec = 100;
    int ret = clock_settime(CLOCK_REALTIME, &ts1);
    if (ret == -1) {
        perror("clock_settime");
    }

    Testclock_gettime();
}

测试结果

CLOCK TYPE                        SEC        NSEC
-----------------------------------------------------------------------------
CLOCK_REALTIME           : 1732451153,  160842537
CLOCK_MONOTONIC          :      45250,  516265743
CLOCK_PROCESS_CPUTIME_ID :          0,     908800
CLOCK_THREAD_CPUTIME_ID  :          0,     910400
CLOCK_MONOTONIC_RAW      :      45249,   35729391
CLOCK_REALTIME_COARSE    : 1732451153,  145187465
CLOCK_MONOTONIC_COARSE   :      45250,  500594052
CLOCK_BOOTTIME           :      45250,  516287258
CLOCK_REALTIME_ALARM     : 1732451153,  160881972
CLOCK_BOOTTIME_ALARM     :      45250,  516289642
-----------------------------------------------------------------------------
CLOCK TYPE                        SEC        NSEC
-----------------------------------------------------------------------------
CLOCK_REALTIME           : 1731985300,      32053
CLOCK_MONOTONIC          :      45250,  516347493
CLOCK_PROCESS_CPUTIME_ID :          0,     988400
CLOCK_THREAD_CPUTIME_ID  :          0,     989400
CLOCK_MONOTONIC_RAW      :      45249,   35795309
CLOCK_REALTIME_COARSE    : 1731985300,        100
CLOCK_MONOTONIC_COARSE   :      45250,  516314680
CLOCK_BOOTTIME           :      45250,  516352354
CLOCK_REALTIME_ALARM     : 1731985300,      38528
CLOCK_BOOTTIME_ALARM     :      45250,  516353885
-----------------------------------------------------------------------------

从测试结果看,更改系统时间时,仅有时间源CLOCK_REALTIMECLOCK_REALTIME_ALARM会随之修改而跳变,其他时间源不会随着系统时间的修改而跳变。在了解这些特性后,在编写应用程序时选择合适的时间源,以满足不同的需求。

测试sleep后,时间的变化

void TestTimeWithSleep(int sec)
{
    std::string name[] = {
        "CLOCK_REALTIME",
        "CLOCK_MONOTONIC",
        "CLOCK_PROCESS_CPUTIME_ID",
        "CLOCK_THREAD_CPUTIME_ID",
        "CLOCK_MONOTONIC_RAW",
        "CLOCK_REALTIME_COARSE",
        "CLOCK_MONOTONIC_COARSE",
        "CLOCK_BOOTTIME",
        "CLOCK_REALTIME_ALARM",
        "CLOCK_BOOTTIME_ALARM",
    };

    struct timespec ots[10];
    for (int i = 0; i < 10; i++) {
        clock_gettime(i, &ots[i]);
    }

    sleep(sec);
    struct  timespec nts[10];
    for (int j = 0; j < 10; j++) {
        clock_gettime(j, &nts[j]);
    }

    printf("%-25s  %10s %10s %10s %10s %7s %8s\n", "CLOCK TYPE", "OLDSEC", "OLDNSEC", "NEWSEC", "NEWNSEC", "DIFFSEC", "DIFFNSEC");
    printf("-------------------------------------------------------------------------------------------\n");
    for (int i = 0; i <= CLOCK_BOOTTIME_ALARM; i++) {
        printf("%-25s: %10ld %10ld %10ld %10ld %7ld %8ld\n",
        name[i].c_str(), (long)ots[i].tv_sec, (long)ots[i].tv_nsec,
        (long)nts[i].tv_sec, (long)nts[i].tv_nsec, (long)(nts[i].tv_sec - ots[i].tv_sec), (long)(nts[i].tv_nsec - ots[i].tv_nsec));
    }
}

测试结果

sleep 5s 结果如下:

CLOCK TYPE                     OLDSEC    OLDNSEC     NEWSEC    NEWNSEC DIFFSEC DIFFNSEC
-------------------------------------------------------------------------------------------
CLOCK_REALTIME           : 1732451618  944834581 1732451623  945683524       5   848943
CLOCK_MONOTONIC          :      45716  300258307      45721  301107230       5   848923
CLOCK_PROCESS_CPUTIME_ID :          0    1010700          0    1048800       0    38100
CLOCK_THREAD_CPUTIME_ID  :          0    1011000          0    1050000       0    39000
CLOCK_MONOTONIC_RAW      :      45714  819871428      45719  820723025       5   851597
CLOCK_REALTIME_COARSE    : 1732451618  935986823 1732451623  935984705       5    -2118
CLOCK_MONOTONIC_COARSE   :      45716  291410495      45721  291408377       5    -2118
CLOCK_BOOTTIME           :      45716  300260726      45721  301110251       5   849525
CLOCK_REALTIME_ALARM     : 1732451618  944837451 1732451623  945687049       5   849598
CLOCK_BOOTTIME_ALARM     :      45716  300287520      45721  301111127       5   823607

  从上述结果看,CLOCK_PROCESS_CPUTIME_IDCLOCK_THREAD_CPUTIME_ID没有记录sleep 5s的时间,也应征了上述所描述的进程挂起或停止时,进程时间不会记录。
  用times接口验证会更明显,sleep前后times获取的时间值基本没有变化。

测试修改时区

void TestSetTimeZone(const std::string& tz)
{
    int ret = 0;
    std::string target = "/usr/share/zoneinfo/" + tz;

    ret = unlink("/etc/localtime");
    if (ret == -1) {
        perror("unlink");
    }

    ret = symlink(target.c_str(), "/etc/localtime");
    if (ret == -1) {
        perror("symlink");
        return;
    }

    tzset();
    TestGetTimeZone();
    TestGetTime();
}

测试结果

设置时区America/New_York

timestamp  : 1732452775
ctime_r    : 1732452775(     0) Sun Nov 24 07:52:55 2024
gmtime_r   : 1732470775( 18000) EST Sun Nov 24 12:52:55 2024
localtime_r: 1732452775(     0) EST Sun Nov 24 07:52:55 2024
strftime   : 1732452775(     0) EST Sun Nov 24 07:52:55 2024

通过打印可看出时区已经显示EST,与Asia/Shanghai时区相差了13h。

总结

上一篇 下一篇

猜你喜欢

热点阅读