1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新
第七章系统信息与系统资源
在应用程序当中,有时往往需要去获取到一些系统相关的信息,譬如时间、日期、以及其它一些系统相关信息,本章将向大家介绍如何通过Linux系统调用或C库函数获取系统信息,譬如获取系统时间、日期以及设置系统时间、日期等;除此之外,还会向大家介绍Linux系统下的/proc虚拟文件系统,包括/proc文件系统是什么以及如何从/proc文件系统中读取系统、进程有关信息。 除了介绍系统信息内容外,本章还会向大家介绍有关系统资源的使用,譬如系统内存资源的申请与使用等。好了,废话不多少,开始本章内容的学习吧!
用于获取系统相关信息的函数; 时间、日期; 进程时间; 使程序进入休眠; 在堆中申请内存; proc文件系统介绍; 定时器。
7.1系统信息 7.1.1系统标识uname 系统调用uname()用于获取有关当前操作系统内核的名称和信息,函数原型如下所示(可通过"man 2 uname"命令查看): #include
int uname(struct utsname *buf); 使用该函数需要包含头文件。 函数参数和返回值含义如下: buf:struct utsname结构体类型指针,指向一个struct utsname结构体类型对象。 返回值:成功返回0;失败将返回-1,并设置errno。 uname()函数用法非常简单,先定义一个struct utsname结构体变量,调用uname()函数时传入变量的地址即可,struct utsname结构体如下所示: 示例代码 7.1.1 struct utsname结构体
struct utsname {
char sysname[]; /* 当前操作系统的名称 */
char nodename[]; /* 网络上的名称(主机名) */
char release[]; /* 操作系统内核版本 */
char version[]; /* 操作系统发行版本 */
char machine[]; /* 硬件架构类型 */
#ifdef _GNU_SOURCE
char domainname[];/* 当前域名 */
#endif
};
可以看到,struct utsname结构体中的所有成员变量都是字符数组,所以获取到的信息都是字符串。 测试 编写一个简单地程序,获取并打印出当前操作系统名称、主机名、内核版本、操作系统发行版本以及处理器硬件架构类型等信息,测试代码如下: 示例代码 7.1.2 uname函数使用示例
#include
#include
#include
int main(void)
{
struct utsname os_info;
int ret;
/* 获取信息 */
ret = uname(&os_info);
if (-1 == ret) {
perror("uname error");
exit(-1);
}
/* 打印信息 */
printf("操作系统名称: %s\n", os_info.sysname);
printf("主机名: %s\n", os_info.nodename);
printf("内核版本: %s\n", os_info.release);
printf("发行版本: %s\n", os_info.version);
printf("硬件架构: %s\n", os_info.machine);
exit(0);
}
运行结果:
图 7.1.1 运行结果 7.1.2sysinfo函数 sysinfo系统调用可用于获取一些系统统计信息,其函数原型如下所示: #include
int sysinfo(struct sysinfo *info); 函数参数和返回值含义如下: info:struct sysinfo结构体类型指针,指向一个struct sysinfo结构体类型对象。 返回值:成功返回0;失败将返回-1,并设置errno。 同样sysinfo()函数用法也非常简单,先定义一个struct sysinfo结构体变量,调用sysinfo()函数时传入变量的地址即可,struct sysinfo结构体如下所示: 示例代码 7.1.3 struct sysinfo结构体
struct sysinfo {
long uptime; /* 自系统启动之后所经过的时间(以秒为单位) */
unsigned long loads[3]; /* 1, 5, and 15 minute load averages */
unsigned long totalram; /* 总的可用内存大小 */
unsigned long freeram; /* 还未被使用的内存大小 */
unsigned long sharedram; /* Amount of shared memory */
unsigned long bufferram; /* Memory used by buffers */
unsigned long totalswap; /* Total swap space size */
unsigned long freeswap; /* swap space still available */
unsigned short procs; /* 系统当前进程数量 */
unsigned long totalhigh; /* Total high memory size */
unsigned long freehigh; /* Available high memory size */
unsigned int mem_unit; /* 内存单元大小(以字节为单位) */
char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding to 64 bytes */
};
测试 示例代码 7.1.4 sysinfo函数使用示例
#include
#include
#include
int main(void)
{
struct sysinfo sys_info;
int ret;
/* 获取信息 */
ret = sysinfo(&sys_info);
if (-1 == ret) {
perror("sysinfo error");
exit(-1);
}
/* 打印信息 */
printf("uptime: %ld\n", sys_info.uptime);
printf("totalram: %lu\n", sys_info.totalram);
printf("freeram: %lu\n", sys_info.freeram);
printf("procs: %u\n", sys_info.procs);
exit(0);
}
运行结果:
图 7.1.2 sysinfo测试结果 7.1.3gethostname函数 此函数可用于单独获取Linux系统主机名,与struct utsname数据结构体中的nodename变量一样,gethostname函数原型如下所示(可通过"man 2 gethostname"命令查看): #include
int gethostname(char *name, size_t len); 使用此函数需要包含头文件。 函数参数和返回值含义如下: name:指向用于存放主机名字符串的缓冲区。 len:缓冲区长度。 返回值:成功返回0,;失败将返回-1,并会设置errno。 测试 使用gethostname函数获取系统主机名: 示例代码 7.1.5 gethostname函数使用示例
#include
#include
#include
#include
int main(void)
{
char hostname[20];
int ret;
memset(hostname, 0x0, sizeof(hostname));
ret = gethostname(hostname, sizeof(hostname));
if (-1 == ret) {
perror("gethostname error");
exit(ret);
}
puts(hostname);
exit(0);
}
运行结果:
图 7.1.3 获取主机名 7.1.4sysconf()函数 sysconf()函数是一个库函数,可在运行时获取系统的一些配置信息,譬如页大小(page size)、主机名的最大长度、进程可以打开的最大文件数、每个用户ID的最大并发进程数等。其函数原型如下所示: #include
long sysconf(int name); 使用该函数需要包含头文件。 调用sysconf()函数获取系统的配置信息,参数name指定了要获取哪个配置信息,参数name可取以下任何一个值(都是宏定义,可通过man手册查询): _SC_ARG_MAX:exec族函数的参数的最大长度,exec族函数后面会介绍,这里先不管! _SC_CHILD_MAX:每个用户的最大并发进程数,也就是同一个用户可以同时运行的最大进程数。 _SC_HOST_NAME_MAX:主机名的最大长度。 _SC_LOGIN_NAME_MAX:登录名的最大长度。 _SC_CLK_TCK:每秒时钟滴答数,也就是系统节拍率。 _SC_OPEN_MAX:一个进程可以打开的最大文件数。 _SC_PAGESIZE:系统页大小(page size)。 _SC_TTY_NAME_MAX:终端设备名称的最大长度。 …… 除以上之外,还有很多,这里就不再一一列举了,可以通过man手册进行查看,用的比较多的是_SC_PAGESIZE和_SC_CLK_TCK,在后面章节示例代码中有使用到。 若指定的参数name为无效值,则sysconf()函数返回-1,并会将errno设置为EINVAL。否则返回的值便是对应的配置值。注意,返回值是一个long类型的数据。 使用示例 获取每个用户的最大并发进程数、系统节拍率和系统页大小。 示例代码 7.1.6 sysconf()函数使用示例
#include
#include
#include
int main(void)
{
printf("每个用户的最大并发进程数: %ld\n", sysconf(_SC_CHILD_MAX));
printf("系统节拍率: %ld\n", sysconf(_SC_CLK_TCK));
printf("系统页大小: %ld\n", sysconf(_SC_PAGESIZE));
exit(0);
}
运行结果:
图 7.1.4 测试结果 7.2时间、日期 本小节向大家介绍下时间、日期相关的系统调用或C库函数以及它们的使用方法。 7.2.1时间的概念 在正式介绍这些时间、日期相关的系统调用或C库函数之前,需要向大家介绍一些时间相关的基本概念,譬如GMT时间、UTC时间以及时区等。 地球总是自西向东自转,东边总比西边先看到太阳,东边的时间也总比西边的早。东边时刻与西边时刻的差值不仅要以时计,而且还要以分和秒来计算,这给人们的日常生活和工作都带来许多不便。 GMT时间 GMT(Greenwich Mean Time)中文全称是格林威治标准时间,这个时间系统的概念在1884年被确立,由英国伦敦的格林威治皇家天文台计算并维护,并在之后的几十年向欧陆其它国家扩散。 由于从19实际开始,因为世界各国往来频繁,而欧洲大陆、美洲大陆以及亚洲大陆都有各自的时区,所以为了避免时间混乱,1884年,各国代表在美国华盛顿召开国际大会,通过协议选出英国伦敦的格林威治作为全球时间的中心点,决定以通过格林威治的子午线作为划分东西两半球的经线零度线(本初子午线、零度经线),由此格林威治标准时间因而诞生! 所以GMT时间就是英国格林威治当地时间,也就是零时区(中时区)所在时间,譬如GMT 12:00就是指英国伦敦的格林威治皇家天文台当地的中午12:00,与我国的标准时间北京时间(东八区)相差8个小时,即早八个小时,所以GMT 12:00对应的北京时间是20:00。 UTC时间 UTC(Coordinated Universal Time)指的是世界协调时间(又称世界标准时间、世界统一时间),是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间,计算过程相当严谨精密,因此若以「世界标准时间」的角度来说,UTC比GMT来得更加精准。 GMT与UTC这两者几乎是同一概念,它们都是指格林威治标准时间,也就是国际标准时间,只不过UTC时间比GMT时间更加精准,所以在我们的编程当中不用刻意去区分它们之间的区别。 在Ubuntu系统下,可以使用"date -u"命令查看到当前的UTC时间,如下所示:
图 7.2.1 查看UTC时间 后面显示的UTC字样就表示当前查看到的时间是UTC时间,也就是国际标准时间。 时区 全球被划分为24个时区,每一个时区横跨经度15度,以英国格林威治的本初子午线作为零度经线,将全球划分为东西两半球,分为东一区、东二区、东三区……东十二区以及西一区、西二区、西三区……西十二区,而本初子午线所在时区被称为中时区(或者叫零时区),划分图如下所示:
图 7.2.2 全球24时区划分 东十二区和西十二区其实是一个时区,就是十二区,东十二区与西十二区各横跨经度7.5度,以180度经线作为分界线。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时。相邻两个时区的时间相差1小时。例如,我国东8区的时间总比泰国东7区的时间早1小时,而比日本东9区的时间晚1小时。因此,出国旅行的人,必须随时调整自己的手表,才能和当地时间相一致。凡向西走,每过一个时区,就要把表向前拨1小时(比如2点拨到1点);凡向东走,每过一个时区,就要把表向后拨1小时(比如1点拨到2点)。 实际上,世界上不少国家和地区都不严格按时区来计算时间。为了在全国范围内采用统一的时间,一般都把某一个时区的时间作为全国统一采用的时间。例如,我国把首都北京所在的东8区的时间作为全国统一的时间,称为北京时间,北京时间就作为我国使用的本地时间,譬如我们电脑上显示的时间就是北京时间,我国国土面积广大,由东到西横跨了5个时区,也就意味着我国最东边的地区与最西边的地区实际上相差了4、5个小时。又例如,英国、法国、荷兰和比利时等国,虽地处中时区,但为了和欧洲大多数国家时间相一致,则采用东1区的时间。 譬如在Ubuntu系统下,可以使用date命令查看系统当前的本地时间,如下所示:
图 7.2.3 date查看本地时间 可以看到显示出来的字符串后面有一个"CST"字样,CST在这里其实指的是China Standard Time(中国标准时间)的缩写,表示当前查看到的时间是中国标准时间,也就是我国所使用的标准时间–北京时间,一般在安装Ubuntu系统的时候会提示用户设置所在城市,那么系统便会根据你所设置的城市来确定系统的本地时间对应的时区,譬如设置的城市为上海,那么系统的本地时间就是北京时间,因为我国统一使用北京时间作为本国的标准时间。 在Ubuntu系统下,时区信息通常以标准格式保存在一些文件当中,这些文件通常位于/usr/share/zoneinfo目录下,该目录下的每一个文件(包括子目录下的文件)都包含了一个特定国家或地区内时区制度的相关信息,且往往根据其所描述的城市或地区缩写来加以命名,譬如EST(美国东部标准时间)、CET(欧洲中部时间)、UTC(世界标准时间)、Hongkong、Iran、Japan(日本标准时间)等,也把这些文件称为时区配置文件,如下图所示:
图 7.2.4 时区信息配置文件 系统的本地时间由时区配置文件/etc/localtime定义,通常链接到/usr/share/zoneinfo目录下的某一个文件(或其子目录下的某一个文件):
图 7.2.5 /etc/localtime配置文件 如果我们要修改Ubuntu系统本地时间的时区信息,可以直接将/etc/localtime链接到/usr/share/zoneinfo目录下的任意一个时区配置文件,譬如EST(美国东部标准时间),首先进入到/etc目录下,执行下面的命令: sudo rm -rf localtime #删除原有链接文件 sudo ln -s /usr/share/zoneinfo/EST localtime #重新建立链接文件
图 7.2.6 修改本地时间对应的时区配置文件 接下来再使用date命令查看下系统当前的时间,如下所示:
图 7.2.7 date查看时间 可以发现后面的标识变成了EST,也就意味着当前系统的本地时间变成了EST时间(美国东部标准时间)。 关于时区的信息就给大家介绍这么多了。 世界标准时间的意义 世界标准时间指的就是格林威治时间,也就是中时区对应的时间,用格林威治当地时间作为全球统一时间,用以描述全球性的事件,方便大家记忆、以免混淆。 本小节关于时间的概念就给大家介绍这么多,其中涉及到的很多内容都是初中地理或高中地理中的知识,譬如本初子午线、经线、时区等,相信大家都学过,这里笔者便不再啰嗦! 7.2.2Linux系统中的时间 点时间和段时间 通常描述时间有两种方式:点时间和段时间;点时间顾名思义指的是某一个时间点,譬如当前时间是2021年2月22日星期一 11:12分35秒,所以这里指的就是某一个时间点;而对于段时间来说,顾名思义指的是某一个时间段,譬如早上8:00到中午12:00这段时间。 实时时钟RTC 操作系统中一般会有两个时钟,一个系统时钟(system clock),一个实时时钟(Real time clock),也叫RTC;系统时钟由系统启动之后由内核来维护,譬如使用date命令查看到的就是系统时钟,所以在系统关机情况下是不存在的;而实时时钟一般由RTC时钟芯片提供,RTC芯片有相应的电池为其供电,以保证系统在关机情况下RTC能够继续工作、继续计时。 Linux系统如何记录时间 Linux系统在开机启动之后首先会读取RTC硬件获取实时时钟作为系统时钟的初始值,之后内核便开始维护自己的系统时钟。所以由此可知,RTC硬件只有在系统开机启动时会读取一次,目的是用于对系统时钟进行初始化操作,之后的运行过程中便不会再对其进行读取操作了。 而在系统关机时,内核会将系统时钟写入到RTC硬件、已进行同步操作。 jiffies的引入 jiffies是内核中定义的一个全局变量,内核使用jiffies来记录系统从启动以来的系统节拍数,所以这个变量用来记录以系统节拍时间为单位的时间长度,Linux内核在编译配置时定义了一个节拍时间,使用节拍率(一秒钟多少个节拍数)来表示,譬如常用的节拍率为100Hz(一秒钟100个节拍数,节拍时间为1s / 100)、200Hz(一秒钟200个节拍,节拍时间为1s / 200)、250Hz(一秒钟250个节拍,节拍时间为1s / 250)、300Hz(一秒钟300个节拍,节拍时间为1s / 300)、500Hz(一秒钟500个节拍,节拍时间为1s / 500)等。由此可以发现配置的节拍率越低,每一个系统节拍的时间就越短,也就意味着jiffies记录的时间精度越高,当然,高节拍率会导致系统中断的产生更加频繁,频繁的中断会加剧系统的负担,一般默认情况下都是采用100Hz作为系统节拍率。 内核其实通过jiffies来维护系统时钟,全局变量jiffies在系统开机启动时会设置一个初始值,上面也给大家提到过,RTC实时时钟会在系统开机启动时读取一次,目的是用于对系统时钟进行初始化,这里说的初始化其实指的就是对内核的jiffies变量进行初始化操作,具体如何将读取到的实时时钟换算成jiffies数值,这里便不再给大家介绍了。 所以由此可知,操作系统使用jiffies这个全局变量来记录当前时间,当我们需要获取到系统当前时间点时,就可以使用jiffies变量去计算,当然并不需要我们手动去计算,Linux系统提供了相应的系统调用或C库函数用于获取当前时间,譬如系统调用time()、gettimeofday(),其实质上就是通过jiffies变量换算得到。 7.2.3获取时间time/gettimeofday (1)time函数 系统调用time()用于获取当前时间,以秒为单位,返回得到的值是自1970-01-01 00:00:00 +0000 (UTC)以来的秒数,其函数原型如下所示(可通过"man 2 time"命令查看): #include
time_t time(time_t *tloc); 使用该函数需要包含头文件。 函数参数和返回值含义如下: tloc:如果tloc参数不是NULL,则返回值也存储在tloc指向的内存中。 返回值:成功则返回自1970-01-01 00:00:00 +0000 (UTC)以来的时间值(以秒为单位);失败则返回-1,并会设置errno。 所以由此可知,time函数获取得到的是一个时间段,也就是从1970-01-01 00:00:00 +0000 (UTC)到现在这段时间所经过的秒数,所以你要计算现在这个时间点,只需要使用time()得到的秒数加1970-01-01 00:00:00即可!当然,这并不需要我们手动去计算,可以直接使用相关系统调用或C库函数来得到当前时间,后面再给大家介绍。 自1970-01-01 00:00:00 +0000 (UTC)以来经过的总秒数,我们把这个称之为日历时间或time_t时间。 测试 使用系统调用time()获取自1970-01-01 00:00:00 +0000 (UTC)以来的时间值: 示例代码 7.2.1 time函数使用示例
#include
#include
#include
int main(void)
{
time_t t;
t = time(NULL);
if (-1 == t) {
perror("time error");
exit(-1);
}
printf("时间值: %ld\n", t);
exit(0);
}
运行结果:
图 7.2.8 time函数测试结果 (2)gettimeofday函数 time()获取到的时间只能精确到秒,如果想要获取更加精确的时间可以使用系统调用gettimeofday来实现,gettimeofday()函数提供微秒级时间精度,函数原型如下所示(可通过"man 2 gettimeofday"命令查看): #include
int gettimeofday(struct timeval *tv, struct timezone *tz); 使用该函数需要包含头文件。 函数参数和返回值含义如下: tv:参数tv是一个struct timeval结构体指针变量,struct timeval结构体在前面章节内容中已经给大家介绍过,具体参考示例代码 5.6.3。 tz:参数tz是个历史产物,早期实现用其来获取系统的时区信息,目前已遭废弃,在调用gettimeofday()函数时应将参数tz设置为NULL。 返回值:成功返回0;失败将返回-1,并设置errno。 获取得到的时间值存储在参数tv所指向的struct timeval结构体变量中,该结构体包含了两个成员变量tv_sec和tv_usec,分别用于表示秒和微秒,所以获取得到的时间值就是tv_sec(秒)+tv_usec(微秒),同样获取得到的秒数与time()函数一样,也是自1970-01-01 00:00:00 +0000 (UTC)到现在这段时间所经过的秒数,也就是日历时间,所以由此可知time()返回得到的值和函数gettimeofday()所返回的tv参数中tv_sec字段的数值相同。 测试 使用gettimeofday获取自1970-01-01 00:00:00 +0000 (UTC)以来的时间值: 示例代码 7.2.2 gettimeofday函数使用示例
#include
#include
#include
int main(void)
{
struct timeval tval;
int ret;
ret = gettimeofday(&tval, NULL);
if (-1 == ret) {
perror("gettimeofday error");
exit(-1);
}
printf("时间值: %ld秒+%ld微秒\n", tval.tv_sec, tval.tv_usec);
exit(0);
}
运行结果:
图 7.2.9 gettimeofday函数测试结果 7.2.4时间转换函数 通过time()或gettimeofday()函数可以获取到当前时间点相对于1970-01-01 00:00:00 +0000 (UTC)这个时间点所经过时间(日历时间),所以获取得到的是一个时间段的长度,但是这并不利于我们查看当前时间,这个结果对于我们来说非常不友好,那么本小节将向大家介绍一些系统调用或C库函数,通过这些API可以将time()或gettimeofday()函数获取到的秒数转换为利于查看和理解的形式。 (1)ctime函数 ctime()是一个C库函数,可以将日历时间转换为可打印输出的字符串形式,ctime()函数原型如下所示:
#include
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);
使用该函数需要包含头文件。 函数参数和返回值含义如下: timep:time_t时间变量指针。 返回值:成功将返回一个char *类型指针,指向转换后得到的字符串;失败将返回NULL。 所以由此可知,使用ctime函数非常简单,只需将time_t时间变量的指针传入即可,调用成功便可返回字符串指针,拿到字符串指针之后,可以使用printf将其打印输出。但是ctime()是一个不可重入函数,存在一些安全上面的隐患,ctime_r()是ctime()的可重入版本,一般推荐大家使用可重入函数ctime_r(),可重入函数ctime_r()多了一个参数buf,也就是缓冲区首地址,所以ctime_r()函数需要调用者提供用于存放字符串的缓冲区。 Tips:关于可重入函数与不可重入函数将会在后面章节内容中进行介绍,这里暂时先不去管这个问题,在Linux系统中,有一些系统调用或C库函数提供了可重入版本与不可重入版本的函数接口,可重入版本函数所对应的函数名一般都会有一个" _r "后缀来表明它是一个可重入函数。 ctime(或ctime_r)转换得到的时间是计算机所在地对应的本地时间(譬如在中国对应的便是北京时间),并不是UTC时间,接下来编写一段简单地代码进行测试。 测试 示例代码 7.2.3 time/time_r函数使用示例
#include
#include
#include
int main(void)
{
char tm_str[100] = {0};
time_t tm;
/* 获取时间 */
tm = time(NULL);
if (-1 == tm) {
perror("time error");
exit(-1);
}
/* 将时间转换为字符串形式 */
ctime_r(&tm, tm_str);
/* 打印输出 */
printf("当前时间: %s", tm_str);
exit(0);
}
运行结果:
图 7.2.10 打印当前时间 从图中可知,打印出来的时间为"Mon Feb 22 17:10:46 2021",Mon表示星期一,这是一个英文单词的缩写,Feb表示二月份,这也是一个英文单词的缩写,22表示22日,所以整个打印信息显示的时间就是2021年2月22日星期一17点10分46秒。 (2)localtime函数 localtime()函数可以把time()或gettimeofday()得到的秒数(time_t时间或日历时间)变成一个struct tm结构体所表示的时间,该时间对应的是本地时间。localtime函数原型如下: #include
struct tm *localtime(const time_t *timep); struct tm *localtime_r(const time_t *timep, struct tm *result); 使用该函数需要包含头文件,localtime()的可重入版本为localtime_r()。 函数参数和返回值含义如下: timep:需要进行转换的time_t时间变量对应的指针,可通过time()或gettimeofday()获取得到。 result:是一个struct tm结构体类型指针,稍后给大家介绍struct tm结构体,参数result是可重入函数localtime_r()需要额外提供的参数。 返回值:对于不可重入版本localtime()来说,成功则返回一个有效的struct tm结构体指针,而对于可重入版本localtime_r()来说,成功执行情况下,返回值将会等于参数result;失败则返回NULL。 使用不可重入函数localtime()并不需要调用者提供struct tm变量,而是它会直接返回出来一个struct tm结构体指针,然后直接通过该指针访问里边的成员变量即可!虽然很方便,但是存在一些安全隐患,所以一般不推荐使用不可重入版本。 使用可重入版本localtime_r()调用者需要自己定义struct tm结构体变量、并将该变量指针赋值给参数result,在函数内部会对该结构体变量进行赋值操作。 struct tm结构体如下所示: 示例代码 7.2.4 struct tm结构体
struct tm {
int tm_sec; /* 秒(0-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, 星期日Sunday = 0、星期一=1…) */
int tm_yday; /* 一年里的第几天(0-365, 1 Jan = 0) */
int tm_isdst; /* 夏令时 */
};
从struct tm结构体内容可知,该结构体中包含了年月日时分秒星期等信息,使用localtime/localtime_r()便可以将time_t时间总秒数分解成了各个独立的时间信息,易于我们查看和理解。 测试 示例代码 7.2.5 localtime/localtime_r函数使用示例
#include
#include
#include
int main(void)
{
struct tm t;
time_t sec;
/* 获取时间 */
sec = time(NULL);
if (-1 == sec) {
perror("time error");
exit(-1);
}
/* 转换得到本地时间 */
localtime_r(&sec, &t);
/* 打印输出 */
printf("当前时间: %d年%d月%d日 %d:%d:%d\n",
t.tm_year + 1900, t.tm_mon, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
exit(0);
}
运行结果:
图 7.2.11 localtime/localtime_r测试结果 (3)gmtime函数 gmtime()函数也可以把time_t时间变成一个struct tm结构体所表示的时间,与localtime()所不同的是,gmtime()函数所得到的是UTC国际标准时间,并不是计算机的本地时间,这是它们之间的唯一区别。gmtime()函数原型如下所示: #include
struct tm *gmtime(const time_t *timep); struct tm *gmtime_r(const time_t *timep, struct tm *result); 同样使用gmtime()函数需要包含头文件。 gmtime_r()是gmtime()的可重入版本,同样也是推荐大家使用可重入版本函数gmtime_r。关于该函数的参数和返回值,这里便不再介绍,与localtime()是一样的。 测试 使用localtime获取本地时间、使用gmtime获取UTC国际标准时间,并进行对比: 示例代码 7.2.6 gmtime函数使用示例
#include
#include
#include
int main(void)
{
struct tm local_t;
struct tm utc_t;
time_t sec;
/* 获取时间 */
sec = time(NULL);
if (-1 == sec) {
perror("time error");
exit(-1);
}
/* 转换得到本地时间 */
localtime_r(&sec, &local_t);
/* 转换得到国际标准时间 */
gmtime_r(&sec, &utc_t);
/* 打印输出 */
printf("本地时间: %d年%d月%d日 %d:%d:%d\n",
local_t.tm_year + 1900, local_t.tm_mon, local_t.tm_mday,
local_t.tm_hour, local_t.tm_min, local_t.tm_sec);
printf("UTC时间: %d年%d月%d日 %d:%d:%d\n",
utc_t.tm_year + 1900, utc_t.tm_mon, utc_t.tm_mday,
utc_t.tm_hour, utc_t.tm_min, utc_t.tm_sec);
exit(0);
}
运行结果:
图 7.2.12 打印本地时间与UTC时间 从打印结果可知,本地时间与UTC时间(国际标准时间)相差8个小时,因为笔者使用的计算机其对应的本地时间指的便是北京时间,而北京时间要早于国际标准时间8个小时(东八区)。 (4)mktime函数 mktime()函数与localtime()函数相反,mktime()可以将使用struct tm结构体表示的分解时间转换为time_t时间(日历时间),同样这也是一个C库函数,其函数原型如下所示: #include
time_t mktime(struct tm *tm); 使用该函数需要包含头文件。 函数参数和返回值含义如下: tm:需要进行转换的struct tm结构体变量对应的指针。 返回值:成功返回转换得到time_t时间值;失败返回-1。 测试 示例代码 7.2.7 mktime函数使用示例
#include
#include
#include
int main(void)
{
struct tm local_t;
time_t sec;
/* 获取时间 */
sec = time(NULL);
if (-1 == sec) {
perror("time error");
exit(-1);
}
printf("获取得到的秒数: %ld\n", sec);
localtime_r(&sec, &local_t);
printf("转换得到的秒数: %ld\n", mktime(&local_t));
exit(0);
}
运行结果:
图 7.2.13 测试结果 (5)asctime函数 asctime()函数与ctime()函数的作用一样,也可将时间转换为可打印输出的字符串形式,与ctime()函数的区别在于,ctime()是将time_t时间转换为固定格式字符串、而asctime()则是将struct tm表示的分解时间转换为固定格式的字符串。asctime()函数原型如下所示: #include
char *asctime(const struct tm *tm); char *asctime_r(const struct tm *tm, char *buf); 使用该函数需要包含头文件。 函数参数和返回值含义如下: tm:需要进行转换的struct tm表示的时间。 buf:可重入版本函数asctime_r需要额外提供的参数buf,指向一个缓冲区,用于存放转换得到的字符串。 返回值:转换失败将返回NULL;成功将返回一个char *类型指针,指向转换后得到的时间字符串,对于asctime_r函数来说,返回值就等于参数buf。 测试 示例代码 7.2.8 asctime函数使用示例
#include
#include
#include
int main(void)
{
struct tm local_t;
char tm_str[100] = {0};
time_t sec;
/* 获取时间 */
sec = time(NULL);
if (-1 == sec) {
perror("time error");
exit(-1);
}
localtime_r(&sec, &local_t);
asctime_r(&local_t, tm_str);
printf("本地时间: %s", tm_str);
exit(0);
}
运行结果:
图 7.2.14 测试结果 (6)strftime函数 除了asctime()函数之外,这里再给大家介绍一个C库函数strftime(),此函数也可以将一个struct tm变量表示的分解时间转换为为格式化字符串,并且在功能上比asctime()和ctime()更加强大,它可以根据自己的喜好自定义时间的显示格式,而asctime()和ctime()转换得到的字符串时间格式的固定的。 strftime()函数原型如下所示: #include
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm); 使用该函数需要包含头文件。 函数参数和返回值含义如下: s:指向一个缓存区的指针,该缓冲区用于存放生成的字符串。 max:字符串的最大字节数。 format:这是一个用字符串表示的字段,包含了普通字符和特殊格式说明符,可以是这两种字符的任意组合。特殊格式说明符将会被替换为struct tm结构体对象所指时间的相应值,这些特殊格式说明符如下: 表 7.2.1 strftime函数特殊格式说明符 说明符 表示含义 实例 %a 星期的缩写 Sun %A 星期的完整名称 Sunday %b 月份的缩写 Mar %B 月份的完整名称 March %c 系统当前语言环境对应的首选日期和时间表示形式 %C 世纪(年/100) 20 %d 十进制数表示一个月中的第几天(01-31) 15、05 %D 相当于%m/%d/%y 01/14/21 %e 与%d相同,但是单个数字时,前导0会被去掉 15、5 %F 相当于%Y-%m-%d 2021-01-14 %h 相当于%b Jan %H 十进制数表示的24小时制的小时(范围00-23) 01、22 %I 十进制数表示的12小时制的小时(范围01-12) 01、11 %j 十进制数表示的一年中的某天(范围001-366) 050、285 %k 与%H相同,但是单个数字时,前导0会被去掉(范围0-23) 1、22 %l 与%I相同,但是单个数字时,前导0会被去掉(范围1-12) 1、11 %m 十进制数表示的月份(范围01-12) 01、10 %M 十进制数表示的分钟(范围00-59) 01、55 %n 换行符 %p 根据给定的时间值,添加“AM”或“PM” PM %P 与%p相同,但会使用小写字母表示 pm %r 相当于%I:%M:%S %p 12:15:31 PM %R 相当于%H:%M 12:16 %S 十进制数表示的秒数(范围00-60) 05、30 %T 相当于%H:%M:%S 12:20:03 %u 十进制数表示的星期(范围1-7,星期一为1) 1、5 %U 十进制数表示,当前年份的第几个星期(范围00-53),从第一个星期日作为01周的第一天开始 %W 十进制数表示,当前年份的第几个星期(范围00-53),从第一个星期一作为第01周的第一天开始 %w 十进制数表示的星期,范围为0-6,星期日为0 %x 系统当前语言环境的首选日期表示形式,没有时间 01/14/21 %X 系统当前语言环境的首选时间表示形式,没有日期 12:30:16 %y 十进制数表示的年份(后两字数字) 21 %Y 十进制数表示的年份(4个数字) 2021 %% 输出%符号 % strftime函数的特殊格式说明符还是比较多的,不用去记它,需要用的时候再去查即可! 通过上表可知,譬如我要想输出"2021-01-14 16:30:25 January Thursday"这样一种形式表示的时间日期,那么就可以这样来设置format参数: “%Y-%m-%d %H:%M:%S %B %A” tm:指向struct tm结构体对象的指针。 返回值:如果转换得到的目标字符串不超过最大字节数(也就是max),则返回放置到s数组中的字节数;如果超过了最大字节数,则返回0。 测试 示例代码 7.2.9 strftime函数使用示例
#include
#include
#include
int main(void)
{
struct tm local_t;
char tm_str[100] = {0};
time_t sec;
/* 获取时间 */
sec = time(NULL);
if (-1 == sec) {
perror("time error");
exit(-1);
}
localtime_r(&sec, &local_t);
strftime(tm_str, sizeof(tm_str), "%Y-%m-%d %A %H:%M:%S", &local_t);
printf("本地时间: %s\n", tm_str);
exit(0);
}
运行结果:
图 7.2.15 测试结果 7.2.5设置时间settimeofday 使用settimeofday()函数可以设置时间,也就是设置系统的本地时间,函数原型如下所示: #include
int settimeofday(const struct timeval *tv, const struct timezone *tz); 首先使用该函数需要包含头文件。 函数参数和返回值含义如下: tv:参数tv是一个struct timeval结构体指针变量,struct timeval结构体在前面章节内容中已经给大家介绍了,需要设置的时间便通过参数tv指向的struct timeval结构体变量传递进去。 tz:参数tz是个历史产物,早期实现用其来设置系统的时区信息,目前已遭废弃,在调用settimeofday()函数时应将参数tz设置为NULL。 返回值:成功返回0;失败将返回-1,并设置errno。 使用settimeofday设置系统时间时内核会进行权限检查,只有超级用户(root)才可以设置系统时间,普通用户将无操作权限。 7.2.6总结 本小节给大家介绍了时间相关的基本概念,譬如GMT时间、UTC时间以及全球24个时区的划分等,并且给大家介绍了Linux系统下常用的时间相关的系统调用和库函数,主要有9个:time/ctime/localtime/gmtime/mktime/asctime/strftime/gettimeofday/settimeofday,对这些函数的功能、作用总结如下:
图 7.2.16 时间相关API总结 通过上图可以帮助大家快速理解各个函数的功能、作用,大家加油! 本小节到这里就结束了。 7.3进程时间 进程时间指的是进程从创建后(也就是程序运行后)到目前为止这段时间内使用CPU资源的时间总数,出于记录的目的,内核把CPU时间(进程时间)分为以下两个部分: 用户CPU时间:进程在用户空间(用户态)下运行所花费的CPU时间。有时也成为虚拟时间(virtual time)。 系统CPU时间:进程在内核空间(内核态)下运行所花费的CPU时间。这是内核执行系统调用或代表进程执行的其它任务(譬如,服务页错误)所花费的时间。 一般来说,进程时间指的是用户CPU时间和系统CPU时间的总和,也就是总的CPU时间。 Tips:进程时间不等于程序的整个生命周期所消耗的时间,如果进程一直处于休眠状态(进程被挂起、不会得到系统调度),那么它并不会使用CPU资源,所以休眠的这段时间并不计算在进程时间中。 7.3.1times函数 times()函数用于获取当前进程时间,其函数原型如下所示: #include
clock_t times(struct tms *buf); 使用该函数需要包含头文件。 函数参数和返回值含义如下: buf:times()会将当前进程时间信息存在一个struct tms结构体数据中,所以我们需要提供struct tms变量,使用参数buf指向该变量,关于struct tms结构体稍后给大家介绍。 返回值:返回值类型为clock_t(实质是long类型),调用成功情况下,将返回从过去任意的一个时间点(譬如系统启动时间)所经过的时钟滴答数(其实就是系统节拍数),将(节拍数 / 节拍率)便可得到秒数,返回值可能会超过clock_t所能表示的范围(溢出);调用失败返回-1,并设置errno。 如果我们想查看程序运行到某一个位置时的进程时间,或者计算出程序中的某一段代码执行过程所花费的进程时间,都可以使用times()函数来实现。 struct tms结构体内容如下所示: 示例代码 7.3.1 struct tms结构体
struct tms {
clock_t tms_utime; /* user time, 进程的用户CPU时间, tms_utime个系统节拍数 */
clock_t tms_stime; /* system time, 进程的系统CPU时间, tms_stime个系统节拍数 */
clock_t tms_cutime; /* user time of children, 已死掉子进程的tms_utime + tms_cutime时间总和 */
clock_t tms_cstime; /* system time of children, 已死掉子进程的tms_stime + tms_cstime时间总和 */
};
测试 以下我们演示了通过times()来计算程序中某一段代码执行所耗费的进程时间和总的时间,测试程序如下所示: 示例代码 7.3.2 times函数使用示例 #include
#include
#include
#include
int main(int argc, 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);
/* 开始时间 */
t_start = times(&t_buf_start);
if (-1 == t_start) {
perror("times error");
exit(-1);
}
/* *****需要进行测试的代码段***** */
for (i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?