当前位置: 首页 > article >正文

10.Linux 时间

文章目录

      • 10.1 日历时间(Calendar Time)
        • 1. **日历时间的表示**
        • 2. **系统调用 `gettimeofday()`**
        • 3. **系统调用 `time()`**
        • 4. **`time()` 与 `gettimeofday()` 的关系**
      • 总结
    • 10.2 时间转换函数
      • 1. **`ctime()` 示例**
      • 2. **`asctime()` 示例**
      • 3. **`strftime()` 示例**
      • 4. **`strptime()` 示例**
      • 5. **综合示例**
      • 总结
      • 10.2.1 将 `time_t` 转换为可打印格式
        • **`ctime()` 函数**
        • **`ctime_r()` 函数**
      • 10.2.2 `time_t` 和分解时间之间的转换
        • **分解时间(Broken-down Time)**
        • **`gmtime()` 函数**
        • **`localtime()` 函数**
        • **`gmtime_r()` 和 `localtime_r()` 函数**
        • **`mktime()` 函数**
      • 总结
        • **时间转换函数**
        • **注意事项**
      • 1. **`ctime()` 示例**
      • 2. **`gmtime()` 示例**
      • 3. **`localtime()` 示例**
      • 4. **`mktime()` 示例**
      • 5. **`mktime()` 自动调整字段示例**
      • 6. **`strftime()` 示例**
      • 总结
      • 10.2.3 分解时间和打印格式之间的转换
        • 1. **`asctime()` 函数**
        • 2. **`asctime_r()` 函数**
        • 3. **`strftime()` 函数**
        • 4. **`strftime()` 的常用格式化说明符**
        • 5. **示例程序**
      • 总结
      • 程序清单 10-2:返回当前时间的字符串的函数
        • **`currTime()` 函数**
      • 程序清单 10-3:将打印格式时间转换为分解时间
        • **`strptime()` 函数**
        • **示例程序**
        • **运行示例**
      • 总结
    • 10.3 时区
        • 1. **时区定义**
        • 2. **为程序指定时区**
        • 3. **`TZ` 环境变量的两种格式**
        • 4. **示例程序**
        • 5. **运行示例**
      • 总结
    • 10.4 地区(Locale)
        • 1. **地区定义**
        • 2. **地区类别**
        • 3. **设置地区**
        • 4. **示例程序**
        • 5. **运行示例**
      • 总结
    • 10.5 更新系统时钟
        • 1. **`settimeofday()` 系统调用**
        • 2. **`stime()` 系统调用**
        • 3. **`adjtime()` 函数**
        • 4. **`adjtimex()` 系统调用**
    • 10.6 软件时钟(Jiffies)
        • 1. **Jiffies 的定义**
        • 2. **Linux 内核中的 Jiffies**
        • 3. **Jiffies 的影响**
      • 总结
      • 1. **`settimeofday()` 示例**
      • 2. **`adjtime()` 示例**
      • 3. **`stime()` 示例**
      • 4. **获取和打印当前时间**
      • 5. **综合示例**
      • 总结
    • 10.7 进程时间
        • 1. **`times()` 系统调用**
        • 2. **`clock()` 函数**
        • 3. **示例程序**
        • 4. **运行示例**
        • 5. **程序说明**
      • 总结
      • 10.9 练习 10-1
        • **问题描述**
        • **1. `times()` 的周期计算**
        • **2. `clock()` 的周期计算**
        • **总结**
        • **注意事项**

这段文字主要讨论了计算机程序中涉及的两种时间类型:真实时间和进程时间,并详细介绍了日历时间的概念及其在UNIX系统中的表示方式。

  1. 真实时间

    • 日历时间:从标准点(如UTC 1970年1月1日零点,称为Epoch)开始计算的时间,用于记录时间戳。
    • 流逝时间:从进程启动开始计算的时间,适用于周期性操作或外部设备的时间度量。
  2. 进程时间:进程使用的CPU时间总量,用于性能检查和优化。

  3. 硬件时钟:计算机体系结构内置的时钟,帮助内核计算真实时间和进程时间。

  4. 日历时间的表示

    • UNIX系统内部使用自Epoch以来的秒数表示时间,存储在time_t类型的变量中。
    • 32位系统中,time_t是有符号整数,时间范围从1901年12月13日到2038年1月19日,存在“2038年问题”。
    • 64位系统可以缓解这一问题,但32位嵌入式系统和历史数据仍可能受到影响。

总结:程序关注真实时间和进程时间,UNIX系统使用Epoch以来的秒数表示日历时间,32位系统存在2038年问题,64位系统可以解决这一问题。

10.1 日历时间(Calendar Time)

1. 日历时间的表示
  • UNIX系统内部使用自Epoch(UTC 1970年1月1日零点)以来的秒数表示时间,存储于time_t类型的变量中。
  • 32位系统中,time_t是有符号整数,时间范围为1901年12月13日20时45分52秒2038年1月19日03:14:07
  • 2038年问题:32位系统在2038年后会溢出,可能导致时间计算错误。64位系统可以解决此问题,但32位嵌入式系统和历史数据仍可能受影响。

2. 系统调用 gettimeofday()
  • 用于获取日历时间,精度可达微秒。

  • 函数原型:

    #include <sys/time.h>
    int gettimeofday(struct timeval *tv, struct timezone *tz);
    
    • 返回值:成功返回0,失败返回-1。
    • 参数
      • tv:指向struct timeval的指针,存储时间值。
      • tz:历史遗留参数,应始终置为NULL
  • struct timeval 结构体:

    struct timeval {
        time_t      tv_sec;   // 自Epoch以来的秒数
        suseconds_t tv_usec;  // 额外的微秒数
    };
    
    • tv_usec 提供微秒级精度,但实际精度依赖于硬件实现。
  • tz 参数

    • 早期用于获取时区信息,现已废弃。
    • 如果提供,返回timezone结构体,包含tz_minuteswest(与UTC的分钟偏移)和tz_dsttime(夏令时标志)。

3. 系统调用 time()
  • 返回自Epoch以来的秒数,与gettimeofday()tv_sec字段相同。

  • 函数原型:

    #include <time.h>
    time_t time(time_t *timep);
    
    • 返回值:自Epoch以来的秒数,失败返回(time_t)-1
    • 参数
      • timep:如果不为NULL,将秒数存储到timep指向的位置。
  • 常见用法

    time_t t = time(NULL);
    

4. time()gettimeofday() 的关系
  • 历史原因
    • 早期UNIX提供time(),4.3BSD补充了更精确的gettimeofday()
    • time()可以视为gettimeofday()的简化版本。
  • 实现
    • time()可以通过调用gettimeofday()实现。

总结

  • 日历时间:UNIX系统使用自Epoch以来的秒数表示时间,32位系统存在2038年问题。
  • gettimeofday():获取日历时间,支持微秒级精度,tz参数已废弃。
  • time():返回自Epoch以来的秒数,是gettimeofday()的简化版本。
  • 时区与夏令时:早期通过tz参数处理,现已废弃,时区信息需通过其他方式管理。
    以下是一些关于时间转换函数的具体示例,帮助你更好地理解如何使用这些函数。

10.2 时间转换函数

1. ctime() 示例

time_t转换为固定格式的字符串。

#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL); // 获取当前时间
    printf("Current time: %s", ctime(&now)); // 转换为固定格式字符串并打印
    return 0;
}

输出

Current time: Tue Oct 10 14:30:00 2023

2. asctime() 示例

struct tm转换为固定格式的字符串。

#include <stdio.h>
#include <time.h>

int main() {
    struct tm tm_info = {
        .tm_year = 123, // 2023年(1900 + 123)
        .tm_mon = 9,    // 10月(0-11)
        .tm_mday = 10,  // 10日
        .tm_hour = 14,  // 14时
        .tm_min = 30,   // 30分
        .tm_sec = 0     // 0秒
    };
    printf("Custom time: %s", asctime(&tm_info)); // 转换为固定格式字符串并打印
    return 0;
}

输出

Custom time: Tue Oct 10 14:30:00 2023

3. strftime() 示例

struct tm转换为自定义格式的字符串。

#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL); // 获取当前时间
    struct tm *tm_info = localtime(&now); // 转换为本地时间
    char buffer[80];

    // 自定义格式:YYYY-MM-DD HH:MM:SS
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_info);
    printf("Formatted time: %s\n", buffer);

    // 自定义格式:Day, Month Day, Year
    strftime(buffer, sizeof(buffer), "%A, %B %d, %Y", tm_info);
    printf("Formatted date: %s\n", buffer);

    return 0;
}

输出

Formatted time: 2023-10-10 14:30:00
Formatted date: Tuesday, October 10, 2023

4. strptime() 示例

将字符串解析为struct tm

#include <stdio.h>
#include <time.h>

int main() {
    const char *time_str = "2023-10-10 14:30:00";
    struct tm tm_info;

    // 解析字符串
    if (strptime(time_str, "%Y-%m-%d %H:%M:%S", &tm_info) != NULL) {
        printf("Parsed time: %d-%02d-%02d %02d:%02d:%02d\n",
               tm_info.tm_year + 1900, tm_info.tm_mon + 1, tm_info.tm_mday,
               tm_info.tm_hour, tm_info.tm_min, tm_info.tm_sec);
    } else {
        printf("Failed to parse time string.\n");
    }

    return 0;
}

输出

Parsed time: 2023-10-10 14:30:00

5. 综合示例

结合time()localtime()strftime(),获取当前时间并格式化输出。

#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL); // 获取当前时间
    struct tm *tm_info = localtime(&now); // 转换为本地时间
    char buffer[80];

    // 自定义格式:YYYY年MM月DD日 HH时MM分SS秒
    strftime(buffer, sizeof(buffer), "%Y年%m月%d日 %H时%M分%S秒", tm_info);
    printf("当前时间: %s\n", buffer);

    return 0;
}

输出

当前时间: 2023年10月10日 14时30分00秒

总结

  • ctime():将time_t转换为固定格式字符串。
  • asctime():将struct tm转换为固定格式字符串。
  • strftime():将struct tm转换为自定义格式字符串。
  • strptime():将字符串解析为struct tm

通过这些函数,可以轻松地在不同时间格式之间进行转换,并处理时区、本地化等复杂性问题。

10.2.1 将 time_t 转换为可打印格式

ctime() 函数
  • 功能:将 time_t 转换为固定格式的字符串。
  • 函数原型
    #include <time.h>
    char *ctime(const time_t *timep);
    
  • 参数
    • timep:指向 time_t 的指针。
  • 返回值
    • 成功时返回一个 26字节 的静态分配字符串,格式为 Wed Jun 8 14:22:34 2011\n\0
    • 失败时返回 NULL
  • 特点
    • 自动考虑本地时区和夏令时(DST)设置。
    • 返回的字符串是静态分配的,后续调用会覆盖其内容。
    • 非线程安全

ctime_r() 函数
  • 功能ctime() 的可重入版本。
  • 特点
    • 允许调用者提供缓冲区存储结果,避免静态分配问题。
    • 线程安全

10.2.2 time_t 和分解时间之间的转换

分解时间(Broken-down Time)
  • 分解时间存储在 struct tm 中,包含以下字段:
    struct tm {
        int tm_sec;   // 秒 [0-60],60 用于闰秒
        int tm_min;   // 分 [0-59]
        int tm_hour;  // 时 [0-23]
        int tm_mday;  // 日 [1-31]
        int tm_mon;   // 月 [0-11]
        int tm_year;  // 年(自 1900 年以来的年数)
        int tm_wday;  // 星期 [0-6],0 表示周日
        int tm_yday;  // 一年中的第几天 [0-365]
        int tm_isdst; // 夏令时标志(>0:启用,0:未启用,<0:未知)
    };
    

gmtime() 函数
  • 功能:将 time_t 转换为 UTC 时间 的分解时间。
  • 函数原型
    #include <time.h>
    struct tm *gmtime(const time_t *timep);
    
  • 返回值
    • 成功时返回指向静态分配 struct tm 的指针。
    • 失败时返回 NULL
  • 特点
    • 返回的 struct tm 是静态分配的,后续调用会覆盖其内容。
    • 非线程安全

localtime() 函数
  • 功能:将 time_t 转换为 本地时间 的分解时间。
  • 函数原型
    #include <time.h>
    struct tm *localtime(const time_t *timep);
    
  • 返回值
    • 成功时返回指向静态分配 struct tm 的指针。
    • 失败时返回 NULL
  • 特点
    • 自动考虑本地时区和夏令时(DST)设置。
    • 返回的 struct tm 是静态分配的,后续调用会覆盖其内容。
    • 非线程安全

gmtime_r()localtime_r() 函数
  • 功能gmtime()localtime() 的可重入版本。
  • 特点
    • 允许调用者提供缓冲区存储结果,避免静态分配问题。
    • 线程安全

mktime() 函数
  • 功能:将本地时间的分解时间转换为 time_t
  • 函数原型
    #include <time.h>
    time_t mktime(struct tm *timeptr);
    
  • 参数
    • timeptr:指向 struct tm 的指针。
  • 返回值
    • 成功时返回自 Epoch 以来的秒数。
    • 失败时返回 (time_t)-1
  • 特点
    • 忽略输入 tm_wdaytm_yday 字段,计算后更新这些字段。
    • 自动调整超出范围的字段值(如 tm_sec = 123 会被调整为 tm_sec = 3,并增加 tm_min)。
    • 考虑时区和夏令时(DST)设置:
      • tm_isdst > 0,视为夏令时。
      • tm_isdst == 0,视为标准时间。
      • tm_isdst < 0,自动判断是否启用夏令时。
    • 转换完成后,更新 tm_isdst 字段以反映实际状态。

总结

时间转换函数
函数功能特点
ctime()time_t → 固定格式字符串静态分配,非线程安全
ctime_r()time_t → 固定格式字符串可重入,线程安全
gmtime()time_t → UTC 分解时间静态分配,非线程安全
gmtime_r()time_t → UTC 分解时间可重入,线程安全
localtime()time_t → 本地分解时间静态分配,非线程安全
localtime_r()time_t → 本地分解时间可重入,线程安全
mktime()本地分解时间 → time_t自动调整字段,考虑时区和 DST
注意事项
  • 静态分配的函数(如 ctime()gmtime()localtime())是非线程安全的,需使用可重入版本(如 ctime_r()gmtime_r()localtime_r())。
  • mktime() 会自动调整字段值,并考虑时区和夏令时设置。

通过这些函数,可以方便地在 time_t、分解时间和字符串格式之间进行转换,并处理时区、夏令时等复杂性问题。

以下是一些具体示例,展示如何使用时间转换函数(如 ctime()gmtime()localtime()mktime())来处理时间。


1. ctime() 示例

time_t 转换为固定格式的字符串。

#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL); // 获取当前时间
    printf("Current time: %s", ctime(&now)); // 转换为固定格式字符串并打印
    return 0;
}

输出

Current time: Tue Oct 10 14:30:00 2023

2. gmtime() 示例

time_t 转换为 UTC 时间的分解时间。

#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL); // 获取当前时间
    struct tm *utc_time = gmtime(&now); // 转换为 UTC 时间

    printf("UTC time: %04d-%02d-%02d %02d:%02d:%02d\n",
           utc_time->tm_year + 1900, utc_time->tm_mon + 1, utc_time->tm_mday,
           utc_time->tm_hour, utc_time->tm_min, utc_time->tm_sec);
    return 0;
}

输出

UTC time: 2023-10-10 12:30:00

3. localtime() 示例

time_t 转换为本地时间的分解时间。

#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL); // 获取当前时间
    struct tm *local_time = localtime(&now); // 转换为本地时间

    printf("Local time: %04d-%02d-%02d %02d:%02d:%02d\n",
           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);
    return 0;
}

输出

Local time: 2023-10-10 14:30:00

4. mktime() 示例

将分解时间转换为 time_t,并自动调整字段值。

#include <stdio.h>
#include <time.h>

int main() {
    struct tm tm_info = {
        .tm_year = 123, // 2023年(1900 + 123)
        .tm_mon = 9,    // 10月(0-11)
        .tm_mday = 10,  // 10日
        .tm_hour = 14,  // 14时
        .tm_min = 30,   // 30分
        .tm_sec = 0     // 0秒
    };

    // 将分解时间转换为 time_t
    time_t t = mktime(&tm_info);
    printf("Seconds since Epoch: %ld\n", (long)t);

    // 打印调整后的分解时间
    printf("Adjusted time: %04d-%02d-%02d %02d:%02d:%02d\n",
           tm_info.tm_year + 1900, tm_info.tm_mon + 1, tm_info.tm_mday,
           tm_info.tm_hour, tm_info.tm_min, tm_info.tm_sec);
    return 0;
}

输出

Seconds since Epoch: 1696948200
Adjusted time: 2023-10-10 14:30:00

5. mktime() 自动调整字段示例

测试 mktime() 自动调整超出范围的字段值。

#include <stdio.h>
#include <time.h>

int main() {
    struct tm tm_info = {
        .tm_year = 123, // 2023年(1900 + 123)
        .tm_mon = 9,    // 10月(0-11)
        .tm_mday = 32,  // 32日(超出范围)
        .tm_hour = 25,  // 25时(超出范围)
        .tm_min = 70,   // 70分(超出范围)
        .tm_sec = 120   // 120秒(超出范围)
    };

    // 将分解时间转换为 time_t
    time_t t = mktime(&tm_info);
    printf("Seconds since Epoch: %ld\n", (long)t);

    // 打印调整后的分解时间
    printf("Adjusted time: %04d-%02d-%02d %02d:%02d:%02d\n",
           tm_info.tm_year + 1900, tm_info.tm_mon + 1, tm_info.tm_mday,
           tm_info.tm_hour, tm_info.tm_min, tm_info.tm_sec);
    return 0;
}

输出

Seconds since Epoch: 1697036400
Adjusted time: 2023-11-01 02:10:00

6. strftime() 示例

将分解时间转换为自定义格式的字符串。

#include <stdio.h>
#include <time.h>

int main() {
    time_t now = time(NULL); // 获取当前时间
    struct tm *local_time = localtime(&now); // 转换为本地时间
    char buffer[80];

    // 自定义格式:YYYY年MM月DD日 HH时MM分SS秒
    strftime(buffer, sizeof(buffer), "%Y年%m月%d日 %H时%M分%S秒", local_time);
    printf("当前时间: %s\n", buffer);

    return 0;
}

输出

当前时间: 2023年10月10日 14时30分00秒

总结

  • ctime():将 time_t 转换为固定格式字符串。
  • gmtime():将 time_t 转换为 UTC 分解时间。
  • localtime():将 time_t 转换为本地分解时间。
  • mktime():将分解时间转换为 time_t,并自动调整字段值。
  • strftime():将分解时间转换为自定义格式字符串。

通过这些示例,可以更好地理解如何使用这些函数处理时间。

10.2.3 分解时间和打印格式之间的转换

本节介绍如何在分解时间(struct tm)和打印格式之间进行转换,主要涉及以下函数:


1. asctime() 函数
  • 功能:将分解时间转换为固定格式的字符串。
  • 函数原型
    #include <time.h>
    char *asctime(const struct tm *timeptr);
    
  • 参数
    • timeptr:指向分解时间结构体 struct tm 的指针。
  • 返回值
    • 成功时返回一个 26字节 的静态分配字符串,格式为 Tue Dec 28 15:01:57 2010\n\0
    • 失败时返回 NULL
  • 特点
    • ctime() 类似,但 asctime() 的输入是分解时间,而非 time_t
    • 本地时区设置对 asctime() 无影响,因为分解时间通常已通过 localtime()gmtime() 处理。
    • 返回的字符串是静态分配的,后续调用会覆盖其内容。
    • 非线程安全

2. asctime_r() 函数
  • 功能asctime() 的可重入版本。
  • 特点
    • 允许调用者提供缓冲区存储结果,避免静态分配问题。
    • 线程安全

3. strftime() 函数
  • 功能:将分解时间转换为自定义格式的字符串。
  • 函数原型
    #include <time.h>
    size_t strftime(char *outstr, size_t maxsize, const char *format, const struct tm *timeptr);
    
  • 参数
    • outstr:存储结果的缓冲区。
    • maxsize:缓冲区的最大长度。
    • format:格式化字符串(如 "%Y-%m-%d %H:%M:%S")。
    • timeptr:指向分解时间结构体 struct tm 的指针。
  • 返回值
    • 成功时返回写入的字符数(不包括终止空字节)。
    • 失败时返回 0
  • 特点
    • 提供对输出格式的精确控制。
    • 不会自动添加换行符,除非 format 中包含换行符。

4. strftime() 的常用格式化说明符

以下是 strftime() 支持的格式化说明符的子集:

说明符描述示例
%%百分号字符%
%a星期几的缩写Tue
%A星期几的全称Tuesday
%b月份名称的缩写Feb
%B月份全称February
%c日期和时间Tue Feb 1 21:39:46 2011
%d一个月中的一天(2位数)01
%D美国日期格式(%m/%d/%y02/01/11
%FISO 日期格式(%Y-%m-%d2011-02-01
%H小时(24小时制,2位数)21
%I小时(12小时制,2位数)09
%j一年中的第几天(3位数)032
%m月份(2位数)02
%M分钟(2位数)39
%pAM/PMPM
%R24小时制时间(%H:%M21:39
%S秒(2位数)46
%T时间(%H:%M:%S21:39:46
%u星期几编号(1-7,周一=1)2
%U一年中的周数(以周日计算)05
%w星期几编号(0-6,周日=0)2

5. 示例程序

以下程序演示了如何使用 asctime()ctime()gmtime()localtime()strftime()

#include <stdio.h>
#include <time.h>
#include <locale.h>
#include "tlpi_hdr.h"

#define SECONDS_IN_TROPICAL_YEAR (365.24219 * 24 * 60 * 60)

int main() {
    time_t t;
    struct tm *gmp, *locp;
    struct tm gm, loc;
    struct timeval tv;

    t = time(NULL);
    printf("Seconds since the Epoch (1 Jan 1970): %ld", (long)t);
    printf(" (about %6.3f years)\n", t / SECONDS_IN_TROPICAL_YEAR);

    if (gettimeofday(&tv, NULL) == -1)
        errExit("gettimeofday");
    printf("gettimeofday() returned %ld secs, %ld microsecs\n", (long)tv.tv_sec, (long)tv.tv_usec);

    gmp = gmtime(&t);
    if (gmp == NULL)
        errExit("gmtime");
    gm = *gmp; // 保存本地副本
    printf("Broken down by gmtime():\n");
    printf("year=%d mon=%d mday=%d hour=%d min=%d sec=%d ",
           gm.tm_year, gm.tm_mon, gm.tm_mday, gm.tm_hour, gm.tm_min, gm.tm_sec);
    printf("wday=%d yday=%d isdst=%d\n", gm.tm_wday, gm.tm_yday, gm.tm_isdst);

    locp = localtime(&t);
    if (locp == NULL)
        errExit("localtime");
    loc = *locp; // 保存本地副本
    printf("Broken down by localtime():\n");
    printf("year=%d mon=%d mday=%d hour=%d min=%d sec=%d ",
           loc.tm_year, loc.tm_mon, loc.tm_mday, loc.tm_hour, loc.tm_min, loc.tm_sec);
    printf("wday=%d yday=%d isdst=%d\n", loc.tm_wday, loc.tm_yday, loc.tm_isdst);

    printf("asctime() formats the gmtime() value as: %s", asctime(&gm));
    printf("ctime() formats the time() value as: %s", ctime(&t));

    printf("mktime() of gmtime() value: %ld secs\n", (long)mktime(&gm));
    printf("mktime() of localtime() value: %ld secs\n", (long)mktime(&loc));

    exit(EXIT_SUCCESS);
}

总结

  • asctime():将分解时间转换为固定格式字符串。
  • strftime():将分解时间转换为自定义格式字符串。
  • 格式化说明符:提供丰富的日期和时间格式化选项。
  • 线程安全:使用可重入版本(如 asctime_r())以避免静态分配问题。

通过这些函数,可以灵活地在分解时间和打印格式之间进行转换,并满足不同的格式化需求。

程序清单 10-2:返回当前时间的字符串的函数

currTime() 函数
  • 功能:返回当前时间的格式化字符串。
  • 函数原型
    #include <time.h>
    #include "curr_time.h"
    
    char *currTime(const char *format);
    
  • 参数
    • format:格式化字符串(如 "%Y-%m-%d %H:%M:%S")。如果为 NULL,默认使用 "%c"
  • 返回值
    • 成功时返回指向静态分配字符串的指针。
    • 失败时返回 NULL
  • 实现
    #define BUF_SIZE 1000
    
    char *currTime(const char *format) {
        static char buf[BUF_SIZE]; // 静态分配的缓冲区
        time_t t;
        size_t s;
        struct tm *tm;
    
        t = time(NULL); // 获取当前时间
        tm = localtime(&t); // 转换为本地时间
        if (tm == NULL)
            return NULL;
    
        s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm); // 格式化时间
        return (s == 0) ? NULL : buf;
    }
    

程序清单 10-3:将打印格式时间转换为分解时间

strptime() 函数
  • 功能:将字符串解析为分解时间。
  • 函数原型
    #define _XOPEN_SOURCE
    #include <time.h>
    
    char *strptime(const char *str, const char *format, struct tm *timeptr);
    
  • 参数
    • str:待解析的字符串。
    • format:格式化字符串(如 "%Y-%m-%d %H:%M:%S")。
    • timeptr:指向 struct tm 的指针,用于存储解析结果。
  • 返回值
    • 成功时返回指向 str 中下一个未处理字符的指针。
    • 失败时返回 NULL
  • 特点
    • 类似于 scanf(),支持灵活的格式匹配。
    • 不区分大小写(如 JanJAN 均可匹配)。
    • 不修改未在 format 中指定的 struct tm 字段。
    • 不设置 tm_isdst 字段,需手动设置或由 mktime() 自动确定。

示例程序

以下程序演示了 strptime()strftime() 的用法。

#define _XOPEN_SOURCE
#include <time.h>
#include <locale.h>
#include <string.h>
#include "tlpi_hdr.h"

#define SBUF_SIZE 1000

int main(int argc, char *argv[]) {
    struct tm tm;
    char sbuf[SBUF_SIZE];
    char *ofmt;

    if (argc < 3 || strcmp(argv[1], "--help") == 0)
        usageErr("%s input-date-time in-format [out-format]\n", argv[0]);

    if (setlocale(LC_ALL, "") == NULL)
        errExit("setlocale"); // 使用本地化设置

    memset(&tm, 0, sizeof(struct tm)); // 初始化 tm 结构体

    if (strptime(argv[1], argv[2], &tm) == NULL)
        fatal("strptime"); // 解析输入字符串

    tm.tm_isdst = -1; // 未设置 DST,由 mktime() 自动确定
    printf("calendar time (seconds since Epoch): %ld\n", (long)mktime(&tm));

    ofmt = (argc > 3) ? argv[3] : "%H:%M:%S %A, %d %B %Y %Z"; // 默认输出格式
    if (strftime(sbuf, SBUF_SIZE, ofmt, &tm) == 0)
        fatal("strftime returned 0"); // 格式化输出

    printf("strftime() yields: %s\n", sbuf);
    exit(EXIT_SUCCESS);
}

运行示例
  1. 基本用法

    $ ./strtime "9:39:46pm 1 Feb 2011" "%I:%M:%S%p %d %b %Y"
    calendar time (seconds since Epoch): 1296592786
    strftime() yields: 21:39:46 Tuesday, 01 February 2011 CET
    
  2. 自定义输出格式

    $ ./strtime "9:39:46pm 1 Feb 2011" "%I:%M:%S%p %d %b %Y" "%F %T"
    calendar time (seconds since Epoch): 1296592786
    strftime() yields: 2011-02-01 21:39:46
    

总结

  • currTime():返回当前时间的格式化字符串,支持自定义格式。
  • strptime():将字符串解析为分解时间,支持灵活的格式匹配。
  • strftime():将分解时间转换为自定义格式的字符串。
  • 本地化支持:通过 setlocale() 设置本地化环境,影响时间格式的本地化表示。

通过这些函数,可以方便地在字符串和分解时间之间进行转换,并满足不同的格式化需求。

10.3 时区

时区是地理区域的标准时间,不同国家或地区可能使用不同的时区,甚至同一国家内的不同地区也可能使用不同的时区。此外,许多地区还采用夏令时(DST)来调整时间。为了处理这些复杂性,C语言函数库提供了对时区和夏令时的支持。


1. 时区定义
  • 时区信息存储

    • 时区信息存储于 /usr/share/zoneinfo 目录下的文件中。
    • 每个文件描述一个特定国家或地区的时区制度,例如:
      • EST(美国东部标准时间)
      • CET(欧洲中部时间)
      • UTC(协调世界时)
      • Turkey(土耳其时间)
      • Iran(伊朗时间)
    • 子目录用于对相关时区进行分组,例如:
      • /usr/share/zoneinfo/Pacific/Auckland(新西兰奥克兰时间)
      • /usr/share/zoneinfo/Pacific/Port_Moresby(巴布亚新几内亚时间)
  • 系统本地时间

    • 系统的本地时间由 /etc/localtime 文件定义,通常是一个指向 /usr/share/zoneinfo 下某个文件的符号链接。
  • 时区文件格式

    • 时区文件的格式在 tzfile(5) 手册页中有详细描述。
    • 时区文件可以通过 zic(8)(时区信息编译器)工具创建。
    • 使用 zdump(8) 命令可以显示指定时区文件的当前时间。

2. 为程序指定时区
  • TZ 环境变量

    • 通过设置 TZ 环境变量,可以为程序指定时区。
    • TZ 的格式为 :<时区名称>,其中时区名称是 /usr/share/zoneinfo 下的文件路径。
    • 例如:
      • TZ=":Europe/Berlin"(欧洲柏林时间)
      • TZ=":Pacific/Auckland"(新西兰奥克兰时间)
  • 时区的影响

    • 设置 TZ 环境变量会影响以下函数的行为:
      • ctime()
      • localtime()
      • mktime()
      • strftime()
  • 默认时区

    • 如果未设置 TZ 环境变量,则使用 /etc/localtime 定义的默认时区。
    • 如果 TZ 为空或无效,则使用 UTC
  • 全局变量

    • 时区设置会初始化以下全局变量:
      char *tzname[2];  // 时区名称和夏令时名称
      int daylight;     // 非零表示启用夏令时
      long timezone;    // UTC 与本地标准时间的秒数差
      

3. TZ 环境变量的两种格式
  • 格式 1:时区文件路径

    • 例如:TZ=":Europe/Berlin"
    • 这是 Linux 和其他 UNIX 系统的常见格式。
  • 格式 2:SUSv3 标准格式

    • 格式:std offset[dst[offset][,start[/time],end[/time]]]
    • 示例:
      TZ="CET-1CEST-2,M3.5.0,M10.5.0"
      
      • CET-1:标准时间比 UTC 提前 1 小时。
      • CEST-2:夏令时比 UTC 提前 2 小时。
      • M3.5.0:夏令时从 3 月最后一个星期日开始。
      • M10.5.0:夏令时到 10 月最后一个星期日结束。

4. 示例程序

以下程序演示了时区设置对时间函数的影响。

#include <time.h>
#include <locale.h>
#include "tlpi_hdr.h"

#define BUF_SIZE 200

int main(int argc, char *argv[]) {
    time_t t;
    struct tm *loc;
    char buf[BUF_SIZE];

    if (setlocale(LC_ALL, "") == NULL)
        errExit("setlocale"); // 使用本地化设置

    t = time(NULL); // 获取当前时间
    printf("ctime() of time() value is: %s", ctime(&t));

    loc = localtime(&t); // 转换为本地时间
    if (loc == NULL)
        errExit("localtime");

    printf("asctime() of local time is: %s", asctime(loc));

    if (strftime(buf, BUF_SIZE, "%A, %d %B %Y, %H:%M:%S %Z", loc) == 0)
        fatal("strftime returned 0"); // 格式化输出

    printf("strftime() of local time is: %s\n", buf);
    exit(EXIT_SUCCESS);
}

5. 运行示例
  1. 默认时区

    $ ./show_time
    ctime() of time() value is: Tue Feb  1 10:25:56 2011
    asctime() of local time is: Tue Feb  1 10:25:56 2011
    strftime() of local time is: Tuesday, 01 February 2011, 10:25:56 CET
    
  2. 指定时区

    $ TZ=":Pacific/Auckland" ./show_time
    ctime() of time() value is: Tue Feb  1 22:26:19 2011
    asctime() of local time is: Tue Feb  1 22:26:19 2011
    strftime() of local time is: Tuesday, 01 February 2011, 22:26:19 NZDT
    

总结

  • 时区文件:位于 /usr/share/zoneinfo,定义了不同地区的时区信息。
  • TZ 环境变量:用于为程序指定时区,影响时间函数的行为。
  • 时间函数ctime()localtime()mktime()strftime() 都会受到时区设置的影响。
  • 夏令时:通过 TZ 环境变量或时区文件自动处理。

通过合理设置时区,程序可以正确地处理不同地区的时间,并自动适应夏令时调整。

10.4 地区(Locale)

地区(Locale)是指用户环境中依赖于语言和文化习俗的一个子集。它决定了程序如何显示和输入信息,例如日期、时间、货币、数字格式等。为了支持国际化(Internationalization,简称 I18N),程序需要能够适应不同地区的语言和文化习俗。


1. 地区定义
  • 地区信息存储

    • 地区信息存储于 /usr/share/locale(或 /usr/lib/locale)目录下的子目录中。

    • 每个子目录代表一个特定地区,命名格式为:

      language[_territory][.codeset][@modifier]
      
      • language:双字母的 ISO 语言代码(如 de 表示德语)。
      • territory:双字母的 ISO 国家代码(如 DE 表示德国)。
      • codeset:字符编码集(如 UTF-8)。
      • modifier:修饰符,用于区分相同语言和国家的不同地区(如 @euro 表示使用欧元)。
    • 示例:

      • de_DE.utf8@euro:德语,德国,UTF-8 编码,使用欧元。
      • en_US:英语,美国。
      • fr_CH:法语,瑞士。
  • 地区匹配规则

    • 如果指定的地区名称与目录不匹配,系统会按以下顺序尝试匹配:
      1. 移除 codeset
      2. 移除 normalized codeset(标准化字符编码集)。
      3. 移除 territory
      4. 移除 modifier
    • 如果仍未找到匹配,则使用默认地区 CPOSIX
  • 默认地区

    • POSIX 地区是 SUSv3 定义的标准地区,使用 ASCII 字符集和英文格式。
    • C 地区与 POSIX 相同,用于历史兼容性。

2. 地区类别

每个地区子目录中包含多个文件,定义了不同类别的地区设置:

文件名描述
LC_CTYPE字符分类和大小写转换规则(如 isalpha() 的行为)。
LC_COLLATE字符排序规则(如 strcoll()strxfrm() 的行为)。
LC_MONETARY货币格式化规则(如货币符号、小数点分隔符)。
LC_NUMERIC数字格式化规则(如小数点分隔符、千位分隔符)。
LC_TIME日期和时间格式化规则(如月份名称、日期格式)。
LC_MESSAGES消息格式(如“是/否”响应的翻译)。
  • 额外类别(GNU 扩展):
    • LC_ADDRESS:邮政地址格式。
    • LC_IDENTIFICATION:地区标识信息。
    • LC_MEASUREMENT:度量系统(如公制或英制)。
    • LC_NAME:人名和头衔格式。
    • LC_PAPER:标准纸张尺寸(如 A4 或信纸)。
    • LC_TELEPHONE:电话号码格式。

3. 设置地区
  • setlocale() 函数

    • 功能:设置或查询程序的当前地区。
    • 函数原型
      #include <locale.h>
      char *setlocale(int category, const char *locale);
      
    • 参数
      • category:地区类别(如 LC_ALLLC_TIMELC_MONETARY 等)。
      • locale:地区名称(如 "de_DE")或空字符串(从环境变量获取)。
    • 返回值
      • 成功时返回指向地区名称字符串的指针。
      • 失败时返回 NULL
  • 设置方法

    1. 直接指定地区
      setlocale(LC_ALL, "de_DE.utf8");
      
    2. 从环境变量获取
      setlocale(LC_ALL, "");
      
  • 环境变量

    • LANG:设置默认地区。
    • LC_ALL:覆盖所有其他 LC_* 变量。
    • LC_CTYPELC_TIME 等:设置特定类别的地区。
  • 优先级

    LC_ALL > LC_* > LANG
    

4. 示例程序

以下程序演示了地区设置对时间格式的影响。

#include <locale.h>
#include <time.h>
#include "tlpi_hdr.h"

#define BUF_SIZE 200

int main(int argc, char *argv[]) {
    time_t t;
    struct tm *loc;
    char buf[BUF_SIZE];

    if (setlocale(LC_ALL, "") == NULL)
        errExit("setlocale"); // 使用环境变量设置地区

    t = time(NULL); // 获取当前时间
    printf("ctime() of time() value is: %s", ctime(&t));

    loc = localtime(&t); // 转换为本地时间
    if (loc == NULL)
        errExit("localtime");

    printf("asctime() of local time is: %s", asctime(loc));

    if (strftime(buf, BUF_SIZE, "%A, %d %B %Y, %H:%M:%S %Z", loc) == 0)
        fatal("strftime returned 0"); // 格式化输出

    printf("strftime() of local time is: %s\n", buf);
    exit(EXIT_SUCCESS);
}

5. 运行示例
  1. 德语地区

    $ LANG=de_DE ./show_time
    ctime() of time() value is: Tue Feb  1 12:23:39 2011
    asctime() of local time is: Tue Feb  1 12:23:39 2011
    strftime() of local time is: Dienstag, 01 Februar 2011, 12:23:39 CET
    
  2. 混合地区

    $ LANG=de_DE LC_TIME=it_IT ./show_time
    ctime() of time() value is: Tue Feb  1 12:24:03 2011
    asctime() of local time is: Tue Feb  1 12:24:03 2011
    strftime() of local time is: martedi, 01 febbraio 2011, 12:24:03 CET
    
  3. 法语地区

    $ LC_ALL=fr_FR ./show_time
    ctime() of time() value is: Tue Feb  1 12:25:38 2011
    asctime() of local time is: Tue Feb  1 12:25:38 2011
    strftime() of local time is: mardi, 01 fevrier 2011, 12:25:38 CET
    

总结

  • 地区:定义了语言和文化习俗相关的设置,影响程序的输入和输出格式。
  • setlocale():用于设置或查询程序的地区。
  • 环境变量:通过 LANGLC_* 变量控制地区的不同类别。
  • 国际化支持:通过合理设置地区,程序可以适应不同语言和文化环境。

10.5 更新系统时钟

系统时钟的更新通常由系统工具(如网络时间协议 NTP 守护进程)维护,但 Linux 提供了两个系统调用用于手动更新系统时钟:settimeofday()adjtime()


1. settimeofday() 系统调用
  • 功能:设置系统的日历时间。
  • 函数原型
    #define _BSD_SOURCE
    #include <sys/time.h>
    int settimeofday(const struct timeval *tv, const struct timezone *tz);
    
  • 参数
    • tv:指向 struct timeval 的指针,包含秒数和微秒数。
      struct timeval {
          time_t      tv_sec;   // 秒数
          suseconds_t tv_usec;  // 微秒数
      };
      
    • tz:历史遗留参数,应始终设置为 NULL
  • 返回值
    • 成功时返回 0
    • 失败时返回 -1,并设置 errno
  • 特点
    • 直接设置系统时间,可能导致时间突然变化。
    • 需要调用者具有 CAP_SYS_TIME 权限。
    • tv_usec 的微秒精度不一定被系统完全支持。

2. stime() 系统调用
  • 功能:以秒为单位设置系统时钟。
  • 函数原型
    #include <time.h>
    int stime(const time_t *t);
    
  • 参数
    • t:指向 time_t 的指针,表示自 Epoch 以来的秒数。
  • 返回值
    • 成功时返回 0
    • 失败时返回 -1,并设置 errno
  • 特点
    • settimeofday() 功能类似,但精度较低(仅秒级)。
    • 需要调用者具有 CAP_SYS_TIME 权限。

3. adjtime() 函数
  • 功能:逐步调整系统时钟,避免时间突然变化。
  • 函数原型
    #define _BSD_SOURCE
    #include <sys/time.h>
    int adjtime(const struct timeval *delta, struct timeval *olddelta);
    
  • 参数
    • delta:指向 struct timeval 的指针,表示需要调整的时间。
      • 如果 delta->tv_sec 为正,系统时钟会逐渐加快。
      • 如果 delta->tv_sec 为负,系统时钟会逐渐减慢。
    • olddelta:指向 struct timeval 的指针,用于存储未完成的调整时间。
      • 如果为 NULL,则不返回未完成的调整时间。
  • 返回值
    • 成功时返回 0
    • 失败时返回 -1,并设置 errno
  • 特点
    • 适用于对时间进行微小调整(几秒钟误差)。
    • 避免时间突然变化对应用程序的影响(如日志文件、数据库时间戳)。
    • 需要调用者具有 CAP_SYS_TIME 权限。

4. adjtimex() 系统调用
  • 功能:更通用和复杂的系统时钟调整接口,被 NTP 守护进程使用。
  • 特点
    • 提供了比 adjtime() 更精细的控制。
    • 详细信息可参考 adjtimex(2) 手册页和 NTP 规范。

10.6 软件时钟(Jiffies)

软件时钟是内核中用于计时和调度的基本单位,其分辨率由内核常量 HZ 定义。


1. Jiffies 的定义
  • Jiffies:软件时钟的基本单位,表示两次时钟中断之间的时间间隔。
  • HZ:内核常量,表示每秒的时钟中断次数。
    • 例如,HZ = 100 表示每秒 100 次时钟中断,每个 jiffy 为 10 毫秒。

2. Linux 内核中的 Jiffies
  • 历史
    • Linux 2.4 及更早版本:HZ = 100(每个 jiffy 为 10 毫秒)。
    • Linux 2.6.0:HZ = 1000(每个 jiffy 为 1 毫秒)。
  • 可配置性
    • 自 Linux 2.6.13 起,HZ 可配置为 100、250(默认)或 1000 赫兹。
    • 自 Linux 2.6.20 起,增加了 300 赫兹选项,适用于视频帧率(25 帧/秒和 30 帧/秒)。

3. Jiffies 的影响
  • 精度
    • 更高的 HZ 值意味着更高的定时器精度和时间测量精度。
  • 开销
    • 更高的 HZ 值会增加时钟中断的频率,导致更多的 CPU 开销。

总结

  • settimeofday():直接设置系统时间,可能导致时间突然变化。
  • adjtime():逐步调整系统时间,适用于微小调整。
  • Jiffies:内核软件时钟的基本单位,HZ 定义了时钟中断频率。
  • 可配置性HZ 可根据需求配置为 100、250、300 或 1000 赫兹。

通过合理使用这些接口,可以有效地管理和调整系统时钟,同时避免对应用程序的负面影响。

以下是一些具体的代码示例,展示如何使用 settimeofday()adjtime()stime() 来更新系统时钟。


1. settimeofday() 示例

设置系统时钟为指定的时间。

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

int main() {
    struct timeval tv;

    // 设置时间为 2023年10月10日 12:00:00
    tv.tv_sec = 1696946400;  // 自 Epoch 以来的秒数
    tv.tv_usec = 0;          // 微秒数

    if (settimeofday(&tv, NULL) == -1) {
        perror("settimeofday");
        exit(EXIT_FAILURE);
    }

    printf("System time set to 2023-10-10 12:00:00\n");
    return 0;
}

注意

  • 需要以 root 权限运行此程序。
  • 直接设置系统时间可能导致应用程序出现问题(如日志时间戳不连续)。

2. adjtime() 示例

逐步调整系统时钟。

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

int main() {
    struct timeval delta, olddelta;

    // 设置需要调整的时间(加快 5 秒)
    delta.tv_sec = 5;
    delta.tv_usec = 0;

    if (adjtime(&delta, &olddelta) == -1) {
        perror("adjtime");
        exit(EXIT_FAILURE);
    }

    printf("Clock is being adjusted by 5 seconds.\n");
    if (olddelta.tv_sec != 0 || olddelta.tv_usec != 0) {
        printf("Previous adjustment remaining: %ld seconds, %ld microseconds\n",
               (long)olddelta.tv_sec, (long)olddelta.tv_usec);
    }

    return 0;
}

注意

  • 需要以 root 权限运行此程序。
  • adjtime() 适用于对时间进行微小调整,避免时间突然变化。

3. stime() 示例

以秒为单位设置系统时钟。

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main() {
    time_t t;

    // 设置时间为 2023年10月10日 12:00:00
    t = 1696946400;  // 自 Epoch 以来的秒数

    if (stime(&t) == -1) {
        perror("stime");
        exit(EXIT_FAILURE);
    }

    printf("System time set to 2023-10-10 12:00:00\n");
    return 0;
}

注意

  • 需要以 root 权限运行此程序。
  • stime() 的精度较低(仅秒级)。

4. 获取和打印当前时间

在调整系统时间后,可以使用 gettimeofday() 获取当前时间并打印。

#include <stdio.h>
#include <sys/time.h>
#include <time.h>

void print_current_time() {
    struct timeval tv;
    struct tm *tm_info;

    if (gettimeofday(&tv, NULL) == -1) {
        perror("gettimeofday");
        return;
    }

    tm_info = localtime(&tv.tv_sec);
    if (tm_info == NULL) {
        perror("localtime");
        return;
    }

    printf("Current time: %04d-%02d-%02d %02d:%02d:%02d.%06ld\n",
           tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
           tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, (long)tv.tv_usec);
}

int main() {
    print_current_time();
    return 0;
}

输出

Current time: 2023-10-10 12:00:00.000000

5. 综合示例

结合 settimeofday()adjtime(),设置系统时间并逐步调整。

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>

void print_current_time() {
    struct timeval tv;
    struct tm *tm_info;

    if (gettimeofday(&tv, NULL) == -1) {
        perror("gettimeofday");
        return;
    }

    tm_info = localtime(&tv.tv_sec);
    if (tm_info == NULL) {
        perror("localtime");
        return;
    }

    printf("Current time: %04d-%02d-%02d %02d:%02d:%02d.%06ld\n",
           tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
           tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, (long)tv.tv_usec);
}

int main() {
    struct timeval tv;

    // 设置时间为 2023年10月10日 12:00:00
    tv.tv_sec = 1696946400;
    tv.tv_usec = 0;

    if (settimeofday(&tv, NULL) == -1) {
        perror("settimeofday");
        exit(EXIT_FAILURE);
    }

    printf("System time set to 2023-10-10 12:00:00\n");
    print_current_time();

    // 逐步调整时间(加快 5 秒)
    struct timeval delta = {5, 0};
    if (adjtime(&delta, NULL) == -1) {
        perror("adjtime");
        exit(EXIT_FAILURE);
    }

    printf("Clock is being adjusted by 5 seconds.\n");
    sleep(2);  // 等待 2 秒
    print_current_time();

    return 0;
}

输出

System time set to 2023-10-10 12:00:00
Current time: 2023-10-10 12:00:00.000000
Clock is being adjusted by 5 seconds.
Current time: 2023-10-10 12:00:02.500000

总结

  • settimeofday():直接设置系统时间,适用于需要精确时间设置的场景。
  • adjtime():逐步调整系统时间,适用于对时间进行微小调整。
  • stime():以秒为单位设置系统时间,精度较低。
  • 注意事项
    • 需要 root 权限。
    • 直接设置系统时间可能导致应用程序出现问题,建议谨慎使用。

10.7 进程时间

进程时间是指进程创建后使用的 CPU 时间总量,分为两部分:

  1. 用户 CPU 时间:在用户模式下执行的时间。
  2. 系统 CPU 时间:在内核模式下执行的时间(如系统调用、页错误处理等)。

进程时间可以通过系统调用 times() 和函数 clock() 来获取。


1. times() 系统调用
  • 功能:获取进程的 CPU 时间信息。

  • 函数原型

    #include <sys/times.h>
    clock_t times(struct tms *buf);
    
  • 参数

    • buf:指向 struct tms 的指针,用于存储时间信息。
      struct tms {
          clock_t tms_utime;  // 用户 CPU 时间
          clock_t tms_stime;  // 系统 CPU 时间
          clock_t tms_cutime; // 所有子进程的用户 CPU 时间
          clock_t tms_cstime; // 所有子进程的系统 CPU 时间
      };
      
  • 返回值

    • 成功时返回自过去某一时刻以来的时钟计时单元数。
    • 失败时返回 (clock_t)-1
  • 时钟计时单元

    • 时钟计时单元的单位是 clock tick,可以通过 sysconf(_SC_CLK_TCK) 获取每秒的时钟计时单元数。
    • 在大多数 Linux 系统上,sysconf(_SC_CLK_TCK) 返回 100

2. clock() 函数
  • 功能:获取进程的总 CPU 时间(用户 + 系统)。

  • 函数原型

    #include <time.h>
    clock_t clock(void);
    
  • 返回值

    • 成功时返回进程使用的总 CPU 时间,单位为 CLOCKS_PER_SEC
    • 失败时返回 (clock_t)-1
  • CLOCKS_PER_SEC

    • 在 POSIX.1 中,CLOCKS_PER_SEC 定义为 1000000
    • 需要将 clock() 的返回值除以 CLOCKS_PER_SEC 以获得秒数。

3. 示例程序

以下程序演示了如何使用 times()clock() 获取进程时间。

#include <sys/times.h>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "tlpi_hdr.h"

static void displayProcessTimes(const char *msg) {
    struct tms t;
    clock_t clockTime;
    static long clockTicks = 0;

    if (msg != NULL)
        printf("%s", msg);

    if (clockTicks == 0) { // 获取时钟计时单元
        clockTicks = sysconf(_SC_CLK_TCK);
        if (clockTicks == -1)
            errExit("sysconf");
    }

    clockTime = clock(); // 获取总 CPU 时间
    if (clockTime == -1)
        errExit("clock");

    printf("clock() returns: %ld clocks-per-sec (%.2f secs)\n",
           (long)clockTime, (double)clockTime / CLOCKS_PER_SEC);

    if (times(&t) == -1) // 获取进程时间
        errExit("times");

    printf("times() yields: user CPU=%.2f; system CPU=%.2f\n",
           (double)t.tms_utime / clockTicks, (double)t.tms_stime / clockTicks);
}

int main(int argc, char *argv[]) {
    int numCalls, j;

    printf("CLOCKS_PER_SEC=%ld sysconf(_SC_CLK_TCK)=%ld\n\n",
           (long)CLOCKS_PER_SEC, sysconf(_SC_CLK_TCK));

    displayProcessTimes("At program start:\n");

    numCalls = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-calls") : 10000000;
    for (j = 0; j < numCalls; j++)
        (void)getppid(); // 消耗 CPU 时间

    displayProcessTimes("After getppid() loop:\n");

    exit(EXIT_SUCCESS);
}

4. 运行示例

编译并运行程序:

$ ./process_time 10000000
CLOCKS_PER_SEC=1000000 sysconf(_SC_CLK_TCK)=100

At program start:
clock() returns: 0 clocks-per-sec (0.00 secs)
times() yields: user CPU=0.00; system CPU=0.00

After getppid() loop:
clock() returns: 2960000 clocks-per-sec (2.96 secs)
times() yields: user CPU=1.09; system CPU=1.87

5. 程序说明
  • displayProcessTimes()
    • 打印当前进程的用户和系统 CPU 时间。
    • 使用 clock() 获取总 CPU 时间。
    • 使用 times() 获取用户和系统 CPU 时间。
  • 主程序
    • 调用 getppid() 消耗 CPU 时间。
    • 在循环前后分别调用 displayProcessTimes(),显示 CPU 时间的变化。

总结

  • times():获取进程的用户和系统 CPU 时间,以及子进程的 CPU 时间。
  • clock():获取进程的总 CPU 时间。
  • 单位转换
    • times() 的时间单位是 clock tick,需除以 sysconf(_SC_CLK_TCK) 转换为秒。
    • clock() 的时间单位是 CLOCKS_PER_SEC,需除以 CLOCKS_PER_SEC 转换为秒。

通过合理使用这些函数,可以测量和分析进程的 CPU 时间消耗。

10.9 练习 10-1

问题描述

假设:

  1. sysconf(_SC_CLK_TCK) 返回的值是 100
  2. times() 返回的 clock_t 值是一个无符号的 32 位整数。
  3. clock() 返回的 CLOCKS_PER_SEC 值是 1000000

计算:

  1. times() 返回的 clock_t 值需要多久才能进入下一个从 0 开始的周期。
  2. clock() 返回的 CLOCKS_PER_SEC 值执行相同的计算。

1. times() 的周期计算
  • clock_t 的范围
    • clock_t 是一个无符号的 32 位整数,其最大值为 (2^{32} - 1 = 4294967295)。
  • 时钟计时单元
    • sysconf(_SC_CLK_TCK) 返回 100,表示每秒有 100 个时钟计时单元。
  • 周期时间
    • 周期时间 (T) 可以通过以下公式计算:
      [
      T = \frac{\text{最大值}}{\text{每秒的时钟计时单元数}} = \frac{4294967295}{100} \text{秒}
      ]
    • 计算结果:
      [
      T = \frac{4294967295}{100} = 42949672.95 \text{秒}
      ]
    • 转换为更易读的单位:
      [
      42949672.95 \text{秒} \approx 497.1 \text{天} \approx 1.36 \text{年}
      ]

2. clock() 的周期计算
  • clock_t 的范围
    • clock_t 是一个无符号的 32 位整数,其最大值为 (2^{32} - 1 = 4294967295)。
  • 时钟计时单元
    • CLOCKS_PER_SEC 定义为 1000000,表示每秒有 1000000 个时钟计时单元。
  • 周期时间
    • 周期时间 (T) 可以通过以下公式计算:
      [
      T = \frac{\text{最大值}}{\text{每秒的时钟计时单元数}} = \frac{4294967295}{1000000} \text{秒}
      ]
    • 计算结果:
      [
      T = \frac{4294967295}{1000000} = 4294.967295 \text{秒}
      ]
    • 转换为更易读的单位:
      [
      4294.967295 \text{秒} \approx 71.58 \text{分钟} \approx 1.19 \text{小时}
      ]

总结
  1. times() 的周期

    • 最大值:4294967295 个时钟计时单元。
    • 每秒的时钟计时单元数:100。
    • 周期时间:约 497.1 天(1.36 年)。
  2. clock() 的周期

    • 最大值:4294967295 个时钟计时单元。
    • 每秒的时钟计时单元数:1000000。
    • 周期时间:约 71.58 分钟(1.19 小时)。

注意事项
  • times() 的周期较长:由于 times() 的时钟计时单元较少(每秒 100 个),其周期时间较长(约 1.36 年)。
  • clock() 的周期较短:由于 clock() 的时钟计时单元较多(每秒 1000000 个),其周期时间较短(约 1.19 小时)。
  • 溢出风险:在长时间运行的进程中,clock() 的返回值可能会溢出,导致时间计算错误。因此,建议使用 gettimeofday()clock_gettime() 来测量经过时间。

http://www.kler.cn/a/501882.html

相关文章:

  • UnityXR Interaction Toolkit 如何检测HandGestures
  • 大语言模型的稀疏性:提升效率与性能的新方向
  • 灌区闸门自动化控制系统-精准渠道量测水-灌区现代化建设
  • nginx负载均衡-基于端口的负载均衡(一)
  • Elasticsearch学习(2) :DSL和RestClient实现搜索文档
  • el-table自定义按钮控制扩展expand
  • 一文讲解常见API开发工具
  • 【机器学习:十、神经网络概述】
  • 1. 初识Scala
  • 一 rk3568 Android 11固件开发环境搭建 (docker)
  • NAT 代理服务器
  • 【芯片设计- RTL 数字逻辑设计入门 9.2 -- flip flop 与 寄存器的关系详细介绍】
  • 【LeetCode】力扣刷题热题100道(26-30题)附源码 轮转数组 乘积 矩阵 螺旋矩阵 旋转图像(C++)
  • 项目实战--网页五子棋(用户模块)(1)
  • 支持selenium的chrome driver更新到131.0.6778.264
  • JDK8新特性详解
  • mermaid大全(语法、流程图、时序图、甘特图、饼图、用户旅行图、类图)
  • 在 VS Code 中使用通义灵码:解锁 AI 编程新体验
  • 【DB-GPT】开启数据库交互新篇章的技术探索与实践
  • 学习华为熵减,激发组织活力
  • Data Mesh: 数据产品化
  • TypeScript 爬虫项目实战:抓取豆瓣电影 Top 250(TypeScript简单应用)
  • BO-SVM贝叶斯算法优化支持向量机的数据多变量时间序列预测
  • MySQL第四天作业
  • 如何借助工程项目管理系统,解决建筑项目管理痛点?
  • 字玩FontPlayer开发笔记11 Tauri2剪切板模块