【TLPI读书笔记】 十、时间
日历时间(Calendar Time)
系统调用gettimeofday()
,可于tv
指向的缓冲区中返回日历时间。
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
Returns 0 on success, or -1 on error
参数
tv
:自 Epoch 以来的秒数(时间戳)信息tz
:时区信息
返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
tv
是指向如下数据结构的一个指针:
struct timeval {
time_t tv_sec; /* Seconds since 00:00:00, 1 Jan 1970 UTC */
suseconds tv_usec; /* Additional microseconds (long int) */
}
time()
系统调用返回自 Epoch 以来的秒数(和函数gettimeofday()
所返回的tv
参数中tv_sec
字段的数值相同)
#include <time.h>
int time(time_t *timep);
Returns number of seconds since the Epoch, or (time_t)-1 on error
参数
timep
:如果不指定为NULL
,函数会将时间戳置于该参数所指向的位置
返回值
- 成功:返回时间戳
- 失败:返回
-1
时间转换函数
下图所示为用于在time_t
值和其他时间格式之间相互转换的函数,其中包括打印输出。
这些函数屏蔽了因时区、夏令时制和本地化等问题给转换所带来的种种复杂性。
将time_t
转换为可打印格式
为了将time_t
转换为可打印格式,ctime()
函数提供了一个简单方法。
#include <time.h>
int *ctime(const time_t *timep);
Returns pointer to statically allocated string terminated by newline and \0 on success, or NULL on error
参数
timep
:指定时间戳
返回值
- 成功:返回一个长达26字节的字符串,内含标准格式的日期和时间,即可打印格式(存储在静态分配的内存)
- 失败:返回
NULL
time_t
和分解时间之间的转换
函数gmtime()
和localtime()
可将一time_t
值转换为一个所谓的分解时间。分解时间被置于一个经由静态分配的结构中,其地址则作为函数结果返回。
#include <time.h>
struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);
Both returns pointer to statically allocated broken-down time structure on success, or NULL on error
参数
timep
:指定时间戳
返回值
- 成功:返回存储分解时间(
tm
结构)的所在内存地址(存储在静态分配的内存) - 失败:返回
NULL
- 成功:返回存储分解时间(
函数gmtime()
能够把日历时间转换为一个对应于UTC的分解时间。相形之下,函数localtime()
需要考虑时区和夏令时设置,返回对应于系统本地时间的一个分解时间。
tm
结构如下所示,日期和时间被分解为多个独立字段,其形式如下:
struct tm {
mt tm_sec; /* Seconds (0-60) */
mt tm_min; /* Minutes (0-59) */
mt tm_hour; /* Hours (0-23) */
mt tm_mday; /* Day of the month (1-31) */
mt tm_mon; /* Month (0-11) */
mt tm_year; /* Year since 1900 */
mt tm_wday; /* Day of the week (Sunday = 0)*/
mt tm_yday; /* Day in the year (0-365; 1 Jan = 0) */
mt tm_isdst; /* Daylight saving time flag
> 0: DST is in effect;
= 0: DST is not effect;
< 0: DST information not available */
}
函数mktime()
将一个本地时区的分解时间翻译为time_t
值,并将其作为函数结果返回。
#include <time.h>
time_t mktime(struct tm *timeptr);
Returns seconds since the Epoch corresponding to timeptr on success, or (time_t)-1 on error
参数
timeptr
:将分解时间置于tm
结构的内存地址,函数会忽略tm
结构中的tm_wday
和tm_yday
字段。
返回值
- 成功:返回时间戳
- 失败:返回
-1
分解时间和打印格式之间的转换
从分解时间转换为打印格式
在参数tm
中提供一个指向分解时间结构的指针,asctime()
则会返回一指针,指向经由静态分配的字符串,内含时间,格式则与ctime()
相同。#include <time.h> time_t asctime(const struct tm *timeptr); Returns pointer to statically allocated string terminated by newline and \0 on success, or NULL on error
相形于
ctime()
,本地时区设置对asctime()
没有影响,因为其所转换的是一个分解时间,该时间为处理后的数据。将打印格式时间转换为分解时间
函数strptime()
是strftime()
的逆向函数,将包含日期和时间的字符串转换成一分解时间。#define _XOPEN_SOURCE #include <time.h> char *strptime(const char *str, const char *format, struct tm *timeptr); Returns pointer to next unprocessed character in str on success, or NULL on error
该函数按照参数
format
内的格式要求,对字符串str
加以解析,并将转换后的分解时间置于指针timeptr
所指向的结构体中。参数
str
:由日期和时间组成的字符串format
:指定格式timeptr
:该指针用来指向转换后的分解时间的结构体
返回值
- 成功:返回指针,指向
str
中下一个未经处理的字符 - 失败:返回
NULL
- 成功:返回指针,指向
时区
时区定义
- 系统没有将时区信息直接编码于程序或函数库中,而是以标准格式保存于文件中并加以维护。
- 这些文件位于
/usr/share/zoneinfo
中。该目录下的每个文件都包含了一个特定国家或地区内时区制度的相关信息,且往往根据其所描述的时区来加以命名。此外,可以利用子目录对相关时区进行有层次的分组。 - 在程序中指定使用的时区,实际上是指定该目录下某一时区文件的相对路径名。
为程序指定时区
为运行中的程序指定一个时区,需要将TZ
环境变量设置为由一冒号(:
)和时区名称组成的字符串(如TZ=":Asia/Shanghai"
),其中时区名称定义于/usr/share/zoneinfo
中。设置时区会自动影响到函数ctime()
、localtime()
、mktime()
和strftime()
。
为了获取当前的时区设置,上述函数都会调用tzset(3)
,对如下3个全局变量进行了初始化:
char *tzname[2]; /* Name of timezone and alternate (DST) timezone */
int daylight; /* Nonzero if there is an alternate (DST) timezone */
long timezone; /* Seconds difference between UTC and local standard time */
函数tzset()
会首先检查环境变量TZ
:
- 如果尚未设置就采用
/etc/localtime
中定义的默认时区来初始化时区。 - 如果
TZ
环境变量的值为空,或无法与时区文件名相匹配,那么就使用UTC
。 - 还可将
TZDIR
环境变量设置为搜寻时区信息的目录名称,以替代默认的/usr/share/zoneinfo
。
地区(Locale)
地区定义
- 和时区信息一样,地区信息按标准格式存储于文件中。
- 地区信息维护于
/usr/share/local
之下的目录层次结构中。该目录下的每个子目录都包含一特定地区的信息。 - 当在程序中指定要使用的地区时,实际上是指定了
/usr/share/locale
下某个子目录的名称。
为程序设置地区
函数setlocale()
既可设置也可查询程序的当前地区。
#include <locale.h>
char *setlocale(int category, const char *locale);
Returns pointer to a (usually statically allocated)string identifying the new or current locale on success, or NULL on error
参数
category
:选择设置或查询地区的哪一部分locale
:可能是一个字符串,指定系统上已定义的一个地区
返回值
- 成功:返回一个指针指向标识这一类地区设置的字符串(通常是静态分配)。如果仅需查看可将
locale
参数指定为空 - 失败:发生错误时返回
NULL
- 成功:返回一个指针指向标识这一类地区设置的字符串(通常是静态分配)。如果仅需查看可将
更新系统时钟
两个更新系统时钟的接口:
settimeofday()
和adjtime()
。系统调用settimeofday()
是gettimeofday()
的逆向操作。#define _BSD_SOURCE #include <sys/time.h> int settimeofday(const struct timeval *tv, const struct timezone *tz); Returns 0 on success, or -1 on error
该函数将
tv
指向timeval
结构体里的秒数和微秒数,设置到系统的日历时间。和函数gettimeofday()
一样,tz
参数已被废弃,应始终指定为NULL
。上述调用所造成的系统时间突然变化可能会对依赖系统时钟单调递增的应用造成有害影响,当对时间做微小调整时,通常推荐使用库函数
adjtime()
,他将系统时钟逐步调整到正确时间。#define _BSD_SOURCE #include <sys/time.h> int adjtime(struct timeval *delta, struct timeval *olddelta); Returns 0 on success, or -1 on error
参数
delta
:该参数指向一个timeval
结构体,指定需要改变时间的秒和微秒数。如果是正数则每秒系统时间都会额外拨快一点点,直到增加完所需时间;如果为负值,时钟以类似方式减慢olddelta
:此函数可能无法完成时钟调整,即该参数用来存放剩余未经调整的时间,如果不关心这个可以设为NULL
返回值
- 成功:返回
0
- 失败:发生错误时返回
-1
- 成功:返回
软件时钟(jiffies)
本书所描述的时间相关的各种系统调用的精度是受限于系统软件时钟(software clock
)的分辨率,它的度量单位被称为jiffies
。
进程时间
进程时间是进程创建后使用的CPU时间数量。出于记录目的,内核把CPU时间分为以下两部分:
- 用户CPU时间:在用户模式下执行所花费的时间数量
- 系统CPU时间:在内核模式中执行所花费的时间数量
shell
中time
命令可以同时获得这两部分的值
系统调用times()
,检索进程时间信息,并把结果通过buf
指向的结构体返回。
#include <sys/times.h>
clock_t times(struct tms *buf);
Returns number of clock ticks(sysconf(_SC_CLK_TCK))since "arbitrary" time in past on success, or (clock_t)-1 on error
参数
buf
:指向tms
结构体,该结构体如下:struct tms{ clock_t tms_utime; /* User CPU time used by caller */ clock_t tms_stime; /* System CPU time used by caller */ clock_t tms_cutime; /* User CPU time of all (waited for) children */ clock_t tms_cstime; /* System CPU time of all (waited for) children */ };
返回值
- 成功:返回自过去的任意点流逝的以时钟计时单元为单位的时间
- 失败:发生错误返回
-1
函数clock()
提供了一个简单的接口用于取得进程时间。它返回一个值描述了调用进程使用的总CPU时间(包括用户和系统)
#include <time.h>
clock_t clock(void);
Returns total CPU time used by calling process measured in CLOCKS_PER_SEC, or (clock_t)-1 on error
该函数的返回值的计量单位是CLOCKS_PER_SEC
,所以我们必须除以这个值来获得进程所使用的CPU时间秒数。
总结
本章主要介绍了真实时间(日历时间)和进程时间(用户和系统时间)的概念,并围绕其介绍了一些相互转换的函数;然后介绍了时区和地区;最后也给出了更新系统时钟的系统调用和库函数。
真实时间
度量这时间的起点有二:- 某个标准点。为日历时间,适用于需要对数据库记录或文件打上时间戳的程序。
- 进程生命周期内的某个固定时间点(通常为程序启动)。称之为流逝时间或挂钟时间,主要针对需要周期性操作或定期从外部输入设备进行度量的程序。
- 进程时间
一个进程所使用的CPU时间总量,适用于对程序、算法性能的检查或优化。