LV05-07-Linux系统-02-系统时间

本文主要是用C语言获取Linux系统时间的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
--- ---

一、日期与时间

1. 时间的基本概念

1.1 时区

地球是自西向东自转,东边比西边先看到太阳,东边的时间也比西边的早。东边时刻与西边时刻的差值不仅要以时计,而且还要以分和秒来计算,这给人们带来不便。
为了克服时间上的混乱,1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议)上,规定将全球划分为24个时区(东、西各12个时区)。

以格林尼治天文台旧址的本初子午线作为零度经线,将全球划分为东西两个半球,24个时区就为东1—12区,西1—12区。每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区其实是一个时区,就是12区,它们各跨经度7.5度,以东、西经180度为界。而本初子午线所在时区被称为中时区(零时区)。

image-20220621064820204

每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时。相邻两个时区的时间相差1小时。例如,我国东8区的时间总比泰国东7区的时间早1小时,而比日本东9区的时间晚1小时。因此,出国旅行的人,必须随时调整自己的手表,才能和当地时间相一致。凡向西走,每过一个时区,就要把表向前拨1小时(比如2点拨到1点);凡向东走,每过一个时区,就要把表向后拨1小时(比如1点拨到2点)。
实际上,世界上不少国家和地区都不严格按时区来计算时间。为了在全国范围内采用统一的时间,一般都把某一个时区的时间作为全国统一采用的时间。例如,我国把首都北京所在的东8区的时间作为全国统一的时间,称为北京时间,北京时间就作为我国使用的本地时间,我们电脑上显示的时间就是北京时间,我国国土面积广大,由东到西横跨了5个时区,也就意味着我国最东边的地区与最西边的地区实际上相差了45个小时。又例如,英国、法国、荷兰和比利时等国,虽地处中时区,但为了和欧洲大多数国家时间相一致,则采用东1区的时间。

1.2 GMT 时间

GMTGreenwich Mean Time)中文全称是格林尼治标准时间,这个时间系统的概念在1884年被确立,由英国伦敦的格林威治皇家天文台计算并维护,并在之后的几十年向欧陆其它国家扩散。
由于从19世纪开始,因为世界各国往来频繁,而欧洲大陆、美洲大陆以及亚洲大陆都有各自的时区,所以为了避免时间混乱,1884年,各国代表在美国华盛顿召开国际大会,通过协议选出英国伦敦的格林威治作为全球时间的中心点,决定以通过格林尼治的子午线作为划分东西两半球的经线零度线(本初子午线、零度经线),由此格林威治标准时间因而诞生。
GMT时间就是英国格林威治当地时间,也就是零时区(中时区)所在时间,例如GMT 12:00就是指英国伦敦的格林威治皇家天文台当地的中午12:00,与我国的标准时间北京时间(东8区)相差8个小时,即早8个小时,所以GMT 12:00对应的北京时间是20:00

1.3 UTC 时间

UTCCoordinated Universal Time)指的是世界协调时间(又称世界标准时间、世界统一时间),是经过平均太阳时(以格林尼治时间GMT为准)、地轴运动修正后的新时标以及以秒为单位的国际原子时所综合精算而成的时间,计算过程相当严谨精密,因此若以世界标准时间的角度来说,UTCGMT更加精准。

GMTUTC这两者几乎是同一概念,它们都是指格林威治标准时间,也就是国际标准时间,只不过UTC时间比GMT时间更加精准。

1.4 CST 时间

Ubuntu中,我们还可能会看到CST时间,这又是什么呢?CST可视为美国、澳大利亚、古巴或中国的标准时间。CST可以为如下4个不同的时区的缩写:

  • 美国中部时间:Central Standard Time (USA) UT-6:00

  • 澳大利亚中部时间:Central Standard Time (Australia) UT+9:30

  • 中国标准时间:China Standard Time UT+8:00

  • 古巴标准时间:Cuba Standard Time UT-4:00

2. Ubuntu 中的时间

2.1 相关命令

Ubuntu中,我们可以通过date命令来查看系统当前的本地时间,我们在终端输入以下命令:

1
date

然后便会出现以下信息:

1
2022年 06月 21日 星期二 07:07:53 CST

CST在这里其实指的是China Standard Time(中国标准时间)的缩写,表示当前查看到的时间是中国标准时间,也就是我国所使用的标准时间——北京时间,一般在安装Ubuntu系统的时候会提示我们设置所在城市,那么系统便会根据我们所设置的城市来确定系统的本地时间对应的时区,就像我设置的城市为上海,那么系统的本地时间就是北京时间,这是因为我国统一使用北京时间作为本国的标准时间。
我们在终端输入以下命令:

1
date -u

然后便会看到输出的时间信息如下:

1
2022年 06月 20日 星期一 23:07:49 UTC

这样显示的就是UTC时间了,可以看到与上边的CST时间相差了8小时。

2.2 时区信息文件

Ubuntu系统,时区信息通常以标准格式保存在一些文件当中,这些文件通常位于/usr/share/zoneinfo目录下,该目录下的每一个文件(包括子目录下的文件)都包含了一个特定国家或地区内时区制度的相关信息,且往往根据其所描述的城市或地区缩写来加以命名,例如,EST(美国东部标准时间)、CET(欧洲中部时间)、UTC(世界标准时间)、Hongkong、等,也把这些文件称为时区配置文件,我们可以通过以下命令查看相关文件名称:

1
ls /usr/share/zoneinfo

然后便会看到以下信息输出:

image-20220621071320066

系统的本地时间由时区配置文件/etc/localtime定义,通常链接到/usr/share/zoneinfo目录下的某一个文件(或其子目录下的某一个文件),我们可以查看一下这个文件:

1
ls -l /etc/localtime

然后便会看到以下信息:

image-20220621071518745

这样的话,如果我们要修改Ubuntu系统本地时间的时区信息,可以直接将/etc/localtime链接到/usr/share/zoneinfo目录下的任意一个时区配置文件:

1
2
sudo rm -rf localtime                        # 删除原有链接文件 
sudo ln -s /usr/share/zoneinfo/EST localtime # 重新建立链接文件

3. Linux 系统中的时间

3.1 实时时钟RTC

操作系统中一般会有两个时钟,一个系统时钟(system clock),一个实时时钟(Real time clock),也叫RTC。系统时钟由系统启动之后由内核来维护,例如,使用date命令查看到的就是系统时钟,它在系统关机情况下是不存在的;而实时时钟一般由RTC时钟芯片提供,RTC芯片有相应的电池供电,以保证系统在关机情况下RTC能够继续工作、继续计时。

3.2 Linux 如何记录时间

Linux系统在开机启动之后首先会读取RTC硬件获取实时时钟作为系统时钟的初始值,之后内核便开始维护自己的系统时钟。所以,RTC硬件只有在系统开机启动时会读取一次,目的是用于对系统时钟进行初始化操作,之后的运行过程中便不会再对其进行读取操作了。而在系统关机时,内核会将系统时钟写入到RTC硬件。

3.3 jiffies 

jiffies是内核中定义的一个全局变量,内核使用jiffies来记录系统从启动以来的系统节拍数,所以这个变量用来记录以系统节拍时间为单位的时间长度。

Linux内核在编译配置时定义了一个节拍时间,使用节拍率(一秒钟多少个节拍数)来表示,例如如常用的节拍率为100Hz1秒钟100个节拍数,节拍时间为1s / 100)、200Hz1秒钟200个节拍,节拍时间为1s / 200)、250Hz1秒钟250个节拍,节拍时间为1s / 250)、300Hz1秒钟300个节拍,节拍时间为1s / 300)、500Hz1秒钟500个节拍,节拍时间为1s / 500)等。由此可以发现配置的节拍率越低,每一个系统节拍的时间就越短,也就意味着jiffies记录的时间精度越高,当然,高节拍率会导致系统中断的产生更加频繁,频繁的中断会加剧系统的负担,一般默认情况下都是采用100Hz作为系统节拍率。

内核其实是通过jiffies来维护系统时钟,全局变量jiffies在系统开机启动时会设置一个初始值,RTC实时时钟会在系统开机启动时读取一次,目的是用于对系统时钟进行初始化,这里说的初始化其实指的就是对内核的jiffies变量进行初始化操作,具体如何将读取到的实时时钟换算成jiffies数值,后边用到了再说吧。

操作系统使用jiffies这个全局变量来记录当前时间,当我们需要获取到系统当前时间点时,就可以使用jiffies变量去计算,当然并不需要我们手动去计算,Linux系统提供了相应的系统调用或C库函数用于获取当前时间,例如如系统调用time()gettimeofday(),其实质上就是通过jiffies变量换算得到。

【说明】系统的节拍率可以使用下边的函数获取:

1
sysconf(_SC_CLK_TCK);

3.4 日历时间

1970-01-01 00:00:00 +0000 (UTC)以来经过的总秒数,我们把这个称之为日历时间time_t时间。为什么是1970-01-01开始嘛,可以上网搜一下,这里就不写了。

4. 获取时间

4.1 time() 函数

4.1.1 函数说明

linux下可以使用man 2 time命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <time.h>

/* 函数声明 */
time_t time(time_t *tloc);

【函数说明】该函数用于获取当前时间,以秒为单位。

【函数参数】

  • tloctime_t类型指针变量,如果tloc参数不是NULL,则返回值也存储在tloc指向的内存中。

【返回值】time_t类型,成功则返回自1970-01-01 00:00:00 +0000 (UTC)以来的时间值(以秒为单位);失败则返回-1,并会设置errno表示错误类型。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
/* 需要包含的头文件 */
#include <time.h>

/* 至少应该有的语句 */
time_t nowTime;
nowTime = time(NULL);

【注意事项】

(1)time函数获取得到的是一个时间段,也就是从1970-01-01 00:00:00 +0000 (UTC)到现在这段时间所经过的秒数,所以我们要计算现在这个时间点,只需要使用time()得到的秒数加1970-01-01 00:00:00即可。

(2)我们要是想打印出这个总的秒数,使用printf的时候,格式字符需要使用%ld

4.1.2 使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int arc, char *argv[])
{
time_t nowTime;
nowTime = time(NULL);
if (nowTime == -1)
{
perror("time error");
return -1;
}
printf("nowTime: %ld\n", nowTime);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
nowTime: 1655778106

4.2 gettimeofday() 函数

4.2.1 函数说明

linux下可以使用man 2 gettimeofday命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <sys/time.h>

/* 函数声明 */
int gettimeofday(struct timeval *tv, struct timezone *tz);

【函数说明】该函数用于获取当前时间,以微秒为单位。

【函数参数】

  • tvstruct timeval类型的结构体指针变量,获取得到的时间值存储在该所指向的struct timeval结构体变量中,该结构体包含了两个成员变量tv_sectv_usec,分别用于表示秒和微秒,所以获取得到的时间值就是tv_sec(秒)+ tv_usec(微秒),同样获取得到的秒数与time()函数一样,也是自1970-01-01 00:00:00 +0000 (UTC)到现在这段时间所经过的秒数,也就是日历时间,time()返回得到的值和函数gettimeofday()所返回的tv参数中tv_sec字段的数值相同。
点击查看 struct timeval 结构体成员

在使用man手册的时候会有说明:

1
2
3
4
5
struct timeval
{
time_t tv_sec; /* seconds 秒*/
suseconds_t tv_usec; /* microseconds 微秒*/
};
  • tzstruct timezone类型结构体指针变量,早期实现用其来获取系统的时区信息,目前已遭废弃,在调用gettimeofday()函数时应将参数tz设置为NULL
点击查看 struct timeval 结构体成员

在使用man手册的时候会有说明:

1
2
3
4
5
struct timezone
{
int tz_minuteswest; /* minutes west of Greenwich */
int tz_dsttime; /* type of DST correction */
};

【返回值】time_t类型,成功则返回自0;失败则返回-1,并会设置errno表示错误类型。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
/* 需要包含的头文件 */
#include <sys/time.h>

/* 至少应该有的语句 */
struct timeval tval;
gettimeofday(&tval, NULL);

【注意事项】none

4.2.2 使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

int main(int arc, char *argv[])
{
int ret;
struct timeval nowTime;
ret = gettimeofday(&nowTime, NULL);
if (ret == -1)
{
perror("gettimeofday error");
return -1;
}
printf("nowTime: %lds+%ldus\n", nowTime.tv_sec, nowTime.tv_usec);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
nowTime: 1655778991s+882392us

5. 时间转换

上边两个函数可以获取到时间,但是呢,获取的都是相对于1970-01-01 00:00:00 +0000 (UTC)这个时间点所经过时间(日历时间),所以获取得到的是一个时间段的长度秒数,我们总不能真的自己去换算吧,C语言中已经为我们提供了相关函数来帮助我们做这个事情。

5.1 ctime() 函数

5.1.1 函数说明

linux下可以使用man 3 ctime命令查看该函数的帮助手册。

1
2
3
4
5
6
/* 需包含的头文件 */
#include <time.h>

/* 函数声明 */
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);

【函数说明】该函数可以将日历时间转换为可打印输出的字符串形式,ctime_r()ctime()函数的可重入版本,具体什么是可重入,什么是不可重入,现在就大概知道可重入版本的函数可以避免一些安全隐患,更加推荐使用。

【函数参数】

  • timeptime_t类型指针变量,该参数包含了一个日历时间,也就是上边获取时间函数获取的那个秒数。
  • bufchar类型指针变量,该参数为可重入版本的函数ctime()所有,它指向存放转换后的时间字符串的缓冲区首地址。

【返回值】char类型指针变量,成功则返回指向转换后得到的字符串;失败将返回NULL

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
/* 需要包含的头文件 */
#include <time.h>

/* 至少应该有的语句 */
char tm_str[100] = {0};
time_t tm;
tm = time(NULL);
ctime_r(&tm, tm_str); /* 一般使用可重入版本 */

【注意事项】none

5.1.2 使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int arc, char *argv[])
{
char tm_str[100] = {0};
time_t nowTime;
nowTime = time(NULL);
if (nowTime == -1)
{
perror("time error");
return -1;
}
printf("nowTime: %lds\n", nowTime);
ctime_r(&nowTime, tm_str);
printf("nowTime: %s\n", tm_str);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
nowTime: 1655781498s
nowTime: Tue Jun 21 11:18:18 2022

5.2 localtime() 函数

5.2.1 函数说明

linux下可以使用man 3 localtime命令查看该函数的帮助手册。

1
2
3
4
5
6
/* 需包含的头文件 */
#include <time.h>

/* 函数声明 */
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

【函数说明】该函数可以把time()gettimeofday()得到的秒数(time_t时间或日历时间)变成一个struct tm结构体所表示的时间,该时间对应的是本地时间。localtime_r()localtime()函数的可重入版本,更加推荐使用。

【函数参数】

  • timeptime_t类型指针变量,该参数包含了一个日历时间,也就是上边获取时间函数获取的那个秒数。
  • resultstruct tm类型结构体指针变量,该参数为可重入版本的函数localtime_r()所有,转换后的本地时间将存放在该参数指向的结构体中。

【返回值】struct tm类型的结构体指针,成功则返回一个有效的struct tm结构体指针,而对于可重入版本localtime_r()来说,成功执行情况下,返回值将会等于参数result;失败则返回NULL

点击查看 struct tm 结构体成员

在使用man手册的时候,下边会有这个结构体成员的说明:

1
2
3
4
5
6
7
8
9
10
11
12
struct tm
{
int tm_sec; /* 秒 Seconds (0-60) */
int tm_min; /* 分 Minutes (0-59)*/
int tm_hour; /* 时 Hours (0-23) */
int tm_mday; /* 日 Day of the month (1-31) */
int tm_mon; /* 月 Month (0-11) */
int tm_year; /* 年 Year - 1900 */
int tm_wday; /* 周 Day of the week (0-6, Sunday = 0) */
int tm_yday; /* 一年中的第几天 Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* 夏令时 Daylight saving time */
};

【注意事项】要想得到实际的时间,那么年份需要加上1900,月份需要加上1。这是因为这个函数内部转换的时候,年份从1900开始算,月份从0开始算了。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
/* 需要包含的头文件 */
#include <time.h>

/* 至少应该有的语句 */
struct tm nowTime;
time_t tm;
tm = time(NULL);
localtime_r(&tm, &nowTime);

【注意事项】none

5.2.2 使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int arc, char *argv[])
{
struct tm nowTime;
time_t tm;
tm = time(NULL);
if (tm == -1)
{
perror("time error");
return -1;
}
printf("tm: %lds\n", tm);
localtime_r(&tm, &nowTime);
printf("nowTime: %d年%d月%d日 %d:%d:%d\n",
nowTime.tm_year + 1900,
nowTime.tm_mon + 1,
nowTime.tm_mday,
nowTime.tm_hour,
nowTime.tm_min,
nowTime.tm_sec);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
tm: 1655782709s
nowTime: 2022年6月21日 11:38:29

6. 时间相关API总结

Linux系统下常用的时间相关的系统调用和库函数,主要有下边几个:

1
2
3
4
5
6
7
8
9
10
11
time_t time(time_t *tloc);
int stime(const time_t *t);
char *ctime(const time_t *timep);
struct tm *localtime(const time_t *timep);
struct tm *gmtime(const time_t *timep);
time_t mktime(struct tm *tm);
char *asctime(const struct tm *tm);
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
char *strptime(const char *s, const char *format, struct tm *tm);
int gettimeofday(struct timeval *tv, struct timezone *tz);
int settimeofday(const struct timeval *tv, const struct timezone *tz);

这些函数总结如下:

image-20220621120417357

三、进程时间

1. 什么是进程时间

进程时间指的是进程从创建后(也就是程序运行后)到目前为止这段时间内使用CPU资源的时间总数,出于记录的目的,内核把CPU时间(进程时间)分为以下两个部分:

  • 用户CPU时间:进程在用户空间(用户态)下运行所花费的CPU时间,有时也称为虚拟时间(virtual time)。这对于程序来说,是它已经得到CPU的时间

  • 系统CPU时间:进程在内核空间(内核态)下运行所花费的CPU时间。这是内核执行系统调用或代表进程执行的其它任务(例如,服务页错误)所花费的时间。

一般来说,进程时间指的是用户CPU时间和系统CPU时间的总和,也就是总的CPU时间。

【注意事项】进程时间不等于程序的整个生命周期所消耗的时间,如果进程一直处于休眠状态(进程被挂起、不会得到系统调度),那么它并不会使用CPU资源,所以休眠的这段时间并不计算在进程时间中。

2. times() 函数

2.1 函数说明

linux下可以使用man 2 times命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <sys/times.h>

/* 函数声明 */
clock_t times(struct tms *buf);

【函数说明】该函数用于获取当前进程的时间。

【函数参数】

  • bufstruct tms类型接结构体指针变量,该参数指向一个保存了当前进程时间信息的struct tms结构体变量。
点击查看 struct tms 结构体变量成员

在使用man手册的时候,会有该结构体的相关说明:

1
2
3
4
5
6
7
struct tms
{
clock_t tms_utime; /* user time */
clock_t tms_stime; /* system time */
clock_t tms_cutime; /* user time of children */
clock_t tms_cstime; /* system time of children */
};

上边的四个成员说明如下:

tms_utime user time, 进程的用户CPU时间, tms_utime个系统节拍数
tms_stime system time, 进程的系统CPU时间, tms_stime个系统节拍数
tms_cutimeuser time of children, 已死掉子进程的tms_utime + tms_cutime时间总和
tms_cstimesystem time of children, 已死掉子进程的tms_stime + tms_cstime时间总和

【返回值】clock_t类型(实质是long类型),成功时将返回从过去任意的一个时间点(例如系统启动时间)所经过的时钟滴答数(其实就是系统节拍数),将(节拍数 / 节拍率)便可得到秒数,返回值可能会超过clock_t所能表示的范围(溢出);调用失败返回(clock_t) -1,并设置errno表示错误类型。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
9
10
11
/* 需要包含的头文件 */
#include <sys/times.h>

/* 至少应该有的语句 */
struct tms t_buf_start, t_buf_end;
clock_t t_start, t_end;
long tck;
tck = sysconf(_SC_CLK_TCK);
t_start = times(&t_buf_start);
/* 要测试的语句块 */
t_end = times(&t_buf_end);

【注意事项】none

2.2 使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>
#include <sys/times.h>
#include <unistd.h>

int main(int arc, char *argv[])
{
struct tms t_buf_start;
struct tms t_buf_end;
clock_t t_start;
clock_t t_end;
long tck;
int i, j;
/* 获取系统的节拍率 */
tck = sysconf(_SC_CLK_TCK);
printf("系统节拍率:%ld\n", tck);
/* 开始时间 */
t_start = times(&t_buf_start);
if (t_start == -1)
{
perror("times error");
return -1;
}

/* ------- 需要进行测试的代码段 ------- */
for (i = 0; i < 20000; i++)
for (j = 0; j < 20000; j++);
/* 休眠挂起 1s */
sleep(1);
/* --------------- end ---------------- */
/* 结束时间 */
t_end = times(&t_buf_end);
if (t_end == -1)
{
perror("times error");
return -1;
}
/* 打印时间 */
printf("时间总和: %f秒\n", (t_end - t_start) / (double)tck);
printf("用户CPU时间: %f秒\n", (t_buf_end.tms_utime - t_buf_start.tms_utime) / (double)tck);
printf("系统CPU时间: %f秒\n", (t_buf_end.tms_stime - t_buf_start.tms_stime) / (double)tck);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
系统节拍率:100
时间总和: 1.180000秒
用户CPU时间: 0.170000秒
系统CPU时间: 0.000000秒

可以看到用户CPU时间为0.17秒,系统CPU时间为0秒,也就是说测试的这段代码并没有进入内核态运行,所以

1
总的进程时间 = 用户CPU时间 + 系统CPU时间 = 0.17

上边显示的时间总和并不是总的进程时间,这个时间总和指的是从起点到终点所经过的时间,并不是进程时间。时间总和包括了进程处于休眠状态时消耗的时间(sleep等会让进程挂起、进入休眠状态),可以发现时间总和比进程时间多1秒,其实这一秒就是进程处于休眠状态的时间。至于还多出来了0.01秒,我自己感觉是语句执行耗费的时间,但也可能不是,这里我就没有追究那么深入了,后边万一用到了,搞明白了,再在这里补充修改吧。

3. clock() 函数

3.1 函数说明

linux下可以使用man 2 clock命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <time.h>

/* 函数声明 */
clock_t clock(void);

【函数说明】该函数也用于获取进程时间,使用方式比times更加简单。

【函数参数】none

【返回值】clock_t类型,返回的值是到目前为止使用CPU的时间,这个时间并不是系统节拍数。要获得使用的秒数,还需要除以CLOCKS_PER_SEC。如果所用的处理器时间不可用,或者它的值无法表示,那么这个函数将返回值(clock_t) -1

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
/* 需要包含的头文件 */
#include <time.h>

/* 至少应该有的语句 */
clock_t t_start, t_end;
t_start = clock();
/* 测试的语句块 */
t_end = clock();

【注意事项】clock()函数虽然可以很方便的获取总的进程时间,但并不能获取到单独的用户CPU时间和系统CPU时间。

3.2 使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

int main(int arc, char *argv[])
{

clock_t t_start;
clock_t t_end;
int i, j;
/* 开始时间 */
t_start = clock();
if (t_start == -1)
{
perror("clock error");
return -1;
}

/* ------- 需要进行测试的代码段 ------- */
for (i = 0; i < 20000; i++)
for (j = 0; j < 20000; j++);
/* 休眠挂起 1s */
sleep(1);
/* --------------- end ---------------- */
/* 结束时间 */
t_end = clock();
if (t_end == -1)
{
perror("clock error");
return -1;
}
/* 打印时间 */
printf("总CPU时间: %f秒, CLOCKS_PER_SEC = %ld\n", (t_end - t_start) / (double)CLOCKS_PER_SEC, CLOCKS_PER_SEC);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
总CPU时间: 0.197372秒, CLOCKS_PER_SEC = 1000000