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系统中的表示方式。
-
真实时间:
- 日历时间:从标准点(如UTC 1970年1月1日零点,称为Epoch)开始计算的时间,用于记录时间戳。
- 流逝时间:从进程启动开始计算的时间,适用于周期性操作或外部设备的时间度量。
-
进程时间:进程使用的CPU时间总量,用于性能检查和优化。
-
硬件时钟:计算机体系结构内置的时钟,帮助内核计算真实时间和进程时间。
-
日历时间的表示:
- UNIX系统内部使用自Epoch以来的秒数表示时间,存储在
time_t
类型的变量中。 - 32位系统中,
time_t
是有符号整数,时间范围从1901年12月13日到2038年1月19日,存在“2038年问题”。 - 64位系统可以缓解这一问题,但32位嵌入式系统和历史数据仍可能受到影响。
- UNIX系统内部使用自Epoch以来的秒数表示时间,存储在
总结:程序关注真实时间和进程时间,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
指向的位置。
- 返回值:自Epoch以来的秒数,失败返回
-
常见用法:
time_t t = time(NULL);
4. time()
与 gettimeofday()
的关系
- 历史原因:
- 早期UNIX提供
time()
,4.3BSD补充了更精确的gettimeofday()
。 time()
可以视为gettimeofday()
的简化版本。
- 早期UNIX提供
- 实现:
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
。
- 成功时返回一个 26字节 的静态分配字符串,格式为
- 特点:
- 自动考虑本地时区和夏令时(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_wday
和tm_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
。
- 成功时返回一个 26字节 的静态分配字符串,格式为
- 特点:
- 与
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/%y ) | 02/01/11 |
%F | ISO 日期格式(%Y-%m-%d ) | 2011-02-01 |
%H | 小时(24小时制,2位数) | 21 |
%I | 小时(12小时制,2位数) | 09 |
%j | 一年中的第几天(3位数) | 032 |
%m | 月份(2位数) | 02 |
%M | 分钟(2位数) | 39 |
%p | AM/PM | PM |
%R | 24小时制时间(%H:%M ) | 21:39 |
%S | 秒(2位数) | 46 |
%T | 时间(%H:%M:%S ) | 21: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()
,支持灵活的格式匹配。 - 不区分大小写(如
Jan
和JAN
均可匹配)。 - 不修改未在
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);
}
运行示例
-
基本用法:
$ ./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
-
自定义输出格式:
$ ./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. 运行示例
-
默认时区:
$ ./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
-
指定时区:
$ 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
:法语,瑞士。
-
-
地区匹配规则:
- 如果指定的地区名称与目录不匹配,系统会按以下顺序尝试匹配:
- 移除
codeset
。 - 移除
normalized codeset
(标准化字符编码集)。 - 移除
territory
。 - 移除
modifier
。
- 移除
- 如果仍未找到匹配,则使用默认地区
C
或POSIX
。
- 如果指定的地区名称与目录不匹配,系统会按以下顺序尝试匹配:
-
默认地区:
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_ALL
、LC_TIME
、LC_MONETARY
等)。locale
:地区名称(如"de_DE"
)或空字符串(从环境变量获取)。
- 返回值:
- 成功时返回指向地区名称字符串的指针。
- 失败时返回
NULL
。
-
设置方法:
- 直接指定地区:
setlocale(LC_ALL, "de_DE.utf8");
- 从环境变量获取:
setlocale(LC_ALL, "");
- 直接指定地区:
-
环境变量:
LANG
:设置默认地区。LC_ALL
:覆盖所有其他LC_*
变量。LC_CTYPE
、LC_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. 运行示例
-
德语地区:
$ 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
-
混合地区:
$ 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
-
法语地区:
$ 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()
:用于设置或查询程序的地区。- 环境变量:通过
LANG
和LC_*
变量控制地区的不同类别。 - 国际化支持:通过合理设置地区,程序可以适应不同语言和文化环境。
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.4 及更早版本:
- 可配置性:
- 自 Linux 2.6.13 起,
HZ
可配置为 100、250(默认)或 1000 赫兹。 - 自 Linux 2.6.20 起,增加了 300 赫兹选项,适用于视频帧率(25 帧/秒和 30 帧/秒)。
- 自 Linux 2.6.13 起,
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 时间总量,分为两部分:
- 用户 CPU 时间:在用户模式下执行的时间。
- 系统 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
。
- 成功时返回进程使用的总 CPU 时间,单位为
-
CLOCKS_PER_SEC
:- 在 POSIX.1 中,
CLOCKS_PER_SEC
定义为1000000
。 - 需要将
clock()
的返回值除以CLOCKS_PER_SEC
以获得秒数。
- 在 POSIX.1 中,
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
问题描述
假设:
sysconf(_SC_CLK_TCK)
返回的值是100
。times()
返回的clock_t
值是一个无符号的 32 位整数。clock()
返回的CLOCKS_PER_SEC
值是1000000
。
计算:
times()
返回的clock_t
值需要多久才能进入下一个从 0 开始的周期。- 对
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{年}
]
- 周期时间 (T) 可以通过以下公式计算:
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{小时}
]
- 周期时间 (T) 可以通过以下公式计算:
总结
-
times()
的周期:- 最大值:4294967295 个时钟计时单元。
- 每秒的时钟计时单元数:100。
- 周期时间:约 497.1 天(1.36 年)。
-
clock()
的周期:- 最大值:4294967295 个时钟计时单元。
- 每秒的时钟计时单元数:1000000。
- 周期时间:约 71.58 分钟(1.19 小时)。
注意事项
times()
的周期较长:由于times()
的时钟计时单元较少(每秒 100 个),其周期时间较长(约 1.36 年)。clock()
的周期较短:由于clock()
的时钟计时单元较多(每秒 1000000 个),其周期时间较短(约 1.19 小时)。- 溢出风险:在长时间运行的进程中,
clock()
的返回值可能会溢出,导致时间计算错误。因此,建议使用gettimeofday()
或clock_gettime()
来测量经过时间。