Linux 下 C/C++ 的时间编程
一、Linux 下时间类型
real time:日历时间。对于 Linux 中,这个时间的起点是 1970年1月1日00点,Linux 上以此为起点的均为 UTC 时间。
格林威治时间(Greenwich Mean Time,GMT)也被称为世界标准的时间(Coordinated Universal Time,UTC)。UTC 和 GMT 两者是同一概念的时间。区别在于 UTC 是天文学上的概念,而 GMT 是基于一个原子钟。
GMT 是中央时区,北京在东8区,相差8小时,所以
北京时间 = GMT时间 + 8小时
注意:会受到修改系统时间的命令/api 或者 ntp 服务的影响,导致时间出现跳跃
monotonic time:单调时间。意为不能被设置和影响的时间,它可以提供精确的时间信息,不会出现时间跳跃。单调时间的起点 posix 标准并没有明确指定,但在 Linux 上是以系统启动的时间为起点的。虽然说单调时钟的时间是稳定的,但它会被 adjtime 函数和 ntp 服务影响,同时当系统挂起或休眠时计时会被暂停。
二、Linux 下时间格式
1. time_t 时间类型
1 | #ifndef __TIME_T |
time_t 是一个长整型,其值表示从 UTC 时间(1970年1月1日00时00分00秒)到当前时刻的秒数。由于 time_t 类型长度限制,它所表示的时间不能晚于 2038年1月19日03时14分07秒(UTC)。
2. struct tm 时间类型
1 | #include <time.h> |
3. struct timeval 时间类型
1 | #include <sys/time.h> |
tv_sec 是 time_t 时间类型,其值表示从 UTC 时间 1970年1月1日00时00分00秒到当前时刻的秒数
4. struct timespec 时间类型
1 | typedef long time_t; |
它是POSIX.4 标准定义的时间结构,精确度到纳秒,一般由 clock_gettime(clockid_t, struct timespec *)
获取特定时钟的时间。常用如下4种时钟:
- CLOCK_REALTIME 系统当前时间,从1970年1月1日算起
- CLOCK_MONOTONIC 系统的启动时间,不能被设置
- CLOCK_PROCESS_CPUTIME_ID 本进程运行时间
- CLOCK_THREAD_CPUTIME_ID 本线程运行时间
三、Linux 时间编程接口
1. time() 函数
1 | #include <time.h> |
- 说明:该函数用于获取日历时间,即从 1970年1月1日00点到现在所经历的秒数。
- 参数:参数 tloc 通常设置为 NULL,若 tloc 不为空,time() 函数也会将返回值存到 tloc 中。
- 返回值:函数执行成功返回秒数,失败则返回
(time_t)-1
,错误原因存在 errno
2. 时间转换函数 gmtime()、localtime()、ctime()、asctime()、mktime()
1 | #include <time.h> |
- gmtime 函数将 time_t 类型的日历时间转换成 struct tm 结构体表示的格林威治时间
- localtime 函数将 time_t 类型的日历时间转换成 struct tm 结构体表示的本地时区时间
- ctime 函数将 time_t 类型的日志时间转换为本地时区时间的字符串形式
- asctime 函数将 struct tm 结构体转换为字符串形式
- mktime 函数将 struct tm 结构体转换为从 1970年1月1日00时00分00秒至今的 GMT 时间经过的秒数
1 | time_t t = time(nullptr); |
3. 时间比较函数 difftime()
1 | double difftime (time_t __time1, time_t __time0) |
比较两个时间是是否相同,并返回之间相差的秒数
4. gettimeofday()、settimeofday()
1 | int gettimeofday (struct timeval *__restrict __tv, void *__restrict __tz) |
- gettimeofday 函数用于获取 UTC 时间1970年1月1日00时00分00秒到当前时刻的时间差,并将此时间存入 struct timeval 结构体中,当地时区信息则放到 tz 中。在 Linux 中 glibc 并未支持 tz,因此未使用 tz。成功返回 0,否则返回 -1,错误码存于 errno 中
- settimeofday 函数设置当前时间。成功返回0,否则返回-1。只有 root 权限才能修改。
5. strftime()
1 | size_t strftime (char *__restrict __s, size_t __maxsize, const char *__restrict __format, |
- strftime 函数将参数 struct tm 结构的时间,按照 format 所指定的字符串格式做转换,转换后的字符串内容复制到参数 s 所指向的字符串数组中,该字符串的最大长度由 maxsize 所控制。返回值为复制到参数 s 所指的字符串数组的总字符数,不包括字符串结束符。
- strptime 函数将一个字符串格式时间解释成为 struct tm 格式结构的时间
6. 获取精确时间 timespec_get()、clock_gettime()
1 | int timespec_get (struct timespec *__ts, int __base) |
获取时间,可以精确到纳秒级别。
- timespec_get 函数,参数 base 目前只定义了
TIME_UTC
,所以还无法直接获取其他时区的时间值 - clock_gettime 函数,它不仅能获得自
1970/1/1
开始的时间,还可以自定义 clock 的类型以便获取不同的时间值。在需要获取高精度的时间值时应该优先考虑使用它。
四、Linux 时间编程简单总结
struct timeval 结构和 struct timespec 结构都无法处理时区。Linux 处理时区的手段有以下两种:
- 函数自定义参数和返回值使用 Local time 还是 UTC time
- 系统根据环境变量 TZ 以及配置文件
/etc/localtime
等改变本地时间(Local time)
因此在处理时间的时候一定要注意,当前处理的时间是本地时间还是 UTC 时间。
五、C++ 中 chrono 库
C++11 中 chrono 库包含了三种类型的时钟
- system_clock:系统时钟。可能会被调整
- steady_clock:单调时钟,不会被调整
- high_resolution_clock:拥有可用的最短滴答周期的时钟。
这三个时钟类有一些共同的成员,如下:
- now():静态成员函数,返回当前时间,类型为
clock::time_point
- time_point:成员类型,当前时钟的时间点类型
- duration:成员类型,时钟的时长类型
- rep:成员类型,时钟的 tick 类型,等同于
clock::duration::rep
- period:成员类型,时钟的单位,等同于
clock::duration::period
- is_steady:静态成员类型,是否是稳定时钟,对于 steady_clock 来说该值一定是 true
1 |
六、Linux 休眠编程接口
1. sleep()、usleep()
1 | #include <unistd.h> |
- sleep 函数使程序休眠直到 seconds 秒之后才被唤醒。函数返回 0 表示 seconds 时间到了,或返回剩余的秒数
- usleep 函数使程序休眠 usec 微妙之后才被唤醒。休眠时间会比实际时间略长。成功返回0,失败返回 -1。该函数可以被信号唤醒,同时返回 EINTER。
七、Linux 定时器编程接口
Linux 应用程序为我们的每一个进程提供了一个定时闹钟 alarm,当定时器指定的时间到时,系统会向调用进程发送SIGALARM 信号,如果忽略或者不捕获此信号,则其默认动作是终止调用该 alarm 函数的进程;当然也可以通过 signal() 函数向系统注册一个自己的定时闹钟处理函数。
1. alarm()
1 | unsigned int alarm(unsigned int seconds); |
- alarm 函数向系统设定一个闹钟,并在闹钟时间到时内核向该进程发送SIGALRM信号
需要注意:
- 一个进程只能有一个alarm闹钟;
- 闹钟时间到了后,若不再次调用alarm(),将不会有新的闹钟产生;
- 任何以seconds非0的调用,都将重新更新闹钟定时时间,并返回上一个闹钟剩余时间;若为第一次设置闹钟,则返回0;以seconds为0的调用,表示取消以前的闹钟,并将剩余时间返回。
- 在Linux系统中提到,sleep()有可能是使用alarm()来实现的,因此,在一个进程中同时使用alarm()和sleep()并不明智。
2. setitimer()
1 | int getitimer(int which, struct itimerval *curr_value); |
Linux 为每个进程提供了三个独立的计时器,每个计时器在不同的时域中递减。当任何一个定时器到点了,都会向进程发送一个信号,并且重启计时器。
getitimer() 函数获取 ITIMER_REAL、ITIMER_PROF 和 ITIMER_VIRTUAL 三个定时器中的一个时间信息,并保存到 curr_value 指向的对象中
setitimer() 函数,定时器为 ITIMER_REAL、ITIMER_PROF 和 ITIMER_VIRTUAL 中的一个;定时时间为 new_value;如果 old_value 非空,则将老的时间信息保存到该对象中。成功返回 0,失败返回 -1。
其中 itimerval 结构中,it_value 变量用于设置计时器的计时时间,为 0 表示禁止;it_interval 变量用于设置当计时器到时需要重置的时间,从而实现循环计时。也就是说,计时器从 it_value 开始递减,当递减到 0 时,向进程发送一个信号,并重置定时器为 it_interval,如此循环,从而实现循环闹钟的功能。
1 | void sig_func(int signo) |