Linux系统信息与系统资源

Posted 行稳方能走远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux系统信息与系统资源相关的知识,希望对你有一定的参考价值。

目录

在应用程序当中,有时往往需要去获取到一些系统相关的信息。

  • 时间、日期、以及其它一些系统相关信息,如何通过Linux 系统调用或C 库函数获取系统信息;
  • Linux 系统下的/proc 虚拟文件系统,包括/proc 文件系统是什么以及如何从/proc 文件系统中读取系统、进程有关信息;
  • 系统资源的使用,譬如系统内存资源的申请与使用等。

系统信息

系统标识uname

系统调用uname()用于获取有关当前操作系统内核的名称和信息,函数原型如下所示(可通过"man 2 uname"命令查看):

#include <sys/utsname.h>

int uname(struct utsname *buf);

buf:struct utsname 结构体类型指针,指向一个struct utsname 结构体类型对象。
返回值:成功返回0;失败将返回-1,并设置errno。

struct utsname 结构体如下所示:

struct utsname 
	char sysname[]; /* 当前操作系统的名称*/
	char nodename[]; /* 网络上的名称(主机名)*/
	char release[]; /* 操作系统内核版本*/
	char version[]; /* 操作系统发行版本*/
	char machine[]; /* 硬件架构类型*/
	#ifdef _GNU_SOURCE
		char domainname[];/* 当前域名*/
	#endif
;

可以看到,struct utsname 结构体中的所有成员变量都是字符数组,所以获取到的信息都是字符串。

测试

#include <stdio.h>
#include <stdlib.h>
#include <sys/utsname.h>

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);

运行结果:

sysinfo 函数

sysinfo 系统调用可用于获取一些系统统计信息,其函数原型如下所示:

#include <sys/sysinfo.h>

int sysinfo(struct sysinfo *info);

info:struct sysinfo 结构体类型指针,指向一个struct sysinfo 结构体类型对象。
返回值:成功返回0;失败将返回-1,并设置errno。

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 */
;

测试

#include <stdio.h>
#include <stdlib.h>
#include <sys/sysinfo.h>
int main(void)

	struct sysinfo sys_info;
	int ret;
	/* 获取信息*/
	ret = sysinfo(&sys_info);
	if (-1 == ret) 
		perror("sysinfo error");
		exit(-1);
	
	/* 打印信息*/
	printf("自系统启动之后所经过的时间: %ld\\n", sys_info.uptime);
	printf("总的可用内存大小: %lu\\n", sys_info.totalram);
	printf("还未被使用的内存大小: %lu\\n", sys_info.freeram);
	printf("系统当前进程数量: %u\\n", sys_info.procs);
	exit(0);

运行结果:

gethostname 函数

用于单独获取Linux 系统主机名,与struct utsname 数据结构体中的nodename 变量一样:

#include <unistd.h>

int gethostname(char *name, size_t len);

name:指向用于存放主机名字符串的缓冲区。
len:缓冲区长度。
返回值:成功返回0,;失败将返回-1,并会设置errno。

测试

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
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);

运行结果:

sysconf()函数

sysconf()函数是一个库函数,可在运行时获取系统的一些配置信息,譬如页大小(page size)、主机名的最大长度、进程可以打开的最大文件数、每个用户ID 的最大并发进程数等。其函数原型如下所示:

#include <unistd.h>

long sysconf(int name);

参数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:终端设备名称的最大长度。
⚫ ……

若指定的参数name 为无效值,则sysconf()函数返回-1,并会将errno 设置为EINVAL。否则返回的值便是对应的配置值。

使用示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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);

运行结果:

时间、日期

GMT 时间

GMT(Greenwich Mean Time)中文全称是格林威治标准时间,1884 年被确立,GMT 时间就是英国格林威治当地时间,也就是零时区(中时区)所在时间,与我国的标准时间北京时间(东八区)相差8 个小时,即早八个小时,所以GMT 12:00 对应的北京时间是20:00。

UTC 时间

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

在Ubuntu 系统下,可以使用"date -u"命令查看到当前的UTC 时间,如下所示:

时区

全球被划分为24 个时区,每一个时区横跨经度15 度,以英国格林威治的本初子午线作为零度经线,将全球划分为东西两半球,分为东一区、东二区、东三区……东十二区以及西一区、西二区、西三区……西十二区,而本初子午线所在时区被称为中时区(或者叫零时区),划分图如下所示:

东十二区和西十二区其实是一个时区,就是十二区,东十二区与西十二区各横跨经度7.5 度,以180 度经线作为分界线。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时。相邻两个时区的时间相差1 小时。例如,我国东8 区的时间总比泰国东7 区的时间早1 小时,而比日本东9 区的时间晚1小时。因此,出国旅行的人,必须随时调整自己的手表,才能和当地时间相一致。凡向西走,每过一个时区,就要把表向前拨1 小时(比如2 点拨到1 点);凡向东走,每过一个时区,就要把表向后拨1 小时(比如1 点拨到2 点)。

实际上,世界上不少国家和地区都不严格按时区来计算时间。为了在全国范围内采用统一的时间,一般都把某一个时区的时间作为全国统一采用的时间。例如,我国把首都北京所在的东8 区的时间作为全国统一的时间,称为北京时间,北京时间就作为我国使用的本地时间,譬如我们电脑上显示的时间就是北京时间,我国国土面积广大,由东到西横跨了5 个时区,也就意味着我国最东边的地区与最西边的地区实际上相差了4、5 个小时。又例如,英国、法国、荷兰和比利时等国,虽地处中时区,但为了和欧洲大多数国家时间相一致,则采用东1 区的时间。

譬如在Ubuntu 系统下,可以使用date 命令查看系统当前的本地时间,如下所示:

可以看到显示出来的字符串后面有一个"CST"字样,CST 在这里其实指的是China Standard Time(中国标准时间)的缩写,表示当前查看到的时间是中国标准时间,也就是我国所使用的标准时间–北京时间,一般在安装Ubuntu 系统的时候会提示用户设置所在城市,那么系统便会根据你所设置的城市来确定系统的本地时间对应的时区,譬如设置的城市为上海,那么系统的本地时间就是北京时间,因为我国统一使用北京时间作为本国的标准时间。

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

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

如果我们要修改Ubuntu 系统本地时间的时区信息,可以直接将/etc/localtime 链接到/usr/share/zoneinfo目录下的任意一个时区配置文件,譬如EST(美国东部标准时间),首先进入到/etc 目录下,执行下面的命令:

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


接下来再使用date 命令查看下系统当前的时间,如下所示:

可以发现后面的标识变成了EST,也就意味着当前系统的本地时间变成了EST 时间(美国东部标准时间)。

实时时钟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 变量换算得到。

获取时间time/gettimeofday

(1)time 函数
系统调用time()用于获取当前时间,以秒为单位,返回得到的值是自1970-01-01 00:00:00 +0000 (UTC)以来的秒数,函数原型如下所示:

#include <time.h>

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)以来的时间值:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)

	time_t t;
	t = time(NULL);
	if (-1 == t) 
		perror("time error");
		exit(-1);
	
	
	printf("时间值: %ld\\n", t);
	exit(0);

运行结果:

(2)gettimeofday 函数
time()获取到的时间只能精确到秒,gettimeofday()函数提供微秒级时间精度,函数原型如下:

#include <sys/time.h>

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)以来的时间值:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

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);

运行结果:

时间转换函数

通过time()或gettimeofday()函数可以获取到当前时间点相对于1970-01-01 00:00:00 +0000 (UTC)这个时间点所经过时间(日历时间),所以获取得到的是一个时间段的长度,但是这并不利于我们查看当前时间,这个结果对于我们来说非常不友好,那么本小节将向大家介绍一些系统调用或C 库函数,通过这些API 可以将time()或gettimeofday()函数获取到的秒数转换为利于查看和理解的形式。

(1)ctime 函数
ctime()是一个C 库函数,可以将日历时间转换为可打印输出的字符串形式,ctime()函数原型如下所示:

#include <time.h>

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时间,接下来编写一段简单地代码进行测试。

测试

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

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);

运行结果:

从图中可知,打印出来的时间为"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 <time.h>

struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

函数参数和返回值含义如下:
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 结构体如下所示:

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 时间总秒数分解成了各个独立的时间信息,易于我们查看和理解。

测试

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

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);

运行结果:

(3)gmtime 函数
gmtime()函数也可以把time_t 时间变成一个struct tm 结构体所表示的时间,与localtime()所不同的是,gmtime()函数所得到的是UTC 国际标准时间,并不是计算机的本地时间,这是它们之间的唯一区别。gmtime()函数原型如下所示:

#include <time.h>

struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);

gmtime_r()是gmtime()的可重入版本,同样也是推荐大家使用可重入版本函数gmtime_r。关于该函数的参数和返回值,这里便不再介绍,与localtime()是一样的。

测试
使用localtime 获取本地时间、使用gmtime 获取UTC 国际标准时间,并进行对比:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)

	struct tm local_t以上是关于Linux系统信息与系统资源的主要内容,如果未能解决你的问题,请参考以下文章

Linux系统信息与系统资源

速通指南《信息资源管理》信息系统资源管理,第3章

Linux系统配置及服务管理_第01章系统部署 (第二小节初识shell)

FreeRTOS 系统时钟节拍和时间管理

Linux驱动开发定时器

Linux驱动开发定时器